diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-04-18 22:01:10 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-04-18 22:01:10 +0000 |
commit | f4c9bb7b2317c8fc610a2805ba7fbc9296ef475b (patch) | |
tree | 2338e093e11bde9ecd3f1fa7dd94b5a963169a89 | |
parent | 27651d2b1adb7c75cf78aead8ef21e94970c89a0 (diff) | |
parent | 4af5b57dd17f345a9b8936d12bdc743b0e0bf7e5 (diff) | |
download | abseil-cpp-androidx-core-animation-release.tar.gz |
Snap for 11735041 from 4af5b57dd17f345a9b8936d12bdc743b0e0bf7e5 to androidx-core-animation-releaseandroidx-core-animation-release
Change-Id: I1c05c0d591af0ff9f4e97d0118d90eebbae08bc6
584 files changed, 24026 insertions, 21325 deletions
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..ff55e352 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,8 @@ +Thank you for your contribution to Abseil! + +Before submitting this PR, please be sure to read our [contributing +guidelines](https://github.com/abseil/abseil-cpp/blob/master/CONTRIBUTING.md). + +If you are a Googler, please also note that it is required that you send us a +Piper CL instead of using the GitHub pull-request process. The code propagation +process will deliver the change to GitHub. @@ -1,3 +1,5 @@ +# Bzlmod lockfile +MODULE.bazel.lock # Ignore all bazel-* symlinks. /bazel-* # Ignore Bazel verbose explanations @@ -9,245 +9,45 @@ license { license_text: ["LICENSE"], } -cc_library_headers { - name: "libabsl_headers", - device_supported: false, +// Monolithic module for use on device. Currently restricted to 3P libraries +// which require it as a dependency. See go/absl-android for more information. +cc_library_static { + name: "libabsl", host_supported: true, - export_include_dirs: ["."], -} - -cc_defaults { - name: "libabsl_library_defaults", - header_libs: ["libabsl_headers"], - export_header_lib_headers: ["libabsl_headers"], - whole_static_libs: ["libabsl_base"], -} - -cc_library_host_static { - name: "libabsl_base", - defaults: ["libabsl_library_defaults"], - srcs: [ - "absl/base/internal/cycleclock.cc", - "absl/base/internal/low_level_alloc.cc", - "absl/base/internal/raw_logging.cc", - "absl/base/internal/scoped_set_env.cc", - "absl/base/internal/spinlock.cc", - "absl/base/internal/spinlock_wait.cc", - "absl/base/internal/strerror.cc", - "absl/base/internal/sysinfo.cc", - "absl/base/internal/thread_identity.cc", - "absl/base/internal/throw_delegate.cc", - "absl/base/internal/unscaledcycleclock.cc", - "absl/base/log_severity.cc", - ], - exclude_static_libs: ["libabsl_base"], // don't depend on itself -} - -cc_library_host_static { - name: "libabsl_container", - defaults: ["libabsl_library_defaults"], - srcs: [ - "absl/container/internal/test_instance_tracker.cc", - "absl/container/internal/hashtablez_sampler.cc", - "absl/container/internal/hashtablez_sampler_force_weak_definition.cc", - "absl/container/internal/raw_hash_set.cc", - ], -} - -cc_library_host_static { - name: "libabsl_debugging", - defaults: ["libabsl_library_defaults"], - srcs: [ - "absl/debugging/failure_signal_handler.cc", - "absl/debugging/internal/address_is_readable.cc", - "absl/debugging/internal/demangle.cc", - "absl/debugging/internal/elf_mem_image.cc", - "absl/debugging/internal/examine_stack.cc", - "absl/debugging/internal/stack_consumption.cc", - "absl/debugging/internal/vdso_support.cc", - "absl/debugging/leak_check.cc", - "absl/debugging/stacktrace.cc", - "absl/debugging/symbolize.cc", - ], -} - -cc_library_host_static { - name: "libabsl_flags", - defaults: ["libabsl_library_defaults"], + vendor_available: true, srcs: [ - "absl/flags/commandlineflag.cc", - "absl/flags/flag_test_defs.cc", - "absl/flags/flag.cc", - "absl/flags/internal/commandlineflag.cc", - "absl/flags/internal/flag.cc", - "absl/flags/internal/private_handle_accessor.cc", - "absl/flags/internal/program_name.cc", - "absl/flags/internal/usage.cc", - "absl/flags/marshalling.cc", - "absl/flags/parse.cc", - "absl/flags/reflection.cc", - "absl/flags/usage_config.cc", - "absl/flags/usage.cc", + "absl/**/*.cc", ], -} - -cc_library_host_static { - name: "libabsl_hash", - defaults: ["libabsl_library_defaults"], - srcs: [ - "absl/hash/internal/city.cc", - "absl/hash/internal/hash.cc", - "absl/hash/internal/low_level_hash.cc", + exclude_srcs: [ + "absl/**/*benchmark.cc", + "absl/**/*benchmarks.cc", + "absl/**/*_test.cc", + "absl/**/*_testing.cc", + "absl/base/spinlock_test_common.cc", "absl/hash/internal/print_hash_of.cc", - ], -} - -cc_library_host_static { - name: "libabsl_numeric", - defaults: ["libabsl_library_defaults"], - srcs: ["absl/numeric/int128.cc"], -} - -cc_library_host_static { - name: "libabsl_profiling", - defaults: ["libabsl_library_defaults"], - srcs: [ - "absl/profiling/internal/exponential_biased.cc", - "absl/profiling/internal/periodic_sampler.cc", - ], -} - -cc_library_host_static { - name: "libabsl_random", - defaults: ["libabsl_library_defaults"], - srcs: [ - "absl/random/discrete_distribution.cc", - "absl/random/gaussian_distribution.cc", - "absl/random/internal/chi_square.cc", - "absl/random/internal/distribution_test_util.cc", + "absl/log/internal/test_helpers.cc", + "absl/log/internal/test_matchers.cc", + "absl/log/scoped_mock_log.cc", "absl/random/internal/gaussian_distribution_gentables.cc", - "absl/random/internal/nanobenchmark.cc", - "absl/random/internal/pool_urbg.cc", - "absl/random/internal/randen_benchmarks.cc", - "absl/random/internal/randen.cc", - "absl/random/internal/randen_detect.cc", - "absl/random/internal/randen_hwaes.cc", - "absl/random/internal/randen_round_keys.cc", - "absl/random/internal/randen_slow.cc", - "absl/random/internal/seed_material.cc", - "absl/random/seed_gen_exception.cc", - "absl/random/seed_sequences.cc", ], - cflags: ["-Wno-unused-parameter"], -} - -cc_library_host_static { - name: "libabsl_status", - defaults: ["libabsl_library_defaults"], - srcs: [ - "absl/status/status.cc", - "absl/status/status_payload_printer.cc", - "absl/status/statusor.cc", - ], -} - -cc_library_host_static { - name: "libabsl_strings", - defaults: ["libabsl_library_defaults"], - srcs: [ - "absl/crc/crc32c.cc", - "absl/crc/internal/cpu_detect.cc", - "absl/crc/internal/crc_cord_state.cc", - "absl/crc/internal/crc_memcpy_fallback.cc", - "absl/crc/internal/crc_memcpy_x86_64.cc", - "absl/crc/internal/crc_non_temporal_memcpy.cc", - "absl/crc/internal/crc_x86_arm_combined.cc", - "absl/crc/internal/crc.cc", - "absl/strings/ascii.cc", - "absl/strings/charconv.cc", - "absl/strings/cord_analysis.cc", - "absl/strings/cord_buffer.cc", - "absl/strings/cord.cc", - "absl/strings/escaping.cc", - "absl/strings/internal/charconv_bigint.cc", - "absl/strings/internal/charconv_parse.cc", - "absl/strings/internal/cord_internal.cc", - "absl/strings/internal/cord_rep_btree.cc", - "absl/strings/internal/cord_rep_btree_navigator.cc", - "absl/strings/internal/cord_rep_btree_reader.cc", - "absl/strings/internal/cord_rep_consume.cc", - "absl/strings/internal/cord_rep_crc.cc", - "absl/strings/internal/cord_rep_ring.cc", - "absl/strings/internal/cordz_functions.cc", - "absl/strings/internal/cordz_handle.cc", - "absl/strings/internal/cordz_info.cc", - "absl/strings/internal/cordz_sample_token.cc", - "absl/strings/internal/damerau_levenshtein_distance.cc", - "absl/strings/internal/escaping.cc", - "absl/strings/internal/memutil.cc", - "absl/strings/internal/ostringstream.cc", - "absl/strings/internal/pow10_helper.cc", - "absl/strings/internal/str_format/arg.cc", - "absl/strings/internal/str_format/bind.cc", - "absl/strings/internal/str_format/extension.cc", - "absl/strings/internal/str_format/float_conversion.cc", - "absl/strings/internal/str_format/output.cc", - "absl/strings/internal/str_format/parser.cc", - "absl/strings/internal/stringify_sink.cc", - "absl/strings/internal/utf8.cc", - "absl/strings/match.cc", - "absl/strings/numbers.cc", - "absl/strings/str_cat.cc", - "absl/strings/str_replace.cc", - "absl/strings/str_split.cc", - "absl/strings/string_view.cc", - "absl/strings/substitute.cc", + export_include_dirs: ["."], + shared_libs: [ + "liblog", ], -} - -cc_library_host_static { - name: "libabsl_synchronization", - defaults: ["libabsl_library_defaults"], - srcs: [ - "absl/synchronization/barrier.cc", - "absl/synchronization/blocking_counter.cc", - "absl/synchronization/internal/create_thread_identity.cc", - "absl/synchronization/internal/per_thread_sem.cc", - "absl/synchronization/internal/waiter.cc", - "absl/synchronization/internal/graphcycles.cc", - "absl/synchronization/mutex.cc", - "absl/synchronization/notification.cc", + stl: "libc++", + apex_available: [ + "//apex_available:platform", ], -} - -cc_library_host_static { - name: "libabsl_time", - defaults: ["libabsl_library_defaults"], - srcs: [ - "absl/time/civil_time.cc", - "absl/time/clock.cc", - "absl/time/duration.cc", - "absl/time/format.cc", - "absl/time/internal/cctz/src/civil_time_detail.cc", - "absl/time/internal/cctz/src/time_zone_fixed.cc", - "absl/time/internal/cctz/src/time_zone_format.cc", - "absl/time/internal/cctz/src/time_zone_if.cc", - "absl/time/internal/cctz/src/time_zone_impl.cc", - "absl/time/internal/cctz/src/time_zone_info.cc", - "absl/time/internal/cctz/src/time_zone_libc.cc", - "absl/time/internal/cctz/src/time_zone_lookup.cc", - "absl/time/internal/cctz/src/time_zone_posix.cc", - "absl/time/internal/cctz/src/zone_info_source.cc", - "absl/time/time.cc", + visibility: [ + "//external/grpc-grpc:__subpackages__", + "//external/kythe:__subpackages__", ], } +// Globally visible host-only library. cc_library_host_static { - name: "libabsl_types", - defaults: ["libabsl_library_defaults"], - srcs: [ - "absl/types/bad_any_cast.cc", - "absl/types/bad_optional_access.cc", - "absl/types/bad_variant_access.cc", - ], + name: "libabsl_host", + whole_static_libs: ["libabsl"], + export_include_dirs: ["."], + stl: "libc++", } diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index c4a41e6d..47f3beeb 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake @@ -26,8 +26,9 @@ set(ABSL_INTERNAL_DLL_FILES "base/internal/low_level_alloc.cc" "base/internal/low_level_alloc.h" "base/internal/low_level_scheduling.h" + "base/internal/nullability_impl.h" "base/internal/per_thread_tls.h" - "base/internal/prefetch.h" + "base/prefetch.h" "base/internal/pretty_function.h" "base/internal/raw_logging.cc" "base/internal/raw_logging.h" @@ -42,7 +43,6 @@ set(ABSL_INTERNAL_DLL_FILES "base/internal/spinlock_wait.h" "base/internal/sysinfo.cc" "base/internal/sysinfo.h" - "base/internal/thread_annotations.h" "base/internal/thread_identity.cc" "base/internal/thread_identity.h" "base/internal/throw_delegate.cc" @@ -55,6 +55,8 @@ set(ABSL_INTERNAL_DLL_FILES "base/log_severity.cc" "base/log_severity.h" "base/macros.h" + "base/no_destructor.h" + "base/nullability.h" "base/optimization.h" "base/options.h" "base/policy_checks.h" @@ -74,7 +76,6 @@ set(ABSL_INTERNAL_DLL_FILES "container/internal/common_policy_traits.h" "container/internal/compressed_tuple.h" "container/internal/container_memory.h" - "container/internal/counting_allocator.h" "container/internal/hash_function_defaults.h" "container/internal/hash_policy_traits.h" "container/internal/hashtable_debug.h" @@ -106,7 +107,7 @@ set(ABSL_INTERNAL_DLL_FILES "crc/internal/crc_x86_arm_combined.cc" "crc/internal/crc_memcpy_fallback.cc" "crc/internal/crc_memcpy.h" - "crc/internal/crc_memcpy_x86_64.cc" + "crc/internal/crc_memcpy_x86_arm_combined.cc" "crc/internal/crc_non_temporal_memcpy.cc" "crc/internal/crc_x86_arm_combined.cc" "crc/internal/non_temporal_arm_intrinsics.h" @@ -138,6 +139,7 @@ set(ABSL_INTERNAL_DLL_FILES "functional/function_ref.h" "functional/internal/any_invocable.h" "functional/internal/function_ref.h" + "functional/overload.h" "hash/hash.h" "hash/internal/city.h" "hash/internal/city.cc" @@ -148,6 +150,7 @@ set(ABSL_INTERNAL_DLL_FILES "hash/internal/low_level_hash.cc" "log/absl_check.h" "log/absl_log.h" + "log/absl_vlog_is_on.h" "log/check.h" "log/die_if_null.cc" "log/die_if_null.h" @@ -160,6 +163,8 @@ set(ABSL_INTERNAL_DLL_FILES "log/internal/conditions.cc" "log/internal/conditions.h" "log/internal/config.h" + "log/internal/fnmatch.h" + "log/internal/fnmatch.cc" "log/internal/globals.cc" "log/internal/globals.h" "log/internal/log_format.cc" @@ -176,6 +181,8 @@ set(ABSL_INTERNAL_DLL_FILES "log/internal/proto.cc" "log/internal/strip.h" "log/internal/structured.h" + "log/internal/vlog_config.cc" + "log/internal/vlog_config.h" "log/internal/voidify.h" "log/initialize.cc" "log/initialize.h" @@ -187,6 +194,7 @@ set(ABSL_INTERNAL_DLL_FILES "log/log_sink_registry.h" "log/log_streamer.h" "log/structured.h" + "log/vlog_is_on.h" "memory/memory.h" "meta/type_traits.h" "numeric/bits.h" @@ -247,6 +255,7 @@ set(ABSL_INTERNAL_DLL_FILES "random/uniform_real_distribution.h" "random/zipf_distribution.h" "status/internal/status_internal.h" + "status/internal/status_internal.cc" "status/internal/statusor_internal.h" "status/status.h" "status/status.cc" @@ -258,6 +267,7 @@ set(ABSL_INTERNAL_DLL_FILES "strings/ascii.h" "strings/charconv.cc" "strings/charconv.h" + "strings/charset.h" "strings/cord.cc" "strings/cord.h" "strings/cord_analysis.cc" @@ -284,9 +294,6 @@ set(ABSL_INTERNAL_DLL_FILES "strings/internal/cord_rep_consume.h" "strings/internal/cord_rep_consume.cc" "strings/internal/cord_rep_flat.h" - "strings/internal/cord_rep_ring.cc" - "strings/internal/cord_rep_ring.h" - "strings/internal/cord_rep_ring_reader.h" "strings/internal/cordz_functions.cc" "strings/internal/cordz_functions.h" "strings/internal/cordz_handle.cc" @@ -305,6 +312,8 @@ set(ABSL_INTERNAL_DLL_FILES "strings/internal/stringify_sink.h" "strings/internal/stringify_sink.cc" "strings/internal/has_absl_stringify.h" + "strings/has_absl_stringify.h" + "strings/has_ostream_operator.h" "strings/match.cc" "strings/match.h" "strings/numbers.cc" @@ -322,7 +331,6 @@ set(ABSL_INTERNAL_DLL_FILES "strings/strip.h" "strings/substitute.cc" "strings/substitute.h" - "strings/internal/char_map.h" "strings/internal/escaping.h" "strings/internal/escaping.cc" "strings/internal/memutil.cc" @@ -361,14 +369,26 @@ set(ABSL_INTERNAL_DLL_FILES "synchronization/internal/create_thread_identity.cc" "synchronization/internal/create_thread_identity.h" "synchronization/internal/futex.h" + "synchronization/internal/futex_waiter.h" + "synchronization/internal/futex_waiter.cc" "synchronization/internal/graphcycles.cc" "synchronization/internal/graphcycles.h" "synchronization/internal/kernel_timeout.h" + "synchronization/internal/kernel_timeout.cc" "synchronization/internal/per_thread_sem.cc" "synchronization/internal/per_thread_sem.h" + "synchronization/internal/pthread_waiter.h" + "synchronization/internal/pthread_waiter.cc" + "synchronization/internal/sem_waiter.h" + "synchronization/internal/sem_waiter.cc" + "synchronization/internal/stdcpp_waiter.h" + "synchronization/internal/stdcpp_waiter.cc" "synchronization/internal/thread_pool.h" - "synchronization/internal/waiter.cc" "synchronization/internal/waiter.h" + "synchronization/internal/waiter_base.h" + "synchronization/internal/waiter_base.cc" + "synchronization/internal/win32_waiter.h" + "synchronization/internal/win32_waiter.cc" "time/civil_time.cc" "time/civil_time.h" "time/clock.cc" @@ -406,17 +426,13 @@ set(ABSL_INTERNAL_DLL_FILES "types/bad_variant_access.cc" "types/bad_variant_access.h" "types/compare.h" - "types/internal/conformance_aliases.h" - "types/internal/conformance_archetype.h" - "types/internal/conformance_profile.h" - "types/internal/parentheses.h" - "types/internal/transform_args.h" "types/internal/variant.h" "types/optional.h" "types/internal/optional.h" "types/span.h" "types/internal/span.h" "types/variant.h" + "utility/internal/if_constexpr.h" "utility/utility.h" "debugging/leak_check.cc" ) @@ -448,8 +464,14 @@ set(ABSL_INTERNAL_DLL_TARGETS "container_common" "container_memory" "cord" + "cord_internal" + "cordz_functions" + "cordz_handle" + "cordz_info" + "cordz_sample_token" "core_headers" "counting_allocator" + "crc_cord_state" "crc_cpu_detect" "crc_internal" "crc32c" @@ -504,6 +526,7 @@ set(ABSL_INTERNAL_DLL_TARGETS "log_internal_structured" "log_severity" "log_structured" + "low_level_hash" "malloc_internal" "memory" "meta" @@ -555,8 +578,10 @@ set(ABSL_INTERNAL_DLL_TARGETS "stack_consumption" "stacktrace" "status" + "statusor" "str_format" "str_format_internal" + "strerror" "strings" "strings_internal" "symbolize" @@ -575,6 +600,10 @@ set(ABSL_INTERNAL_TEST_DLL_FILES "hash/hash_testing.h" "log/scoped_mock_log.cc" "log/scoped_mock_log.h" + "random/internal/chi_square.cc" + "random/internal/chi_square.h" + "random/internal/distribution_test_util.cc" + "random/internal/distribution_test_util.h" "random/internal/mock_helpers.h" "random/internal/mock_overload_set.h" "random/mocking_bit_gen.h" @@ -588,34 +617,42 @@ set(ABSL_INTERNAL_TEST_DLL_TARGETS "cordz_test_helpers" "hash_testing" "random_mocking_bit_gen" + "random_internal_distribution_test_util" "random_internal_mock_overload_set" "scoped_mock_log" ) -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 +# if _MSVC_LANG < 201703L # error "The compiler defaults or is configured for C++ < 17" # endif -#elif __cplusplus < 201700L +#elif __cplusplus < 201703L # 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) +check_cxx_source_compiles( + [==[ +#ifdef _MSC_VER +# if _MSVC_LANG < 202002L +# error "The compiler defaults or is configured for C++ < 20" +# endif +#elif __cplusplus < 202002L +# error "The compiler defaults or is configured for C++ < 20" +#endif +int main() { return 0; } +]==] + ABSL_INTERNAL_AT_LEAST_CXX20) + +if(ABSL_INTERNAL_AT_LEAST_CXX20) + set(ABSL_INTERNAL_CXX_STD_FEATURE cxx_std_20) +elseif(ABSL_INTERNAL_AT_LEAST_CXX17) set(ABSL_INTERNAL_CXX_STD_FEATURE cxx_std_17) else() set(ABSL_INTERNAL_CXX_STD_FEATURE cxx_std_14) @@ -766,7 +803,7 @@ Name: ${_dll}\n\ Description: Abseil DLL library\n\ URL: https://abseil.io/\n\ Version: ${absl_VERSION}\n\ -Libs: -L\${libdir} ${PC_LINKOPTS} $<$<NOT:$<BOOL:${ABSL_CC_LIB_IS_INTERFACE}>>:-l${_dll}>\n\ +Libs: -L\${libdir} $<$<NOT:$<BOOL:${ABSL_CC_LIB_IS_INTERFACE}>>:-l${_dll}> ${PC_LINKOPTS}\n\ Cflags: -I\${includedir}${PC_CFLAGS}\n") INSTALL(FILES "${CMAKE_BINARY_DIR}/lib/pkgconfig/${_dll}.pc" DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") @@ -785,20 +822,9 @@ Cflags: -I\${includedir}${PC_CFLAGS}\n") 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) + # compiled with a higher minimum (either because it is the compiler's + # default or explicitly requested), then Abseil requires that standard. + target_compile_features(${_dll} PUBLIC ${ABSL_INTERNAL_CXX_STD_FEATURE}) endif() install(TARGETS ${_dll} EXPORT ${PROJECT_NAME}Targets @@ -806,4 +832,6 @@ Cflags: -I\${includedir}${PC_CFLAGS}\n") LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} ) + + add_library(absl::${_dll} ALIAS ${_dll}) endfunction() diff --git a/CMake/AbseilHelpers.cmake b/CMake/AbseilHelpers.cmake index 6d059e7e..c53b3584 100644 --- a/CMake/AbseilHelpers.cmake +++ b/CMake/AbseilHelpers.cmake @@ -80,7 +80,7 @@ endif() # absl::fantastic_lib # ) # -# TODO: Implement "ALWAYSLINK" +# TODO(b/320467376): Implement "ALWAYSLINK". function(absl_cc_library) cmake_parse_arguments(ABSL_CC_LIB "DISABLE_INSTALL;PUBLIC;TESTONLY" @@ -153,55 +153,54 @@ function(absl_cc_library) # Generate a pkg-config file for every library: if(ABSL_ENABLE_INSTALL) - if(NOT ABSL_CC_LIB_TESTONLY) - if(absl_VERSION) - set(PC_VERSION "${absl_VERSION}") - else() - set(PC_VERSION "head") - endif() - if(NOT _build_type STREQUAL "dll") - set(LNK_LIB "${LNK_LIB} -labsl_${_NAME}") - endif() - foreach(dep ${ABSL_CC_LIB_DEPS}) - if(${dep} MATCHES "^absl::(.*)") - # for DLL builds many libs are not created, but add - # the pkgconfigs nevertheless, pointing to the dll. - if(_build_type STREQUAL "dll") - # hide this MATCHES in an if-clause so it doesn't overwrite - # the CMAKE_MATCH_1 from (${dep} MATCHES "^absl::(.*)") - if(NOT PC_DEPS MATCHES "abseil_dll") - # Join deps with commas. - if(PC_DEPS) - set(PC_DEPS "${PC_DEPS},") - endif() - # don't duplicate dll-dep if it exists already - set(PC_DEPS "${PC_DEPS} abseil_dll = ${PC_VERSION}") - set(LNK_LIB "${LNK_LIB} -labseil_dll") - endif() - else() + if(absl_VERSION) + set(PC_VERSION "${absl_VERSION}") + else() + set(PC_VERSION "head") + endif() + if(NOT _build_type STREQUAL "dll") + set(LNK_LIB "${LNK_LIB} -labsl_${_NAME}") + endif() + foreach(dep ${ABSL_CC_LIB_DEPS}) + if(${dep} MATCHES "^absl::(.*)") + # for DLL builds many libs are not created, but add + # the pkgconfigs nevertheless, pointing to the dll. + if(_build_type STREQUAL "dll") + # hide this MATCHES in an if-clause so it doesn't overwrite + # the CMAKE_MATCH_1 from (${dep} MATCHES "^absl::(.*)") + if(NOT PC_DEPS MATCHES "abseil_dll") # Join deps with commas. if(PC_DEPS) set(PC_DEPS "${PC_DEPS},") endif() - set(PC_DEPS "${PC_DEPS} absl_${CMAKE_MATCH_1} = ${PC_VERSION}") + # don't duplicate dll-dep if it exists already + set(PC_DEPS "${PC_DEPS} abseil_dll = ${PC_VERSION}") + set(LNK_LIB "${LNK_LIB} -labseil_dll") endif() - endif() - endforeach() - foreach(cflag ${ABSL_CC_LIB_COPTS}) - if(${cflag} MATCHES "^(-Wno|/wd)") - # These flags are needed to suppress warnings that might fire in our headers. - set(PC_CFLAGS "${PC_CFLAGS} ${cflag}") - elseif(${cflag} MATCHES "^(-W|/w[1234eo])") - # Don't impose our warnings on others. - elseif(${cflag} MATCHES "^-m") - # Don't impose CPU instruction requirements on others, as - # the code performs feature detection on runtime. else() - set(PC_CFLAGS "${PC_CFLAGS} ${cflag}") + # Join deps with commas. + if(PC_DEPS) + set(PC_DEPS "${PC_DEPS},") + endif() + set(PC_DEPS "${PC_DEPS} absl_${CMAKE_MATCH_1} = ${PC_VERSION}") endif() - endforeach() - string(REPLACE ";" " " PC_LINKOPTS "${ABSL_CC_LIB_LINKOPTS}") - FILE(GENERATE OUTPUT "${CMAKE_BINARY_DIR}/lib/pkgconfig/absl_${_NAME}.pc" CONTENT "\ + endif() + endforeach() + foreach(cflag ${ABSL_CC_LIB_COPTS}) + if(${cflag} MATCHES "^(-Wno|/wd)") + # These flags are needed to suppress warnings that might fire in our headers. + set(PC_CFLAGS "${PC_CFLAGS} ${cflag}") + elseif(${cflag} MATCHES "^(-W|/w[1234eo])") + # Don't impose our warnings on others. + elseif(${cflag} MATCHES "^-m") + # Don't impose CPU instruction requirements on others, as + # the code performs feature detection on runtime. + else() + set(PC_CFLAGS "${PC_CFLAGS} ${cflag}") + endif() + endforeach() + string(REPLACE ";" " " PC_LINKOPTS "${ABSL_CC_LIB_LINKOPTS}") + FILE(GENERATE OUTPUT "${CMAKE_BINARY_DIR}/lib/pkgconfig/absl_${_NAME}.pc" CONTENT "\ prefix=${CMAKE_INSTALL_PREFIX}\n\ exec_prefix=\${prefix}\n\ libdir=${CMAKE_INSTALL_FULL_LIBDIR}\n\ @@ -212,11 +211,10 @@ Description: Abseil ${_NAME} library\n\ URL: https://abseil.io/\n\ Version: ${PC_VERSION}\n\ Requires:${PC_DEPS}\n\ -Libs: -L\${libdir} ${PC_LINKOPTS} $<$<NOT:$<BOOL:${ABSL_CC_LIB_IS_INTERFACE}>>:${LNK_LIB}>\n\ +Libs: -L\${libdir} $<$<NOT:$<BOOL:${ABSL_CC_LIB_IS_INTERFACE}>>:${LNK_LIB}> ${PC_LINKOPTS}\n\ Cflags: -I\${includedir}${PC_CFLAGS}\n") - INSTALL(FILES "${CMAKE_BINARY_DIR}/lib/pkgconfig/absl_${_NAME}.pc" - DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") - endif() + INSTALL(FILES "${CMAKE_BINARY_DIR}/lib/pkgconfig/absl_${_NAME}.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") endif() if(NOT ABSL_CC_LIB_IS_INTERFACE) @@ -289,20 +287,9 @@ Cflags: -I\${includedir}${PC_CFLAGS}\n") 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) + # compiled with a higher standard (either because it is the compiler's + # default or explicitly requested), then Abseil requires that standard. + target_compile_features(${_NAME} PUBLIC ${ABSL_INTERNAL_CXX_STD_FEATURE}) endif() # When being installed, we lose the absl_ prefix. We want to put it back @@ -311,7 +298,7 @@ Cflags: -I\${includedir}${PC_CFLAGS}\n") if(ABSL_ENABLE_INSTALL) set_target_properties(${_NAME} PROPERTIES OUTPUT_NAME "absl_${_NAME}" - SOVERSION "2301.0.0" + SOVERSION "2401.0.0" ) endif() else() @@ -339,16 +326,11 @@ 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. - _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). + target_compile_features(${_NAME} INTERFACE ${ABSL_INTERNAL_CXX_STD_FEATURE}) endif() endif() - # TODO currently we don't install googletest alongside abseil sources, so - # installed abseil can't be tested. - if(NOT ABSL_CC_LIB_TESTONLY AND ABSL_ENABLE_INSTALL) + if(ABSL_ENABLE_INSTALL) install(TARGETS ${_NAME} EXPORT ${PROJECT_NAME}Targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} @@ -431,6 +413,10 @@ function(absl_cc_test) DEPS ${ABSL_CC_TEST_DEPS} OUTPUT ABSL_CC_TEST_DEPS ) + absl_internal_dll_targets( + DEPS ${ABSL_CC_TEST_LINKOPTS} + OUTPUT ABSL_CC_TEST_LINKOPTS + ) else() target_compile_definitions(${_NAME} PUBLIC @@ -452,18 +438,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. - _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) + target_compile_features(${_NAME} PUBLIC ${ABSL_INTERNAL_CXX_STD_FEATURE}) endif() add_test(NAME ${_NAME} COMMAND ${_NAME}) diff --git a/CMake/README.md b/CMake/README.md index 19fb327c..c7ddee64 100644 --- a/CMake/README.md +++ b/CMake/README.md @@ -170,7 +170,7 @@ And finally install: cmake --build /temporary/build/abseil-cpp --target install ``` -# CMake Option Synposis +# CMake Option Synopsis ## Enable Standard CMake Installation diff --git a/CMakeLists.txt b/CMakeLists.txt index 37d1e684..194f8708 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -33,6 +33,11 @@ if (POLICY CMP0048) cmake_policy(SET CMP0048 NEW) endif (POLICY CMP0048) +# Honor the GTest_ROOT variable if specified +if (POLICY CMP0074) + cmake_policy(SET CMP0074 NEW) +endif (POLICY CMP0074) + # option() honor variables if (POLICY CMP0077) cmake_policy(SET CMP0077 NEW) @@ -48,7 +53,12 @@ if (POLICY CMP0067) cmake_policy(SET CMP0067 NEW) endif (POLICY CMP0067) -project(absl LANGUAGES CXX VERSION 20230125) +# Allow the user to specify the CMAKE_MSVC_DEBUG_INFORMATION_FORMAT +if (POLICY CMP0141) + cmake_policy(SET CMP0141 NEW) +endif (POLICY CMP0141) + +project(absl LANGUAGES CXX VERSION 20240116) include(CTest) # Output directory is correct by default for most build setups. However, when @@ -68,7 +78,7 @@ endif() option(ABSL_PROPAGATE_CXX_STD "Use CMake C++ standard meta features (e.g. cxx_std_14) that propagate to targets that link to Abseil" OFF) # TODO: Default to ON for CMake 3.8 and greater. -if((${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.8) AND (NOT ABSL_PROPAGATE_CXX_STD)) +if(NOT ABSL_PROPAGATE_CXX_STD) message(WARNING "A future Abseil release will default ABSL_PROPAGATE_CXX_STD to ON for CMake 3.8 and up. We recommend enabling this option to ensure your project still builds correctly.") endif() @@ -176,6 +186,7 @@ endif() add_subdirectory(absl) if(ABSL_ENABLE_INSTALL) + # install as a subdirectory only install(EXPORT ${PROJECT_NAME}Targets @@ -215,20 +226,41 @@ if(ABSL_ENABLE_INSTALL) PATTERN "testdata" EXCLUDE ) + # Rewrite options.h to use the compiled ABI. file(READ "absl/base/options.h" ABSL_INTERNAL_OPTIONS_H_CONTENTS) - if (ABSL_INTERNAL_AT_LEAST_CXX17) - string(REGEX REPLACE - "#define ABSL_OPTION_USE_STD_([^ ]*) 2" - "#define ABSL_OPTION_USE_STD_\\1 1" + + # Handle features that require at least C++20. + if (ABSL_INTERNAL_AT_LEAST_CXX20) + foreach(FEATURE "ORDERING") + string(REPLACE + "#define ABSL_OPTION_USE_STD_${FEATURE} 2" + "#define ABSL_OPTION_USE_STD_${FEATURE} 1" ABSL_INTERNAL_OPTIONS_H_PINNED "${ABSL_INTERNAL_OPTIONS_H_CONTENTS}") - else() - string(REGEX REPLACE - "#define ABSL_OPTION_USE_STD_([^ ]*) 2" - "#define ABSL_OPTION_USE_STD_\\1 0" + set(ABSL_INTERNAL_OPTIONS_H_CONTENTS "${ABSL_INTERNAL_OPTIONS_H_PINNED}") + endforeach() + endif() + + # Handle features that require at least C++17. + if (ABSL_INTERNAL_AT_LEAST_CXX17) + foreach(FEATURE "ANY" "OPTIONAL" "STRING_VIEW" "VARIANT") + string(REPLACE + "#define ABSL_OPTION_USE_STD_${FEATURE} 2" + "#define ABSL_OPTION_USE_STD_${FEATURE} 1" ABSL_INTERNAL_OPTIONS_H_PINNED "${ABSL_INTERNAL_OPTIONS_H_CONTENTS}") + set(ABSL_INTERNAL_OPTIONS_H_CONTENTS "${ABSL_INTERNAL_OPTIONS_H_PINNED}") + endforeach() endif() + + # Any feature that still has the value of 2 (because it was not handled above) + # should be set to 0. + string(REGEX REPLACE + "#define ABSL_OPTION_USE_STD_([^ ]*) 2" + "#define ABSL_OPTION_USE_STD_\\1 0" + ABSL_INTERNAL_OPTIONS_H_PINNED + "${ABSL_INTERNAL_OPTIONS_H_CONTENTS}") + file(WRITE "${CMAKE_BINARY_DIR}/options-pinned.h" "${ABSL_INTERNAL_OPTIONS_H_PINNED}") install(FILES "${CMAKE_BINARY_DIR}/options-pinned.h" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9dadae93..a87254c5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -75,9 +75,9 @@ will be expected to conform to the style outlined ## Guidelines for Pull Requests -* If you are a Googler, it is preferable to first create an internal CL and - have it reviewed and submitted. The code propagation process will deliver - the change to GitHub. +* If you are a Googler, it is required that you send us a Piper CL instead of + using the GitHub pull-request process. The code propagation process will + deliver the change to GitHub. * Create **small PRs** that are narrowly focused on **addressing a single concern**. We often receive PRs that are trying to fix several things at a @@ -12,7 +12,7 @@ third_party { type: GIT value: "https://github.com/abseil/abseil-cpp" } - version: "20230125.2" - last_upgrade_date { year: 2023 month: 4 day: 23 } + version: "20240116.1" + last_upgrade_date { year: 2024 month: 2 day: 28 } license_type: NOTICE } diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 00000000..efbc88b2 --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,39 @@ +# Copyright 2024 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. + +# https://bazel.build/external/overview#bzlmod + +module( + name = "abseil-cpp", + version = "20240116.0", + compatibility_level = 1, +) + +# Only direct dependencies need to be listed below. +# Please keep the versions in sync with the versions in the WORKSPACE file. + +bazel_dep(name = "bazel_skylib", + version = "1.5.0") + +bazel_dep(name = "google_benchmark", + version = "1.8.3", + repo_name = "com_github_google_benchmark", + dev_dependency = True) + +bazel_dep(name = "googletest", + version = "1.14.0.bcr.1", + repo_name = "com_google_googletest") + +bazel_dep(name = "platforms", + version = "0.0.8") diff --git a/NOTICE b/NOTICE deleted file mode 120000 index 7a694c96..00000000 --- a/NOTICE +++ /dev/null @@ -1 +0,0 @@ -LICENSE
\ No newline at end of file diff --git a/PrivacyInfo.xcprivacy b/PrivacyInfo.xcprivacy new file mode 100644 index 00000000..6af16412 --- /dev/null +++ b/PrivacyInfo.xcprivacy @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>NSPrivacyTracking</key> + <false/> + <key>NSPrivacyCollectedDataTypes</key> + <array/> + <key>NSPrivacyTrackingDomains</key> + <array/> + <key>NSPrivacyAccessedAPITypes</key> + <array/> +</dict> +</plist> @@ -91,9 +91,6 @@ Abseil contains the following C++ library components: * [`hash`](absl/hash/) <br /> The `hash` library contains the hashing framework and default hash functor implementations for hashable types in Abseil. -* [`iterator`](absl/iterator/) - <br /> The `iterator` library contains utilities for augmenting ranges in - range-based for loops. * [`log`](absl/log/) <br /> The `log` library contains `LOG` and `CHECK` macros and facilities for writing logged messages out to disk, `stderr`, or user-extensible diff --git a/UPGRADES.md b/UPGRADES.md index 35599d08..3cac141d 100644 --- a/UPGRADES.md +++ b/UPGRADES.md @@ -1,6 +1,6 @@ # C++ Upgrade Tools -Abseil may occassionally release API-breaking changes. As noted in our +Abseil may occasionally release API-breaking changes. As noted in our [Compatibility Guidelines][compatibility-guide], we will aim to provide a tool to do the work of effecting such API-breaking changes, when absolutely necessary. @@ -20,43 +20,40 @@ load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") # GoogleTest/GoogleMock framework. Used by most unit-tests. http_archive( - name = "com_google_googletest", # 2023-01-05T19:15:29Z - sha256 = "ffa17fbc5953900994e2deec164bb8949879ea09b411e07f215bfbb1f87f4632", - strip_prefix = "googletest-1.13.0", - # Keep this URL in sync with ABSL_GOOGLETEST_COMMIT in ci/cmake_common.sh. - urls = ["https://github.com/google/googletest/archive/refs/tags/v1.13.0.zip"], + name = "com_google_googletest", + sha256 = "8ad598c73ad796e0d8280b082cebd82a630d73e73cd3c70057938a6501bba5d7", + strip_prefix = "googletest-1.14.0", + # Keep this URL in sync with ABSL_GOOGLETEST_COMMIT in ci/cmake_common.sh and + # ci/windows_msvc_cmake.bat. + urls = ["https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz"], ) # RE2 (the regular expression library used by GoogleTest) -# Note this must use a commit from the `abseil` branch of the RE2 project. -# https://github.com/google/re2/tree/abseil http_archive( name = "com_googlesource_code_re2", - sha256 = "0a890c2aa0bb05b2ce906a15efb520d0f5ad4c7d37b8db959c43772802991887", - strip_prefix = "re2-a427f10b9fb4622dd6d8643032600aa1b50fbd12", - urls = ["https://github.com/google/re2/archive/a427f10b9fb4622dd6d8643032600aa1b50fbd12.zip"], # 2022-06-09 + sha256 = "828341ad08524618a626167bd320b0c2acc97bd1c28eff693a9ea33a7ed2a85f", + strip_prefix = "re2-2023-11-01", + urls = ["https://github.com/google/re2/releases/download/2023-11-01/re2-2023-11-01.zip"], ) # Google benchmark. http_archive( - name = "com_github_google_benchmark", # 2023-01-10T16:48:17Z - sha256 = "ede6830512f21490eeea1f238f083702eb178890820c14451c1c3d69fd375b19", - strip_prefix = "benchmark-a3235d7b69c84e8c9ff8722a22b8ac5e1bc716a6", - urls = ["https://github.com/google/benchmark/archive/a3235d7b69c84e8c9ff8722a22b8ac5e1bc716a6.zip"], + name = "com_github_google_benchmark", + sha256 = "6bc180a57d23d4d9515519f92b0c83d61b05b5bab188961f36ac7b06b0d9e9ce", + strip_prefix = "benchmark-1.8.3", + urls = ["https://github.com/google/benchmark/archive/refs/tags/v1.8.3.tar.gz"], ) # Bazel Skylib. http_archive( - name = "bazel_skylib", # 2022-11-16T18:29:32Z - sha256 = "a22290c26d29d3ecca286466f7f295ac6cbe32c0a9da3a91176a90e0725e3649", - strip_prefix = "bazel-skylib-5bfcb1a684550626ce138fe0fe8f5f702b3764c3", - urls = ["https://github.com/bazelbuild/bazel-skylib/archive/5bfcb1a684550626ce138fe0fe8f5f702b3764c3.zip"], + name = "bazel_skylib", + sha256 = "cd55a062e763b9349921f0f5db8c3933288dc8ba4f76dd9416aac68acee3cb94", + urls = ["https://github.com/bazelbuild/bazel-skylib/releases/download/1.5.0/bazel-skylib-1.5.0.tar.gz"], ) # Bazel platform rules. http_archive( - name = "platforms", # 2022-11-09T19:18:22Z - sha256 = "b4a3b45dc4202e2b3e34e3bc49d2b5b37295fc23ea58d88fb9e01f3642ad9b55", - strip_prefix = "platforms-3fbc687756043fb58a407c2ea8c944bc2fe1d922", - urls = ["https://github.com/bazelbuild/platforms/archive/3fbc687756043fb58a407c2ea8c944bc2fe1d922.zip"], + name = "platforms", + sha256 = "8150406605389ececb6da07cbcb509d5637a3ab9a24bc69b1101531367d89d74", + urls = ["https://github.com/bazelbuild/platforms/releases/download/0.0.8/platforms-0.0.8.tar.gz"], ) diff --git a/absl/BUILD.bazel b/absl/BUILD.bazel index 29963ccc..14c30b38 100644 --- a/absl/BUILD.bazel +++ b/absl/BUILD.bazel @@ -12,6 +12,7 @@ # 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. +# load("@bazel_skylib//lib:selects.bzl", "selects") @@ -36,6 +37,22 @@ config_setting( ) config_setting( + name = "mingw_unspecified_compiler", + flag_values = { + "@bazel_tools//tools/cpp:compiler": "mingw", + }, + visibility = [":__subpackages__"], +) + +config_setting( + name = "mingw-gcc_compiler", + flag_values = { + "@bazel_tools//tools/cpp:compiler": "mingw-gcc", + }, + visibility = [":__subpackages__"], +) + +config_setting( name = "msvc_compiler", flag_values = { "@bazel_tools//tools/cpp:compiler": "msvc-cl", @@ -51,6 +68,17 @@ config_setting( visibility = [":__subpackages__"], ) +# x64_windows-clang-cl - used for selecting clang-cl for CI builds +platform( + name = "x64_windows-clang-cl", + constraint_values = [ + "@platforms//cpu:x86_64", + "@platforms//os:windows", + "@bazel_tools//tools/cpp:clang-cl", + ], + visibility = [":__subpackages__"], +) + config_setting( name = "osx", constraint_values = [ @@ -123,3 +151,12 @@ config_setting( }, visibility = [":__subpackages__"], ) + +selects.config_setting_group( + name = "mingw_compiler", + match_any = [ + ":mingw_unspecified_compiler", + ":mingw-gcc_compiler", + ], + visibility = [":__subpackages__"], +) diff --git a/absl/abseil.podspec.gen.py b/absl/abseil.podspec.gen.py index 63752980..c83edbfe 100755 --- a/absl/abseil.podspec.gen.py +++ b/absl/abseil.podspec.gen.py @@ -30,6 +30,9 @@ Pod::Spec.new do |s| :git => 'https://github.com/abseil/abseil-cpp.git', :tag => '${tag}', } + s.resource_bundles = { + s.module_name => 'PrivacyInfo.xcprivacy', + } s.module_name = 'absl' s.header_mappings_dir = 'absl' s.header_dir = 'absl' diff --git a/absl/algorithm/BUILD.bazel b/absl/algorithm/BUILD.bazel index 3a9ab013..ddf9e11f 100644 --- a/absl/algorithm/BUILD.bazel +++ b/absl/algorithm/BUILD.bazel @@ -21,7 +21,14 @@ load( "ABSL_TEST_COPTS", ) -package(default_visibility = ["//visibility:public"]) +package( + default_visibility = ["//visibility:public"], + features = [ + "header_modules", + "layering_check", + "parse_headers", + ], +) licenses(["notice"]) @@ -44,24 +51,11 @@ cc_test( deps = [ ":algorithm", "//absl/base:config", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) -cc_binary( - name = "algorithm_benchmark", - testonly = 1, - srcs = ["equal_benchmark.cc"], - copts = ABSL_TEST_COPTS, - linkopts = ABSL_DEFAULT_LINKOPTS, - tags = ["benchmark"], - deps = [ - ":algorithm", - "//absl/base:core_headers", - "@com_github_google_benchmark//:benchmark_main", - ], -) - cc_library( name = "container", hdrs = [ @@ -72,6 +66,7 @@ cc_library( deps = [ ":algorithm", "//absl/base:core_headers", + "//absl/base:nullability", "//absl/meta:type_traits", ], ) @@ -87,6 +82,7 @@ cc_test( "//absl/base:core_headers", "//absl/memory", "//absl/types:span", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/algorithm/CMakeLists.txt b/absl/algorithm/CMakeLists.txt index 181b49ca..5577164d 100644 --- a/absl/algorithm/CMakeLists.txt +++ b/absl/algorithm/CMakeLists.txt @@ -50,6 +50,7 @@ absl_cc_library( absl::algorithm absl::core_headers absl::meta + absl::nullability PUBLIC ) diff --git a/absl/algorithm/algorithm.h b/absl/algorithm/algorithm.h index e9b47338..59aeed7d 100644 --- a/absl/algorithm/algorithm.h +++ b/absl/algorithm/algorithm.h @@ -31,92 +31,17 @@ namespace absl { ABSL_NAMESPACE_BEGIN -namespace algorithm_internal { - -// Performs comparisons with operator==, similar to C++14's `std::equal_to<>`. -struct EqualTo { - template <typename T, typename U> - bool operator()(const T& a, const U& b) const { - return a == b; - } -}; - -template <typename InputIter1, typename InputIter2, typename Pred> -bool EqualImpl(InputIter1 first1, InputIter1 last1, InputIter2 first2, - InputIter2 last2, Pred pred, std::input_iterator_tag, - std::input_iterator_tag) { - while (true) { - if (first1 == last1) return first2 == last2; - if (first2 == last2) return false; - if (!pred(*first1, *first2)) return false; - ++first1; - ++first2; - } -} - -template <typename InputIter1, typename InputIter2, typename Pred> -bool EqualImpl(InputIter1 first1, InputIter1 last1, InputIter2 first2, - InputIter2 last2, Pred&& pred, std::random_access_iterator_tag, - std::random_access_iterator_tag) { - return (last1 - first1 == last2 - first2) && - std::equal(first1, last1, first2, std::forward<Pred>(pred)); -} - -// When we are using our own internal predicate that just applies operator==, we -// forward to the non-predicate form of std::equal. This enables an optimization -// in libstdc++ that can result in std::memcmp being used for integer types. -template <typename InputIter1, typename InputIter2> -bool EqualImpl(InputIter1 first1, InputIter1 last1, InputIter2 first2, - InputIter2 last2, algorithm_internal::EqualTo /* unused */, - std::random_access_iterator_tag, - std::random_access_iterator_tag) { - return (last1 - first1 == last2 - first2) && - std::equal(first1, last1, first2); -} - -template <typename It> -It RotateImpl(It first, It middle, It last, std::true_type) { - return std::rotate(first, middle, last); -} - -template <typename It> -It RotateImpl(It first, It middle, It last, std::false_type) { - std::rotate(first, middle, last); - return std::next(first, std::distance(middle, last)); -} - -} // namespace algorithm_internal - // equal() +// rotate() // -// Compares the equality of two ranges specified by pairs of iterators, using -// the given predicate, returning true iff for each corresponding iterator i1 -// and i2 in the first and second range respectively, pred(*i1, *i2) == true -// -// This comparison takes at most min(`last1` - `first1`, `last2` - `first2`) -// invocations of the predicate. Additionally, if InputIter1 and InputIter2 are -// both random-access iterators, and `last1` - `first1` != `last2` - `first2`, -// then the predicate is never invoked and the function returns false. +// Historical note: Abseil once provided implementations of these algorithms +// prior to their adoption in C++14. New code should prefer to use the std +// variants. // -// This is a C++11-compatible implementation of C++14 `std::equal`. See -// https://en.cppreference.com/w/cpp/algorithm/equal for more information. -template <typename InputIter1, typename InputIter2, typename Pred> -bool equal(InputIter1 first1, InputIter1 last1, InputIter2 first2, - InputIter2 last2, Pred&& pred) { - return algorithm_internal::EqualImpl( - first1, last1, first2, last2, std::forward<Pred>(pred), - typename std::iterator_traits<InputIter1>::iterator_category{}, - typename std::iterator_traits<InputIter2>::iterator_category{}); -} - -// Overload of equal() that performs comparison of two ranges specified by pairs -// of iterators using operator==. -template <typename InputIter1, typename InputIter2> -bool equal(InputIter1 first1, InputIter1 last1, InputIter2 first2, - InputIter2 last2) { - return absl::equal(first1, last1, first2, last2, - algorithm_internal::EqualTo{}); -} +// See the documentation for the STL <algorithm> header for more information: +// https://en.cppreference.com/w/cpp/header/algorithm +using std::equal; +using std::rotate; // linear_search() // @@ -133,26 +58,6 @@ bool linear_search(InputIterator first, InputIterator last, return std::find(first, last, value) != last; } -// rotate() -// -// Performs a left rotation on a range of elements (`first`, `last`) such that -// `middle` is now the first element. `rotate()` returns an iterator pointing to -// the first element before rotation. This function is exactly the same as -// `std::rotate`, but fixes a bug in gcc -// <= 4.9 where `std::rotate` returns `void` instead of an iterator. -// -// The complexity of this algorithm is the same as that of `std::rotate`, but if -// `ForwardIterator` is not a random-access iterator, then `absl::rotate` -// performs an additional pass over the range to construct the return value. -template <typename ForwardIterator> -ForwardIterator rotate(ForwardIterator first, ForwardIterator middle, - ForwardIterator last) { - return algorithm_internal::RotateImpl( - first, middle, last, - std::is_same<decltype(std::rotate(first, middle, last)), - ForwardIterator>()); -} - ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/algorithm/algorithm_test.cc b/absl/algorithm/algorithm_test.cc index d18df024..e6ee4695 100644 --- a/absl/algorithm/algorithm_test.cc +++ b/absl/algorithm/algorithm_test.cc @@ -24,137 +24,6 @@ namespace { -TEST(EqualTest, DefaultComparisonRandomAccess) { - std::vector<int> v1{1, 2, 3}; - std::vector<int> v2 = v1; - std::vector<int> v3 = {1, 2}; - std::vector<int> v4 = {1, 2, 4}; - - EXPECT_TRUE(absl::equal(v1.begin(), v1.end(), v2.begin(), v2.end())); - EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), v3.begin(), v3.end())); - EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), v4.begin(), v4.end())); -} - -TEST(EqualTest, DefaultComparison) { - std::list<int> lst1{1, 2, 3}; - std::list<int> lst2 = lst1; - std::list<int> lst3{1, 2}; - std::list<int> lst4{1, 2, 4}; - - EXPECT_TRUE(absl::equal(lst1.begin(), lst1.end(), lst2.begin(), lst2.end())); - EXPECT_FALSE(absl::equal(lst1.begin(), lst1.end(), lst3.begin(), lst3.end())); - EXPECT_FALSE(absl::equal(lst1.begin(), lst1.end(), lst4.begin(), lst4.end())); -} - -TEST(EqualTest, EmptyRange) { - std::vector<int> v1{1, 2, 3}; - std::vector<int> empty1; - std::vector<int> empty2; - - // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105705 -#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wnonnull" -#endif - EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), empty1.begin(), empty1.end())); -#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0) -#pragma GCC diagnostic pop -#endif - EXPECT_FALSE(absl::equal(empty1.begin(), empty1.end(), v1.begin(), v1.end())); - EXPECT_TRUE( - absl::equal(empty1.begin(), empty1.end(), empty2.begin(), empty2.end())); -} - -TEST(EqualTest, MixedIterTypes) { - std::vector<int> v1{1, 2, 3}; - std::list<int> lst1{v1.begin(), v1.end()}; - std::list<int> lst2{1, 2, 4}; - std::list<int> lst3{1, 2}; - - EXPECT_TRUE(absl::equal(v1.begin(), v1.end(), lst1.begin(), lst1.end())); - EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), lst2.begin(), lst2.end())); - EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), lst3.begin(), lst3.end())); -} - -TEST(EqualTest, MixedValueTypes) { - std::vector<int> v1{1, 2, 3}; - std::vector<char> v2{1, 2, 3}; - std::vector<char> v3{1, 2}; - std::vector<char> v4{1, 2, 4}; - - EXPECT_TRUE(absl::equal(v1.begin(), v1.end(), v2.begin(), v2.end())); - EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), v3.begin(), v3.end())); - EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), v4.begin(), v4.end())); -} - -TEST(EqualTest, WeirdIterators) { - std::vector<bool> v1{true, false}; - std::vector<bool> v2 = v1; - std::vector<bool> v3{true}; - std::vector<bool> v4{true, true, true}; - - EXPECT_TRUE(absl::equal(v1.begin(), v1.end(), v2.begin(), v2.end())); - EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), v3.begin(), v3.end())); - EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), v4.begin(), v4.end())); -} - -TEST(EqualTest, CustomComparison) { - int n[] = {1, 2, 3, 4}; - std::vector<int*> v1{&n[0], &n[1], &n[2]}; - std::vector<int*> v2 = v1; - std::vector<int*> v3{&n[0], &n[1], &n[3]}; - std::vector<int*> v4{&n[0], &n[1]}; - - auto eq = [](int* a, int* b) { return *a == *b; }; - - EXPECT_TRUE(absl::equal(v1.begin(), v1.end(), v2.begin(), v2.end(), eq)); - EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), v3.begin(), v3.end(), eq)); - EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), v4.begin(), v4.end(), eq)); -} - -TEST(EqualTest, MoveOnlyPredicate) { - std::vector<int> v1{1, 2, 3}; - std::vector<int> v2{4, 5, 6}; - - // move-only equality predicate - struct Eq { - Eq() = default; - Eq(Eq &&) = default; - Eq(const Eq &) = delete; - Eq &operator=(const Eq &) = delete; - bool operator()(const int a, const int b) const { return a == b; } - }; - - EXPECT_TRUE(absl::equal(v1.begin(), v1.end(), v1.begin(), v1.end(), Eq())); - EXPECT_FALSE(absl::equal(v1.begin(), v1.end(), v2.begin(), v2.end(), Eq())); -} - -struct CountingTrivialPred { - int* count; - bool operator()(int, int) const { - ++*count; - return true; - } -}; - -TEST(EqualTest, RandomAccessComplexity) { - std::vector<int> v1{1, 1, 3}; - std::vector<int> v2 = v1; - std::vector<int> v3{1, 2}; - - do { - int count = 0; - absl::equal(v1.begin(), v1.end(), v2.begin(), v2.end(), - CountingTrivialPred{&count}); - EXPECT_LE(count, 3); - } while (std::next_permutation(v2.begin(), v2.end())); - - int count = 0; - absl::equal(v1.begin(), v1.end(), v3.begin(), v3.end(), - CountingTrivialPred{&count}); - EXPECT_EQ(count, 0); -} - class LinearSearchTest : public testing::Test { protected: LinearSearchTest() : container_{1, 2, 3} {} @@ -178,14 +47,4 @@ TEST_F(LinearSearchTest, linear_searchConst) { absl::linear_search(const_container->begin(), const_container->end(), 4)); } -TEST(RotateTest, Rotate) { - std::vector<int> v{0, 1, 2, 3, 4}; - EXPECT_EQ(*absl::rotate(v.begin(), v.begin() + 2, v.end()), 0); - EXPECT_THAT(v, testing::ElementsAreArray({2, 3, 4, 0, 1})); - - std::list<int> l{0, 1, 2, 3, 4}; - EXPECT_EQ(*absl::rotate(l.begin(), std::next(l.begin(), 3), l.end()), 0); - EXPECT_THAT(l, testing::ElementsAreArray({3, 4, 0, 1, 2})); -} - } // namespace diff --git a/absl/algorithm/container.h b/absl/algorithm/container.h index c7782d4f..c7bafae1 100644 --- a/absl/algorithm/container.h +++ b/absl/algorithm/container.h @@ -52,6 +52,7 @@ #include "absl/algorithm/algorithm.h" #include "absl/base/macros.h" +#include "absl/base/nullability.h" #include "absl/meta/type_traits.h" namespace absl { @@ -116,18 +117,6 @@ template <class Key, class Hash, class KeyEqual, class Allocator> struct IsUnorderedContainer<std::unordered_set<Key, Hash, KeyEqual, Allocator>> : std::true_type {}; -// container_algorithm_internal::c_size. It is meant for internal use only. - -template <class C> -auto c_size(C& c) -> decltype(c.size()) { - return c.size(); -} - -template <class T, std::size_t N> -constexpr std::size_t c_size(T (&)[N]) { - return N; -} - } // namespace container_algorithm_internal // PUBLIC API @@ -348,20 +337,10 @@ container_algorithm_internal::ContainerDifferenceType<const C> c_count_if( template <typename C1, typename C2> container_algorithm_internal::ContainerIterPairType<C1, C2> c_mismatch(C1& c1, C2& c2) { - auto first1 = container_algorithm_internal::c_begin(c1); - auto last1 = container_algorithm_internal::c_end(c1); - auto first2 = container_algorithm_internal::c_begin(c2); - auto last2 = container_algorithm_internal::c_end(c2); - - for (; first1 != last1 && first2 != last2; ++first1, (void)++first2) { - // Negates equality because Cpp17EqualityComparable doesn't require clients - // to overload both `operator==` and `operator!=`. - if (!(*first1 == *first2)) { - break; - } - } - - return std::make_pair(first1, first2); + return std::mismatch(container_algorithm_internal::c_begin(c1), + container_algorithm_internal::c_end(c1), + container_algorithm_internal::c_begin(c2), + container_algorithm_internal::c_end(c2)); } // Overload of c_mismatch() for using a predicate evaluation other than `==` as @@ -370,56 +349,33 @@ container_algorithm_internal::ContainerIterPairType<C1, C2> c_mismatch(C1& c1, template <typename C1, typename C2, typename BinaryPredicate> container_algorithm_internal::ContainerIterPairType<C1, C2> c_mismatch( C1& c1, C2& c2, BinaryPredicate pred) { - auto first1 = container_algorithm_internal::c_begin(c1); - auto last1 = container_algorithm_internal::c_end(c1); - auto first2 = container_algorithm_internal::c_begin(c2); - auto last2 = container_algorithm_internal::c_end(c2); - - for (; first1 != last1 && first2 != last2; ++first1, (void)++first2) { - if (!pred(*first1, *first2)) { - break; - } - } - - return std::make_pair(first1, first2); + return std::mismatch(container_algorithm_internal::c_begin(c1), + container_algorithm_internal::c_end(c1), + container_algorithm_internal::c_begin(c2), + container_algorithm_internal::c_end(c2), pred); } // c_equal() // // Container-based version of the <algorithm> `std::equal()` function to // test whether two containers are equal. -// -// NOTE: the semantics of c_equal() are slightly different than those of -// equal(): while the latter iterates over the second container only up to the -// size of the first container, c_equal() also checks whether the container -// sizes are equal. This better matches expectations about c_equal() based on -// its signature. -// -// Example: -// vector v1 = <1, 2, 3>; -// vector v2 = <1, 2, 3, 4>; -// equal(std::begin(v1), std::end(v1), std::begin(v2)) returns true -// c_equal(v1, v2) returns false - template <typename C1, typename C2> bool c_equal(const C1& c1, const C2& c2) { - return ((container_algorithm_internal::c_size(c1) == - container_algorithm_internal::c_size(c2)) && - std::equal(container_algorithm_internal::c_begin(c1), - container_algorithm_internal::c_end(c1), - container_algorithm_internal::c_begin(c2))); + return std::equal(container_algorithm_internal::c_begin(c1), + container_algorithm_internal::c_end(c1), + container_algorithm_internal::c_begin(c2), + container_algorithm_internal::c_end(c2)); } // Overload of c_equal() for using a predicate evaluation other than `==` as // the function's test condition. template <typename C1, typename C2, typename BinaryPredicate> bool c_equal(const C1& c1, const C2& c2, BinaryPredicate&& pred) { - return ((container_algorithm_internal::c_size(c1) == - container_algorithm_internal::c_size(c2)) && - std::equal(container_algorithm_internal::c_begin(c1), - container_algorithm_internal::c_end(c1), - container_algorithm_internal::c_begin(c2), - std::forward<BinaryPredicate>(pred))); + return std::equal(container_algorithm_internal::c_begin(c1), + container_algorithm_internal::c_end(c1), + container_algorithm_internal::c_begin(c2), + container_algorithm_internal::c_end(c2), + std::forward<BinaryPredicate>(pred)); } // c_is_permutation() @@ -428,20 +384,20 @@ bool c_equal(const C1& c1, const C2& c2, BinaryPredicate&& pred) { // to test whether a container is a permutation of another. template <typename C1, typename C2> bool c_is_permutation(const C1& c1, const C2& c2) { - using std::begin; - using std::end; - return c1.size() == c2.size() && - std::is_permutation(begin(c1), end(c1), begin(c2)); + return std::is_permutation(container_algorithm_internal::c_begin(c1), + container_algorithm_internal::c_end(c1), + container_algorithm_internal::c_begin(c2), + container_algorithm_internal::c_end(c2)); } // Overload of c_is_permutation() for using a predicate evaluation other than // `==` as the function's test condition. template <typename C1, typename C2, typename BinaryPredicate> bool c_is_permutation(const C1& c1, const C2& c2, BinaryPredicate&& pred) { - using std::begin; - using std::end; - return c1.size() == c2.size() && - std::is_permutation(begin(c1), end(c1), begin(c2), + return std::is_permutation(container_algorithm_internal::c_begin(c1), + container_algorithm_internal::c_end(c1), + container_algorithm_internal::c_begin(c2), + container_algorithm_internal::c_end(c2), std::forward<BinaryPredicate>(pred)); } @@ -818,6 +774,36 @@ void c_shuffle(RandomAccessContainer& c, UniformRandomBitGenerator&& gen) { std::forward<UniformRandomBitGenerator>(gen)); } +// c_sample() +// +// Container-based version of the <algorithm> `std::sample()` function to +// randomly sample elements from the container without replacement using a +// `gen()` uniform random number generator and write them to an iterator range. +template <typename C, typename OutputIterator, typename Distance, + typename UniformRandomBitGenerator> +OutputIterator c_sample(const C& c, OutputIterator result, Distance n, + UniformRandomBitGenerator&& gen) { +#if defined(__cpp_lib_sample) && __cpp_lib_sample >= 201603L + return std::sample(container_algorithm_internal::c_begin(c), + container_algorithm_internal::c_end(c), result, n, + std::forward<UniformRandomBitGenerator>(gen)); +#else + // Fall back to a stable selection-sampling implementation. + auto first = container_algorithm_internal::c_begin(c); + Distance unsampled_elements = c_distance(c); + n = (std::min)(n, unsampled_elements); + for (; n != 0; ++first) { + Distance r = + std::uniform_int_distribution<Distance>(0, --unsampled_elements)(gen); + if (r < n) { + *result++ = *first; + --n; + } + } + return result; +#endif +} + //------------------------------------------------------------------------------ // <algorithm> Partition functions //------------------------------------------------------------------------------ @@ -1131,7 +1117,7 @@ c_equal_range(Sequence& sequence, const T& value, LessThan&& comp) { // to test if any element in the sorted container contains a value equivalent to // 'value'. template <typename Sequence, typename T> -bool c_binary_search(Sequence&& sequence, const T& value) { +bool c_binary_search(const Sequence& sequence, const T& value) { return std::binary_search(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), value); @@ -1140,7 +1126,8 @@ bool c_binary_search(Sequence&& sequence, const T& value) { // Overload of c_binary_search() for performing a `comp` comparison other than // the default `operator<`. template <typename Sequence, typename T, typename LessThan> -bool c_binary_search(Sequence&& sequence, const T& value, LessThan&& comp) { +bool c_binary_search(const Sequence& sequence, const T& value, + LessThan&& comp) { return std::binary_search(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), value, std::forward<LessThan>(comp)); @@ -1656,7 +1643,7 @@ bool c_prev_permutation(C& c, LessThan&& comp) { // // Container-based version of the <numeric> `std::iota()` function // to compute successive values of `value`, as if incremented with `++value` -// after each element is written. and write them to the container. +// after each element is written, and write them to the container. template <typename Sequence, typename T> void c_iota(Sequence& sequence, const T& value) { std::iota(container_algorithm_internal::c_begin(sequence), diff --git a/absl/algorithm/container_test.cc b/absl/algorithm/container_test.cc index 0fbc7773..c01f5fc0 100644 --- a/absl/algorithm/container_test.cc +++ b/absl/algorithm/container_test.cc @@ -14,6 +14,7 @@ #include "absl/algorithm/container.h" +#include <algorithm> #include <functional> #include <initializer_list> #include <iterator> @@ -40,8 +41,10 @@ using ::testing::Each; using ::testing::ElementsAre; using ::testing::Gt; using ::testing::IsNull; +using ::testing::IsSubsetOf; using ::testing::Lt; using ::testing::Pointee; +using ::testing::SizeIs; using ::testing::Truly; using ::testing::UnorderedElementsAre; @@ -963,12 +966,29 @@ TEST(MutatingTest, RotateCopy) { EXPECT_THAT(actual, ElementsAre(3, 4, 1, 2, 5)); } +template <typename T> +T RandomlySeededPrng() { + std::random_device rdev; + std::seed_seq::result_type data[T::state_size]; + std::generate_n(data, T::state_size, std::ref(rdev)); + std::seed_seq prng_seed(data, data + T::state_size); + return T(prng_seed); +} + TEST(MutatingTest, Shuffle) { std::vector<int> actual = {1, 2, 3, 4, 5}; - absl::c_shuffle(actual, std::random_device()); + absl::c_shuffle(actual, RandomlySeededPrng<std::mt19937_64>()); EXPECT_THAT(actual, UnorderedElementsAre(1, 2, 3, 4, 5)); } +TEST(MutatingTest, Sample) { + std::vector<int> actual; + absl::c_sample(std::vector<int>{1, 2, 3, 4, 5}, std::back_inserter(actual), 3, + RandomlySeededPrng<std::mt19937_64>()); + EXPECT_THAT(actual, IsSubsetOf({1, 2, 3, 4, 5})); + EXPECT_THAT(actual, SizeIs(3)); +} + TEST(MutatingTest, PartialSort) { std::vector<int> sequence{5, 3, 42, 0}; absl::c_partial_sort(sequence, sequence.begin() + 2); diff --git a/absl/algorithm/equal_benchmark.cc b/absl/algorithm/equal_benchmark.cc deleted file mode 100644 index 948cd65c..00000000 --- a/absl/algorithm/equal_benchmark.cc +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright 2017 The Abseil Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// 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 <cstdint> -#include <cstring> - -#include "absl/algorithm/algorithm.h" -#include "benchmark/benchmark.h" - -namespace { - -// The range of sequence sizes to benchmark. -constexpr int kMinBenchmarkSize = 1024; -constexpr int kMaxBenchmarkSize = 8 * 1024 * 1024; - -// A user-defined type for use in equality benchmarks. Note that we expect -// std::memcmp to win for this type: libstdc++'s std::equal only defers to -// memcmp for integral types. This is because it is not straightforward to -// guarantee that std::memcmp would produce a result "as-if" compared by -// operator== for other types (example gotchas: NaN floats, structs with -// padding). -struct EightBits { - explicit EightBits(int /* unused */) : data(0) {} - bool operator==(const EightBits& rhs) const { return data == rhs.data; } - uint8_t data; -}; - -template <typename T> -void BM_absl_equal_benchmark(benchmark::State& state) { - std::vector<T> xs(state.range(0), T(0)); - std::vector<T> ys = xs; - while (state.KeepRunning()) { - const bool same = absl::equal(xs.begin(), xs.end(), ys.begin(), ys.end()); - benchmark::DoNotOptimize(same); - } -} - -template <typename T> -void BM_std_equal_benchmark(benchmark::State& state) { - std::vector<T> xs(state.range(0), T(0)); - std::vector<T> ys = xs; - while (state.KeepRunning()) { - const bool same = std::equal(xs.begin(), xs.end(), ys.begin()); - benchmark::DoNotOptimize(same); - } -} - -template <typename T> -void BM_memcmp_benchmark(benchmark::State& state) { - std::vector<T> xs(state.range(0), T(0)); - std::vector<T> ys = xs; - while (state.KeepRunning()) { - const bool same = - std::memcmp(xs.data(), ys.data(), xs.size() * sizeof(T)) == 0; - benchmark::DoNotOptimize(same); - } -} - -// The expectation is that the compiler should be able to elide the equality -// comparison altogether for sufficiently simple types. -template <typename T> -void BM_absl_equal_self_benchmark(benchmark::State& state) { - std::vector<T> xs(state.range(0), T(0)); - while (state.KeepRunning()) { - const bool same = absl::equal(xs.begin(), xs.end(), xs.begin(), xs.end()); - benchmark::DoNotOptimize(same); - } -} - -BENCHMARK_TEMPLATE(BM_absl_equal_benchmark, uint8_t) - ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); -BENCHMARK_TEMPLATE(BM_std_equal_benchmark, uint8_t) - ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); -BENCHMARK_TEMPLATE(BM_memcmp_benchmark, uint8_t) - ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); -BENCHMARK_TEMPLATE(BM_absl_equal_self_benchmark, uint8_t) - ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); - -BENCHMARK_TEMPLATE(BM_absl_equal_benchmark, uint16_t) - ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); -BENCHMARK_TEMPLATE(BM_std_equal_benchmark, uint16_t) - ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); -BENCHMARK_TEMPLATE(BM_memcmp_benchmark, uint16_t) - ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); -BENCHMARK_TEMPLATE(BM_absl_equal_self_benchmark, uint16_t) - ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); - -BENCHMARK_TEMPLATE(BM_absl_equal_benchmark, uint32_t) - ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); -BENCHMARK_TEMPLATE(BM_std_equal_benchmark, uint32_t) - ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); -BENCHMARK_TEMPLATE(BM_memcmp_benchmark, uint32_t) - ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); -BENCHMARK_TEMPLATE(BM_absl_equal_self_benchmark, uint32_t) - ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); - -BENCHMARK_TEMPLATE(BM_absl_equal_benchmark, uint64_t) - ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); -BENCHMARK_TEMPLATE(BM_std_equal_benchmark, uint64_t) - ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); -BENCHMARK_TEMPLATE(BM_memcmp_benchmark, uint64_t) - ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); -BENCHMARK_TEMPLATE(BM_absl_equal_self_benchmark, uint64_t) - ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); - -BENCHMARK_TEMPLATE(BM_absl_equal_benchmark, EightBits) - ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); -BENCHMARK_TEMPLATE(BM_std_equal_benchmark, EightBits) - ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); -BENCHMARK_TEMPLATE(BM_memcmp_benchmark, EightBits) - ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); -BENCHMARK_TEMPLATE(BM_absl_equal_self_benchmark, EightBits) - ->Range(kMinBenchmarkSize, kMaxBenchmarkSize); - -} // namespace diff --git a/absl/base/BUILD.bazel b/absl/base/BUILD.bazel index ded26d6a..0eb735da 100644 --- a/absl/base/BUILD.bazel +++ b/absl/base/BUILD.bazel @@ -21,7 +21,14 @@ load( "ABSL_TEST_COPTS", ) -package(default_visibility = ["//visibility:public"]) +package( + default_visibility = ["//visibility:public"], + features = [ + "header_modules", + "layering_check", + "parse_headers", + ], +) licenses(["notice"]) @@ -63,6 +70,26 @@ cc_library( ) cc_library( + name = "no_destructor", + hdrs = ["no_destructor.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [":config"], +) + +cc_library( + name = "nullability", + srcs = ["internal/nullability_impl.h"], + hdrs = ["nullability.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":core_headers", + "//absl/meta:type_traits", + ], +) + +cc_library( name = "raw_logging_internal", srcs = ["internal/raw_logging.cc"], hdrs = ["internal/raw_logging.h"], @@ -148,9 +175,6 @@ cc_library( cc_library( name = "core_headers", - srcs = [ - "internal/thread_annotations.h", - ], hdrs = [ "attributes.h", "const_init.h", @@ -246,6 +270,10 @@ cc_library( "//absl:clang-cl_compiler": [ "-DEFAULTLIB:advapi32.lib", ], + "//absl:mingw_compiler": [ + "-DEFAULTLIB:advapi32.lib", + "-ladvapi32", + ], "//absl:wasm": [], "//conditions:default": ["-pthread"], }) + ABSL_DEFAULT_LINKOPTS, @@ -257,6 +285,7 @@ cc_library( ":cycleclock_internal", ":dynamic_annotations", ":log_severity", + ":nullability", ":raw_logging_internal", ":spinlock_wait", "//absl/meta:type_traits", @@ -286,6 +315,7 @@ cc_test( ":atomic_hook", ":atomic_hook_test_helper", ":core_headers", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -301,6 +331,7 @@ cc_test( deps = [ ":base", ":core_headers", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -328,6 +359,7 @@ cc_test( deps = [ ":config", ":throw_delegate", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -341,6 +373,7 @@ cc_test( deps = [ ":errno_saver", ":strerror", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -364,7 +397,9 @@ cc_library( name = "pretty_function", hdrs = ["internal/pretty_function.h"], linkopts = ABSL_DEFAULT_LINKOPTS, - visibility = ["//absl:__subpackages__"], + visibility = [ + "//absl:__subpackages__", + ], ) cc_library( @@ -393,6 +428,7 @@ cc_test( deps = [ ":exception_safety_testing", "//absl/memory", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -410,6 +446,7 @@ cc_test( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":base_internal", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -424,6 +461,7 @@ cc_test( ":base_internal", "//absl/memory", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -462,6 +500,7 @@ cc_test( ":config", ":core_headers", "//absl/synchronization", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -478,6 +517,7 @@ cc_library( deps = [ ":base", ":base_internal", + ":no_destructor", ":raw_logging_internal", "//absl/synchronization", "@com_github_google_benchmark//:benchmark_main", @@ -509,6 +549,7 @@ cc_library( ":base", ":config", ":core_headers", + ":nullability", ], ) @@ -519,6 +560,7 @@ cc_test( deps = [ ":config", ":endian", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -531,6 +573,7 @@ cc_test( deps = [ ":config", "//absl/synchronization:thread_pool", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -544,6 +587,47 @@ cc_test( ":base", ":core_headers", "//absl/synchronization", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "no_destructor_test", + srcs = ["no_destructor_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":config", + ":no_destructor", + ":raw_logging_internal", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) + +cc_binary( + name = "no_destructor_benchmark", + testonly = 1, + srcs = ["no_destructor_benchmark.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = ["benchmark"], + visibility = ["//visibility:private"], + deps = [ + ":no_destructor", + ":raw_logging_internal", + "@com_github_google_benchmark//:benchmark_main", + ], +) + +cc_test( + name = "nullability_test", + srcs = ["nullability_test.cc"], + deps = [ + ":core_headers", + ":nullability", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -556,6 +640,7 @@ cc_test( deps = [ ":raw_logging_internal", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -569,6 +654,7 @@ cc_test( deps = [ ":base", "//absl/synchronization", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -602,6 +688,7 @@ cc_test( ":base", ":core_headers", "//absl/synchronization", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -617,6 +704,7 @@ cc_test( ":base", "//absl/synchronization", "@com_github_google_benchmark//:benchmark_main", + "@com_google_googletest//:gtest", ], ) @@ -643,6 +731,7 @@ cc_test( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":scoped_set_env", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -658,6 +747,7 @@ cc_test( "//absl/flags:flag_internal", "//absl/flags:marshalling", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -687,6 +777,7 @@ cc_test( deps = [ ":strerror", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -726,31 +817,35 @@ cc_test( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":fast_type_id", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) cc_library( name = "prefetch", - hdrs = ["internal/prefetch.h"], + hdrs = [ + "prefetch.h", + ], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, - visibility = [ - "//absl:__subpackages__", - ], deps = [ ":config", + ":core_headers", ], ) cc_test( name = "prefetch_test", size = "small", - srcs = ["internal/prefetch_test.cc"], + srcs = [ + "prefetch_test.cc", + ], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":prefetch", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -765,6 +860,7 @@ cc_test( deps = [ ":core_headers", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -778,6 +874,7 @@ cc_test( deps = [ ":core_headers", "//absl/types:optional", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/base/CMakeLists.txt b/absl/base/CMakeLists.txt index 26e2b48a..4cfc2285 100644 --- a/absl/base/CMakeLists.txt +++ b/absl/base/CMakeLists.txt @@ -49,11 +49,50 @@ absl_cc_library( SRCS "log_severity.cc" DEPS + absl::config + absl::core_headers + COPTS + ${ABSL_DEFAULT_COPTS} +) + +absl_cc_library( + NAME + no_destructor + HDRS + "no_destructor.h" + DEPS + absl::config + COPTS + ${ABSL_DEFAULT_COPTS} +) + +absl_cc_library( + NAME + nullability + HDRS + "nullability.h" + SRCS + "internal/nullability_impl.h" + DEPS absl::core_headers + absl::type_traits COPTS ${ABSL_DEFAULT_COPTS} ) +absl_cc_test( + NAME + nullability_test + SRCS + "nullability_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::core_headers + absl::nullability + GTest::gtest_main +) + # Internal-only target, do not depend on directly. absl_cc_library( NAME @@ -128,7 +167,6 @@ absl_cc_library( "optimization.h" "port.h" "thread_annotations.h" - "internal/thread_annotations.h" COPTS ${ABSL_DEFAULT_COPTS} DEPS @@ -209,6 +247,7 @@ absl_cc_library( absl::core_headers absl::dynamic_annotations absl::log_severity + absl::nullability absl::raw_logging_internal absl::spinlock_wait absl::type_traits @@ -437,6 +476,7 @@ absl_cc_library( absl::base absl::config absl::core_headers + absl::nullability PUBLIC ) @@ -483,6 +523,20 @@ absl_cc_test( absl_cc_test( NAME + no_destructor_test + SRCS + "no_destructor_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::no_destructor + absl::config + absl::raw_logging_internal + GTest::gtest_main +) + +absl_cc_test( + NAME raw_logging_test SRCS "raw_logging_test.cc" @@ -645,25 +699,25 @@ absl_cc_test( GTest::gtest_main ) -# Internal-only target, do not depend on directly. absl_cc_library( NAME prefetch HDRS - "internal/prefetch.h" + "prefetch.h" COPTS ${ABSL_DEFAULT_COPTS} LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS absl::config + absl::core_headers ) absl_cc_test( NAME prefetch_test SRCS - "internal/prefetch_test.cc" + "prefetch_test.cc" COPTS ${ABSL_TEST_COPTS} DEPS diff --git a/absl/base/attributes.h b/absl/base/attributes.h index b7826e77..d4f67a12 100644 --- a/absl/base/attributes.h +++ b/absl/base/attributes.h @@ -211,11 +211,20 @@ // out of bounds or does other scary things with memory. // NOTE: GCC supports AddressSanitizer(asan) since 4.8. // https://gcc.gnu.org/gcc-4.8/changes.html -#if ABSL_HAVE_ATTRIBUTE(no_sanitize_address) +#if defined(ABSL_HAVE_ADDRESS_SANITIZER) && \ + ABSL_HAVE_ATTRIBUTE(no_sanitize_address) #define ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS __attribute__((no_sanitize_address)) -#elif defined(_MSC_VER) && _MSC_VER >= 1928 +#elif defined(ABSL_HAVE_ADDRESS_SANITIZER) && defined(_MSC_VER) && \ + _MSC_VER >= 1928 // https://docs.microsoft.com/en-us/cpp/cpp/no-sanitize-address #define ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS __declspec(no_sanitize_address) +#elif defined(ABSL_HAVE_HWADDRESS_SANITIZER) && ABSL_HAVE_ATTRIBUTE(no_sanitize) +// HWAddressSanitizer is a sanitizer similar to AddressSanitizer, which uses CPU +// features to detect similar bugs with less CPU and memory overhead. +// NOTE: GCC supports HWAddressSanitizer(hwasan) since 11. +// https://gcc.gnu.org/gcc-11/changes.html +#define ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS \ + __attribute__((no_sanitize("hwaddress"))) #else #define ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS #endif @@ -265,7 +274,7 @@ // // Tells the ControlFlowIntegrity sanitizer to not instrument a given function. // See https://clang.llvm.org/docs/ControlFlowIntegrity.html for details. -#if ABSL_HAVE_ATTRIBUTE(no_sanitize) +#if ABSL_HAVE_ATTRIBUTE(no_sanitize) && defined(__llvm__) #define ABSL_ATTRIBUTE_NO_SANITIZE_CFI __attribute__((no_sanitize("cfi"))) #else #define ABSL_ATTRIBUTE_NO_SANITIZE_CFI @@ -322,8 +331,8 @@ // This functionality is supported by GNU linker. #ifndef ABSL_ATTRIBUTE_SECTION_VARIABLE #ifdef _AIX -// __attribute__((section(#name))) on AIX is achived by using the `.csect` psudo -// op which includes an additional integer as part of its syntax indcating +// __attribute__((section(#name))) on AIX is achieved by using the `.csect` +// psudo op which includes an additional integer as part of its syntax indcating // alignment. If data fall under different alignments then you might get a // compilation error indicating a `Section type conflict`. #define ABSL_ATTRIBUTE_SECTION_VARIABLE(name) @@ -676,6 +685,28 @@ #define ABSL_DEPRECATED(message) #endif +// When deprecating Abseil code, it is sometimes necessary to turn off the +// warning within Abseil, until the deprecated code is actually removed. The +// deprecated code can be surrounded with these directives to achieve that +// result. +// +// class ABSL_DEPRECATED("Use Bar instead") Foo; +// +// ABSL_INTERNAL_DISABLE_DEPRECATED_DECLARATION_WARNING +// Baz ComputeBazFromFoo(Foo f); +// ABSL_INTERNAL_RESTORE_DEPRECATED_DECLARATION_WARNING +#if defined(__GNUC__) || defined(__clang__) +// Clang also supports these GCC pragmas. +#define ABSL_INTERNAL_DISABLE_DEPRECATED_DECLARATION_WARNING \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wdeprecated-declarations\"") +#define ABSL_INTERNAL_RESTORE_DEPRECATED_DECLARATION_WARNING \ + _Pragma("GCC diagnostic pop") +#else +#define ABSL_INTERNAL_DISABLE_DEPRECATED_DECLARATION_WARNING +#define ABSL_INTERNAL_RESTORE_DEPRECATED_DECLARATION_WARNING +#endif // defined(__GNUC__) || defined(__clang__) + // ABSL_CONST_INIT // // A variable declaration annotated with the `ABSL_CONST_INIT` attribute will @@ -716,9 +747,52 @@ #define ABSL_CONST_INIT #endif -// These annotations are not available yet due to fear of breaking code. -#define ABSL_ATTRIBUTE_PURE_FUNCTION -#define ABSL_ATTRIBUTE_CONST_FUNCTION +// ABSL_ATTRIBUTE_PURE_FUNCTION +// +// ABSL_ATTRIBUTE_PURE_FUNCTION is used to annotate declarations of "pure" +// functions. A function is pure if its return value is only a function of its +// arguments. The pure attribute prohibits a function from modifying the state +// of the program that is observable by means other than inspecting the +// function's return value. Declaring such functions with the pure attribute +// allows the compiler to avoid emitting some calls in repeated invocations of +// the function with the same argument values. +// +// Example: +// +// ABSL_ATTRIBUTE_PURE_FUNCTION std::string FormatTime(Time t); +#if ABSL_HAVE_CPP_ATTRIBUTE(gnu::pure) +#define ABSL_ATTRIBUTE_PURE_FUNCTION [[gnu::pure]] +#elif ABSL_HAVE_ATTRIBUTE(pure) +#define ABSL_ATTRIBUTE_PURE_FUNCTION __attribute__((pure)) +#else +// If the attribute isn't defined, we'll fallback to ABSL_MUST_USE_RESULT since +// pure functions are useless if its return is ignored. +#define ABSL_ATTRIBUTE_PURE_FUNCTION ABSL_MUST_USE_RESULT +#endif + +// ABSL_ATTRIBUTE_CONST_FUNCTION +// +// ABSL_ATTRIBUTE_CONST_FUNCTION is used to annotate declarations of "const" +// functions. A const function is similar to a pure function, with one +// exception: Pure functions may return value that depend on a non-volatile +// object that isn't provided as a function argument, while the const function +// is guaranteed to return the same result given the same arguments. +// +// Example: +// +// ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToInt64Milliseconds(Duration d); +#if defined(_MSC_VER) && !defined(__clang__) +// Put the MSVC case first since MSVC seems to parse const as a C++ keyword. +#define ABSL_ATTRIBUTE_CONST_FUNCTION ABSL_ATTRIBUTE_PURE_FUNCTION +#elif ABSL_HAVE_CPP_ATTRIBUTE(gnu::const) +#define ABSL_ATTRIBUTE_CONST_FUNCTION [[gnu::const]] +#elif ABSL_HAVE_ATTRIBUTE(const) +#define ABSL_ATTRIBUTE_CONST_FUNCTION __attribute__((const)) +#else +// Since const functions are more restrictive pure function, we'll fallback to a +// pure function if the const attribute is not handled. +#define ABSL_ATTRIBUTE_CONST_FUNCTION ABSL_ATTRIBUTE_PURE_FUNCTION +#endif // ABSL_ATTRIBUTE_LIFETIME_BOUND indicates that a resource owned by a function // parameter or implicit object parameter is retained by the return value of the @@ -769,14 +843,32 @@ // See also the upstream documentation: // https://clang.llvm.org/docs/AttributeReference.html#trivial-abi // -#if ABSL_HAVE_CPP_ATTRIBUTE(clang::trivial_abi) -#define ABSL_ATTRIBUTE_TRIVIAL_ABI [[clang::trivial_abi]] -#define ABSL_HAVE_ATTRIBUTE_TRIVIAL_ABI 1 -#elif ABSL_HAVE_ATTRIBUTE(trivial_abi) -#define ABSL_ATTRIBUTE_TRIVIAL_ABI __attribute__((trivial_abi)) -#define ABSL_HAVE_ATTRIBUTE_TRIVIAL_ABI 1 -#else +// b/321691395 - This is currently disabled in open-source builds since +// compiler support differs. If system libraries compiled with GCC are mixed +// with libraries compiled with Clang, types will have different ideas about +// their ABI, leading to hard to debug crashes. #define ABSL_ATTRIBUTE_TRIVIAL_ABI + +// ABSL_ATTRIBUTE_NO_UNIQUE_ADDRESS +// +// Indicates a data member can be optimized to occupy no space (if it is empty) +// and/or its tail padding can be used for other members. +// +// For code that is assured to only build with C++20 or later, prefer using +// the standard attribute `[[no_unique_address]]` directly instead of this +// macro. +// +// https://devblogs.microsoft.com/cppblog/msvc-cpp20-and-the-std-cpp20-switch/#c20-no_unique_address +// Current versions of MSVC have disabled `[[no_unique_address]]` since it +// breaks ABI compatibility, but offers `[[msvc::no_unique_address]]` for +// situations when it can be assured that it is desired. Since Abseil does not +// claim ABI compatibility in mixed builds, we can offer it unconditionally. +#if defined(_MSC_VER) && _MSC_VER >= 1929 +#define ABSL_ATTRIBUTE_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]] +#elif ABSL_HAVE_CPP_ATTRIBUTE(no_unique_address) +#define ABSL_ATTRIBUTE_NO_UNIQUE_ADDRESS [[no_unique_address]] +#else +#define ABSL_ATTRIBUTE_NO_UNIQUE_ADDRESS #endif #endif // ABSL_BASE_ATTRIBUTES_H_ diff --git a/absl/base/call_once.h b/absl/base/call_once.h index 96109f53..7b0e69cc 100644 --- a/absl/base/call_once.h +++ b/absl/base/call_once.h @@ -37,6 +37,7 @@ #include "absl/base/internal/scheduling_mode.h" #include "absl/base/internal/spinlock_wait.h" #include "absl/base/macros.h" +#include "absl/base/nullability.h" #include "absl/base/optimization.h" #include "absl/base/port.h" @@ -46,7 +47,8 @@ ABSL_NAMESPACE_BEGIN class once_flag; namespace base_internal { -std::atomic<uint32_t>* ControlWord(absl::once_flag* flag); +absl::Nonnull<std::atomic<uint32_t>*> ControlWord( + absl::Nonnull<absl::once_flag*> flag); } // namespace base_internal // call_once() @@ -89,7 +91,8 @@ class once_flag { once_flag& operator=(const once_flag&) = delete; private: - friend std::atomic<uint32_t>* base_internal::ControlWord(once_flag* flag); + friend absl::Nonnull<std::atomic<uint32_t>*> base_internal::ControlWord( + absl::Nonnull<once_flag*> flag); std::atomic<uint32_t> control_; }; @@ -103,7 +106,8 @@ namespace base_internal { // Like call_once, but uses KERNEL_ONLY scheduling. Intended to be used to // initialize entities used by the scheduler implementation. template <typename Callable, typename... Args> -void LowLevelCallOnce(absl::once_flag* flag, Callable&& fn, Args&&... args); +void LowLevelCallOnce(absl::Nonnull<absl::once_flag*> flag, Callable&& fn, + Args&&... args); // Disables scheduling while on stack when scheduling mode is non-cooperative. // No effect for cooperative scheduling modes. @@ -123,7 +127,7 @@ class SchedulingHelper { private: base_internal::SchedulingMode mode_; - bool guard_result_; + bool guard_result_ = false; }; // Bit patterns for call_once state machine values. Internal implementation @@ -143,10 +147,10 @@ enum { }; template <typename Callable, typename... Args> -ABSL_ATTRIBUTE_NOINLINE -void CallOnceImpl(std::atomic<uint32_t>* control, - base_internal::SchedulingMode scheduling_mode, Callable&& fn, - Args&&... args) { +ABSL_ATTRIBUTE_NOINLINE void CallOnceImpl( + absl::Nonnull<std::atomic<uint32_t>*> control, + base_internal::SchedulingMode scheduling_mode, Callable&& fn, + Args&&... args) { #ifndef NDEBUG { uint32_t old_control = control->load(std::memory_order_relaxed); @@ -185,12 +189,14 @@ void CallOnceImpl(std::atomic<uint32_t>* control, } // else *control is already kOnceDone } -inline std::atomic<uint32_t>* ControlWord(once_flag* flag) { +inline absl::Nonnull<std::atomic<uint32_t>*> ControlWord( + absl::Nonnull<once_flag*> flag) { return &flag->control_; } template <typename Callable, typename... Args> -void LowLevelCallOnce(absl::once_flag* flag, Callable&& fn, Args&&... args) { +void LowLevelCallOnce(absl::Nonnull<absl::once_flag*> flag, Callable&& fn, + Args&&... args) { std::atomic<uint32_t>* once = base_internal::ControlWord(flag); uint32_t s = once->load(std::memory_order_acquire); if (ABSL_PREDICT_FALSE(s != base_internal::kOnceDone)) { diff --git a/absl/base/casts.h b/absl/base/casts.h index b99adb06..e0b11bbe 100644 --- a/absl/base/casts.h +++ b/absl/base/casts.h @@ -90,7 +90,7 @@ ABSL_NAMESPACE_BEGIN // // Such implicit cast chaining may be useful within template logic. template <typename To> -constexpr To implicit_cast(typename absl::internal::identity_t<To> to) { +constexpr To implicit_cast(typename absl::internal::type_identity_t<To> to) { return to; } @@ -149,16 +149,16 @@ using std::bit_cast; #else // defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L -template <typename Dest, typename Source, - typename std::enable_if< - sizeof(Dest) == sizeof(Source) && - type_traits_internal::is_trivially_copyable<Source>::value && - type_traits_internal::is_trivially_copyable<Dest>::value +template < + typename Dest, typename Source, + typename std::enable_if<sizeof(Dest) == sizeof(Source) && + std::is_trivially_copyable<Source>::value && + std::is_trivially_copyable<Dest>::value #if !ABSL_HAVE_BUILTIN(__builtin_bit_cast) - && std::is_default_constructible<Dest>::value + && std::is_default_constructible<Dest>::value #endif // !ABSL_HAVE_BUILTIN(__builtin_bit_cast) - , - int>::type = 0> + , + int>::type = 0> #if ABSL_HAVE_BUILTIN(__builtin_bit_cast) inline constexpr Dest bit_cast(const Source& source) { return __builtin_bit_cast(Dest, source); diff --git a/absl/base/config.h b/absl/base/config.h index 0631ab60..c9165acd 100644 --- a/absl/base/config.h +++ b/absl/base/config.h @@ -75,6 +75,12 @@ #define ABSL_INTERNAL_CPLUSPLUS_LANG __cplusplus #endif +#if defined(ABSL_INTERNAL_CPLUSPLUS_LANG) && \ + ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L +// Include library feature test macros. +#include <version> +#endif + #if defined(__APPLE__) // Included for TARGET_OS_IPHONE, __IPHONE_OS_VERSION_MIN_REQUIRED, // __IPHONE_8_0. @@ -111,8 +117,8 @@ // // LTS releases can be obtained from // https://github.com/abseil/abseil-cpp/releases. -#define ABSL_LTS_RELEASE_VERSION 20230125 -#define ABSL_LTS_RELEASE_PATCH_LEVEL 2 +#define ABSL_LTS_RELEASE_VERSION 20240116 +#define ABSL_LTS_RELEASE_PATCH_LEVEL 1 // Helper macro to convert a CPP variable to a string literal. #define ABSL_INTERNAL_DO_TOKEN_STR(x) #x @@ -237,15 +243,8 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || // ABSL_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE // // Checks whether `std::is_trivially_destructible<T>` is supported. -// -// Notes: All supported compilers using libc++ support this feature, as does -// gcc >= 4.8.1 using libstdc++, and Visual Studio. #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 #endif @@ -253,36 +252,26 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || // // Checks whether `std::is_trivially_default_constructible<T>` and // `std::is_trivially_copy_constructible<T>` are supported. +#ifdef ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE +#error ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE cannot be directly set +#else +#define ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE 1 +#endif // ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE // // Checks whether `std::is_trivially_copy_assignable<T>` is supported. - -// Notes: Clang with libc++ supports these features, as does gcc >= 7.4 with -// libstdc++, or gcc >= 8.2 with libc++, and Visual Studio (but not NVCC). -#if defined(ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE) -#error ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE cannot be directly set -#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) && \ - defined(_LIBCPP_VERSION)))) || \ - (defined(_MSC_VER) && !defined(__NVCC__) && !defined(__clang__)) -#define ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE 1 +#ifdef ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE +#error ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE cannot be directly set +#else #define ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE 1 #endif // ABSL_HAVE_STD_IS_TRIVIALLY_COPYABLE // // Checks whether `std::is_trivially_copyable<T>` is supported. -// -// Notes: Clang 15+ with libc++ supports these features, GCC hasn't been tested. -#if defined(ABSL_HAVE_STD_IS_TRIVIALLY_COPYABLE) +#ifdef ABSL_HAVE_STD_IS_TRIVIALLY_COPYABLE #error ABSL_HAVE_STD_IS_TRIVIALLY_COPYABLE cannot be directly set -#elif defined(__clang__) && (__clang_major__ >= 15) #define ABSL_HAVE_STD_IS_TRIVIALLY_COPYABLE 1 #endif @@ -349,8 +338,8 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || #ifdef ABSL_HAVE_INTRINSIC_INT128 #error ABSL_HAVE_INTRINSIC_INT128 cannot be directly set #elif defined(__SIZEOF_INT128__) -#if (defined(__clang__) && !defined(_WIN32)) || \ - (defined(__CUDACC__) && __CUDACC_VER_MAJOR__ >= 9) || \ +#if (defined(__clang__) && !defined(_WIN32)) || \ + (defined(__CUDACC__) && __CUDACC_VER_MAJOR__ >= 9) || \ (defined(__GNUC__) && !defined(__clang__) && !defined(__CUDACC__)) #define ABSL_HAVE_INTRINSIC_INT128 1 #elif defined(__CUDACC__) @@ -412,7 +401,7 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || // Windows _WIN32 // NaCL __native_client__ // AsmJS __asmjs__ -// WebAssembly __wasm__ +// WebAssembly (Emscripten) __EMSCRIPTEN__ // Fuchsia __Fuchsia__ // // Note that since Android defines both __ANDROID__ and __linux__, one @@ -424,12 +413,12 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || // POSIX.1-2001. #ifdef ABSL_HAVE_MMAP #error ABSL_HAVE_MMAP cannot be directly set -#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \ - defined(_AIX) || defined(__ros__) || defined(__native_client__) || \ - defined(__asmjs__) || defined(__wasm__) || defined(__Fuchsia__) || \ - defined(__sun) || defined(__ASYLO__) || defined(__myriad2__) || \ - defined(__HAIKU__) || defined(__OpenBSD__) || defined(__NetBSD__) || \ - defined(__QNX__) +#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \ + defined(_AIX) || defined(__ros__) || defined(__native_client__) || \ + defined(__asmjs__) || defined(__EMSCRIPTEN__) || defined(__Fuchsia__) || \ + defined(__sun) || defined(__ASYLO__) || defined(__myriad2__) || \ + defined(__HAIKU__) || defined(__OpenBSD__) || defined(__NetBSD__) || \ + defined(__QNX__) || defined(__VXWORKS__) || defined(__hexagon__) #define ABSL_HAVE_MMAP 1 #endif @@ -441,7 +430,7 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || #error ABSL_HAVE_PTHREAD_GETSCHEDPARAM cannot be directly set #elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \ defined(_AIX) || defined(__ros__) || defined(__OpenBSD__) || \ - defined(__NetBSD__) + defined(__NetBSD__) || defined(__VXWORKS__) #define ABSL_HAVE_PTHREAD_GETSCHEDPARAM 1 #endif @@ -460,7 +449,8 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || // POSIX.1-2001. #ifdef ABSL_HAVE_SCHED_YIELD #error ABSL_HAVE_SCHED_YIELD cannot be directly set -#elif defined(__linux__) || defined(__ros__) || defined(__native_client__) +#elif defined(__linux__) || defined(__ros__) || defined(__native_client__) || \ + defined(__VXWORKS__) #define ABSL_HAVE_SCHED_YIELD 1 #endif @@ -475,7 +465,7 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || // platforms. #ifdef ABSL_HAVE_SEMAPHORE_H #error ABSL_HAVE_SEMAPHORE_H cannot be directly set -#elif defined(__linux__) || defined(__ros__) +#elif defined(__linux__) || defined(__ros__) || defined(__VXWORKS__) #define ABSL_HAVE_SEMAPHORE_H 1 #endif @@ -500,9 +490,13 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || // https://sourceforge.net/p/mingw-w64/mingw-w64/ci/master/tree/mingw-w64-crt/misc/alarm.c #elif defined(__EMSCRIPTEN__) // emscripten doesn't support signals +#elif defined(__wasi__) +// WASI doesn't support signals #elif defined(__Fuchsia__) // Signals don't exist on fuchsia. #elif defined(__native_client__) +// Signals don't exist on hexagon/QuRT +#elif defined(__hexagon__) #else // other standard libraries #define ABSL_HAVE_ALARM 1 @@ -536,41 +530,29 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || #error "absl endian detection needs to be set up for your compiler" #endif -// macOS < 10.13 and iOS < 11 don't let you use <any>, <optional>, or <variant> -// even though the headers exist and are publicly noted to work, because the -// libc++ shared library shipped on the system doesn't have the requisite -// exported symbols. See https://github.com/abseil/abseil-cpp/issues/207 and +// macOS < 10.13 and iOS < 12 don't support <any>, <optional>, or <variant> +// because the libc++ shared library shipped on the system doesn't have the +// requisite exported symbols. See +// https://github.com/abseil/abseil-cpp/issues/207 and // https://developer.apple.com/documentation/xcode_release_notes/xcode_10_release_notes // // libc++ spells out the availability requirements in the file // llvm-project/libcxx/include/__config via the #define -// _LIBCPP_AVAILABILITY_BAD_OPTIONAL_ACCESS. -// -// Unfortunately, Apple initially mis-stated the requirements as macOS < 10.14 -// and iOS < 12 in the libc++ headers. This was corrected by +// _LIBCPP_AVAILABILITY_BAD_OPTIONAL_ACCESS. The set of versions has been +// modified a few times, via // https://github.com/llvm/llvm-project/commit/7fb40e1569dd66292b647f4501b85517e9247953 -// which subsequently made it into the XCode 12.5 release. We need to match the -// old (incorrect) conditions when built with old XCode, but can use the -// corrected earlier versions with new XCode. -#if defined(__APPLE__) && defined(_LIBCPP_VERSION) && \ - ((_LIBCPP_VERSION >= 11000 && /* XCode 12.5 or later: */ \ - ((defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && \ - __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 101300) || \ - (defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__) && \ - __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ < 110000) || \ - (defined(__ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__) && \ - __ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__ < 40000) || \ - (defined(__ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__) && \ - __ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__ < 110000))) || \ - (_LIBCPP_VERSION < 11000 && /* Pre-XCode 12.5: */ \ - ((defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && \ - __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 101400) || \ - (defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__) && \ - __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ < 120000) || \ - (defined(__ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__) && \ - __ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__ < 50000) || \ - (defined(__ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__) && \ - __ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__ < 120000)))) +// and +// https://github.com/llvm/llvm-project/commit/0bc451e7e137c4ccadcd3377250874f641ca514a +// The second has the actually correct versions, thus, is what we copy here. +#if defined(__APPLE__) && \ + ((defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) && \ + __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ < 101300) || \ + (defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__) && \ + __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ < 120000) || \ + (defined(__ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__) && \ + __ENVIRONMENT_WATCH_OS_VERSION_MIN_REQUIRED__ < 50000) || \ + (defined(__ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__) && \ + __ENVIRONMENT_TV_OS_VERSION_MIN_REQUIRED__ < 120000)) #define ABSL_INTERNAL_APPLE_CXX17_TYPES_UNAVAILABLE 1 #else #define ABSL_INTERNAL_APPLE_CXX17_TYPES_UNAVAILABLE 0 @@ -578,30 +560,28 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || // ABSL_HAVE_STD_ANY // -// Checks whether C++17 std::any is available by checking whether <any> exists. +// Checks whether C++17 std::any is available. #ifdef ABSL_HAVE_STD_ANY #error "ABSL_HAVE_STD_ANY cannot be directly set." -#endif - -#ifdef __has_include -#if __has_include(<any>) && defined(__cplusplus) && __cplusplus >= 201703L && \ +#elif defined(__cpp_lib_any) && __cpp_lib_any >= 201606L +#define ABSL_HAVE_STD_ANY 1 +#elif defined(ABSL_INTERNAL_CPLUSPLUS_LANG) && \ + ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L && \ !ABSL_INTERNAL_APPLE_CXX17_TYPES_UNAVAILABLE #define ABSL_HAVE_STD_ANY 1 #endif -#endif // ABSL_HAVE_STD_OPTIONAL // // Checks whether C++17 std::optional is available. #ifdef ABSL_HAVE_STD_OPTIONAL #error "ABSL_HAVE_STD_OPTIONAL cannot be directly set." -#endif - -#ifdef __has_include -#if __has_include(<optional>) && defined(__cplusplus) && \ - __cplusplus >= 201703L && !ABSL_INTERNAL_APPLE_CXX17_TYPES_UNAVAILABLE +#elif defined(__cpp_lib_optional) && __cpp_lib_optional >= 202106L +#define ABSL_HAVE_STD_OPTIONAL 1 +#elif defined(ABSL_INTERNAL_CPLUSPLUS_LANG) && \ + ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L && \ + !ABSL_INTERNAL_APPLE_CXX17_TYPES_UNAVAILABLE #define ABSL_HAVE_STD_OPTIONAL 1 -#endif #endif // ABSL_HAVE_STD_VARIANT @@ -609,13 +589,12 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || // Checks whether C++17 std::variant is available. #ifdef ABSL_HAVE_STD_VARIANT #error "ABSL_HAVE_STD_VARIANT cannot be directly set." -#endif - -#ifdef __has_include -#if __has_include(<variant>) && defined(__cplusplus) && \ - __cplusplus >= 201703L && !ABSL_INTERNAL_APPLE_CXX17_TYPES_UNAVAILABLE +#elif defined(__cpp_lib_variant) && __cpp_lib_variant >= 201606L +#define ABSL_HAVE_STD_VARIANT 1 +#elif defined(ABSL_INTERNAL_CPLUSPLUS_LANG) && \ + ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L && \ + !ABSL_INTERNAL_APPLE_CXX17_TYPES_UNAVAILABLE #define ABSL_HAVE_STD_VARIANT 1 -#endif #endif // ABSL_HAVE_STD_STRING_VIEW @@ -623,29 +602,27 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || // Checks whether C++17 std::string_view is available. #ifdef ABSL_HAVE_STD_STRING_VIEW #error "ABSL_HAVE_STD_STRING_VIEW cannot be directly set." -#endif - -#ifdef __has_include -#if __has_include(<string_view>) && defined(__cplusplus) && \ - __cplusplus >= 201703L +#elif defined(__cpp_lib_string_view) && __cpp_lib_string_view >= 201606L +#define ABSL_HAVE_STD_STRING_VIEW 1 +#elif defined(ABSL_INTERNAL_CPLUSPLUS_LANG) && \ + ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L #define ABSL_HAVE_STD_STRING_VIEW 1 -#endif #endif -// For MSVC, `__has_include` is supported in VS 2017 15.3, which is later than -// the support for <optional>, <any>, <string_view>, <variant>. So we use -// _MSC_VER to check whether we have VS 2017 RTM (when <optional>, <any>, -// <string_view>, <variant> is implemented) or higher. Also, `__cplusplus` is -// not correctly set by MSVC, so we use `_MSVC_LANG` to check the language -// version. -// TODO(zhangxy): fix tests before enabling aliasing for `std::any`. -#if defined(_MSC_VER) && _MSC_VER >= 1910 && \ - ((defined(_MSVC_LANG) && _MSVC_LANG > 201402) || \ - (defined(__cplusplus) && __cplusplus > 201402)) -// #define ABSL_HAVE_STD_ANY 1 -#define ABSL_HAVE_STD_OPTIONAL 1 -#define ABSL_HAVE_STD_VARIANT 1 -#define ABSL_HAVE_STD_STRING_VIEW 1 +// ABSL_HAVE_STD_ORDERING +// +// Checks whether C++20 std::{partial,weak,strong}_ordering are available. +// +// __cpp_lib_three_way_comparison is missing on libc++ +// (https://github.com/llvm/llvm-project/issues/73953) so treat it as defined +// when building in C++20 mode. +#ifdef ABSL_HAVE_STD_ORDERING +#error "ABSL_HAVE_STD_ORDERING cannot be directly set." +#elif (defined(__cpp_lib_three_way_comparison) && \ + __cpp_lib_three_way_comparison >= 201907L) || \ + (defined(ABSL_INTERNAL_CPLUSPLUS_LANG) && \ + ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L) +#define ABSL_HAVE_STD_ORDERING 1 #endif // ABSL_USES_STD_ANY @@ -710,6 +687,22 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || #error options.h is misconfigured. #endif +// ABSL_USES_STD_ORDERING +// +// Indicates whether absl::{partial,weak,strong}_ordering are aliases for the +// std:: ordering types. +#if !defined(ABSL_OPTION_USE_STD_ORDERING) +#error options.h is misconfigured. +#elif ABSL_OPTION_USE_STD_ORDERING == 0 || \ + (ABSL_OPTION_USE_STD_ORDERING == 2 && !defined(ABSL_HAVE_STD_ORDERING)) +#undef ABSL_USES_STD_ORDERING +#elif ABSL_OPTION_USE_STD_ORDERING == 1 || \ + (ABSL_OPTION_USE_STD_ORDERING == 2 && defined(ABSL_HAVE_STD_ORDERING)) +#define ABSL_USES_STD_ORDERING 1 +#else +#error options.h is misconfigured. +#endif + // In debug mode, MSVC 2017's std::variant throws a EXCEPTION_ACCESS_VIOLATION // SEH exception from emplace for variant<SomeStruct> when constructing the // struct can throw. This defeats some of variant_test and @@ -816,6 +809,20 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || #define ABSL_HAVE_HWADDRESS_SANITIZER 1 #endif +// ABSL_HAVE_DATAFLOW_SANITIZER +// +// Dataflow Sanitizer (or DFSAN) is a generalised dynamic data flow analysis. +#ifdef ABSL_HAVE_DATAFLOW_SANITIZER +#error "ABSL_HAVE_DATAFLOW_SANITIZER cannot be directly set." +#elif defined(DATAFLOW_SANITIZER) +// GCC provides no method for detecting the presence of the standalone +// DataFlowSanitizer (-fsanitize=dataflow), so GCC users of -fsanitize=dataflow +// should also use -DDATAFLOW_SANITIZER. +#define ABSL_HAVE_DATAFLOW_SANITIZER 1 +#elif ABSL_HAVE_FEATURE(dataflow_sanitizer) +#define ABSL_HAVE_DATAFLOW_SANITIZER 1 +#endif + // ABSL_HAVE_LEAK_SANITIZER // // LeakSanitizer (or lsan) is a detector of memory leaks. @@ -830,7 +837,7 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || #ifdef ABSL_HAVE_LEAK_SANITIZER #error "ABSL_HAVE_LEAK_SANITIZER cannot be directly set." #elif defined(LEAK_SANITIZER) -// GCC provides no method for detecting the presense of the standalone +// GCC provides no method for detecting the presence of the standalone // LeakSanitizer (-fsanitize=leak), so GCC users of -fsanitize=leak should also // use -DLEAK_SANITIZER. #define ABSL_HAVE_LEAK_SANITIZER 1 @@ -878,9 +885,30 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || // RTTI support. #ifdef ABSL_INTERNAL_HAS_RTTI #error ABSL_INTERNAL_HAS_RTTI cannot be directly set -#elif !defined(__GNUC__) || defined(__GXX_RTTI) +#elif ABSL_HAVE_FEATURE(cxx_rtti) +#define ABSL_INTERNAL_HAS_RTTI 1 +#elif defined(__GNUC__) && defined(__GXX_RTTI) +#define ABSL_INTERNAL_HAS_RTTI 1 +#elif defined(_MSC_VER) && defined(_CPPRTTI) #define ABSL_INTERNAL_HAS_RTTI 1 -#endif // !defined(__GNUC__) || defined(__GXX_RTTI) +#elif !defined(__GNUC__) && !defined(_MSC_VER) +// Unknown compiler, default to RTTI +#define ABSL_INTERNAL_HAS_RTTI 1 +#endif + +// `ABSL_INTERNAL_HAS_CXA_DEMANGLE` determines whether `abi::__cxa_demangle` is +// available. +#ifdef ABSL_INTERNAL_HAS_CXA_DEMANGLE +#error ABSL_INTERNAL_HAS_CXA_DEMANGLE cannot be directly set +#elif defined(OS_ANDROID) && (defined(__i386__) || defined(__x86_64__)) +#define ABSL_INTERNAL_HAS_CXA_DEMANGLE 0 +#elif defined(__GNUC__) && defined(__GNUC_MINOR__) && \ + (__GNUC__ >= 4 || (__GNUC__ >= 3 && __GNUC_MINOR__ >= 4)) && \ + !defined(__mips__) +#define ABSL_INTERNAL_HAS_CXA_DEMANGLE 1 +#elif defined(__clang__) && !defined(_MSC_VER) +#define ABSL_INTERNAL_HAS_CXA_DEMANGLE 1 +#endif // ABSL_INTERNAL_HAVE_SSE is used for compile-time detection of SSE support. // See https://gcc.gnu.org/onlinedocs/gcc/x86-Options.html for an overview of @@ -889,7 +917,8 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || #error ABSL_INTERNAL_HAVE_SSE cannot be directly set #elif defined(__SSE__) #define ABSL_INTERNAL_HAVE_SSE 1 -#elif defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 1) +#elif (defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 1)) && \ + !defined(_M_ARM64EC) // MSVC only defines _M_IX86_FP for x86 32-bit code, and _M_IX86_FP >= 1 // indicates that at least SSE was targeted with the /arch:SSE option. // All x86-64 processors support SSE, so support can be assumed. @@ -904,7 +933,8 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || #error ABSL_INTERNAL_HAVE_SSE2 cannot be directly set #elif defined(__SSE2__) #define ABSL_INTERNAL_HAVE_SSE2 1 -#elif defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2) +#elif (defined(_M_X64) || (defined(_M_IX86_FP) && _M_IX86_FP >= 2)) && \ + !defined(_M_ARM64EC) // MSVC only defines _M_IX86_FP for x86 32-bit code, and _M_IX86_FP >= 2 // indicates that at least SSE2 was targeted with the /arch:SSE2 option. // All x86-64 processors support SSE2, so support can be assumed. @@ -951,4 +981,24 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || #define ABSL_HAVE_CONSTANT_EVALUATED 1 #endif +// ABSL_INTERNAL_EMSCRIPTEN_VERSION combines Emscripten's three version macros +// into an integer that can be compared against. +#ifdef ABSL_INTERNAL_EMSCRIPTEN_VERSION +#error ABSL_INTERNAL_EMSCRIPTEN_VERSION cannot be directly set +#endif +#ifdef __EMSCRIPTEN__ +#include <emscripten/version.h> +#ifdef __EMSCRIPTEN_major__ +#if __EMSCRIPTEN_minor__ >= 1000 +#error __EMSCRIPTEN_minor__ is too big to fit in ABSL_INTERNAL_EMSCRIPTEN_VERSION +#endif +#if __EMSCRIPTEN_tiny__ >= 1000 +#error __EMSCRIPTEN_tiny__ is too big to fit in ABSL_INTERNAL_EMSCRIPTEN_VERSION +#endif +#define ABSL_INTERNAL_EMSCRIPTEN_VERSION \ + ((__EMSCRIPTEN_major__) * 1000000 + (__EMSCRIPTEN_minor__) * 1000 + \ + (__EMSCRIPTEN_tiny__)) +#endif +#endif + #endif // ABSL_BASE_CONFIG_H_ diff --git a/absl/base/dynamic_annotations.h b/absl/base/dynamic_annotations.h index 3ea7c156..7ba8912e 100644 --- a/absl/base/dynamic_annotations.h +++ b/absl/base/dynamic_annotations.h @@ -46,6 +46,7 @@ #define ABSL_BASE_DYNAMIC_ANNOTATIONS_H_ #include <stddef.h> +#include <stdint.h> #include "absl/base/attributes.h" #include "absl/base/config.h" @@ -53,6 +54,10 @@ #include "absl/base/macros.h" #endif +#ifdef ABSL_HAVE_HWADDRESS_SANITIZER +#include <sanitizer/hwasan_interface.h> +#endif + // TODO(rogeeff): Remove after the backward compatibility period. #include "absl/base/internal/dynamic_annotations.h" // IWYU pragma: export @@ -111,7 +116,7 @@ #if ABSL_INTERNAL_RACE_ANNOTATIONS_ENABLED == 1 // Some of the symbols used in this section (e.g. AnnotateBenignRaceSized) are -// defined by the compiler-based santizer implementation, not by the Abseil +// defined by the compiler-based sanitizer implementation, not by the Abseil // library. Therefore they do not use ABSL_INTERNAL_C_SYMBOL. // ------------------------------------------------------------- @@ -457,6 +462,26 @@ ABSL_NAMESPACE_END #endif // ABSL_HAVE_ADDRESS_SANITIZER // ------------------------------------------------------------------------- +// HWAddress sanitizer annotations + +#ifdef __cplusplus +namespace absl { +#ifdef ABSL_HAVE_HWADDRESS_SANITIZER +// Under HWASAN changes the tag of the pointer. +template <typename T> +T* HwasanTagPointer(T* ptr, uintptr_t tag) { + return reinterpret_cast<T*>(__hwasan_tag_pointer(ptr, tag)); +} +#else +template <typename T> +T* HwasanTagPointer(T* ptr, uintptr_t) { + return ptr; +} +#endif +} // namespace absl +#endif + +// ------------------------------------------------------------------------- // Undefine the macros intended only for this file. #undef ABSL_INTERNAL_RACE_ANNOTATIONS_ENABLED diff --git a/absl/base/exception_safety_testing_test.cc b/absl/base/exception_safety_testing_test.cc index a87fd6a9..bf5aa7cf 100644 --- a/absl/base/exception_safety_testing_test.cc +++ b/absl/base/exception_safety_testing_test.cc @@ -148,7 +148,7 @@ TEST(ThrowingValueTest, ThrowingBitwiseOps) { ThrowingValue<> bomb1, bomb2; TestOp([&bomb1]() { ~bomb1; }); - TestOp([&]() { bomb1& bomb2; }); + TestOp([&]() { bomb1 & bomb2; }); TestOp([&]() { bomb1 | bomb2; }); TestOp([&]() { bomb1 ^ bomb2; }); } @@ -332,13 +332,16 @@ TEST(ThrowingValueTest, NonThrowingPlacementDelete) { constexpr int kArrayLen = 2; // We intentionally create extra space to store the tag allocated by placement // new[]. - constexpr int kStorageLen = 4; + constexpr size_t kExtraSpaceLen = sizeof(size_t) * 2; alignas(ThrowingValue<>) unsigned char buf[sizeof(ThrowingValue<>)]; alignas(ThrowingValue<>) unsigned char - array_buf[sizeof(ThrowingValue<>[kStorageLen])]; + array_buf[kExtraSpaceLen + sizeof(ThrowingValue<>[kArrayLen])]; auto* placed = new (&buf) ThrowingValue<>(1); auto placed_array = new (&array_buf) ThrowingValue<>[kArrayLen]; + auto* placed_array_end = reinterpret_cast<unsigned char*>(placed_array) + + sizeof(ThrowingValue<>[kArrayLen]); + EXPECT_LE(placed_array_end, array_buf + sizeof(array_buf)); SetCountdown(); ExpectNoThrow([placed, &buf]() { diff --git a/absl/base/internal/direct_mmap.h b/absl/base/internal/direct_mmap.h index 815b8d23..1beb2ee4 100644 --- a/absl/base/internal/direct_mmap.h +++ b/absl/base/internal/direct_mmap.h @@ -72,7 +72,7 @@ namespace base_internal { // Platform specific logic extracted from // https://chromium.googlesource.com/linux-syscall-support/+/master/linux_syscall_support.h inline void* DirectMmap(void* start, size_t length, int prot, int flags, int fd, - off64_t offset) noexcept { + off_t offset) noexcept { #if defined(__i386__) || defined(__ARM_ARCH_3__) || defined(__ARM_EABI__) || \ defined(__m68k__) || defined(__sh__) || \ (defined(__hppa__) && !defined(__LP64__)) || \ @@ -102,7 +102,7 @@ inline void* DirectMmap(void* start, size_t length, int prot, int flags, int fd, #else return reinterpret_cast<void*>( syscall(SYS_mmap2, start, length, prot, flags, fd, - static_cast<off_t>(offset / pagesize))); + static_cast<unsigned long>(offset / pagesize))); // NOLINT #endif #elif defined(__s390x__) // On s390x, mmap() arguments are passed in memory. diff --git a/absl/base/internal/endian.h b/absl/base/internal/endian.h index 50747d75..943f3d97 100644 --- a/absl/base/internal/endian.h +++ b/absl/base/internal/endian.h @@ -22,6 +22,7 @@ #include "absl/base/casts.h" #include "absl/base/config.h" #include "absl/base/internal/unaligned_access.h" +#include "absl/base/nullability.h" #include "absl/base/port.h" namespace absl { @@ -160,27 +161,27 @@ inline int64_t ToHost(int64_t x) { } // Functions to do unaligned loads and stores in little-endian order. -inline uint16_t Load16(const void *p) { +inline uint16_t Load16(absl::Nonnull<const void *> p) { return ToHost16(ABSL_INTERNAL_UNALIGNED_LOAD16(p)); } -inline void Store16(void *p, uint16_t v) { +inline void Store16(absl::Nonnull<void *> p, uint16_t v) { ABSL_INTERNAL_UNALIGNED_STORE16(p, FromHost16(v)); } -inline uint32_t Load32(const void *p) { +inline uint32_t Load32(absl::Nonnull<const void *> p) { return ToHost32(ABSL_INTERNAL_UNALIGNED_LOAD32(p)); } -inline void Store32(void *p, uint32_t v) { +inline void Store32(absl::Nonnull<void *> p, uint32_t v) { ABSL_INTERNAL_UNALIGNED_STORE32(p, FromHost32(v)); } -inline uint64_t Load64(const void *p) { +inline uint64_t Load64(absl::Nonnull<const void *> p) { return ToHost64(ABSL_INTERNAL_UNALIGNED_LOAD64(p)); } -inline void Store64(void *p, uint64_t v) { +inline void Store64(absl::Nonnull<void *> p, uint64_t v) { ABSL_INTERNAL_UNALIGNED_STORE64(p, FromHost64(v)); } @@ -250,27 +251,27 @@ inline int64_t ToHost(int64_t x) { } // Functions to do unaligned loads and stores in big-endian order. -inline uint16_t Load16(const void *p) { +inline uint16_t Load16(absl::Nonnull<const void *> p) { return ToHost16(ABSL_INTERNAL_UNALIGNED_LOAD16(p)); } -inline void Store16(void *p, uint16_t v) { +inline void Store16(absl::Nonnull<void *> p, uint16_t v) { ABSL_INTERNAL_UNALIGNED_STORE16(p, FromHost16(v)); } -inline uint32_t Load32(const void *p) { +inline uint32_t Load32(absl::Nonnull<const void *> p) { return ToHost32(ABSL_INTERNAL_UNALIGNED_LOAD32(p)); } -inline void Store32(void *p, uint32_t v) { +inline void Store32(absl::Nonnull<void *>p, uint32_t v) { ABSL_INTERNAL_UNALIGNED_STORE32(p, FromHost32(v)); } -inline uint64_t Load64(const void *p) { +inline uint64_t Load64(absl::Nonnull<const void *> p) { return ToHost64(ABSL_INTERNAL_UNALIGNED_LOAD64(p)); } -inline void Store64(void *p, uint64_t v) { +inline void Store64(absl::Nonnull<void *> p, uint64_t v) { ABSL_INTERNAL_UNALIGNED_STORE64(p, FromHost64(v)); } diff --git a/absl/base/internal/exception_safety_testing.h b/absl/base/internal/exception_safety_testing.h index 77a5aec6..c1061544 100644 --- a/absl/base/internal/exception_safety_testing.h +++ b/absl/base/internal/exception_safety_testing.h @@ -946,7 +946,7 @@ class ExceptionSafetyTest { * `std::unique_ptr<T> operator()() const` where T is the type being tested. * It is used for reliably creating identical T instances to test on. * - * - Operation: The operation object (passsed in via tester.WithOperation(...) + * - Operation: The operation object (passed in via tester.WithOperation(...) * or tester.Test(...)) must be invocable with the signature * `void operator()(T*) const` where T is the type being tested. It is used * for performing steps on a T instance that may throw and that need to be diff --git a/absl/base/internal/identity.h b/absl/base/internal/identity.h index a3154ed7..365207b7 100644 --- a/absl/base/internal/identity.h +++ b/absl/base/internal/identity.h @@ -22,13 +22,15 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace internal { +// This is a back-fill of C++20's `std::type_identity`. template <typename T> -struct identity { +struct type_identity { typedef T type; }; +// This is a back-fill of C++20's `std::type_identity_t`. template <typename T> -using identity_t = typename identity<T>::type; +using type_identity_t = typename type_identity<T>::type; } // namespace internal ABSL_NAMESPACE_END diff --git a/absl/base/internal/inline_variable.h b/absl/base/internal/inline_variable.h index df933faf..09daf0f5 100644 --- a/absl/base/internal/inline_variable.h +++ b/absl/base/internal/inline_variable.h @@ -63,12 +63,12 @@ // Bug: https://bugs.llvm.org/show_bug.cgi?id=35862 // // Note: -// identity_t is used here so that the const and name are in the +// type_identity_t is used here so that the const and name are in the // appropriate place for pointer types, reference types, function pointer // types, etc.. #if defined(__clang__) #define ABSL_INTERNAL_EXTERN_DECL(type, name) \ - extern const ::absl::internal::identity_t<type> name; + extern const ::absl::internal::type_identity_t<type> name; #else // Otherwise, just define the macro to do nothing. #define ABSL_INTERNAL_EXTERN_DECL(type, name) #endif // defined(__clang__) @@ -76,30 +76,31 @@ // See above comment at top of file for details. #define ABSL_INTERNAL_INLINE_CONSTEXPR(type, name, init) \ ABSL_INTERNAL_EXTERN_DECL(type, name) \ - inline constexpr ::absl::internal::identity_t<type> name = init + inline constexpr ::absl::internal::type_identity_t<type> name = init #else // See above comment at top of file for details. // // Note: -// identity_t is used here so that the const and name are in the +// type_identity_t is used here so that the const and name are in the // appropriate place for pointer types, reference types, function pointer // types, etc.. -#define ABSL_INTERNAL_INLINE_CONSTEXPR(var_type, name, init) \ - template <class /*AbslInternalDummy*/ = void> \ - struct AbslInternalInlineVariableHolder##name { \ - static constexpr ::absl::internal::identity_t<var_type> kInstance = init; \ - }; \ - \ - template <class AbslInternalDummy> \ - constexpr ::absl::internal::identity_t<var_type> \ - AbslInternalInlineVariableHolder##name<AbslInternalDummy>::kInstance; \ - \ - static constexpr const ::absl::internal::identity_t<var_type>& \ - name = /* NOLINT */ \ - AbslInternalInlineVariableHolder##name<>::kInstance; \ - static_assert(sizeof(void (*)(decltype(name))) != 0, \ +#define ABSL_INTERNAL_INLINE_CONSTEXPR(var_type, name, init) \ + template <class /*AbslInternalDummy*/ = void> \ + struct AbslInternalInlineVariableHolder##name { \ + static constexpr ::absl::internal::type_identity_t<var_type> kInstance = \ + init; \ + }; \ + \ + template <class AbslInternalDummy> \ + constexpr ::absl::internal::type_identity_t<var_type> \ + AbslInternalInlineVariableHolder##name<AbslInternalDummy>::kInstance; \ + \ + static constexpr const ::absl::internal::type_identity_t<var_type>& \ + name = /* NOLINT */ \ + AbslInternalInlineVariableHolder##name<>::kInstance; \ + static_assert(sizeof(void (*)(decltype(name))) != 0, \ "Silence unused variable warnings.") #endif // __cpp_inline_variables diff --git a/absl/base/internal/low_level_alloc.cc b/absl/base/internal/low_level_alloc.cc index 662167b0..a563f7b9 100644 --- a/absl/base/internal/low_level_alloc.cc +++ b/absl/base/internal/low_level_alloc.cc @@ -42,25 +42,25 @@ #include <windows.h> #endif +#ifdef __linux__ +#include <sys/prctl.h> +#endif + #include <string.h> + #include <algorithm> #include <atomic> #include <cerrno> #include <cstddef> -#include <new> // for placement-new +#include <new> // for placement-new #include "absl/base/dynamic_annotations.h" #include "absl/base/internal/raw_logging.h" #include "absl/base/internal/spinlock.h" -// MAP_ANONYMOUS -#if defined(__APPLE__) -// For mmap, Linux defines both MAP_ANONYMOUS and MAP_ANON and says MAP_ANON is -// deprecated. In Darwin, MAP_ANON is all there is. -#if !defined MAP_ANONYMOUS +#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS) #define MAP_ANONYMOUS MAP_ANON -#endif // !MAP_ANONYMOUS -#endif // __APPLE__ +#endif namespace absl { ABSL_NAMESPACE_BEGIN @@ -122,7 +122,7 @@ static int IntLog2(size_t size, size_t base) { static int Random(uint32_t *state) { uint32_t r = *state; int result = 1; - while ((((r = r*1103515245 + 12345) >> 30) & 1) == 0) { + while ((((r = r * 1103515245 + 12345) >> 30) & 1) == 0) { result++; } *state = r; @@ -144,7 +144,7 @@ static int LLA_SkiplistLevels(size_t size, size_t base, uint32_t *random) { size_t max_fit = (size - offsetof(AllocList, next)) / sizeof(AllocList *); int level = IntLog2(size, base) + (random != nullptr ? Random(random) : 1); if (static_cast<size_t>(level) > max_fit) level = static_cast<int>(max_fit); - if (level > kMaxLevel-1) level = kMaxLevel - 1; + if (level > kMaxLevel - 1) level = kMaxLevel - 1; ABSL_RAW_CHECK(level >= 1, "block not big enough for even one level"); return level; } @@ -153,8 +153,8 @@ static int LLA_SkiplistLevels(size_t size, size_t base, uint32_t *random) { // For 0 <= i < head->levels, set prev[i] to "no_greater", where no_greater // points to the last element at level i in the AllocList less than *e, or is // head if no such element exists. -static AllocList *LLA_SkiplistSearch(AllocList *head, - AllocList *e, AllocList **prev) { +static AllocList *LLA_SkiplistSearch(AllocList *head, AllocList *e, + AllocList **prev) { AllocList *p = head; for (int level = head->levels - 1; level >= 0; level--) { for (AllocList *n; (n = p->next[level]) != nullptr && n < e; p = n) { @@ -190,7 +190,7 @@ static void LLA_SkiplistDelete(AllocList *head, AllocList *e, prev[i]->next[i] = e->next[i]; } while (head->levels > 0 && head->next[head->levels - 1] == nullptr) { - head->levels--; // reduce head->levels if level unused + head->levels--; // reduce head->levels if level unused } } @@ -249,9 +249,9 @@ void CreateGlobalArenas() { // Returns a global arena that does not call into hooks. Used by NewArena() // when kCallMallocHook is not set. -LowLevelAlloc::Arena* UnhookedArena() { +LowLevelAlloc::Arena *UnhookedArena() { base_internal::LowLevelCallOnce(&create_globals_once, CreateGlobalArenas); - return reinterpret_cast<LowLevelAlloc::Arena*>(&unhooked_arena_storage); + return reinterpret_cast<LowLevelAlloc::Arena *>(&unhooked_arena_storage); } #ifndef ABSL_LOW_LEVEL_ALLOC_ASYNC_SIGNAL_SAFE_MISSING @@ -269,7 +269,7 @@ LowLevelAlloc::Arena *UnhookedAsyncSigSafeArena() { // Returns the default arena, as used by LowLevelAlloc::Alloc() and friends. LowLevelAlloc::Arena *LowLevelAlloc::DefaultArena() { base_internal::LowLevelCallOnce(&create_globals_once, CreateGlobalArenas); - return reinterpret_cast<LowLevelAlloc::Arena*>(&default_arena_storage); + return reinterpret_cast<LowLevelAlloc::Arena *>(&default_arena_storage); } // magic numbers to identify allocated and unallocated blocks @@ -329,7 +329,7 @@ size_t GetPageSize() { SYSTEM_INFO system_info; GetSystemInfo(&system_info); return std::max(system_info.dwPageSize, system_info.dwAllocationGranularity); -#elif defined(__wasm__) || defined(__asmjs__) +#elif defined(__wasm__) || defined(__asmjs__) || defined(__hexagon__) return getpagesize(); #else return static_cast<size_t>(sysconf(_SC_PAGESIZE)); @@ -356,8 +356,7 @@ LowLevelAlloc::Arena::Arena(uint32_t flags_value) min_size(2 * round_up), random(0) { freelist.header.size = 0; - freelist.header.magic = - Magic(kMagicUnallocated, &freelist.header); + freelist.header.magic = Magic(kMagicUnallocated, &freelist.header); freelist.header.arena = this; freelist.levels = 0; memset(freelist.next, 0, sizeof(freelist.next)); @@ -375,7 +374,7 @@ LowLevelAlloc::Arena *LowLevelAlloc::NewArena(uint32_t flags) { meta_data_arena = UnhookedArena(); } Arena *result = - new (AllocWithArena(sizeof (*result), meta_data_arena)) Arena(flags); + new (AllocWithArena(sizeof(*result), meta_data_arena)) Arena(flags); return result; } @@ -480,8 +479,8 @@ static void Coalesce(AllocList *a) { AllocList *prev[kMaxLevel]; LLA_SkiplistDelete(&arena->freelist, n, prev); LLA_SkiplistDelete(&arena->freelist, a, prev); - a->levels = LLA_SkiplistLevels(a->header.size, arena->min_size, - &arena->random); + a->levels = + LLA_SkiplistLevels(a->header.size, arena->min_size, &arena->random); LLA_SkiplistInsert(&arena->freelist, a, prev); } } @@ -489,27 +488,27 @@ static void Coalesce(AllocList *a) { // Adds block at location "v" to the free list // L >= arena->mu static void AddToFreelist(void *v, LowLevelAlloc::Arena *arena) { - AllocList *f = reinterpret_cast<AllocList *>( - reinterpret_cast<char *>(v) - sizeof (f->header)); + AllocList *f = reinterpret_cast<AllocList *>(reinterpret_cast<char *>(v) - + sizeof(f->header)); ABSL_RAW_CHECK(f->header.magic == Magic(kMagicAllocated, &f->header), "bad magic number in AddToFreelist()"); ABSL_RAW_CHECK(f->header.arena == arena, "bad arena pointer in AddToFreelist()"); - f->levels = LLA_SkiplistLevels(f->header.size, arena->min_size, - &arena->random); + f->levels = + LLA_SkiplistLevels(f->header.size, arena->min_size, &arena->random); AllocList *prev[kMaxLevel]; LLA_SkiplistInsert(&arena->freelist, f, prev); f->header.magic = Magic(kMagicUnallocated, &f->header); - Coalesce(f); // maybe coalesce with successor - Coalesce(prev[0]); // maybe coalesce with predecessor + Coalesce(f); // maybe coalesce with successor + Coalesce(prev[0]); // maybe coalesce with predecessor } // Frees storage allocated by LowLevelAlloc::Alloc(). // L < arena->mu void LowLevelAlloc::Free(void *v) { if (v != nullptr) { - AllocList *f = reinterpret_cast<AllocList *>( - reinterpret_cast<char *>(v) - sizeof (f->header)); + AllocList *f = reinterpret_cast<AllocList *>(reinterpret_cast<char *>(v) - + sizeof(f->header)); LowLevelAlloc::Arena *arena = f->header.arena; ArenaLock section(arena); AddToFreelist(v, arena); @@ -524,21 +523,21 @@ void LowLevelAlloc::Free(void *v) { static void *DoAllocWithArena(size_t request, LowLevelAlloc::Arena *arena) { void *result = nullptr; if (request != 0) { - AllocList *s; // will point to region that satisfies request + AllocList *s; // will point to region that satisfies request ArenaLock section(arena); // round up with header - size_t req_rnd = RoundUp(CheckedAdd(request, sizeof (s->header)), - arena->round_up); - for (;;) { // loop until we find a suitable region + size_t req_rnd = + RoundUp(CheckedAdd(request, sizeof(s->header)), arena->round_up); + for (;;) { // loop until we find a suitable region // find the minimum levels that a block of this size must have int i = LLA_SkiplistLevels(req_rnd, arena->min_size, nullptr) - 1; - if (i < arena->freelist.levels) { // potential blocks exist + if (i < arena->freelist.levels) { // potential blocks exist AllocList *before = &arena->freelist; // predecessor of s while ((s = Next(i, before, arena)) != nullptr && s->header.size < req_rnd) { before = s; } - if (s != nullptr) { // we found a region + if (s != nullptr) { // we found a region break; } } @@ -550,7 +549,7 @@ static void *DoAllocWithArena(size_t request, LowLevelAlloc::Arena *arena) { size_t new_pages_size = RoundUp(req_rnd, arena->pagesize * 16); void *new_pages; #ifdef _WIN32 - new_pages = VirtualAlloc(0, new_pages_size, + new_pages = VirtualAlloc(nullptr, new_pages_size, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); ABSL_RAW_CHECK(new_pages != nullptr, "VirtualAlloc failed"); #else @@ -570,6 +569,18 @@ static void *DoAllocWithArena(size_t request, LowLevelAlloc::Arena *arena) { ABSL_RAW_LOG(FATAL, "mmap error: %d", errno); } +#ifdef __linux__ +#if defined(PR_SET_VMA) && defined(PR_SET_VMA_ANON_NAME) + // Attempt to name the allocated address range in /proc/$PID/smaps on + // Linux. + // + // This invocation of prctl() may fail if the Linux kernel was not + // configured with the CONFIG_ANON_VMA_NAME option. This is OK since + // the naming of arenas is primarily a debugging aid. + prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, new_pages, new_pages_size, + "absl"); +#endif +#endif // __linux__ #endif // _WIN32 arena->mu.Lock(); s = reinterpret_cast<AllocList *>(new_pages); @@ -580,12 +591,12 @@ static void *DoAllocWithArena(size_t request, LowLevelAlloc::Arena *arena) { AddToFreelist(&s->levels, arena); // insert new region into free list } AllocList *prev[kMaxLevel]; - LLA_SkiplistDelete(&arena->freelist, s, prev); // remove from free list + LLA_SkiplistDelete(&arena->freelist, s, prev); // remove from free list // s points to the first free region that's big enough if (CheckedAdd(req_rnd, arena->min_size) <= s->header.size) { // big enough to split - AllocList *n = reinterpret_cast<AllocList *> - (req_rnd + reinterpret_cast<char *>(s)); + AllocList *n = + reinterpret_cast<AllocList *>(req_rnd + reinterpret_cast<char *>(s)); n->header.size = s->header.size - req_rnd; n->header.magic = Magic(kMagicAllocated, &n->header); n->header.arena = arena; diff --git a/absl/base/internal/low_level_alloc.h b/absl/base/internal/low_level_alloc.h index eabb14a9..c2f1f25d 100644 --- a/absl/base/internal/low_level_alloc.h +++ b/absl/base/internal/low_level_alloc.h @@ -46,7 +46,8 @@ // for more information. #ifdef ABSL_LOW_LEVEL_ALLOC_ASYNC_SIGNAL_SAFE_MISSING #error ABSL_LOW_LEVEL_ALLOC_ASYNC_SIGNAL_SAFE_MISSING cannot be directly set -#elif defined(_WIN32) || defined(__asmjs__) || defined(__wasm__) +#elif defined(_WIN32) || defined(__asmjs__) || defined(__wasm__) || \ + defined(__hexagon__) #define ABSL_LOW_LEVEL_ALLOC_ASYNC_SIGNAL_SAFE_MISSING 1 #endif diff --git a/absl/base/internal/nullability_impl.h b/absl/base/internal/nullability_impl.h new file mode 100644 index 00000000..36e1b33d --- /dev/null +++ b/absl/base/internal/nullability_impl.h @@ -0,0 +1,106 @@ +// Copyright 2023 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_BASE_INTERNAL_NULLABILITY_IMPL_H_ +#define ABSL_BASE_INTERNAL_NULLABILITY_IMPL_H_ + +#include <memory> +#include <type_traits> + +#include "absl/base/attributes.h" +#include "absl/meta/type_traits.h" + +namespace absl { + +namespace nullability_internal { + +// `IsNullabilityCompatible` checks whether its first argument is a class +// explicitly tagged as supporting nullability annotations. The tag is the type +// declaration `absl_nullability_compatible`. +template <typename, typename = void> +struct IsNullabilityCompatible : std::false_type {}; + +template <typename T> +struct IsNullabilityCompatible< + T, absl::void_t<typename T::absl_nullability_compatible>> : std::true_type { +}; + +template <typename T> +constexpr bool IsSupportedType = IsNullabilityCompatible<T>::value; + +template <typename T> +constexpr bool IsSupportedType<T*> = true; + +template <typename T, typename U> +constexpr bool IsSupportedType<T U::*> = true; + +template <typename T, typename... Deleter> +constexpr bool IsSupportedType<std::unique_ptr<T, Deleter...>> = true; + +template <typename T> +constexpr bool IsSupportedType<std::shared_ptr<T>> = true; + +template <typename T> +struct EnableNullable { + static_assert(nullability_internal::IsSupportedType<std::remove_cv_t<T>>, + "Template argument must be a raw or supported smart pointer " + "type. See absl/base/nullability.h."); + using type = T; +}; + +template <typename T> +struct EnableNonnull { + static_assert(nullability_internal::IsSupportedType<std::remove_cv_t<T>>, + "Template argument must be a raw or supported smart pointer " + "type. See absl/base/nullability.h."); + using type = T; +}; + +template <typename T> +struct EnableNullabilityUnknown { + static_assert(nullability_internal::IsSupportedType<std::remove_cv_t<T>>, + "Template argument must be a raw or supported smart pointer " + "type. See absl/base/nullability.h."); + using type = T; +}; + +// Note: we do not apply Clang nullability attributes (e.g. _Nullable). These +// only support raw pointers, and conditionally enabling them only for raw +// pointers inhibits template arg deduction. Ideally, they would support all +// pointer-like types. +template <typename T, typename = typename EnableNullable<T>::type> +using NullableImpl +#if ABSL_HAVE_CPP_ATTRIBUTE(clang::annotate) + [[clang::annotate("Nullable")]] +#endif + = T; + +template <typename T, typename = typename EnableNonnull<T>::type> +using NonnullImpl +#if ABSL_HAVE_CPP_ATTRIBUTE(clang::annotate) + [[clang::annotate("Nonnull")]] +#endif + = T; + +template <typename T, typename = typename EnableNullabilityUnknown<T>::type> +using NullabilityUnknownImpl +#if ABSL_HAVE_CPP_ATTRIBUTE(clang::annotate) + [[clang::annotate("Nullability_Unspecified")]] +#endif + = T; + +} // namespace nullability_internal +} // namespace absl + +#endif // ABSL_BASE_INTERNAL_NULLABILITY_IMPL_H_ diff --git a/absl/base/internal/prefetch.h b/absl/base/internal/prefetch.h deleted file mode 100644 index 06419283..00000000 --- a/absl/base/internal/prefetch.h +++ /dev/null @@ -1,138 +0,0 @@ -// 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_BASE_INTERNAL_PREFETCH_H_ -#define ABSL_BASE_INTERNAL_PREFETCH_H_ - -#include "absl/base/config.h" - -#ifdef __SSE__ -#include <xmmintrin.h> -#endif - -#if defined(_MSC_VER) && defined(ABSL_INTERNAL_HAVE_SSE) -#include <intrin.h> -#pragma intrinsic(_mm_prefetch) -#endif - -// Compatibility wrappers around __builtin_prefetch, to prefetch data -// for read if supported by the toolchain. - -// Move data into the cache before it is read, or "prefetch" it. -// -// The value of `addr` is the address of the memory to prefetch. If -// the target and compiler support it, data prefetch instructions are -// generated. If the prefetch is done some time before the memory is -// read, it may be in the cache by the time the read occurs. -// -// The function names specify the temporal locality heuristic applied, -// using the names of Intel prefetch instructions: -// -// T0 - high degree of temporal locality; data should be left in as -// many levels of the cache possible -// T1 - moderate degree of temporal locality -// T2 - low degree of temporal locality -// Nta - no temporal locality, data need not be left in the cache -// after the read -// -// Incorrect or gratuitous use of these functions can degrade -// performance, so use them only when representative benchmarks show -// an improvement. -// -// Example usage: -// -// absl::base_internal::PrefetchT0(addr); -// -// Currently, the different prefetch calls behave on some Intel -// architectures as follows: -// -// SNB..SKL SKX -// PrefetchT0() L1/L2/L3 L1/L2 -// PrefetchT1() L2/L3 L2 -// PrefetchT2() L2/L3 L2 -// PrefetchNta() L1/--/L3 L1* -// -// * On SKX PrefetchNta() will bring the line into L1 but will evict -// from L3 cache. This might result in surprising behavior. -// -// SNB = Sandy Bridge, SKL = Skylake, SKX = Skylake Xeon. -// -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace base_internal { - -void PrefetchT0(const void* addr); -void PrefetchT1(const void* addr); -void PrefetchT2(const void* addr); -void PrefetchNta(const void* addr); - -// Implementation details follow. - -#if ABSL_HAVE_BUILTIN(__builtin_prefetch) || defined(__GNUC__) - -#define ABSL_INTERNAL_HAVE_PREFETCH 1 - -// See __builtin_prefetch: -// https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html. -// -// These functions speculatively load for read only. This is -// safe for all currently supported platforms. However, prefetch for -// store may have problems depending on the target platform. -// -inline void PrefetchT0(const void* addr) { - // Note: this uses prefetcht0 on Intel. - __builtin_prefetch(addr, 0, 3); -} -inline void PrefetchT1(const void* addr) { - // Note: this uses prefetcht1 on Intel. - __builtin_prefetch(addr, 0, 2); -} -inline void PrefetchT2(const void* addr) { - // Note: this uses prefetcht2 on Intel. - __builtin_prefetch(addr, 0, 1); -} -inline void PrefetchNta(const void* addr) { - // Note: this uses prefetchtnta on Intel. - __builtin_prefetch(addr, 0, 0); -} - -#elif defined(ABSL_INTERNAL_HAVE_SSE) - -#define ABSL_INTERNAL_HAVE_PREFETCH 1 - -inline void PrefetchT0(const void* addr) { - _mm_prefetch(reinterpret_cast<const char*>(addr), _MM_HINT_T0); -} -inline void PrefetchT1(const void* addr) { - _mm_prefetch(reinterpret_cast<const char*>(addr), _MM_HINT_T1); -} -inline void PrefetchT2(const void* addr) { - _mm_prefetch(reinterpret_cast<const char*>(addr), _MM_HINT_T2); -} -inline void PrefetchNta(const void* addr) { - _mm_prefetch(reinterpret_cast<const char*>(addr), _MM_HINT_NTA); -} - -#else -inline void PrefetchT0(const void*) {} -inline void PrefetchT1(const void*) {} -inline void PrefetchT2(const void*) {} -inline void PrefetchNta(const void*) {} -#endif - -} // namespace base_internal -ABSL_NAMESPACE_END -} // namespace absl - -#endif // ABSL_BASE_INTERNAL_PREFETCH_H_ diff --git a/absl/base/internal/prefetch_test.cc b/absl/base/internal/prefetch_test.cc deleted file mode 100644 index 7c1dae46..00000000 --- a/absl/base/internal/prefetch_test.cc +++ /dev/null @@ -1,43 +0,0 @@ -// 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/base/internal/prefetch.h" - -#include "gtest/gtest.h" - -namespace { - -int number = 42; - -TEST(Prefetch, TemporalLocalityNone) { - absl::base_internal::PrefetchNta(&number); - EXPECT_EQ(number, 42); -} - -TEST(Prefetch, TemporalLocalityLow) { - absl::base_internal::PrefetchT2(&number); - EXPECT_EQ(number, 42); -} - -TEST(Prefetch, TemporalLocalityMedium) { - absl::base_internal::PrefetchT1(&number); - EXPECT_EQ(number, 42); -} - -TEST(Prefetch, TemporalLocalityHigh) { - absl::base_internal::PrefetchT0(&number); - EXPECT_EQ(number, 42); -} - -} // namespace diff --git a/absl/base/internal/raw_logging.cc b/absl/base/internal/raw_logging.cc index 6273e847..d32b40a8 100644 --- a/absl/base/internal/raw_logging.cc +++ b/absl/base/internal/raw_logging.cc @@ -21,6 +21,10 @@ #include <cstring> #include <string> +#ifdef __EMSCRIPTEN__ +#include <emscripten/console.h> +#endif + #include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/internal/atomic_hook.h" @@ -38,8 +42,9 @@ // This preprocessor token is also defined in raw_io.cc. If you need to copy // this, consider moving both to config.h instead. #if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \ - defined(__Fuchsia__) || defined(__native_client__) || \ - defined(__OpenBSD__) || defined(__EMSCRIPTEN__) || defined(__ASYLO__) + defined(__hexagon__) || defined(__Fuchsia__) || \ + defined(__native_client__) || defined(__OpenBSD__) || \ + defined(__EMSCRIPTEN__) || defined(__ASYLO__) #include <unistd.h> @@ -52,8 +57,7 @@ // ABSL_HAVE_SYSCALL_WRITE is defined when the platform provides the syscall // syscall(SYS_write, /*int*/ fd, /*char* */ buf, /*size_t*/ len); // for low level operations that want to avoid libc. -#if (defined(__linux__) || defined(__FreeBSD__) || defined(__OpenBSD__)) && \ - !defined(__ANDROID__) +#if (defined(__linux__) || defined(__FreeBSD__)) && !defined(__ANDROID__) #include <sys/syscall.h> #define ABSL_HAVE_SYSCALL_WRITE 1 #define ABSL_LOW_LEVEL_WRITE_SUPPORTED 1 @@ -89,8 +93,7 @@ constexpr char kTruncated[] = " ... (message truncated)\n"; bool VADoRawLog(char** buf, int* size, const char* format, va_list ap) ABSL_PRINTF_ATTRIBUTE(3, 0); bool VADoRawLog(char** buf, int* size, const char* format, va_list ap) { - if (*size < 0) - return false; + if (*size < 0) return false; int n = vsnprintf(*buf, static_cast<size_t>(*size), format, ap); bool result = true; if (n < 0 || n > *size) { @@ -118,8 +121,7 @@ constexpr int kLogBufSize = 3000; bool DoRawLog(char** buf, int* size, const char* format, ...) ABSL_PRINTF_ATTRIBUTE(3, 4); bool DoRawLog(char** buf, int* size, const char* format, ...) { - if (*size < 0) - return false; + if (*size < 0) return false; va_list ap; va_start(ap, format); int n = vsnprintf(*buf, static_cast<size_t>(*size), format, ap); @@ -173,7 +175,7 @@ void RawLogVA(absl::LogSeverity severity, const char* file, int line, } else { DoRawLog(&buf, &size, "%s", kTruncated); } - AsyncSignalSafeWriteToStderr(buffer, strlen(buffer)); + AsyncSignalSafeWriteError(buffer, strlen(buffer)); } #else static_cast<void>(format); @@ -201,9 +203,34 @@ void DefaultInternalLog(absl::LogSeverity severity, const char* file, int line, } // namespace -void AsyncSignalSafeWriteToStderr(const char* s, size_t len) { +void AsyncSignalSafeWriteError(const char* s, size_t len) { + if (!len) return; absl::base_internal::ErrnoSaver errno_saver; -#if defined(ABSL_HAVE_SYSCALL_WRITE) +#if defined(__EMSCRIPTEN__) + // In WebAssembly, bypass filesystem emulation via fwrite. + if (s[len - 1] == '\n') { + // Skip a trailing newline character as emscripten_errn adds one itself. + len--; + } + // emscripten_errn was introduced in 3.1.41 but broken in standalone mode + // until 3.1.43. +#if ABSL_INTERNAL_EMSCRIPTEN_VERSION >= 3001043 + emscripten_errn(s, len); +#else + char buf[kLogBufSize]; + if (len >= kLogBufSize) { + len = kLogBufSize - 1; + constexpr size_t trunc_len = sizeof(kTruncated) - 2; + memcpy(buf + len - trunc_len, kTruncated, trunc_len); + buf[len] = '\0'; + len -= trunc_len; + } else { + buf[len] = '\0'; + } + memcpy(buf, s, len); + _emscripten_err(buf); +#endif +#elif defined(ABSL_HAVE_SYSCALL_WRITE) // We prefer calling write via `syscall` to minimize the risk of libc doing // something "helpful". syscall(SYS_write, STDERR_FILENO, s, len); @@ -213,8 +240,8 @@ void AsyncSignalSafeWriteToStderr(const char* s, size_t len) { _write(/* stderr */ 2, s, static_cast<unsigned>(len)); #else // stderr logging unsupported on this platform - (void) s; - (void) len; + (void)s; + (void)len; #endif } @@ -229,7 +256,7 @@ void RawLog(absl::LogSeverity severity, const char* file, int line, bool RawLoggingFullySupported() { #ifdef ABSL_LOW_LEVEL_WRITE_SUPPORTED return true; -#else // !ABSL_LOW_LEVEL_WRITE_SUPPORTED +#else // !ABSL_LOW_LEVEL_WRITE_SUPPORTED return false; #endif // !ABSL_LOW_LEVEL_WRITE_SUPPORTED } diff --git a/absl/base/internal/raw_logging.h b/absl/base/internal/raw_logging.h index c7b889cd..d7cfbc57 100644 --- a/absl/base/internal/raw_logging.h +++ b/absl/base/internal/raw_logging.h @@ -48,6 +48,7 @@ ::absl::raw_log_internal::RawLog(ABSL_RAW_LOG_INTERNAL_##severity, \ absl_raw_log_internal_basename, __LINE__, \ __VA_ARGS__); \ + ABSL_RAW_LOG_INTERNAL_MAYBE_UNREACHABLE_##severity; \ } while (0) // Similar to CHECK(condition) << message, but for low-level modules: @@ -77,8 +78,7 @@ ::absl::raw_log_internal::internal_log_function( \ ABSL_RAW_LOG_INTERNAL_##severity, absl_raw_log_internal_filename, \ __LINE__, message); \ - if (ABSL_RAW_LOG_INTERNAL_##severity == ::absl::LogSeverity::kFatal) \ - ABSL_UNREACHABLE(); \ + ABSL_RAW_LOG_INTERNAL_MAYBE_UNREACHABLE_##severity; \ } while (0) #define ABSL_INTERNAL_CHECK(condition, message) \ @@ -90,13 +90,35 @@ } \ } while (0) +#ifndef NDEBUG + +#define ABSL_RAW_DLOG(severity, ...) ABSL_RAW_LOG(severity, __VA_ARGS__) +#define ABSL_RAW_DCHECK(condition, message) ABSL_RAW_CHECK(condition, message) + +#else // NDEBUG + +#define ABSL_RAW_DLOG(severity, ...) \ + while (false) ABSL_RAW_LOG(severity, __VA_ARGS__) +#define ABSL_RAW_DCHECK(condition, message) \ + while (false) ABSL_RAW_CHECK(condition, message) + +#endif // NDEBUG + #define ABSL_RAW_LOG_INTERNAL_INFO ::absl::LogSeverity::kInfo #define ABSL_RAW_LOG_INTERNAL_WARNING ::absl::LogSeverity::kWarning #define ABSL_RAW_LOG_INTERNAL_ERROR ::absl::LogSeverity::kError #define ABSL_RAW_LOG_INTERNAL_FATAL ::absl::LogSeverity::kFatal +#define ABSL_RAW_LOG_INTERNAL_DFATAL ::absl::kLogDebugFatal #define ABSL_RAW_LOG_INTERNAL_LEVEL(severity) \ ::absl::NormalizeLogSeverity(severity) +#define ABSL_RAW_LOG_INTERNAL_MAYBE_UNREACHABLE_INFO +#define ABSL_RAW_LOG_INTERNAL_MAYBE_UNREACHABLE_WARNING +#define ABSL_RAW_LOG_INTERNAL_MAYBE_UNREACHABLE_ERROR +#define ABSL_RAW_LOG_INTERNAL_MAYBE_UNREACHABLE_FATAL ABSL_UNREACHABLE() +#define ABSL_RAW_LOG_INTERNAL_MAYBE_UNREACHABLE_DFATAL +#define ABSL_RAW_LOG_INTERNAL_MAYBE_UNREACHABLE_LEVEL(severity) + namespace absl { ABSL_NAMESPACE_BEGIN namespace raw_log_internal { @@ -109,8 +131,8 @@ void RawLog(absl::LogSeverity severity, const char* file, int line, const char* format, ...) ABSL_PRINTF_ATTRIBUTE(4, 5); // Writes the provided buffer directly to stderr, in a signal-safe, low-level -// manner. -void AsyncSignalSafeWriteToStderr(const char* s, size_t len); +// manner. Preserves errno. +void AsyncSignalSafeWriteError(const char* s, size_t len); // compile-time function to get the "base" filename, that is, the part of // a filename after the last "/" or "\" path separator. The search starts at diff --git a/absl/base/internal/spinlock.h b/absl/base/internal/spinlock.h index 09ba5824..2929cd6f 100644 --- a/absl/base/internal/spinlock.h +++ b/absl/base/internal/spinlock.h @@ -19,10 +19,10 @@ // - for use by Abseil internal code that Mutex itself depends on // - for async signal safety (see below) -// SpinLock is async signal safe. If a spinlock is used within a signal -// handler, all code that acquires the lock must ensure that the signal cannot -// arrive while they are holding the lock. Typically, this is done by blocking -// the signal. +// SpinLock with a base_internal::SchedulingMode::SCHEDULE_KERNEL_ONLY is async +// signal safe. If a spinlock is used within a signal handler, all code that +// acquires the lock must ensure that the signal cannot arrive while they are +// holding the lock. Typically, this is done by blocking the signal. // // Threads waiting on a SpinLock may be woken in an arbitrary order. @@ -41,6 +41,14 @@ #include "absl/base/internal/tsan_mutex_interface.h" #include "absl/base/thread_annotations.h" +namespace tcmalloc { +namespace tcmalloc_internal { + +class AllocationGuardSpinLockHolder; + +} // namespace tcmalloc_internal +} // namespace tcmalloc + namespace absl { ABSL_NAMESPACE_BEGIN namespace base_internal { @@ -137,6 +145,7 @@ class ABSL_LOCKABLE SpinLock { // Provide access to protected method above. Use for testing only. friend struct SpinLockTest; + friend class tcmalloc::tcmalloc_internal::AllocationGuardSpinLockHolder; private: // lockword_ is used to store the following: @@ -171,6 +180,10 @@ class ABSL_LOCKABLE SpinLock { return scheduling_mode == base_internal::SCHEDULE_COOPERATIVE_AND_KERNEL; } + bool IsCooperative() const { + return lockword_.load(std::memory_order_relaxed) & kSpinLockCooperative; + } + uint32_t TryLockInternal(uint32_t lock_value, uint32_t wait_cycles); void SlowLock() ABSL_ATTRIBUTE_COLD; void SlowUnlock(uint32_t lock_value) ABSL_ATTRIBUTE_COLD; diff --git a/absl/base/internal/spinlock_benchmark.cc b/absl/base/internal/spinlock_benchmark.cc index 0451c65f..1790d967 100644 --- a/absl/base/internal/spinlock_benchmark.cc +++ b/absl/base/internal/spinlock_benchmark.cc @@ -18,22 +18,39 @@ #include "absl/base/internal/raw_logging.h" #include "absl/base/internal/scheduling_mode.h" #include "absl/base/internal/spinlock.h" +#include "absl/base/no_destructor.h" #include "absl/synchronization/internal/create_thread_identity.h" #include "benchmark/benchmark.h" namespace { template <absl::base_internal::SchedulingMode scheduling_mode> +static void BM_TryLock(benchmark::State& state) { + // Ensure a ThreadIdentity is installed so that KERNEL_ONLY has an effect. + ABSL_INTERNAL_CHECK( + absl::synchronization_internal::GetOrCreateCurrentThreadIdentity() != + nullptr, + "GetOrCreateCurrentThreadIdentity() failed"); + + static absl::NoDestructor<absl::base_internal::SpinLock> spinlock( + scheduling_mode); + for (auto _ : state) { + if (spinlock->TryLock()) spinlock->Unlock(); + } +} + +template <absl::base_internal::SchedulingMode scheduling_mode> static void BM_SpinLock(benchmark::State& state) { - // Ensure a ThreadIdentity is installed. + // Ensure a ThreadIdentity is installed so that KERNEL_ONLY has an effect. ABSL_INTERNAL_CHECK( absl::synchronization_internal::GetOrCreateCurrentThreadIdentity() != nullptr, "GetOrCreateCurrentThreadIdentity() failed"); - static auto* spinlock = new absl::base_internal::SpinLock(scheduling_mode); + static absl::NoDestructor<absl::base_internal::SpinLock> spinlock( + scheduling_mode); for (auto _ : state) { - absl::base_internal::SpinLockHolder holder(spinlock); + absl::base_internal::SpinLockHolder holder(spinlock.get()); } } @@ -49,4 +66,15 @@ BENCHMARK_TEMPLATE(BM_SpinLock, ->Threads(1) ->ThreadPerCpu(); +BENCHMARK_TEMPLATE(BM_TryLock, absl::base_internal::SCHEDULE_KERNEL_ONLY) + ->UseRealTime() + ->Threads(1) + ->ThreadPerCpu(); + +BENCHMARK_TEMPLATE(BM_TryLock, + absl::base_internal::SCHEDULE_COOPERATIVE_AND_KERNEL) + ->UseRealTime() + ->Threads(1) + ->ThreadPerCpu(); + } // namespace diff --git a/absl/base/internal/sysinfo.cc b/absl/base/internal/sysinfo.cc index da499d3a..79eaba3e 100644 --- a/absl/base/internal/sysinfo.cc +++ b/absl/base/internal/sysinfo.cc @@ -34,6 +34,14 @@ #include <sys/sysctl.h> #endif +#ifdef __FreeBSD__ +#include <pthread_np.h> +#endif + +#ifdef __NetBSD__ +#include <lwp.h> +#endif + #if defined(__myriad2__) #include <rtems.h> #endif @@ -41,6 +49,7 @@ #include <string.h> #include <cassert> +#include <cerrno> #include <cstdint> #include <cstdio> #include <cstdlib> @@ -159,7 +168,7 @@ static double GetNominalCPUFrequency() { DWORD type = 0; DWORD data = 0; DWORD data_size = sizeof(data); - auto result = RegQueryValueExA(key, "~MHz", 0, &type, + auto result = RegQueryValueExA(key, "~MHz", nullptr, &type, reinterpret_cast<LPBYTE>(&data), &data_size); RegCloseKey(key); if (result == ERROR_SUCCESS && type == REG_DWORD && @@ -189,7 +198,13 @@ static double GetNominalCPUFrequency() { // and the memory location pointed to by value is set to the value read. static bool ReadLongFromFile(const char *file, long *value) { bool ret = false; - int fd = open(file, O_RDONLY | O_CLOEXEC); +#if defined(_POSIX_C_SOURCE) + const int file_mode = (O_RDONLY | O_CLOEXEC); +#else + const int file_mode = O_RDONLY; +#endif + + int fd = open(file, file_mode); if (fd != -1) { char line[1024]; char *err; @@ -225,8 +240,8 @@ static int64_t ReadMonotonicClockNanos() { int rc = clock_gettime(CLOCK_MONOTONIC, &t); #endif if (rc != 0) { - perror("clock_gettime() failed"); - abort(); + ABSL_INTERNAL_LOG( + FATAL, "clock_gettime() failed: (" + std::to_string(errno) + ")"); } return int64_t{t.tv_sec} * 1000000000 + t.tv_nsec; } @@ -414,82 +429,45 @@ pid_t GetTID() { return tid; } -#else +#elif defined(__APPLE__) -// Fallback implementation of GetTID using pthread_getspecific. -ABSL_CONST_INIT static once_flag tid_once; -ABSL_CONST_INIT static pthread_key_t tid_key; -ABSL_CONST_INIT static absl::base_internal::SpinLock tid_lock( - absl::kConstInit, base_internal::SCHEDULE_KERNEL_ONLY); - -// We set a bit per thread in this array to indicate that an ID is in -// use. ID 0 is unused because it is the default value returned by -// pthread_getspecific(). -ABSL_CONST_INIT static std::vector<uint32_t> *tid_array - ABSL_GUARDED_BY(tid_lock) = nullptr; -static constexpr int kBitsPerWord = 32; // tid_array is uint32_t. - -// Returns the TID to tid_array. -static void FreeTID(void *v) { - intptr_t tid = reinterpret_cast<intptr_t>(v); - intptr_t word = tid / kBitsPerWord; - uint32_t mask = ~(1u << (tid % kBitsPerWord)); - absl::base_internal::SpinLockHolder lock(&tid_lock); - assert(0 <= word && static_cast<size_t>(word) < tid_array->size()); - (*tid_array)[static_cast<size_t>(word)] &= mask; +pid_t GetTID() { + uint64_t tid; + // `nullptr` here implies this thread. This only fails if the specified + // thread is invalid or the pointer-to-tid is null, so we needn't worry about + // it. + pthread_threadid_np(nullptr, &tid); + return static_cast<pid_t>(tid); } -static void InitGetTID() { - if (pthread_key_create(&tid_key, FreeTID) != 0) { - // The logging system calls GetTID() so it can't be used here. - perror("pthread_key_create failed"); - abort(); - } +#elif defined(__FreeBSD__) - // Initialize tid_array. - absl::base_internal::SpinLockHolder lock(&tid_lock); - tid_array = new std::vector<uint32_t>(1); - (*tid_array)[0] = 1; // ID 0 is never-allocated. -} +pid_t GetTID() { return static_cast<pid_t>(pthread_getthreadid_np()); } -// Return a per-thread small integer ID from pthread's thread-specific data. -pid_t GetTID() { - absl::call_once(tid_once, InitGetTID); +#elif defined(__OpenBSD__) - intptr_t tid = reinterpret_cast<intptr_t>(pthread_getspecific(tid_key)); - if (tid != 0) { - return static_cast<pid_t>(tid); - } +pid_t GetTID() { return getthrid(); } - int bit; // tid_array[word] = 1u << bit; - size_t word; - { - // Search for the first unused ID. - absl::base_internal::SpinLockHolder lock(&tid_lock); - // First search for a word in the array that is not all ones. - word = 0; - while (word < tid_array->size() && ~(*tid_array)[word] == 0) { - ++word; - } - if (word == tid_array->size()) { - tid_array->push_back(0); // No space left, add kBitsPerWord more IDs. - } - // Search for a zero bit in the word. - bit = 0; - while (bit < kBitsPerWord && (((*tid_array)[word] >> bit) & 1) != 0) { - ++bit; - } - tid = - static_cast<intptr_t>((word * kBitsPerWord) + static_cast<size_t>(bit)); - (*tid_array)[word] |= 1u << bit; // Mark the TID as allocated. - } +#elif defined(__NetBSD__) - if (pthread_setspecific(tid_key, reinterpret_cast<void *>(tid)) != 0) { - perror("pthread_setspecific failed"); - abort(); - } +pid_t GetTID() { return static_cast<pid_t>(_lwp_self()); } - return static_cast<pid_t>(tid); +#elif defined(__native_client__) + +pid_t GetTID() { + auto* thread = pthread_self(); + static_assert(sizeof(pid_t) == sizeof(thread), + "In NaCL int expected to be the same size as a pointer"); + return reinterpret_cast<pid_t>(thread); +} + +#else + +// Fallback implementation of `GetTID` using `pthread_self`. +pid_t GetTID() { + // `pthread_t` need not be arithmetic per POSIX; platforms where it isn't + // should be handled above. + return static_cast<pid_t>(pthread_self()); } #endif diff --git a/absl/base/internal/thread_annotations.h b/absl/base/internal/thread_annotations.h deleted file mode 100644 index 8c5c67e0..00000000 --- a/absl/base/internal/thread_annotations.h +++ /dev/null @@ -1,280 +0,0 @@ -// Copyright 2019 The Abseil Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// ----------------------------------------------------------------------------- -// File: thread_annotations.h -// ----------------------------------------------------------------------------- -// -// WARNING: This is a backwards compatible header and it will be removed after -// the migration to prefixed thread annotations is finished; please include -// "absl/base/thread_annotations.h". -// -// This header file contains macro definitions for thread safety annotations -// that allow developers to document the locking policies of multi-threaded -// code. The annotations can also help program analysis tools to identify -// potential thread safety issues. -// -// These annotations are implemented using compiler attributes. Using the macros -// defined here instead of raw attributes allow for portability and future -// compatibility. -// -// When referring to mutexes in the arguments of the attributes, you should -// use variable names or more complex expressions (e.g. my_object->mutex_) -// that evaluate to a concrete mutex object whenever possible. If the mutex -// you want to refer to is not in scope, you may use a member pointer -// (e.g. &MyClass::mutex_) to refer to a mutex in some (unknown) object. - -#ifndef ABSL_BASE_INTERNAL_THREAD_ANNOTATIONS_H_ -#define ABSL_BASE_INTERNAL_THREAD_ANNOTATIONS_H_ - -// ABSL_LEGACY_THREAD_ANNOTATIONS is a *temporary* compatibility macro that can -// be defined on the compile command-line to restore the legacy spellings of the -// thread annotations macros/functions. The macros in this file are available -// under ABSL_ prefixed spellings in absl/base/thread_annotations.h. This macro -// and the legacy spellings will be removed in the future. -#ifdef ABSL_LEGACY_THREAD_ANNOTATIONS - -#if defined(__clang__) -#define THREAD_ANNOTATION_ATTRIBUTE__(x) __attribute__((x)) -#else -#define THREAD_ANNOTATION_ATTRIBUTE__(x) // no-op -#endif - -// GUARDED_BY() -// -// Documents if a shared field or global variable needs to be protected by a -// mutex. GUARDED_BY() allows the user to specify a particular mutex that -// should be held when accessing the annotated variable. -// -// Although this annotation (and PT_GUARDED_BY, below) cannot be applied to -// local variables, a local variable and its associated mutex can often be -// combined into a small class or struct, thereby allowing the annotation. -// -// Example: -// -// class Foo { -// Mutex mu_; -// int p1_ GUARDED_BY(mu_); -// ... -// }; -#define GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(guarded_by(x)) - -// PT_GUARDED_BY() -// -// Documents if the memory location pointed to by a pointer should be guarded -// by a mutex when dereferencing the pointer. -// -// Example: -// class Foo { -// Mutex mu_; -// int *p1_ PT_GUARDED_BY(mu_); -// ... -// }; -// -// Note that a pointer variable to a shared memory location could itself be a -// shared variable. -// -// Example: -// -// // `q_`, guarded by `mu1_`, points to a shared memory location that is -// // guarded by `mu2_`: -// int *q_ GUARDED_BY(mu1_) PT_GUARDED_BY(mu2_); -#define PT_GUARDED_BY(x) THREAD_ANNOTATION_ATTRIBUTE__(pt_guarded_by(x)) - -// ACQUIRED_AFTER() / ACQUIRED_BEFORE() -// -// Documents the acquisition order between locks that can be held -// simultaneously by a thread. For any two locks that need to be annotated -// to establish an acquisition order, only one of them needs the annotation. -// (i.e. You don't have to annotate both locks with both ACQUIRED_AFTER -// and ACQUIRED_BEFORE.) -// -// As with GUARDED_BY, this is only applicable to mutexes that are shared -// fields or global variables. -// -// Example: -// -// Mutex m1_; -// Mutex m2_ ACQUIRED_AFTER(m1_); -#define ACQUIRED_AFTER(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(acquired_after(__VA_ARGS__)) - -#define ACQUIRED_BEFORE(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(acquired_before(__VA_ARGS__)) - -// EXCLUSIVE_LOCKS_REQUIRED() / SHARED_LOCKS_REQUIRED() -// -// Documents a function that expects a mutex to be held prior to entry. -// The mutex is expected to be held both on entry to, and exit from, the -// function. -// -// An exclusive lock allows read-write access to the guarded data member(s), and -// only one thread can acquire a lock exclusively at any one time. A shared lock -// allows read-only access, and any number of threads can acquire a shared lock -// concurrently. -// -// Generally, non-const methods should be annotated with -// EXCLUSIVE_LOCKS_REQUIRED, while const methods should be annotated with -// SHARED_LOCKS_REQUIRED. -// -// Example: -// -// Mutex mu1, mu2; -// int a GUARDED_BY(mu1); -// int b GUARDED_BY(mu2); -// -// void foo() EXCLUSIVE_LOCKS_REQUIRED(mu1, mu2) { ... } -// void bar() const SHARED_LOCKS_REQUIRED(mu1, mu2) { ... } -#define EXCLUSIVE_LOCKS_REQUIRED(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(exclusive_locks_required(__VA_ARGS__)) - -#define SHARED_LOCKS_REQUIRED(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(shared_locks_required(__VA_ARGS__)) - -// LOCKS_EXCLUDED() -// -// Documents the locks acquired in the body of the function. These locks -// cannot be held when calling this function (as Abseil's `Mutex` locks are -// non-reentrant). -#define LOCKS_EXCLUDED(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(locks_excluded(__VA_ARGS__)) - -// LOCK_RETURNED() -// -// Documents a function that returns a mutex without acquiring it. For example, -// a public getter method that returns a pointer to a private mutex should -// be annotated with LOCK_RETURNED. -#define LOCK_RETURNED(x) \ - THREAD_ANNOTATION_ATTRIBUTE__(lock_returned(x)) - -// LOCKABLE -// -// Documents if a class/type is a lockable type (such as the `Mutex` class). -#define LOCKABLE \ - THREAD_ANNOTATION_ATTRIBUTE__(lockable) - -// SCOPED_LOCKABLE -// -// Documents if a class does RAII locking (such as the `MutexLock` class). -// The constructor should use `LOCK_FUNCTION()` to specify the mutex that is -// acquired, and the destructor should use `UNLOCK_FUNCTION()` with no -// arguments; the analysis will assume that the destructor unlocks whatever the -// constructor locked. -#define SCOPED_LOCKABLE \ - THREAD_ANNOTATION_ATTRIBUTE__(scoped_lockable) - -// EXCLUSIVE_LOCK_FUNCTION() -// -// Documents functions that acquire a lock in the body of a function, and do -// not release it. -#define EXCLUSIVE_LOCK_FUNCTION(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(exclusive_lock_function(__VA_ARGS__)) - -// SHARED_LOCK_FUNCTION() -// -// Documents functions that acquire a shared (reader) lock in the body of a -// function, and do not release it. -#define SHARED_LOCK_FUNCTION(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(shared_lock_function(__VA_ARGS__)) - -// UNLOCK_FUNCTION() -// -// Documents functions that expect a lock to be held on entry to the function, -// and release it in the body of the function. -#define UNLOCK_FUNCTION(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(unlock_function(__VA_ARGS__)) - -// EXCLUSIVE_TRYLOCK_FUNCTION() / SHARED_TRYLOCK_FUNCTION() -// -// Documents functions that try to acquire a lock, and return success or failure -// (or a non-boolean value that can be interpreted as a boolean). -// The first argument should be `true` for functions that return `true` on -// success, or `false` for functions that return `false` on success. The second -// argument specifies the mutex that is locked on success. If unspecified, this -// mutex is assumed to be `this`. -#define EXCLUSIVE_TRYLOCK_FUNCTION(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(exclusive_trylock_function(__VA_ARGS__)) - -#define SHARED_TRYLOCK_FUNCTION(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(shared_trylock_function(__VA_ARGS__)) - -// ASSERT_EXCLUSIVE_LOCK() / ASSERT_SHARED_LOCK() -// -// Documents functions that dynamically check to see if a lock is held, and fail -// if it is not held. -#define ASSERT_EXCLUSIVE_LOCK(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(assert_exclusive_lock(__VA_ARGS__)) - -#define ASSERT_SHARED_LOCK(...) \ - THREAD_ANNOTATION_ATTRIBUTE__(assert_shared_lock(__VA_ARGS__)) - -// NO_THREAD_SAFETY_ANALYSIS -// -// Turns off thread safety checking within the body of a particular function. -// This annotation is used to mark functions that are known to be correct, but -// the locking behavior is more complicated than the analyzer can handle. -#define NO_THREAD_SAFETY_ANALYSIS \ - THREAD_ANNOTATION_ATTRIBUTE__(no_thread_safety_analysis) - -//------------------------------------------------------------------------------ -// Tool-Supplied Annotations -//------------------------------------------------------------------------------ - -// TS_UNCHECKED should be placed around lock expressions that are not valid -// C++ syntax, but which are present for documentation purposes. These -// annotations will be ignored by the analysis. -#define TS_UNCHECKED(x) "" - -// TS_FIXME is used to mark lock expressions that are not valid C++ syntax. -// It is used by automated tools to mark and disable invalid expressions. -// The annotation should either be fixed, or changed to TS_UNCHECKED. -#define TS_FIXME(x) "" - -// Like NO_THREAD_SAFETY_ANALYSIS, this turns off checking within the body of -// a particular function. However, this attribute is used to mark functions -// that are incorrect and need to be fixed. It is used by automated tools to -// avoid breaking the build when the analysis is updated. -// Code owners are expected to eventually fix the routine. -#define NO_THREAD_SAFETY_ANALYSIS_FIXME NO_THREAD_SAFETY_ANALYSIS - -// Similar to NO_THREAD_SAFETY_ANALYSIS_FIXME, this macro marks a GUARDED_BY -// annotation that needs to be fixed, because it is producing thread safety -// warning. It disables the GUARDED_BY. -#define GUARDED_BY_FIXME(x) - -// Disables warnings for a single read operation. This can be used to avoid -// warnings when it is known that the read is not actually involved in a race, -// but the compiler cannot confirm that. -#define TS_UNCHECKED_READ(x) thread_safety_analysis::ts_unchecked_read(x) - - -namespace thread_safety_analysis { - -// Takes a reference to a guarded data member, and returns an unguarded -// reference. -template <typename T> -inline const T& ts_unchecked_read(const T& v) NO_THREAD_SAFETY_ANALYSIS { - return v; -} - -template <typename T> -inline T& ts_unchecked_read(T& v) NO_THREAD_SAFETY_ANALYSIS { - return v; -} - -} // namespace thread_safety_analysis - -#endif // defined(ABSL_LEGACY_THREAD_ANNOTATIONS) - -#endif // ABSL_BASE_INTERNAL_THREAD_ANNOTATIONS_H_ diff --git a/absl/base/internal/thread_identity.cc b/absl/base/internal/thread_identity.cc index 79853f09..0471a25d 100644 --- a/absl/base/internal/thread_identity.cc +++ b/absl/base/internal/thread_identity.cc @@ -16,8 +16,12 @@ #if !defined(_WIN32) || defined(__MINGW32__) #include <pthread.h> +#ifndef __wasi__ +// WASI does not provide this header, either way we disable use +// of signals with it below. #include <signal.h> #endif +#endif #include <atomic> #include <cassert> @@ -58,18 +62,19 @@ void AllocateThreadIdentityKey(ThreadIdentityReclaimerFunction reclaimer) { // that protected visibility is unsupported. ABSL_CONST_INIT // Must come before __attribute__((visibility("protected"))) #if ABSL_HAVE_ATTRIBUTE(visibility) && !defined(__APPLE__) -__attribute__((visibility("protected"))) + __attribute__((visibility("protected"))) #endif // ABSL_HAVE_ATTRIBUTE(visibility) && !defined(__APPLE__) #if ABSL_PER_THREAD_TLS -// Prefer __thread to thread_local as benchmarks indicate it is a bit faster. -ABSL_PER_THREAD_TLS_KEYWORD ThreadIdentity* thread_identity_ptr = nullptr; + // Prefer __thread to thread_local as benchmarks indicate it is a bit + // faster. + ABSL_PER_THREAD_TLS_KEYWORD ThreadIdentity* thread_identity_ptr = nullptr; #elif defined(ABSL_HAVE_THREAD_LOCAL) -thread_local ThreadIdentity* thread_identity_ptr = nullptr; + thread_local ThreadIdentity* thread_identity_ptr = nullptr; #endif // ABSL_PER_THREAD_TLS #endif // TLS or CPP11 -void SetCurrentThreadIdentity( - ThreadIdentity* identity, ThreadIdentityReclaimerFunction reclaimer) { +void SetCurrentThreadIdentity(ThreadIdentity* identity, + ThreadIdentityReclaimerFunction reclaimer) { assert(CurrentThreadIdentityIfPresent() == nullptr); // Associate our destructor. // NOTE: This call to pthread_setspecific is currently the only immovable @@ -79,10 +84,12 @@ void SetCurrentThreadIdentity( absl::call_once(init_thread_identity_key_once, AllocateThreadIdentityKey, reclaimer); -#if defined(__EMSCRIPTEN__) || defined(__MINGW32__) - // Emscripten and MinGW pthread implementations does not support signals. - // See https://kripken.github.io/emscripten-site/docs/porting/pthreads.html - // for more information. +#if defined(__wasi__) || defined(__EMSCRIPTEN__) || defined(__MINGW32__) || \ + defined(__hexagon__) + // Emscripten, WASI and MinGW pthread implementations does not support + // signals. See + // https://kripken.github.io/emscripten-site/docs/porting/pthreads.html for + // more information. pthread_setspecific(thread_identity_pthread_key, reinterpret_cast<void*>(identity)); #else @@ -134,7 +141,7 @@ void ClearCurrentThreadIdentity() { ABSL_THREAD_IDENTITY_MODE == ABSL_THREAD_IDENTITY_MODE_USE_CPP11 thread_identity_ptr = nullptr; #elif ABSL_THREAD_IDENTITY_MODE == \ - ABSL_THREAD_IDENTITY_MODE_USE_POSIX_SETSPECIFIC + ABSL_THREAD_IDENTITY_MODE_USE_POSIX_SETSPECIFIC // pthread_setspecific expected to clear value on destruction assert(CurrentThreadIdentityIfPresent() == nullptr); #endif diff --git a/absl/base/internal/thread_identity.h b/absl/base/internal/thread_identity.h index 463acbc7..b6e917ce 100644 --- a/absl/base/internal/thread_identity.h +++ b/absl/base/internal/thread_identity.h @@ -62,8 +62,8 @@ struct PerThreadSynch { return reinterpret_cast<ThreadIdentity*>(this); } - PerThreadSynch *next; // Circular waiter queue; initialized to 0. - PerThreadSynch *skip; // If non-zero, all entries in Mutex queue + PerThreadSynch* next; // Circular waiter queue; initialized to 0. + PerThreadSynch* skip; // If non-zero, all entries in Mutex queue // up to and including "skip" have same // condition as this, and will be woken later bool may_skip; // if false while on mutex queue, a mutex unlocker @@ -104,10 +104,7 @@ struct PerThreadSynch { // // Transitions from kAvailable to kQueued require no barrier, they // are externally ordered by the Mutex. - enum State { - kAvailable, - kQueued - }; + enum State { kAvailable, kQueued }; std::atomic<State> state; // The wait parameters of the current wait. waitp is null if the @@ -122,14 +119,14 @@ struct PerThreadSynch { // pointer unchanged. SynchWaitParams* waitp; - intptr_t readers; // Number of readers in mutex. + intptr_t readers; // Number of readers in mutex. // When priority will next be read (cycles). int64_t next_priority_read_cycles; // Locks held; used during deadlock detection. // Allocated in Synch_GetAllLocks() and freed in ReclaimThreadIdentity(). - SynchLocksHeld *all_locks; + SynchLocksHeld* all_locks; }; // The instances of this class are allocated in NewThreadIdentity() with an @@ -147,7 +144,7 @@ struct ThreadIdentity { // Private: Reserved for absl::synchronization_internal::Waiter. struct WaiterState { - alignas(void*) char data[128]; + alignas(void*) char data[256]; } waiter_state; // Used by PerThreadSem::{Get,Set}ThreadBlockedCounter(). @@ -170,7 +167,10 @@ struct ThreadIdentity { // // Does not malloc(*), and is async-signal safe. // [*] Technically pthread_setspecific() does malloc on first use; however this -// is handled internally within tcmalloc's initialization already. +// is handled internally within tcmalloc's initialization already. Note that +// darwin does *not* use tcmalloc, so this can catch you if using MallocHooks +// on Apple platforms. Whatever function is calling your MallocHooks will need +// to watch for recursion on Apple platforms. // // New ThreadIdentity objects can be constructed and associated with a thread // by calling GetOrCreateCurrentThreadIdentity() in per-thread-sem.h. @@ -217,7 +217,7 @@ void ClearCurrentThreadIdentity(); #define ABSL_THREAD_IDENTITY_MODE ABSL_THREAD_IDENTITY_MODE_USE_CPP11 #elif defined(__APPLE__) && defined(ABSL_HAVE_THREAD_LOCAL) #define ABSL_THREAD_IDENTITY_MODE ABSL_THREAD_IDENTITY_MODE_USE_CPP11 -#elif ABSL_PER_THREAD_TLS && defined(__GOOGLE_GRTE_VERSION__) && \ +#elif ABSL_PER_THREAD_TLS && defined(__GOOGLE_GRTE_VERSION__) && \ (__GOOGLE_GRTE_VERSION__ >= 20140228L) // Support for async-safe TLS was specifically added in GRTEv4. It's not // present in the upstream eglibc. diff --git a/absl/base/internal/thread_identity_test.cc b/absl/base/internal/thread_identity_test.cc index 46a6f743..5f17553e 100644 --- a/absl/base/internal/thread_identity_test.cc +++ b/absl/base/internal/thread_identity_test.cc @@ -95,7 +95,7 @@ TEST(ThreadIdentityTest, BasicIdentityWorksThreaded) { } TEST(ThreadIdentityTest, ReusedThreadIdentityMutexTest) { - // This test repeatly creates and joins a series of threads, each of + // This test repeatedly creates and joins a series of threads, each of // which acquires and releases shared Mutex locks. This verifies // Mutex operations work correctly under a reused // ThreadIdentity. Note that the most likely failure mode of this diff --git a/absl/base/internal/throw_delegate.cc b/absl/base/internal/throw_delegate.cc index c260ff1e..337e870c 100644 --- a/absl/base/internal/throw_delegate.cc +++ b/absl/base/internal/throw_delegate.cc @@ -26,22 +26,13 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace base_internal { -// NOTE: The various STL exception throwing functions are placed within the -// #ifdef blocks so the symbols aren't exposed on platforms that don't support -// them, such as the Android NDK. For example, ANGLE fails to link when building -// within AOSP without them, since the STL functions don't exist. -namespace { -#ifdef ABSL_HAVE_EXCEPTIONS -template <typename T> -[[noreturn]] void Throw(const T& error) { - throw error; -} -#endif -} // namespace +// NOTE: The exception types, like `std::logic_error`, do not exist on all +// platforms. (For example, the Android NDK does not have them.) +// Therefore, their use must be guarded by `#ifdef` or equivalent. void ThrowStdLogicError(const std::string& what_arg) { #ifdef ABSL_HAVE_EXCEPTIONS - Throw(std::logic_error(what_arg)); + throw std::logic_error(what_arg); #else ABSL_RAW_LOG(FATAL, "%s", what_arg.c_str()); std::abort(); @@ -49,7 +40,7 @@ void ThrowStdLogicError(const std::string& what_arg) { } void ThrowStdLogicError(const char* what_arg) { #ifdef ABSL_HAVE_EXCEPTIONS - Throw(std::logic_error(what_arg)); + throw std::logic_error(what_arg); #else ABSL_RAW_LOG(FATAL, "%s", what_arg); std::abort(); @@ -57,7 +48,7 @@ void ThrowStdLogicError(const char* what_arg) { } void ThrowStdInvalidArgument(const std::string& what_arg) { #ifdef ABSL_HAVE_EXCEPTIONS - Throw(std::invalid_argument(what_arg)); + throw std::invalid_argument(what_arg); #else ABSL_RAW_LOG(FATAL, "%s", what_arg.c_str()); std::abort(); @@ -65,7 +56,7 @@ void ThrowStdInvalidArgument(const std::string& what_arg) { } void ThrowStdInvalidArgument(const char* what_arg) { #ifdef ABSL_HAVE_EXCEPTIONS - Throw(std::invalid_argument(what_arg)); + throw std::invalid_argument(what_arg); #else ABSL_RAW_LOG(FATAL, "%s", what_arg); std::abort(); @@ -74,7 +65,7 @@ void ThrowStdInvalidArgument(const char* what_arg) { void ThrowStdDomainError(const std::string& what_arg) { #ifdef ABSL_HAVE_EXCEPTIONS - Throw(std::domain_error(what_arg)); + throw std::domain_error(what_arg); #else ABSL_RAW_LOG(FATAL, "%s", what_arg.c_str()); std::abort(); @@ -82,7 +73,7 @@ void ThrowStdDomainError(const std::string& what_arg) { } void ThrowStdDomainError(const char* what_arg) { #ifdef ABSL_HAVE_EXCEPTIONS - Throw(std::domain_error(what_arg)); + throw std::domain_error(what_arg); #else ABSL_RAW_LOG(FATAL, "%s", what_arg); std::abort(); @@ -91,7 +82,7 @@ void ThrowStdDomainError(const char* what_arg) { void ThrowStdLengthError(const std::string& what_arg) { #ifdef ABSL_HAVE_EXCEPTIONS - Throw(std::length_error(what_arg)); + throw std::length_error(what_arg); #else ABSL_RAW_LOG(FATAL, "%s", what_arg.c_str()); std::abort(); @@ -99,7 +90,7 @@ void ThrowStdLengthError(const std::string& what_arg) { } void ThrowStdLengthError(const char* what_arg) { #ifdef ABSL_HAVE_EXCEPTIONS - Throw(std::length_error(what_arg)); + throw std::length_error(what_arg); #else ABSL_RAW_LOG(FATAL, "%s", what_arg); std::abort(); @@ -108,7 +99,7 @@ void ThrowStdLengthError(const char* what_arg) { void ThrowStdOutOfRange(const std::string& what_arg) { #ifdef ABSL_HAVE_EXCEPTIONS - Throw(std::out_of_range(what_arg)); + throw std::out_of_range(what_arg); #else ABSL_RAW_LOG(FATAL, "%s", what_arg.c_str()); std::abort(); @@ -116,7 +107,7 @@ void ThrowStdOutOfRange(const std::string& what_arg) { } void ThrowStdOutOfRange(const char* what_arg) { #ifdef ABSL_HAVE_EXCEPTIONS - Throw(std::out_of_range(what_arg)); + throw std::out_of_range(what_arg); #else ABSL_RAW_LOG(FATAL, "%s", what_arg); std::abort(); @@ -125,7 +116,7 @@ void ThrowStdOutOfRange(const char* what_arg) { void ThrowStdRuntimeError(const std::string& what_arg) { #ifdef ABSL_HAVE_EXCEPTIONS - Throw(std::runtime_error(what_arg)); + throw std::runtime_error(what_arg); #else ABSL_RAW_LOG(FATAL, "%s", what_arg.c_str()); std::abort(); @@ -133,7 +124,7 @@ void ThrowStdRuntimeError(const std::string& what_arg) { } void ThrowStdRuntimeError(const char* what_arg) { #ifdef ABSL_HAVE_EXCEPTIONS - Throw(std::runtime_error(what_arg)); + throw std::runtime_error(what_arg); #else ABSL_RAW_LOG(FATAL, "%s", what_arg); std::abort(); @@ -142,7 +133,7 @@ void ThrowStdRuntimeError(const char* what_arg) { void ThrowStdRangeError(const std::string& what_arg) { #ifdef ABSL_HAVE_EXCEPTIONS - Throw(std::range_error(what_arg)); + throw std::range_error(what_arg); #else ABSL_RAW_LOG(FATAL, "%s", what_arg.c_str()); std::abort(); @@ -150,7 +141,7 @@ void ThrowStdRangeError(const std::string& what_arg) { } void ThrowStdRangeError(const char* what_arg) { #ifdef ABSL_HAVE_EXCEPTIONS - Throw(std::range_error(what_arg)); + throw std::range_error(what_arg); #else ABSL_RAW_LOG(FATAL, "%s", what_arg); std::abort(); @@ -159,7 +150,7 @@ void ThrowStdRangeError(const char* what_arg) { void ThrowStdOverflowError(const std::string& what_arg) { #ifdef ABSL_HAVE_EXCEPTIONS - Throw(std::overflow_error(what_arg)); + throw std::overflow_error(what_arg); #else ABSL_RAW_LOG(FATAL, "%s", what_arg.c_str()); std::abort(); @@ -167,7 +158,7 @@ void ThrowStdOverflowError(const std::string& what_arg) { } void ThrowStdOverflowError(const char* what_arg) { #ifdef ABSL_HAVE_EXCEPTIONS - Throw(std::overflow_error(what_arg)); + throw std::overflow_error(what_arg); #else ABSL_RAW_LOG(FATAL, "%s", what_arg); std::abort(); @@ -176,7 +167,7 @@ void ThrowStdOverflowError(const char* what_arg) { void ThrowStdUnderflowError(const std::string& what_arg) { #ifdef ABSL_HAVE_EXCEPTIONS - Throw(std::underflow_error(what_arg)); + throw std::underflow_error(what_arg); #else ABSL_RAW_LOG(FATAL, "%s", what_arg.c_str()); std::abort(); @@ -184,7 +175,7 @@ void ThrowStdUnderflowError(const std::string& what_arg) { } void ThrowStdUnderflowError(const char* what_arg) { #ifdef ABSL_HAVE_EXCEPTIONS - Throw(std::underflow_error(what_arg)); + throw std::underflow_error(what_arg); #else ABSL_RAW_LOG(FATAL, "%s", what_arg); std::abort(); @@ -193,7 +184,7 @@ void ThrowStdUnderflowError(const char* what_arg) { void ThrowStdBadFunctionCall() { #ifdef ABSL_HAVE_EXCEPTIONS - Throw(std::bad_function_call()); + throw std::bad_function_call(); #else std::abort(); #endif @@ -201,7 +192,7 @@ void ThrowStdBadFunctionCall() { void ThrowStdBadAlloc() { #ifdef ABSL_HAVE_EXCEPTIONS - Throw(std::bad_alloc()); + throw std::bad_alloc(); #else std::abort(); #endif diff --git a/absl/base/internal/unaligned_access.h b/absl/base/internal/unaligned_access.h index 093dd9b4..4fea4574 100644 --- a/absl/base/internal/unaligned_access.h +++ b/absl/base/internal/unaligned_access.h @@ -23,6 +23,7 @@ #include "absl/base/attributes.h" #include "absl/base/config.h" +#include "absl/base/nullability.h" // unaligned APIs @@ -35,29 +36,35 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace base_internal { -inline uint16_t UnalignedLoad16(const void *p) { +inline uint16_t UnalignedLoad16(absl::Nonnull<const void *> p) { uint16_t t; memcpy(&t, p, sizeof t); return t; } -inline uint32_t UnalignedLoad32(const void *p) { +inline uint32_t UnalignedLoad32(absl::Nonnull<const void *> p) { uint32_t t; memcpy(&t, p, sizeof t); return t; } -inline uint64_t UnalignedLoad64(const void *p) { +inline uint64_t UnalignedLoad64(absl::Nonnull<const void *> p) { uint64_t t; memcpy(&t, p, sizeof t); return t; } -inline void UnalignedStore16(void *p, uint16_t v) { memcpy(p, &v, sizeof v); } +inline void UnalignedStore16(absl::Nonnull<void *> p, uint16_t v) { + memcpy(p, &v, sizeof v); +} -inline void UnalignedStore32(void *p, uint32_t v) { memcpy(p, &v, sizeof v); } +inline void UnalignedStore32(absl::Nonnull<void *> p, uint32_t v) { + memcpy(p, &v, sizeof v); +} -inline void UnalignedStore64(void *p, uint64_t v) { memcpy(p, &v, sizeof v); } +inline void UnalignedStore64(absl::Nonnull<void *> p, uint64_t v) { + memcpy(p, &v, sizeof v); +} } // namespace base_internal ABSL_NAMESPACE_END diff --git a/absl/base/internal/unscaledcycleclock.cc b/absl/base/internal/unscaledcycleclock.cc index b1c396c6..05e0e7ba 100644 --- a/absl/base/internal/unscaledcycleclock.cc +++ b/absl/base/internal/unscaledcycleclock.cc @@ -71,13 +71,12 @@ int64_t UnscaledCycleClock::Now() { #else int32_t tbu, tbl, tmp; asm volatile( - "0:\n" "mftbu %[hi32]\n" "mftb %[lo32]\n" "mftbu %[tmp]\n" "cmpw %[tmp],%[hi32]\n" - "bne 0b\n" - : [ hi32 ] "=r"(tbu), [ lo32 ] "=r"(tbl), [ tmp ] "=r"(tmp)); + "bne $-16\n" // Retry on failure. + : [hi32] "=r"(tbu), [lo32] "=r"(tbl), [tmp] "=r"(tmp)); return (static_cast<int64_t>(tbu) << 32) | tbl; #endif #endif diff --git a/absl/base/log_severity.cc b/absl/base/log_severity.cc index 60a8fc1f..8e7bbbc9 100644 --- a/absl/base/log_severity.cc +++ b/absl/base/log_severity.cc @@ -17,6 +17,7 @@ #include <ostream> #include "absl/base/attributes.h" +#include "absl/base/config.h" namespace absl { ABSL_NAMESPACE_BEGIN diff --git a/absl/base/log_severity.h b/absl/base/log_severity.h index 8bdca38b..de9702ab 100644 --- a/absl/base/log_severity.h +++ b/absl/base/log_severity.h @@ -64,6 +64,8 @@ ABSL_NAMESPACE_BEGIN // --my_log_level=info // --my_log_level=0 // +// `DFATAL` and `kLogDebugFatal` are similarly accepted. +// // Unparsing a flag produces the same result as `absl::LogSeverityName()` for // the standard levels and a base-ten integer otherwise. enum class LogSeverity : int { @@ -82,18 +84,28 @@ constexpr std::array<absl::LogSeverity, 4> LogSeverities() { absl::LogSeverity::kError, absl::LogSeverity::kFatal}}; } +// `absl::kLogDebugFatal` equals `absl::LogSeverity::kFatal` in debug builds +// (i.e. when `NDEBUG` is not defined) and `absl::LogSeverity::kError` +// otherwise. Avoid ODR-using this variable as it has internal linkage and thus +// distinct storage in different TUs. +#ifdef NDEBUG +static constexpr absl::LogSeverity kLogDebugFatal = absl::LogSeverity::kError; +#else +static constexpr absl::LogSeverity kLogDebugFatal = absl::LogSeverity::kFatal; +#endif + // LogSeverityName() // // Returns the all-caps string representation (e.g. "INFO") of the specified // severity level if it is one of the standard levels and "UNKNOWN" otherwise. constexpr const char* LogSeverityName(absl::LogSeverity s) { - return s == absl::LogSeverity::kInfo - ? "INFO" - : s == absl::LogSeverity::kWarning - ? "WARNING" - : s == absl::LogSeverity::kError - ? "ERROR" - : s == absl::LogSeverity::kFatal ? "FATAL" : "UNKNOWN"; + switch (s) { + case absl::LogSeverity::kInfo: return "INFO"; + case absl::LogSeverity::kWarning: return "WARNING"; + case absl::LogSeverity::kError: return "ERROR"; + case absl::LogSeverity::kFatal: return "FATAL"; + } + return "UNKNOWN"; } // NormalizeLogSeverity() @@ -101,9 +113,10 @@ constexpr const char* LogSeverityName(absl::LogSeverity s) { // Values less than `kInfo` normalize to `kInfo`; values greater than `kFatal` // normalize to `kError` (**NOT** `kFatal`). constexpr absl::LogSeverity NormalizeLogSeverity(absl::LogSeverity s) { - return s < absl::LogSeverity::kInfo - ? absl::LogSeverity::kInfo - : s > absl::LogSeverity::kFatal ? absl::LogSeverity::kError : s; + absl::LogSeverity n = s; + if (n < absl::LogSeverity::kInfo) n = absl::LogSeverity::kInfo; + if (n > absl::LogSeverity::kFatal) n = absl::LogSeverity::kError; + return n; } constexpr absl::LogSeverity NormalizeLogSeverity(int s) { return absl::NormalizeLogSeverity(static_cast<absl::LogSeverity>(s)); diff --git a/absl/base/log_severity_test.cc b/absl/base/log_severity_test.cc index 16091a5b..3394ecd7 100644 --- a/absl/base/log_severity_test.cc +++ b/absl/base/log_severity_test.cc @@ -146,7 +146,12 @@ INSTANTIATE_TEST_SUITE_P( std::make_tuple("fatal", absl::LogSeverity::kFatal), std::make_tuple("kFatal", absl::LogSeverity::kFatal), std::make_tuple("FaTaL", absl::LogSeverity::kFatal), - std::make_tuple("KfAtAl", absl::LogSeverity::kFatal))); + std::make_tuple("KfAtAl", absl::LogSeverity::kFatal), + std::make_tuple("DFATAL", absl::kLogDebugFatal), + std::make_tuple("dfatal", absl::kLogDebugFatal), + std::make_tuple("kLogDebugFatal", absl::kLogDebugFatal), + std::make_tuple("dFaTaL", absl::kLogDebugFatal), + std::make_tuple("kLoGdEbUgFaTaL", absl::kLogDebugFatal))); TEST_P(ParseFlagFromEnumeratorTest, YieldsExpectedValue) { const absl::string_view to_parse = std::get<0>(GetParam()); const absl::LogSeverity expected = std::get<1>(GetParam()); @@ -158,7 +163,8 @@ TEST_P(ParseFlagFromEnumeratorTest, YieldsExpectedValue) { using ParseFlagFromGarbageTest = TestWithParam<absl::string_view>; INSTANTIATE_TEST_SUITE_P(Instantiation, ParseFlagFromGarbageTest, - Values("", "\0", " ", "garbage", "kkinfo", "I")); + Values("", "\0", " ", "garbage", "kkinfo", "I", + "kDFATAL", "LogDebugFatal", "lOgDeBuGfAtAl")); TEST_P(ParseFlagFromGarbageTest, ReturnsError) { const absl::string_view to_parse = GetParam(); absl::LogSeverity value; diff --git a/absl/base/no_destructor.h b/absl/base/no_destructor.h new file mode 100644 index 00000000..d4b16a6e --- /dev/null +++ b/absl/base/no_destructor.h @@ -0,0 +1,217 @@ +// Copyright 2023 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: no_destructor.h +// ----------------------------------------------------------------------------- +// +// This header file defines the absl::NoDestructor<T> wrapper for defining a +// static type that does not need to be destructed upon program exit. Instead, +// such an object survives during program exit (and can be safely accessed at +// any time). +// +// Objects of such type, if constructed safely and under the right conditions, +// provide two main benefits over other alternatives: +// +// * Global objects not normally allowed due to concerns of destruction order +// (i.e. no "complex globals") can be safely allowed, provided that such +// objects can be constant initialized. +// * Function scope static objects can be optimized to avoid heap allocation, +// pointer chasing, and allow lazy construction. +// +// See below for complete details. + + +#ifndef ABSL_BASE_NO_DESTRUCTOR_H_ +#define ABSL_BASE_NO_DESTRUCTOR_H_ + +#include <new> +#include <type_traits> +#include <utility> + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +// absl::NoDestructor<T> +// +// NoDestructor<T> is a wrapper around an object of type T that behaves as an +// object of type T but never calls T's destructor. NoDestructor<T> makes it +// safer and/or more efficient to use such objects in static storage contexts: +// as global or function scope static variables. +// +// An instance of absl::NoDestructor<T> has similar type semantics to an +// instance of T: +// +// * Constructs in the same manner as an object of type T through perfect +// forwarding. +// * Provides pointer/reference semantic access to the object of type T via +// `->`, `*`, and `get()`. +// (Note that `const NoDestructor<T>` works like a pointer to const `T`.) +// +// An object of type NoDestructor<T> should be defined in static storage: +// as either a global static object, or as a function scope static variable. +// +// Additionally, NoDestructor<T> provides the following benefits: +// +// * Never calls T's destructor for the object +// * If the object is a function-local static variable, the type can be +// lazily constructed. +// +// An object of type NoDestructor<T> is "trivially destructible" in the notion +// that its destructor is never run. Provided that an object of this type can be +// safely initialized and does not need to be cleaned up on program shutdown, +// NoDestructor<T> allows you to define global static variables, since Google's +// C++ style guide ban on such objects doesn't apply to objects that are +// trivially destructible. +// +// Usage as Global Static Variables +// +// NoDestructor<T> allows declaration of a global object with a non-trivial +// constructor in static storage without needing to add a destructor. +// However, such objects still need to worry about initialization order, so +// such objects should be const initialized: +// +// // Global or namespace scope. +// ABSL_CONST_INIT absl::NoDestructor<MyRegistry> reg{"foo", "bar", 8008}; +// +// Note that if your object already has a trivial destructor, you don't need to +// use NoDestructor<T>. +// +// Usage as Function Scope Static Variables +// +// Function static objects will be lazily initialized within static storage: +// +// // Function scope. +// const std::string& MyString() { +// static const absl::NoDestructor<std::string> x("foo"); +// return *x; +// } +// +// For function static variables, NoDestructor avoids heap allocation and can be +// inlined in static storage, resulting in exactly-once, thread-safe +// construction of an object, and very fast access thereafter (the cost is a few +// extra cycles). +// +// Using NoDestructor<T> in this manner is generally better than other patterns +// which require pointer chasing: +// +// // Prefer using absl::NoDestructor<T> instead for the static variable. +// const std::string& MyString() { +// static const std::string* x = new std::string("foo"); +// return *x; +// } +// +template <typename T> +class NoDestructor { + public: + // Forwards arguments to the T's constructor: calls T(args...). + template <typename... Ts, + // Disable this overload when it might collide with copy/move. + typename std::enable_if<!std::is_same<void(std::decay_t<Ts>&...), + void(NoDestructor&)>::value, + int>::type = 0> + explicit constexpr NoDestructor(Ts&&... args) + : impl_(std::forward<Ts>(args)...) {} + + // Forwards copy and move construction for T. Enables usage like this: + // static NoDestructor<std::array<string, 3>> x{{{"1", "2", "3"}}}; + // static NoDestructor<std::vector<int>> x{{1, 2, 3}}; + explicit constexpr NoDestructor(const T& x) : impl_(x) {} + explicit constexpr NoDestructor(T&& x) + : impl_(std::move(x)) {} + + // No copying. + NoDestructor(const NoDestructor&) = delete; + NoDestructor& operator=(const NoDestructor&) = delete; + + // Pretend to be a smart pointer to T with deep constness. + // Never returns a null pointer. + T& operator*() { return *get(); } + T* operator->() { return get(); } + T* get() { return impl_.get(); } + const T& operator*() const { return *get(); } + const T* operator->() const { return get(); } + const T* get() const { return impl_.get(); } + + private: + class DirectImpl { + public: + template <typename... Args> + explicit constexpr DirectImpl(Args&&... args) + : value_(std::forward<Args>(args)...) {} + const T* get() const { return &value_; } + T* get() { return &value_; } + + private: + T value_; + }; + + class PlacementImpl { + public: + template <typename... Args> + explicit PlacementImpl(Args&&... args) { + new (&space_) T(std::forward<Args>(args)...); + } + const T* get() const { + return Launder(reinterpret_cast<const T*>(&space_)); + } + T* get() { return Launder(reinterpret_cast<T*>(&space_)); } + + private: + template <typename P> + static P* Launder(P* p) { +#if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606L + return std::launder(p); +#elif ABSL_HAVE_BUILTIN(__builtin_launder) + return __builtin_launder(p); +#else + // When `std::launder` or equivalent are not available, we rely on + // undefined behavior, which works as intended on Abseil's officially + // supported platforms as of Q3 2023. +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif + return p; +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif +#endif + } + + alignas(T) unsigned char space_[sizeof(T)]; + }; + + // If the object is trivially destructible we use a member directly to avoid + // potential once-init runtime initialization. It somewhat defeats the + // purpose of NoDestructor in this case, but this makes the class more + // friendly to generic code. + std::conditional_t<std::is_trivially_destructible<T>::value, DirectImpl, + PlacementImpl> + impl_; +}; + +#ifdef ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION +// Provide 'Class Template Argument Deduction': the type of NoDestructor's T +// will be the same type as the argument passed to NoDestructor's constructor. +template <typename T> +NoDestructor(T) -> NoDestructor<T>; +#endif // ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_BASE_NO_DESTRUCTOR_H_ diff --git a/absl/base/no_destructor_benchmark.cc b/absl/base/no_destructor_benchmark.cc new file mode 100644 index 00000000..5fc88f1d --- /dev/null +++ b/absl/base/no_destructor_benchmark.cc @@ -0,0 +1,165 @@ +// Copyright 2023 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 <cstdint> + +#include "absl/base/internal/raw_logging.h" +#include "absl/base/no_destructor.h" +#include "benchmark/benchmark.h" + +namespace { + +// Number of static-NoDestructor-in-a-function to exercise. +// This must be low enough not to hit template instantiation limits +// (happens around 1000). +constexpr int kNumObjects = 1; // set to 512 when doing benchmarks + // 1 is faster to compile: just one templated + // function instantiation + +// Size of individual objects to benchmark static-NoDestructor-in-a-function +// usage with. +constexpr int kObjSize = sizeof(void*)*1; + +// Simple object of kObjSize bytes (rounded to int). +// We benchmark complete reading of its state via Verify(). +class BM_Blob { + public: + BM_Blob(int val) { for (auto& d : data_) d = val; } + BM_Blob() : BM_Blob(-1) {} + void Verify(int val) const { // val must be the c-tor argument + for (auto& d : data_) ABSL_INTERNAL_CHECK(d == val, ""); + } + private: + int data_[kObjSize / sizeof(int) > 0 ? kObjSize / sizeof(int) : 1]; +}; + +// static-NoDestructor-in-a-function pattern instances. +// We'll instantiate kNumObjects of them. +template<int i> +const BM_Blob& NoDestrBlobFunc() { + static absl::NoDestructor<BM_Blob> x(i); + return *x; +} + +// static-heap-ptr-in-a-function pattern instances +// We'll instantiate kNumObjects of them. +template<int i> +const BM_Blob& OnHeapBlobFunc() { + static BM_Blob* x = new BM_Blob(i); + return *x; +} + +// Type for NoDestrBlobFunc or OnHeapBlobFunc. +typedef const BM_Blob& (*FuncType)(); + +// ========================================================================= // +// Simple benchmarks that read a single BM_Blob over and over, hence +// all they touch fits into L1 CPU cache: + +// Direct non-POD global variable (style guide violation) as a baseline. +static BM_Blob direct_blob(0); + +void BM_Direct(benchmark::State& state) { + for (auto s : state) { + direct_blob.Verify(0); + } +} +BENCHMARK(BM_Direct); + +void BM_NoDestr(benchmark::State& state) { + for (auto s : state) { + NoDestrBlobFunc<0>().Verify(0); + } +} +BENCHMARK(BM_NoDestr); + +void BM_OnHeap(benchmark::State& state) { + for (auto s : state) { + OnHeapBlobFunc<0>().Verify(0); + } +} +BENCHMARK(BM_OnHeap); + +// ========================================================================= // +// Benchmarks that read kNumObjects of BM_Blob over and over, hence with +// appropriate values of sizeof(BM_Blob) and kNumObjects their working set +// can exceed a given layer of CPU cache. + +// Type of benchmark to select between NoDestrBlobFunc and OnHeapBlobFunc. +enum BM_Type { kNoDestr, kOnHeap, kDirect }; + +// BlobFunc<n>(t, i) returns the i-th function of type t. +// n must be larger than i (we'll use kNumObjects for n). +template<int n> +FuncType BlobFunc(BM_Type t, int i) { + if (i == n) { + switch (t) { + case kNoDestr: return &NoDestrBlobFunc<n>; + case kOnHeap: return &OnHeapBlobFunc<n>; + case kDirect: return nullptr; + } + } + return BlobFunc<n-1>(t, i); +} + +template<> +FuncType BlobFunc<0>(BM_Type t, int i) { + ABSL_INTERNAL_CHECK(i == 0, ""); + switch (t) { + case kNoDestr: return &NoDestrBlobFunc<0>; + case kOnHeap: return &OnHeapBlobFunc<0>; + case kDirect: return nullptr; + } + return nullptr; +} + +// Direct non-POD global variables (style guide violation) as a baseline. +static BM_Blob direct_blobs[kNumObjects]; + +// Helper that cheaply maps benchmark iteration to randomish index in +// [0, kNumObjects). +int RandIdx(int i) { + // int64 is to avoid overflow and generating negative return values: + return (static_cast<int64_t>(i) * 13) % kNumObjects; +} + +// Generic benchmark working with kNumObjects for any of the possible BM_Type. +template <BM_Type t> +void BM_Many(benchmark::State& state) { + FuncType funcs[kNumObjects]; + for (int i = 0; i < kNumObjects; ++i) { + funcs[i] = BlobFunc<kNumObjects-1>(t, i); + } + if (t == kDirect) { + for (auto s : state) { + int idx = RandIdx(state.iterations()); + direct_blobs[idx].Verify(-1); + } + } else { + for (auto s : state) { + int idx = RandIdx(state.iterations()); + funcs[idx]().Verify(idx); + } + } +} + +void BM_DirectMany(benchmark::State& state) { BM_Many<kDirect>(state); } +void BM_NoDestrMany(benchmark::State& state) { BM_Many<kNoDestr>(state); } +void BM_OnHeapMany(benchmark::State& state) { BM_Many<kOnHeap>(state); } + +BENCHMARK(BM_DirectMany); +BENCHMARK(BM_NoDestrMany); +BENCHMARK(BM_OnHeapMany); + +} // namespace diff --git a/absl/base/no_destructor_test.cc b/absl/base/no_destructor_test.cc new file mode 100644 index 00000000..71693c7e --- /dev/null +++ b/absl/base/no_destructor_test.cc @@ -0,0 +1,209 @@ +// Copyright 2023 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/base/no_destructor.h" + +#include <array> +#include <initializer_list> +#include <string> +#include <type_traits> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/base/internal/raw_logging.h" + +namespace { + +struct Blob { + Blob() : val(42) {} + Blob(int x, int y) : val(x + y) {} + Blob(std::initializer_list<int> xs) { + val = 0; + for (auto& x : xs) val += x; + } + + Blob(const Blob& /*b*/) = delete; + Blob(Blob&& b) noexcept : val(b.val) { + b.moved_out = true; + } // moving is fine + + // no crash: NoDestructor indeed does not destruct (the moved-out Blob + // temporaries do get destroyed though) + ~Blob() { ABSL_INTERNAL_CHECK(moved_out, "~Blob"); } + + int val; + bool moved_out = false; +}; + +struct TypeWithDeletedDestructor { + ~TypeWithDeletedDestructor() = delete; +}; + +TEST(NoDestructorTest, DestructorNeverCalled) { + absl::NoDestructor<TypeWithDeletedDestructor> a; + (void)a; +} + +TEST(NoDestructorTest, Noncopyable) { + using T = absl::NoDestructor<int>; + + EXPECT_FALSE((std::is_constructible<T, T>::value)); + EXPECT_FALSE((std::is_constructible<T, const T>::value)); + EXPECT_FALSE((std::is_constructible<T, T&>::value)); + EXPECT_FALSE((std::is_constructible<T, const T&>::value)); + + EXPECT_FALSE((std::is_assignable<T&, T>::value)); + EXPECT_FALSE((std::is_assignable<T&, const T>::value)); + EXPECT_FALSE((std::is_assignable<T&, T&>::value)); + EXPECT_FALSE((std::is_assignable<T&, const T&>::value)); +} + +TEST(NoDestructorTest, Interface) { + EXPECT_TRUE(std::is_trivially_destructible<absl::NoDestructor<Blob>>::value); + EXPECT_TRUE( + std::is_trivially_destructible<absl::NoDestructor<const Blob>>::value); + { + absl::NoDestructor<Blob> b; // default c-tor + // access: *, ->, get() + EXPECT_EQ(42, (*b).val); + (*b).val = 55; + EXPECT_EQ(55, b->val); + b->val = 66; + EXPECT_EQ(66, b.get()->val); + b.get()->val = 42; // NOLINT + EXPECT_EQ(42, (*b).val); + } + { + absl::NoDestructor<const Blob> b(70, 7); // regular c-tor, const + EXPECT_EQ(77, (*b).val); + EXPECT_EQ(77, b->val); + EXPECT_EQ(77, b.get()->val); + } + { + const absl::NoDestructor<Blob> b{ + {20, 28, 40}}; // init-list c-tor, deep const + // This only works in clang, not in gcc: + // const absl::NoDestructor<Blob> b({20, 28, 40}); + EXPECT_EQ(88, (*b).val); + EXPECT_EQ(88, b->val); + EXPECT_EQ(88, b.get()->val); + } +} + +TEST(NoDestructorTest, SfinaeRegressionAbstractArg) { + struct Abstract { + virtual ~Abstract() = default; + virtual int foo() const = 0; + }; + + struct Concrete : Abstract { + int foo() const override { return 17; } + }; + + struct UsesAbstractInConstructor { + explicit UsesAbstractInConstructor(const Abstract& abstract) + : i(abstract.foo()) {} + int i; + }; + + Concrete input; + absl::NoDestructor<UsesAbstractInConstructor> foo1(input); + EXPECT_EQ(foo1->i, 17); + absl::NoDestructor<UsesAbstractInConstructor> foo2( + static_cast<const Abstract&>(input)); + EXPECT_EQ(foo2->i, 17); +} + +// ========================================================================= // + +std::string* Str0() { + static absl::NoDestructor<std::string> x; + return x.get(); +} + +extern const std::string& Str2(); + +const char* Str1() { + static absl::NoDestructor<std::string> x(Str2() + "_Str1"); + return x->c_str(); +} + +const std::string& Str2() { + static absl::NoDestructor<std::string> x("Str2"); + return *x; +} + +const std::string& Str2Copy() { + // Exercise copy construction + static absl::NoDestructor<std::string> x(Str2()); + return *x; +} + +typedef std::array<std::string, 3> MyArray; +const MyArray& Array() { + static absl::NoDestructor<MyArray> x{{{"foo", "bar", "baz"}}}; + // This only works in clang, not in gcc: + // static absl::NoDestructor<MyArray> x({{"foo", "bar", "baz"}}); + return *x; +} + +typedef std::vector<int> MyVector; +const MyVector& Vector() { + static absl::NoDestructor<MyVector> x{{1, 2, 3}}; + return *x; +} + +const int& Int() { + static absl::NoDestructor<int> x; + return *x; +} + +TEST(NoDestructorTest, StaticPattern) { + EXPECT_TRUE( + std::is_trivially_destructible<absl::NoDestructor<std::string>>::value); + EXPECT_TRUE( + std::is_trivially_destructible<absl::NoDestructor<MyArray>>::value); + EXPECT_TRUE( + std::is_trivially_destructible<absl::NoDestructor<MyVector>>::value); + EXPECT_TRUE(std::is_trivially_destructible<absl::NoDestructor<int>>::value); + + EXPECT_EQ(*Str0(), ""); + Str0()->append("foo"); + EXPECT_EQ(*Str0(), "foo"); + + EXPECT_EQ(std::string(Str1()), "Str2_Str1"); + + EXPECT_EQ(Str2(), "Str2"); + EXPECT_EQ(Str2Copy(), "Str2"); + + EXPECT_THAT(Array(), testing::ElementsAre("foo", "bar", "baz")); + + EXPECT_THAT(Vector(), testing::ElementsAre(1, 2, 3)); + + EXPECT_EQ(0, Int()); // should get zero-initialized +} + +#ifdef ABSL_HAVE_CLASS_TEMPLATE_ARGUMENT_DEDUCTION +// This would fail to compile if Class Template Argument Deduction was not +// provided for absl::NoDestructor. +TEST(NoDestructorTest, ClassTemplateArgumentDeduction) { + absl::NoDestructor i(1); + static_assert(std::is_same<decltype(i), absl::NoDestructor<int>>::value, + "Expected deduced type to be int."); +} +#endif + +} // namespace diff --git a/absl/base/nullability.h b/absl/base/nullability.h new file mode 100644 index 00000000..6f49b6f5 --- /dev/null +++ b/absl/base/nullability.h @@ -0,0 +1,224 @@ +// Copyright 2023 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: nullability.h +// ----------------------------------------------------------------------------- +// +// This header file defines a set of "templated annotations" for designating the +// expected nullability of pointers. These annotations allow you to designate +// pointers in one of three classification states: +// +// * "Non-null" (for pointers annotated `Nonnull<T>`), indicating that it is +// invalid for the given pointer to ever be null. +// * "Nullable" (for pointers annotated `Nullable<T>`), indicating that it is +// valid for the given pointer to be null. +// * "Unknown" (for pointers annotated `NullabilityUnknown<T>`), indicating +// that the given pointer has not been yet classified as either nullable or +// non-null. This is the default state of unannotated pointers. +// +// NOTE: unannotated pointers implicitly bear the annotation +// `NullabilityUnknown<T>`; you should rarely, if ever, see this annotation used +// in the codebase explicitly. +// +// ----------------------------------------------------------------------------- +// Nullability and Contracts +// ----------------------------------------------------------------------------- +// +// These nullability annotations allow you to more clearly specify contracts on +// software components by narrowing the *preconditions*, *postconditions*, and +// *invariants* of pointer state(s) in any given interface. It then depends on +// context who is responsible for fulfilling the annotation's requirements. +// +// For example, a function may receive a pointer argument. Designating that +// pointer argument as "non-null" tightens the precondition of the contract of +// that function. It is then the responsibility of anyone calling such a +// function to ensure that the passed pointer is not null. +// +// Similarly, a function may have a pointer as a return value. Designating that +// return value as "non-null" tightens the postcondition of the contract of that +// function. In this case, however, it is the responsibility of the function +// itself to ensure that the returned pointer is not null. +// +// Clearly defining these contracts allows providers (and consumers) of such +// pointers to have more confidence in their null state. If a function declares +// a return value as "non-null", for example, the caller should not need to +// check whether the returned value is `nullptr`; it can simply assume the +// pointer is valid. +// +// Of course most interfaces already have expectations on the nullability state +// of pointers, and these expectations are, in effect, a contract; often, +// however, those contracts are either poorly or partially specified, assumed, +// or misunderstood. These nullability annotations are designed to allow you to +// formalize those contracts within the codebase. +// +// ----------------------------------------------------------------------------- +// Using Nullability Annotations +// ----------------------------------------------------------------------------- +// +// It is important to note that these annotations are not distinct strong +// *types*. They are alias templates defined to be equal to the underlying +// pointer type. A pointer annotated `Nonnull<T*>`, for example, is simply a +// pointer of type `T*`. Each annotation acts as a form of documentation about +// the contract for the given pointer. Each annotation requires providers or +// consumers of these pointers across API boundaries to take appropriate steps +// when setting or using these pointers: +// +// * "Non-null" pointers should never be null. It is the responsibility of the +// provider of this pointer to ensure that the pointer may never be set to +// null. Consumers of such pointers can treat such pointers as non-null. +// * "Nullable" pointers may or may not be null. Consumers of such pointers +// should precede any usage of that pointer (e.g. a dereference operation) +// with a a `nullptr` check. +// * "Unknown" pointers may be either "non-null" or "nullable" but have not been +// definitively determined to be in either classification state. Providers of +// such pointers across API boundaries should determine -- over time -- to +// annotate the pointer in either of the above two states. Consumers of such +// pointers across an API boundary should continue to treat such pointers as +// they currently do. +// +// Example: +// +// // PaySalary() requires the passed pointer to an `Employee` to be non-null. +// void PaySalary(absl::Nonnull<Employee *> e) { +// pay(e->salary); // OK to dereference +// } +// +// // CompleteTransaction() guarantees the returned pointer to an `Account` to +// // be non-null. +// absl::Nonnull<Account *> balance CompleteTransaction(double fee) { +// ... +// } +// +// // Note that specifying a nullability annotation does not prevent someone +// // from violating the contract: +// +// Nullable<Employee *> find(Map& employees, std::string_view name); +// +// void g(Map& employees) { +// Employee *e = find(employees, "Pat"); +// // `e` can now be null. +// PaySalary(e); // Violates contract, but compiles! +// } +// +// Nullability annotations, in other words, are useful for defining and +// narrowing contracts; *enforcement* of those contracts depends on use and any +// additional (static or dynamic analysis) tooling. +// +// NOTE: The "unknown" annotation state indicates that a pointer's contract has +// not yet been positively identified. The unknown state therefore acts as a +// form of documentation of your technical debt, and a codebase that adopts +// nullability annotations should aspire to annotate every pointer as either +// "non-null" or "nullable". +// +// ----------------------------------------------------------------------------- +// Applicability of Nullability Annotations +// ----------------------------------------------------------------------------- +// +// By default, nullability annotations are applicable to raw and smart +// pointers. User-defined types can indicate compatibility with nullability +// annotations by providing an `absl_nullability_compatible` nested type. The +// actual definition of this inner type is not relevant as it is used merely as +// a marker. It is common to use a using declaration of +// `absl_nullability_compatible` set to void. +// +// // Example: +// struct MyPtr { +// using absl_nullability_compatible = void; +// ... +// }; +// +// DISCLAIMER: +// =========================================================================== +// These nullability annotations are primarily a human readable signal about the +// intended contract of the pointer. They are not *types* and do not currently +// provide any correctness guarantees. For example, a pointer annotated as +// `Nonnull<T*>` is *not guaranteed* to be non-null, and the compiler won't +// alert or prevent assignment of a `Nullable<T*>` to a `Nonnull<T*>`. +// =========================================================================== +#ifndef ABSL_BASE_NULLABILITY_H_ +#define ABSL_BASE_NULLABILITY_H_ + +#include "absl/base/internal/nullability_impl.h" + +namespace absl { + +// absl::Nonnull +// +// The indicated pointer is never null. It is the responsibility of the provider +// of this pointer across an API boundary to ensure that the pointer is never be +// set to null. Consumers of this pointer across an API boundary may safely +// dereference the pointer. +// +// Example: +// +// // `employee` is designated as not null. +// void PaySalary(absl::Nonnull<Employee *> employee) { +// pay(*employee); // OK to dereference +// } +template <typename T> +using Nonnull = nullability_internal::NonnullImpl<T>; + +// absl::Nullable +// +// The indicated pointer may, by design, be either null or non-null. Consumers +// of this pointer across an API boundary should perform a `nullptr` check +// before performing any operation using the pointer. +// +// Example: +// +// // `employee` may be null. +// void PaySalary(absl::Nullable<Employee *> employee) { +// if (employee != nullptr) { +// Pay(*employee); // OK to dereference +// } +// } +template <typename T> +using Nullable = nullability_internal::NullableImpl<T>; + +// absl::NullabilityUnknown (default) +// +// The indicated pointer has not yet been determined to be definitively +// "non-null" or "nullable." Providers of such pointers across API boundaries +// should, over time, annotate such pointers as either "non-null" or "nullable." +// Consumers of these pointers across an API boundary should treat such pointers +// with the same caution they treat currently unannotated pointers. Most +// existing code will have "unknown" pointers, which should eventually be +// migrated into one of the above two nullability states: `Nonnull<T>` or +// `Nullable<T>`. +// +// NOTE: Because this annotation is the global default state, pointers without +// any annotation are assumed to have "unknown" semantics. This assumption is +// designed to minimize churn and reduce clutter within the codebase. +// +// Example: +// +// // `employee`s nullability state is unknown. +// void PaySalary(absl::NullabilityUnknown<Employee *> employee) { +// Pay(*employee); // Potentially dangerous. API provider should investigate. +// } +// +// Note that a pointer without an annotation, by default, is assumed to have the +// annotation `NullabilityUnknown`. +// +// // `employee`s nullability state is unknown. +// void PaySalary(Employee* employee) { +// Pay(*employee); // Potentially dangerous. API provider should investigate. +// } +template <typename T> +using NullabilityUnknown = nullability_internal::NullabilityUnknownImpl<T>; + +} // namespace absl + +#endif // ABSL_BASE_NULLABILITY_H_ diff --git a/absl/base/nullability_test.cc b/absl/base/nullability_test.cc new file mode 100644 index 00000000..028ea6ca --- /dev/null +++ b/absl/base/nullability_test.cc @@ -0,0 +1,129 @@ +// Copyright 2023 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/base/nullability.h" + +#include <cassert> +#include <memory> +#include <utility> + +#include "gtest/gtest.h" +#include "absl/base/attributes.h" + +namespace { +using ::absl::Nonnull; +using ::absl::NullabilityUnknown; +using ::absl::Nullable; + +void funcWithNonnullArg(Nonnull<int*> /*arg*/) {} +template <typename T> +void funcWithDeducedNonnullArg(Nonnull<T*> /*arg*/) {} + +TEST(NonnullTest, NonnullArgument) { + int var = 0; + funcWithNonnullArg(&var); + funcWithDeducedNonnullArg(&var); +} + +Nonnull<int*> funcWithNonnullReturn() { + static int var = 0; + return &var; +} + +TEST(NonnullTest, NonnullReturn) { + auto var = funcWithNonnullReturn(); + (void)var; +} + +TEST(PassThroughTest, PassesThroughRawPointerToInt) { + EXPECT_TRUE((std::is_same<Nonnull<int*>, int*>::value)); + EXPECT_TRUE((std::is_same<Nullable<int*>, int*>::value)); + EXPECT_TRUE((std::is_same<NullabilityUnknown<int*>, int*>::value)); +} + +TEST(PassThroughTest, PassesThroughRawPointerToVoid) { + EXPECT_TRUE((std::is_same<Nonnull<void*>, void*>::value)); + EXPECT_TRUE((std::is_same<Nullable<void*>, void*>::value)); + EXPECT_TRUE((std::is_same<NullabilityUnknown<void*>, void*>::value)); +} + +TEST(PassThroughTest, PassesThroughUniquePointerToInt) { + using T = std::unique_ptr<int>; + EXPECT_TRUE((std::is_same<Nonnull<T>, T>::value)); + EXPECT_TRUE((std::is_same<Nullable<T>, T>::value)); + EXPECT_TRUE((std::is_same<NullabilityUnknown<T>, T>::value)); +} + +TEST(PassThroughTest, PassesThroughSharedPointerToInt) { + using T = std::shared_ptr<int>; + EXPECT_TRUE((std::is_same<Nonnull<T>, T>::value)); + EXPECT_TRUE((std::is_same<Nullable<T>, T>::value)); + EXPECT_TRUE((std::is_same<NullabilityUnknown<T>, T>::value)); +} + +TEST(PassThroughTest, PassesThroughSharedPointerToVoid) { + using T = std::shared_ptr<void>; + EXPECT_TRUE((std::is_same<Nonnull<T>, T>::value)); + EXPECT_TRUE((std::is_same<Nullable<T>, T>::value)); + EXPECT_TRUE((std::is_same<NullabilityUnknown<T>, T>::value)); +} + +TEST(PassThroughTest, PassesThroughPointerToMemberObject) { + using T = decltype(&std::pair<int, int>::first); + EXPECT_TRUE((std::is_same<Nonnull<T>, T>::value)); + EXPECT_TRUE((std::is_same<Nullable<T>, T>::value)); + EXPECT_TRUE((std::is_same<NullabilityUnknown<T>, T>::value)); +} + +TEST(PassThroughTest, PassesThroughPointerToMemberFunction) { + using T = decltype(&std::unique_ptr<int>::reset); + EXPECT_TRUE((std::is_same<Nonnull<T>, T>::value)); + EXPECT_TRUE((std::is_same<Nullable<T>, T>::value)); + EXPECT_TRUE((std::is_same<NullabilityUnknown<T>, T>::value)); +} + +} // namespace + +// Nullable ADL lookup test +namespace util { +// Helper for NullableAdlTest. Returns true, denoting that argument-dependent +// lookup found this implementation of DidAdlWin. Must be in namespace +// util itself, not a nested anonymous namespace. +template <typename T> +bool DidAdlWin(T*) { + return true; +} + +// Because this type is defined in namespace util, an unqualified call to +// DidAdlWin with a pointer to MakeAdlWin will find the above implementation. +struct MakeAdlWin {}; +} // namespace util + +namespace { +// Returns false, denoting that ADL did not inspect namespace util. If it +// had, the better match (T*) above would have won out over the (...) here. +bool DidAdlWin(...) { return false; } + +TEST(NullableAdlTest, NullableAddsNothingToArgumentDependentLookup) { + // Treatment: util::Nullable<int*> contributes nothing to ADL because + // int* itself doesn't. + EXPECT_FALSE(DidAdlWin((int*)nullptr)); + EXPECT_FALSE(DidAdlWin((Nullable<int*>)nullptr)); + + // Control: Argument-dependent lookup does find the implementation in + // namespace util when the underlying pointee type resides there. + EXPECT_TRUE(DidAdlWin((util::MakeAdlWin*)nullptr)); + EXPECT_TRUE(DidAdlWin((Nullable<util::MakeAdlWin*>)nullptr)); +} +} // namespace diff --git a/absl/base/optimization.h b/absl/base/optimization.h index ad0121ad..f9859958 100644 --- a/absl/base/optimization.h +++ b/absl/base/optimization.h @@ -25,6 +25,7 @@ #include <assert.h> #include "absl/base/config.h" +#include "absl/base/options.h" // ABSL_BLOCK_TAIL_CALL_OPTIMIZATION // diff --git a/absl/base/options.h b/absl/base/options.h index d5300c55..67cbf456 100644 --- a/absl/base/options.h +++ b/absl/base/options.h @@ -176,6 +176,32 @@ #define ABSL_OPTION_USE_STD_VARIANT 1 +// ABSL_OPTION_USE_STD_ORDERING +// +// This option controls whether absl::{partial,weak,strong}_ordering are +// implemented as aliases to the std:: ordering types, or as an independent +// implementation. +// +// A value of 0 means to use Abseil's implementation. This requires only C++11 +// support, and is expected to work on every toolchain we support. +// +// A value of 1 means to use aliases. This requires that all code using Abseil +// is built in C++20 mode or later. +// +// A value of 2 means to detect the C++ version being used to compile Abseil, +// and use an alias only if working std:: ordering types are available. This +// option is useful when you are building your program from source. It should +// not be used otherwise -- for example, if you are distributing Abseil in a +// binary package manager -- since in mode 2, they will name different types, +// with different mangled names and binary layout, depending on the compiler +// flags passed by the end user. For more info, see +// https://abseil.io/about/design/dropin-types. +// +// User code should not inspect this macro. To check in the preprocessor if +// the ordering types are aliases of std:: ordering types, use the feature macro +// ABSL_USES_STD_ORDERING. + +#define ABSL_OPTION_USE_STD_ORDERING 2 // ABSL_OPTION_USE_INLINE_NAMESPACE // ABSL_OPTION_INLINE_NAMESPACE_NAME @@ -200,7 +226,7 @@ // allowed. #define ABSL_OPTION_USE_INLINE_NAMESPACE 1 -#define ABSL_OPTION_INLINE_NAMESPACE_NAME lts_20230125 +#define ABSL_OPTION_INLINE_NAMESPACE_NAME lts_20240116 // ABSL_OPTION_HARDENED // diff --git a/absl/base/policy_checks.h b/absl/base/policy_checks.h index b8cd4c94..372e848d 100644 --- a/absl/base/policy_checks.h +++ b/absl/base/policy_checks.h @@ -44,10 +44,10 @@ // Toolchain Check // ----------------------------------------------------------------------------- -// We support Visual Studio 2017 (MSVC++ 15.0) and later. +// We support Visual Studio 2019 (MSVC++ 16.0) and later. // This minimum will go up. -#if defined(_MSC_VER) && _MSC_VER < 1910 && !defined(__clang__) -#error "This package requires Visual Studio 2017 (MSVC++ 15.0) or higher." +#if defined(_MSC_VER) && _MSC_VER < 1920 && !defined(__clang__) +#error "This package requires Visual Studio 2019 (MSVC++ 16.0) or higher." #endif // We support GCC 7 and later. diff --git a/absl/base/prefetch.h b/absl/base/prefetch.h new file mode 100644 index 00000000..eb40a445 --- /dev/null +++ b/absl/base/prefetch.h @@ -0,0 +1,209 @@ +// Copyright 2023 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: prefetch.h +// ----------------------------------------------------------------------------- +// +// This header file defines prefetch functions to prefetch memory contents +// into the first level cache (L1) for the current CPU. The prefetch logic +// offered in this header is limited to prefetching first level cachelines +// only, and is aimed at relatively 'simple' prefetching logic. +// +#ifndef ABSL_BASE_PREFETCH_H_ +#define ABSL_BASE_PREFETCH_H_ + +#include "absl/base/attributes.h" +#include "absl/base/config.h" + +#if defined(ABSL_INTERNAL_HAVE_SSE) +#include <xmmintrin.h> +#endif + +#if defined(_MSC_VER) +#include <intrin.h> +#if defined(ABSL_INTERNAL_HAVE_SSE) +#pragma intrinsic(_mm_prefetch) +#endif +#endif + +namespace absl { +ABSL_NAMESPACE_BEGIN + +// Moves data into the L1 cache before it is read, or "prefetches" it. +// +// The value of `addr` is the address of the memory to prefetch. If +// the target and compiler support it, data prefetch instructions are +// generated. If the prefetch is done some time before the memory is +// read, it may be in the cache by the time the read occurs. +// +// This method prefetches data with the highest degree of temporal locality; +// data is prefetched where possible into all levels of the cache. +// +// Incorrect or gratuitous use of this function can degrade performance. +// Use this function only when representative benchmarks show an improvement. +// +// Example: +// +// // Computes incremental checksum for `data`. +// int ComputeChecksum(int sum, absl::string_view data); +// +// // Computes cumulative checksum for all values in `data` +// int ComputeChecksum(absl::Span<const std::string> data) { +// int sum = 0; +// auto it = data.begin(); +// auto pit = data.begin(); +// auto end = data.end(); +// for (int dist = 8; dist > 0 && pit != data.end(); --dist, ++pit) { +// absl::PrefetchToLocalCache(pit->data()); +// } +// for (; pit != end; ++pit, ++it) { +// sum = ComputeChecksum(sum, *it); +// absl::PrefetchToLocalCache(pit->data()); +// } +// for (; it != end; ++it) { +// sum = ComputeChecksum(sum, *it); +// } +// return sum; +// } +// +void PrefetchToLocalCache(const void* addr); + +// Moves data into the L1 cache before it is read, or "prefetches" it. +// +// This function is identical to `PrefetchToLocalCache()` except that it has +// non-temporal locality: the fetched data should not be left in any of the +// cache tiers. This is useful for cases where the data is used only once / +// short term, for example, invoking a destructor on an object. +// +// Incorrect or gratuitous use of this function can degrade performance. +// Use this function only when representative benchmarks show an improvement. +// +// Example: +// +// template <typename Iterator> +// void DestroyPointers(Iterator begin, Iterator end) { +// size_t distance = std::min(8U, bars.size()); +// +// int dist = 8; +// auto prefetch_it = begin; +// while (prefetch_it != end && --dist;) { +// absl::PrefetchToLocalCacheNta(*prefetch_it++); +// } +// while (prefetch_it != end) { +// delete *begin++; +// absl::PrefetchToLocalCacheNta(*prefetch_it++); +// } +// while (begin != end) { +// delete *begin++; +// } +// } +// +void PrefetchToLocalCacheNta(const void* addr); + +// Moves data into the L1 cache with the intent to modify it. +// +// This function is similar to `PrefetchToLocalCache()` except that it +// prefetches cachelines with an 'intent to modify' This typically includes +// invalidating cache entries for this address in all other cache tiers, and an +// exclusive access intent. +// +// Incorrect or gratuitous use of this function can degrade performance. As this +// function can invalidate cached cachelines on other caches and computer cores, +// incorrect usage of this function can have an even greater negative impact +// than incorrect regular prefetches. +// Use this function only when representative benchmarks show an improvement. +// +// Example: +// +// void* Arena::Allocate(size_t size) { +// void* ptr = AllocateBlock(size); +// absl::PrefetchToLocalCacheForWrite(p); +// return ptr; +// } +// +void PrefetchToLocalCacheForWrite(const void* addr); + +#if ABSL_HAVE_BUILTIN(__builtin_prefetch) || defined(__GNUC__) + +#define ABSL_HAVE_PREFETCH 1 + +// See __builtin_prefetch: +// https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html. +// +ABSL_ATTRIBUTE_ALWAYS_INLINE inline void PrefetchToLocalCache( + const void* addr) { + __builtin_prefetch(addr, 0, 3); +} + +ABSL_ATTRIBUTE_ALWAYS_INLINE inline void PrefetchToLocalCacheNta( + const void* addr) { + __builtin_prefetch(addr, 0, 0); +} + +ABSL_ATTRIBUTE_ALWAYS_INLINE inline void PrefetchToLocalCacheForWrite( + const void* addr) { + // [x86] gcc/clang don't generate PREFETCHW for __builtin_prefetch(.., 1) + // unless -march=broadwell or newer; this is not generally the default, so we + // manually emit prefetchw. PREFETCHW is recognized as a no-op on older Intel + // processors and has been present on AMD processors since the K6-2. +#if defined(__x86_64__) && !defined(__PRFCHW__) + asm("prefetchw %0" : : "m"(*reinterpret_cast<const char*>(addr))); +#else + __builtin_prefetch(addr, 1, 3); +#endif +} + +#elif defined(ABSL_INTERNAL_HAVE_SSE) + +#define ABSL_HAVE_PREFETCH 1 + +ABSL_ATTRIBUTE_ALWAYS_INLINE inline void PrefetchToLocalCache( + const void* addr) { + _mm_prefetch(reinterpret_cast<const char*>(addr), _MM_HINT_T0); +} + +ABSL_ATTRIBUTE_ALWAYS_INLINE inline void PrefetchToLocalCacheNta( + const void* addr) { + _mm_prefetch(reinterpret_cast<const char*>(addr), _MM_HINT_NTA); +} + +ABSL_ATTRIBUTE_ALWAYS_INLINE inline void PrefetchToLocalCacheForWrite( + const void* addr) { +#if defined(_MM_HINT_ET0) + _mm_prefetch(reinterpret_cast<const char*>(addr), _MM_HINT_ET0); +#elif !defined(_MSC_VER) && defined(__x86_64__) + // _MM_HINT_ET0 is not universally supported. As we commented further + // up, PREFETCHW is recognized as a no-op on older Intel processors + // and has been present on AMD processors since the K6-2. We have this + // disabled for MSVC compilers as this miscompiles on older MSVC compilers. + asm("prefetchw %0" : : "m"(*reinterpret_cast<const char*>(addr))); +#endif +} + +#else + +ABSL_ATTRIBUTE_ALWAYS_INLINE inline void PrefetchToLocalCache( + const void* addr) {} +ABSL_ATTRIBUTE_ALWAYS_INLINE inline void PrefetchToLocalCacheNta( + const void* addr) {} +ABSL_ATTRIBUTE_ALWAYS_INLINE inline void PrefetchToLocalCacheForWrite( + const void* addr) {} + +#endif + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_BASE_PREFETCH_H_ diff --git a/absl/base/prefetch_test.cc b/absl/base/prefetch_test.cc new file mode 100644 index 00000000..ee219897 --- /dev/null +++ b/absl/base/prefetch_test.cc @@ -0,0 +1,64 @@ +// Copyright 2023 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/base/prefetch.h" + +#include <memory> + +#include "gtest/gtest.h" + +namespace { + +// Below tests exercise the functions only to guarantee they compile and execute +// correctly. We make no attempt at verifying any prefetch instructions being +// generated and executed: we assume the various implementation in terms of +// __builtin_prefetch() or x86 intrinsics to be correct and well tested. + +TEST(PrefetchTest, PrefetchToLocalCache_StackA) { + char buf[100] = {}; + absl::PrefetchToLocalCache(buf); + absl::PrefetchToLocalCacheNta(buf); + absl::PrefetchToLocalCacheForWrite(buf); +} + +TEST(PrefetchTest, PrefetchToLocalCache_Heap) { + auto memory = std::make_unique<char[]>(200 << 10); + memset(memory.get(), 0, 200 << 10); + absl::PrefetchToLocalCache(memory.get()); + absl::PrefetchToLocalCacheNta(memory.get()); + absl::PrefetchToLocalCacheForWrite(memory.get()); + absl::PrefetchToLocalCache(memory.get() + (50 << 10)); + absl::PrefetchToLocalCacheNta(memory.get() + (50 << 10)); + absl::PrefetchToLocalCacheForWrite(memory.get() + (50 << 10)); + absl::PrefetchToLocalCache(memory.get() + (100 << 10)); + absl::PrefetchToLocalCacheNta(memory.get() + (100 << 10)); + absl::PrefetchToLocalCacheForWrite(memory.get() + (100 << 10)); + absl::PrefetchToLocalCache(memory.get() + (150 << 10)); + absl::PrefetchToLocalCacheNta(memory.get() + (150 << 10)); + absl::PrefetchToLocalCacheForWrite(memory.get() + (150 << 10)); +} + +TEST(PrefetchTest, PrefetchToLocalCache_Nullptr) { + absl::PrefetchToLocalCache(nullptr); + absl::PrefetchToLocalCacheNta(nullptr); + absl::PrefetchToLocalCacheForWrite(nullptr); +} + +TEST(PrefetchTest, PrefetchToLocalCache_InvalidPtr) { + absl::PrefetchToLocalCache(reinterpret_cast<const void*>(0x785326532L)); + absl::PrefetchToLocalCacheNta(reinterpret_cast<const void*>(0x785326532L)); + absl::PrefetchToLocalCacheForWrite(reinterpret_cast<const void*>(0x78532L)); +} + +} // namespace diff --git a/absl/base/spinlock_test_common.cc b/absl/base/spinlock_test_common.cc index 52ecf580..e9047158 100644 --- a/absl/base/spinlock_test_common.cc +++ b/absl/base/spinlock_test_common.cc @@ -51,6 +51,8 @@ struct SpinLockTest { static int64_t DecodeWaitCycles(uint32_t lock_value) { return SpinLock::DecodeWaitCycles(lock_value); } + + static bool IsCooperative(const SpinLock& l) { return l.IsCooperative(); } }; namespace { @@ -266,6 +268,17 @@ TEST(SpinLockWithThreads, DoesNotDeadlock) { base_internal::NumCPUs() * 2); } +TEST(SpinLockTest, IsCooperative) { + SpinLock default_constructor; + EXPECT_TRUE(SpinLockTest::IsCooperative(default_constructor)); + + SpinLock cooperative(base_internal::SCHEDULE_COOPERATIVE_AND_KERNEL); + EXPECT_TRUE(SpinLockTest::IsCooperative(cooperative)); + + SpinLock kernel_only(base_internal::SCHEDULE_KERNEL_ONLY); + EXPECT_FALSE(SpinLockTest::IsCooperative(kernel_only)); +} + } // namespace } // namespace base_internal ABSL_NAMESPACE_END diff --git a/absl/base/thread_annotations.h b/absl/base/thread_annotations.h index bc8a6203..4a3f3e33 100644 --- a/absl/base/thread_annotations.h +++ b/absl/base/thread_annotations.h @@ -36,8 +36,6 @@ #include "absl/base/attributes.h" #include "absl/base/config.h" -// TODO(mbonadei): Remove after the backward compatibility period. -#include "absl/base/internal/thread_annotations.h" // IWYU pragma: export // ABSL_GUARDED_BY() // diff --git a/absl/cleanup/BUILD.bazel b/absl/cleanup/BUILD.bazel index 2154d9f1..984d5714 100644 --- a/absl/cleanup/BUILD.bazel +++ b/absl/cleanup/BUILD.bazel @@ -19,7 +19,14 @@ load( "ABSL_TEST_COPTS", ) -package(default_visibility = ["//visibility:public"]) +package( + default_visibility = ["//visibility:public"], + features = [ + "header_modules", + "layering_check", + "parse_headers", + ], +) licenses(["notice"]) @@ -60,6 +67,7 @@ cc_test( ":cleanup", "//absl/base:config", "//absl/utility", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/container/BUILD.bazel b/absl/container/BUILD.bazel index 7a966d63..0ba2fa76 100644 --- a/absl/container/BUILD.bazel +++ b/absl/container/BUILD.bazel @@ -21,7 +21,14 @@ load( "ABSL_TEST_COPTS", ) -package(default_visibility = ["//visibility:public"]) +package( + default_visibility = ["//visibility:public"], + features = [ + "header_modules", + "layering_check", + "parse_headers", + ], +) licenses(["notice"]) @@ -47,6 +54,7 @@ cc_test( "//absl/types:any", "//absl/types:optional", "//absl/utility", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -73,12 +81,13 @@ cc_test( copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ - ":counting_allocator", ":fixed_array", + ":test_allocator", "//absl/base:config", "//absl/base:exception_testing", "//absl/hash:hash_testing", "//absl/memory", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -92,6 +101,7 @@ cc_test( ":fixed_array", "//absl/base:config", "//absl/base:exception_safety_testing", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -116,6 +126,7 @@ cc_library( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":compressed_tuple", + "//absl/base:config", "//absl/base:core_headers", "//absl/memory", "//absl/meta:type_traits", @@ -139,13 +150,12 @@ cc_library( ) cc_library( - name = "counting_allocator", + name = "test_allocator", testonly = 1, - hdrs = ["internal/counting_allocator.h"], - copts = ABSL_DEFAULT_COPTS, + copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + textual_hdrs = ["internal/test_allocator.h"], visibility = ["//visibility:private"], - deps = ["//absl/base:config"], ) cc_test( @@ -154,16 +164,17 @@ cc_test( copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ - ":counting_allocator", ":inlined_vector", + ":test_allocator", ":test_instance_tracker", "//absl/base:config", "//absl/base:core_headers", "//absl/base:exception_testing", - "//absl/base:raw_logging_internal", "//absl/hash:hash_testing", + "//absl/log:check", "//absl/memory", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -192,6 +203,7 @@ cc_test( ":inlined_vector", "//absl/base:config", "//absl/base:exception_safety_testing", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -216,6 +228,7 @@ cc_test( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":test_instance_tracker", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -255,8 +268,10 @@ cc_test( ":unordered_map_lookup_test", ":unordered_map_members_test", ":unordered_map_modifiers_test", - "//absl/base:raw_logging_internal", + "//absl/log:check", + "//absl/meta:type_traits", "//absl/types:any", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -283,15 +298,18 @@ cc_test( linkopts = ABSL_DEFAULT_LINKOPTS, tags = ["no_test_loonix"], deps = [ + ":container_memory", ":flat_hash_set", ":hash_generator_testing", ":unordered_set_constructor_test", ":unordered_set_lookup_test", ":unordered_set_members_test", ":unordered_set_modifiers_test", - "//absl/base:raw_logging_internal", + "//absl/base:config", + "//absl/log:check", "//absl/memory", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -326,6 +344,7 @@ cc_test( ":unordered_map_lookup_test", ":unordered_map_members_test", ":unordered_map_modifiers_test", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -357,6 +376,7 @@ cc_test( ":unordered_set_lookup_test", ":unordered_set_members_test", ":unordered_set_modifiers_test", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -383,7 +403,10 @@ cc_test( deps = [ ":container_memory", ":test_instance_tracker", + "//absl/base:no_destructor", + "//absl/meta:type_traits", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -417,6 +440,7 @@ cc_test( "//absl/strings", "//absl/strings:cord", "//absl/strings:cord_test_helpers", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -430,6 +454,7 @@ cc_library( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":hash_policy_testing", + "//absl/base:no_destructor", "//absl/memory", "//absl/meta:type_traits", "//absl/strings", @@ -455,6 +480,7 @@ cc_test( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":hash_policy_testing", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -477,6 +503,7 @@ cc_test( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":hash_policy_traits", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -497,6 +524,8 @@ cc_test( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":common_policy_traits", + "//absl/base:config", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -534,11 +563,13 @@ cc_library( "//absl/base", "//absl/base:config", "//absl/base:core_headers", + "//absl/base:raw_logging_internal", "//absl/debugging:stacktrace", "//absl/memory", "//absl/profiling:exponential_biased", "//absl/profiling:sample_recorder", "//absl/synchronization", + "//absl/time", "//absl/utility", ], ) @@ -558,6 +589,7 @@ cc_test( "//absl/synchronization", "//absl/synchronization:thread_pool", "//absl/time", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -578,6 +610,8 @@ cc_test( deps = [ ":hash_policy_traits", ":node_slot_policy", + "//absl/base:config", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -590,6 +624,8 @@ cc_library( deps = [ ":container_memory", ":raw_hash_set", + "//absl/base:config", + "//absl/base:core_headers", "//absl/base:throw_delegate", ], ) @@ -620,9 +656,11 @@ cc_library( ":hashtablez_sampler", "//absl/base:config", "//absl/base:core_headers", + "//absl/base:dynamic_annotations", "//absl/base:endian", "//absl/base:prefetch", "//absl/base:raw_logging_internal", + "//absl/hash", "//absl/memory", "//absl/meta:type_traits", "//absl/numeric:bits", @@ -647,14 +685,19 @@ cc_test( ":hash_function_defaults", ":hash_policy_testing", ":hashtable_debug", + ":hashtablez_sampler", ":raw_hash_set", + ":test_allocator", "//absl/base", "//absl/base:config", "//absl/base:core_headers", "//absl/base:prefetch", - "//absl/base:raw_logging_internal", + "//absl/hash", "//absl/log", + "//absl/memory", + "//absl/meta:type_traits", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -691,10 +734,12 @@ cc_binary( ":hash_function_defaults", ":hashtable_debug", ":raw_hash_set", + "//absl/base:no_destructor", "//absl/random", "//absl/random:distributions", "//absl/strings", "//absl/strings:str_format", + "//absl/types:optional", ], ) @@ -707,7 +752,8 @@ cc_test( deps = [ ":raw_hash_set", ":tracked", - "//absl/base:core_headers", + "//absl/base:config", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -720,6 +766,7 @@ cc_library( deps = [ "//absl/base:config", "//absl/base:core_headers", + "//absl/debugging:demangle_internal", "//absl/meta:type_traits", "//absl/strings", "//absl/types:span", @@ -738,9 +785,10 @@ cc_test( deps = [ ":layout", "//absl/base:config", - "//absl/base:core_headers", - "//absl/base:raw_logging_internal", + "//absl/log:check", "//absl/types:span", + "//absl/utility", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -886,6 +934,7 @@ cc_test( ":unordered_set_lookup_test", ":unordered_set_members_test", ":unordered_set_modifiers_test", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -901,6 +950,7 @@ cc_test( ":unordered_map_lookup_test", ":unordered_map_members_test", ":unordered_map_modifiers_test", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -917,6 +967,7 @@ cc_test( ":flat_hash_set", ":node_hash_map", ":node_hash_set", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -986,7 +1037,7 @@ cc_test( deps = [ ":btree", ":btree_test_common", - ":counting_allocator", + ":test_allocator", ":test_instance_tracker", "//absl/algorithm:container", "//absl/base:core_headers", @@ -997,6 +1048,8 @@ cc_test( "//absl/random", "//absl/strings", "//absl/types:compare", + "//absl/types:optional", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -1027,5 +1080,6 @@ cc_binary( "//absl/strings:str_format", "//absl/time", "@com_github_google_benchmark//:benchmark_main", + "@com_google_googletest//:gtest", ], ) diff --git a/absl/container/CMakeLists.txt b/absl/container/CMakeLists.txt index 416e3e38..128cc0e9 100644 --- a/absl/container/CMakeLists.txt +++ b/absl/container/CMakeLists.txt @@ -77,12 +77,13 @@ absl_cc_test( absl::btree_test_common absl::compare absl::core_headers - absl::counting_allocator absl::flags absl::hash_testing + absl::optional absl::random_random absl::raw_logging_internal absl::strings + absl::test_allocator absl::test_instance_tracker GTest::gmock_main ) @@ -144,11 +145,11 @@ absl_cc_test( ${ABSL_TEST_COPTS} DEPS absl::fixed_array - absl::counting_allocator absl::config absl::exception_testing absl::hash_testing absl::memory + absl::test_allocator GTest::gmock_main ) @@ -176,6 +177,7 @@ absl_cc_library( ${ABSL_DEFAULT_COPTS} DEPS absl::compressed_tuple + absl::config absl::core_headers absl::memory absl::span @@ -203,13 +205,14 @@ absl_cc_library( # Internal-only target, do not depend on directly. absl_cc_library( NAME - counting_allocator + test_allocator HDRS - "internal/counting_allocator.h" + "internal/test_allocator.h" COPTS ${ABSL_DEFAULT_COPTS} DEPS absl::config + GTest::gmock ) absl_cc_test( @@ -220,16 +223,16 @@ absl_cc_test( COPTS ${ABSL_TEST_COPTS} DEPS - absl::counting_allocator - absl::inlined_vector - absl::test_instance_tracker + absl::check absl::config absl::core_headers absl::exception_testing absl::hash_testing + absl::inlined_vector absl::memory - absl::raw_logging_internal absl::strings + absl::test_allocator + absl::test_instance_tracker GTest::gmock_main ) @@ -299,14 +302,15 @@ absl_cc_test( COPTS ${ABSL_TEST_COPTS} DEPS + absl::any + absl::check absl::flat_hash_map absl::hash_generator_testing + absl::type_traits absl::unordered_map_constructor_test absl::unordered_map_lookup_test absl::unordered_map_members_test absl::unordered_map_modifiers_test - absl::any - absl::raw_logging_internal GTest::gmock_main ) @@ -336,15 +340,17 @@ absl_cc_test( ${ABSL_TEST_COPTS} "-DUNORDERED_SET_CXX17" DEPS + absl::check + absl::config + absl::container_memory absl::flat_hash_set absl::hash_generator_testing + absl::memory + absl::strings absl::unordered_set_constructor_test absl::unordered_set_lookup_test absl::unordered_set_members_test absl::unordered_set_modifiers_test - absl::memory - absl::raw_logging_internal - absl::strings GTest::gmock_main ) @@ -444,8 +450,10 @@ absl_cc_test( ${ABSL_TEST_COPTS} DEPS absl::container_memory + absl::no_destructor absl::strings absl::test_instance_tracker + absl::type_traits GTest::gmock_main ) @@ -496,6 +504,7 @@ absl_cc_library( absl::hash_policy_testing absl::memory absl::meta + absl::no_destructor absl::strings TESTONLY ) @@ -574,6 +583,7 @@ absl_cc_test( ${ABSL_TEST_COPTS} DEPS absl::common_policy_traits + absl::config GTest::gmock_main ) @@ -592,8 +602,10 @@ absl_cc_library( absl::base absl::config absl::exponential_biased + absl::raw_logging_internal absl::sample_recorder absl::synchronization + absl::time ) absl_cc_test( @@ -655,6 +667,7 @@ absl_cc_test( COPTS ${ABSL_TEST_COPTS} DEPS + absl::config absl::hash_policy_traits absl::node_slot_policy GTest::gmock_main @@ -669,7 +682,9 @@ absl_cc_library( COPTS ${ABSL_DEFAULT_COPTS} DEPS + absl::config absl::container_memory + absl::core_headers absl::raw_hash_set absl::throw_delegate PUBLIC @@ -704,7 +719,9 @@ absl_cc_library( absl::container_common absl::container_memory absl::core_headers + absl::dynamic_annotations absl::endian + absl::hash absl::hash_policy_traits absl::hashtable_debug_hooks absl::hashtablez_sampler @@ -731,14 +748,18 @@ absl_cc_test( absl::core_headers absl::flat_hash_map absl::flat_hash_set + absl::hash absl::hash_function_defaults absl::hash_policy_testing absl::hashtable_debug + absl::hashtablez_sampler absl::log + absl::memory absl::prefetch absl::raw_hash_set - absl::raw_logging_internal absl::strings + absl::test_allocator + absl::type_traits GTest::gmock_main ) @@ -750,9 +771,9 @@ absl_cc_test( COPTS ${ABSL_TEST_COPTS} DEPS + absl::config absl::raw_hash_set absl::tracked - absl::core_headers GTest::gmock_main ) @@ -767,6 +788,7 @@ absl_cc_library( DEPS absl::config absl::core_headers + absl::debugging_internal absl::meta absl::strings absl::span @@ -783,10 +805,10 @@ absl_cc_test( ${ABSL_TEST_COPTS} DEPS absl::layout + absl::check absl::config - absl::core_headers - absl::raw_logging_internal absl::span + absl::utility GTest::gmock_main ) diff --git a/absl/container/btree_map.h b/absl/container/btree_map.h index cd3ee2b4..0f62f0bd 100644 --- a/absl/container/btree_map.h +++ b/absl/container/btree_map.h @@ -53,6 +53,7 @@ #ifndef ABSL_CONTAINER_BTREE_MAP_H_ #define ABSL_CONTAINER_BTREE_MAP_H_ +#include "absl/base/attributes.h" #include "absl/container/internal/btree.h" // IWYU pragma: export #include "absl/container/internal/btree_container.h" // IWYU pragma: export @@ -864,7 +865,8 @@ struct map_params : common_params<Key, Compare, Alloc, TargetNodeSize, IsMulti, using init_type = typename super_type::init_type; template <typename V> - static auto key(const V &value) -> decltype(value.first) { + static auto key(const V &value ABSL_ATTRIBUTE_LIFETIME_BOUND) + -> decltype((value.first)) { return value.first; } static const Key &key(const slot_type *s) { return slot_policy::key(s); } diff --git a/absl/container/btree_test.cc b/absl/container/btree_test.cc index cc763b29..d7102fe4 100644 --- a/absl/container/btree_test.cc +++ b/absl/container/btree_test.cc @@ -18,6 +18,7 @@ #include <array> #include <cstdint> #include <functional> +#include <iostream> #include <iterator> #include <limits> #include <map> @@ -36,7 +37,7 @@ #include "absl/base/macros.h" #include "absl/container/btree_map.h" #include "absl/container/btree_set.h" -#include "absl/container/internal/counting_allocator.h" +#include "absl/container/internal/test_allocator.h" #include "absl/container/internal/test_instance_tracker.h" #include "absl/flags/flag.h" #include "absl/hash/hash_testing.h" @@ -46,6 +47,7 @@ #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" #include "absl/types/compare.h" +#include "absl/types/optional.h" ABSL_FLAG(int, test_values, 10000, "The number of values to use for tests"); @@ -74,16 +76,6 @@ void CheckPairEquals(const std::pair<T, U> &x, const std::pair<V, W> &y) { CheckPairEquals(x.first, y.first); CheckPairEquals(x.second, y.second); } - -bool IsAssertEnabled() { - // Use an assert with side-effects to figure out if they are actually enabled. - bool assert_enabled = false; - assert([&]() { // NOLINT - assert_enabled = true; - return true; - }()); - return assert_enabled; -} } // namespace // The base class for a sorted associative container checker. TreeType is the @@ -666,111 +658,6 @@ void BtreeMultiTest() { } template <typename T> -struct PropagatingCountingAlloc : public CountingAllocator<T> { - using propagate_on_container_copy_assignment = std::true_type; - using propagate_on_container_move_assignment = std::true_type; - using propagate_on_container_swap = std::true_type; - - using Base = CountingAllocator<T>; - using Base::Base; - - template <typename U> - explicit PropagatingCountingAlloc(const PropagatingCountingAlloc<U> &other) - : Base(other.bytes_used_) {} - - template <typename U> - struct rebind { - using other = PropagatingCountingAlloc<U>; - }; -}; - -template <typename T> -void BtreeAllocatorTest() { - using value_type = typename T::value_type; - - int64_t bytes1 = 0, bytes2 = 0; - PropagatingCountingAlloc<T> allocator1(&bytes1); - PropagatingCountingAlloc<T> allocator2(&bytes2); - Generator<value_type> generator(1000); - - // Test that we allocate properly aligned memory. If we don't, then Layout - // will assert fail. - auto unused1 = allocator1.allocate(1); - auto unused2 = allocator2.allocate(1); - - // Test copy assignment - { - T b1(typename T::key_compare(), allocator1); - T b2(typename T::key_compare(), allocator2); - - int64_t original_bytes1 = bytes1; - b1.insert(generator(0)); - EXPECT_GT(bytes1, original_bytes1); - - // This should propagate the allocator. - b1 = b2; - EXPECT_EQ(b1.size(), 0); - EXPECT_EQ(b2.size(), 0); - EXPECT_EQ(bytes1, original_bytes1); - - for (int i = 1; i < 1000; i++) { - b1.insert(generator(i)); - } - - // We should have allocated out of allocator2. - EXPECT_GT(bytes2, bytes1); - } - - // Test move assignment - { - T b1(typename T::key_compare(), allocator1); - T b2(typename T::key_compare(), allocator2); - - int64_t original_bytes1 = bytes1; - b1.insert(generator(0)); - EXPECT_GT(bytes1, original_bytes1); - - // This should propagate the allocator. - b1 = std::move(b2); - EXPECT_EQ(b1.size(), 0); - EXPECT_EQ(bytes1, original_bytes1); - - for (int i = 1; i < 1000; i++) { - b1.insert(generator(i)); - } - - // We should have allocated out of allocator2. - EXPECT_GT(bytes2, bytes1); - } - - // Test swap - { - T b1(typename T::key_compare(), allocator1); - T b2(typename T::key_compare(), allocator2); - - int64_t original_bytes1 = bytes1; - b1.insert(generator(0)); - EXPECT_GT(bytes1, original_bytes1); - - // This should swap the allocators. - swap(b1, b2); - EXPECT_EQ(b1.size(), 0); - EXPECT_EQ(b2.size(), 1); - EXPECT_GT(bytes1, original_bytes1); - - for (int i = 1; i < 1000; i++) { - b1.insert(generator(i)); - } - - // We should have allocated out of allocator2. - EXPECT_GT(bytes2, bytes1); - } - - allocator1.deallocate(unused1, 1); - allocator2.deallocate(unused2, 1); -} - -template <typename T> void BtreeMapTest() { using value_type = typename T::value_type; using mapped_type = typename T::mapped_type; @@ -809,10 +696,7 @@ void SetTest() { sizeof(absl::btree_set<K>), 2 * sizeof(void *) + sizeof(typename absl::btree_set<K>::size_type)); using BtreeSet = absl::btree_set<K>; - using CountingBtreeSet = - absl::btree_set<K, std::less<K>, PropagatingCountingAlloc<K>>; BtreeTest<BtreeSet, std::set<K>>(); - BtreeAllocatorTest<CountingBtreeSet>(); } template <typename K, int N = 256> @@ -821,24 +705,16 @@ void MapTest() { sizeof(absl::btree_map<K, K>), 2 * sizeof(void *) + sizeof(typename absl::btree_map<K, K>::size_type)); using BtreeMap = absl::btree_map<K, K>; - using CountingBtreeMap = - absl::btree_map<K, K, std::less<K>, - PropagatingCountingAlloc<std::pair<const K, K>>>; BtreeTest<BtreeMap, std::map<K, K>>(); - BtreeAllocatorTest<CountingBtreeMap>(); BtreeMapTest<BtreeMap>(); } TEST(Btree, set_int32) { SetTest<int32_t>(); } -TEST(Btree, set_int64) { SetTest<int64_t>(); } TEST(Btree, set_string) { SetTest<std::string>(); } TEST(Btree, set_cord) { SetTest<absl::Cord>(); } -TEST(Btree, set_pair) { SetTest<std::pair<int, int>>(); } TEST(Btree, map_int32) { MapTest<int32_t>(); } -TEST(Btree, map_int64) { MapTest<int64_t>(); } TEST(Btree, map_string) { MapTest<std::string>(); } TEST(Btree, map_cord) { MapTest<absl::Cord>(); } -TEST(Btree, map_pair) { MapTest<std::pair<int, int>>(); } template <typename K, int N = 256> void MultiSetTest() { @@ -846,10 +722,7 @@ void MultiSetTest() { sizeof(absl::btree_multiset<K>), 2 * sizeof(void *) + sizeof(typename absl::btree_multiset<K>::size_type)); using BtreeMSet = absl::btree_multiset<K>; - using CountingBtreeMSet = - absl::btree_multiset<K, std::less<K>, PropagatingCountingAlloc<K>>; BtreeMultiTest<BtreeMSet, std::multiset<K>>(); - BtreeAllocatorTest<CountingBtreeMSet>(); } template <typename K, int N = 256> @@ -858,24 +731,16 @@ void MultiMapTest() { 2 * sizeof(void *) + sizeof(typename absl::btree_multimap<K, K>::size_type)); using BtreeMMap = absl::btree_multimap<K, K>; - using CountingBtreeMMap = - absl::btree_multimap<K, K, std::less<K>, - PropagatingCountingAlloc<std::pair<const K, K>>>; BtreeMultiTest<BtreeMMap, std::multimap<K, K>>(); BtreeMultiMapTest<BtreeMMap>(); - BtreeAllocatorTest<CountingBtreeMMap>(); } TEST(Btree, multiset_int32) { MultiSetTest<int32_t>(); } -TEST(Btree, multiset_int64) { MultiSetTest<int64_t>(); } TEST(Btree, multiset_string) { MultiSetTest<std::string>(); } TEST(Btree, multiset_cord) { MultiSetTest<absl::Cord>(); } -TEST(Btree, multiset_pair) { MultiSetTest<std::pair<int, int>>(); } TEST(Btree, multimap_int32) { MultiMapTest<int32_t>(); } -TEST(Btree, multimap_int64) { MultiMapTest<int64_t>(); } TEST(Btree, multimap_string) { MultiMapTest<std::string>(); } TEST(Btree, multimap_cord) { MultiMapTest<absl::Cord>(); } -TEST(Btree, multimap_pair) { MultiMapTest<std::pair<int, int>>(); } struct CompareIntToString { bool operator()(const std::string &a, const std::string &b) const { @@ -1231,8 +1096,10 @@ class BtreeNodePeer { } template <typename Btree> - constexpr static bool UsesGenerations() { - return Btree::params_type::kEnableGenerations; + constexpr static bool FieldTypeEqualsSlotType() { + return std::is_same< + typename btree_node<typename Btree::params_type>::field_type, + typename btree_node<typename Btree::params_type>::slot_type>::value; } }; @@ -1461,7 +1328,7 @@ class SizedBtreeSet using Base = typename SizedBtreeSet::btree_set_container; public: - SizedBtreeSet() {} + SizedBtreeSet() = default; using Base::Base; }; @@ -1479,9 +1346,18 @@ void ExpectOperationCounts(const int expected_moves, tracker->ResetCopiesMovesSwaps(); } +#if defined(ABSL_HAVE_ADDRESS_SANITIZER) || \ + defined(ABSL_HAVE_HWADDRESS_SANITIZER) +constexpr bool kAsan = true; +#else +constexpr bool kAsan = false; +#endif + // Note: when the values in this test change, it is expected to have an impact // on performance. TEST(Btree, MovesComparisonsCopiesSwapsTracking) { + if (kAsan) GTEST_SKIP() << "We do extra operations in ASan mode."; + InstanceTracker tracker; // Note: this is minimum number of values per node. SizedBtreeSet<MovableOnlyInstance, /*TargetValuesPerNode=*/4> set4; @@ -1499,10 +1375,9 @@ TEST(Btree, MovesComparisonsCopiesSwapsTracking) { EXPECT_EQ(BtreeNodePeer::GetNumSlotsPerNode<decltype(set61)>(), 61); EXPECT_EQ(BtreeNodePeer::GetNumSlotsPerNode<decltype(set100)>(), 100); if (sizeof(void *) == 8) { - EXPECT_EQ( - BtreeNodePeer::GetNumSlotsPerNode<absl::btree_set<int32_t>>(), - // When we have generations, there is one fewer slot. - BtreeNodePeer::UsesGenerations<absl::btree_set<int32_t>>() ? 60 : 61); + EXPECT_EQ(BtreeNodePeer::GetNumSlotsPerNode<absl::btree_set<int32_t>>(), + // When we have generations, there is one fewer slot. + BtreeGenerationsEnabled() ? 60 : 61); } // Test key insertion/deletion in random order. @@ -1533,6 +1408,8 @@ struct MovableOnlyInstanceThreeWayCompare { // Note: when the values in this test change, it is expected to have an impact // on performance. TEST(Btree, MovesComparisonsCopiesSwapsTrackingThreeWayCompare) { + if (kAsan) GTEST_SKIP() << "We do extra operations in ASan mode."; + InstanceTracker tracker; // Note: this is minimum number of values per node. SizedBtreeSet<MovableOnlyInstance, /*TargetValuesPerNode=*/4, @@ -1556,10 +1433,9 @@ TEST(Btree, MovesComparisonsCopiesSwapsTrackingThreeWayCompare) { EXPECT_EQ(BtreeNodePeer::GetNumSlotsPerNode<decltype(set61)>(), 61); EXPECT_EQ(BtreeNodePeer::GetNumSlotsPerNode<decltype(set100)>(), 100); if (sizeof(void *) == 8) { - EXPECT_EQ( - BtreeNodePeer::GetNumSlotsPerNode<absl::btree_set<int32_t>>(), - // When we have generations, there is one fewer slot. - BtreeNodePeer::UsesGenerations<absl::btree_set<int32_t>>() ? 60 : 61); + EXPECT_EQ(BtreeNodePeer::GetNumSlotsPerNode<absl::btree_set<int32_t>>(), + // When we have generations, there is one fewer slot. + BtreeGenerationsEnabled() ? 60 : 61); } // Test key insertion/deletion in random order. @@ -2514,50 +2390,23 @@ TEST(Btree, TryEmplaceWithHintAndMultipleValueArgsWorks) { EXPECT_EQ(std::string(10, 'a'), m[1]); } -TEST(Btree, MoveAssignmentAllocatorPropagation) { - InstanceTracker tracker; - - int64_t bytes1 = 0, bytes2 = 0; - PropagatingCountingAlloc<MovableOnlyInstance> allocator1(&bytes1); - PropagatingCountingAlloc<MovableOnlyInstance> allocator2(&bytes2); - std::less<MovableOnlyInstance> cmp; - - // Test propagating allocator_type. - { - absl::btree_set<MovableOnlyInstance, std::less<MovableOnlyInstance>, - PropagatingCountingAlloc<MovableOnlyInstance>> - set1(cmp, allocator1), set2(cmp, allocator2); - - for (int i = 0; i < 100; ++i) set1.insert(MovableOnlyInstance(i)); - - tracker.ResetCopiesMovesSwaps(); - set2 = std::move(set1); - EXPECT_EQ(tracker.moves(), 0); - } - // Test non-propagating allocator_type with equal allocators. - { - absl::btree_set<MovableOnlyInstance, std::less<MovableOnlyInstance>, - CountingAllocator<MovableOnlyInstance>> - set1(cmp, allocator1), set2(cmp, allocator1); +template <typename Alloc> +using BtreeSetAlloc = absl::btree_set<int, std::less<int>, Alloc>; - for (int i = 0; i < 100; ++i) set1.insert(MovableOnlyInstance(i)); +TEST(Btree, AllocatorPropagation) { + TestAllocPropagation<BtreeSetAlloc>(); +} - tracker.ResetCopiesMovesSwaps(); - set2 = std::move(set1); - EXPECT_EQ(tracker.moves(), 0); - } - // Test non-propagating allocator_type with different allocators. - { - absl::btree_set<MovableOnlyInstance, std::less<MovableOnlyInstance>, - CountingAllocator<MovableOnlyInstance>> - set1(cmp, allocator1), set2(cmp, allocator2); +TEST(Btree, MinimumAlignmentAllocator) { + absl::btree_set<int8_t, std::less<int8_t>, MinimumAlignmentAlloc<int8_t>> set; - for (int i = 0; i < 100; ++i) set1.insert(MovableOnlyInstance(i)); + // Do some basic operations. Test that everything is fine when allocator uses + // minimal alignment. + for (int8_t i = 0; i < 100; ++i) set.insert(i); + set.erase(set.find(50), set.end()); + for (int8_t i = 51; i < 101; ++i) set.insert(i); - tracker.ResetCopiesMovesSwaps(); - set2 = std::move(set1); - EXPECT_GE(tracker.moves(), 100); - } + EXPECT_EQ(set.size(), 100); } TEST(Btree, EmptyTree) { @@ -2953,6 +2802,20 @@ TYPED_TEST(BtreeMultiKeyTest, Count) { EXPECT_EQ(set.count(2), 2); } +TEST(Btree, SetIteratorsAreConst) { + using Set = absl::btree_set<int>; + EXPECT_TRUE( + (std::is_same<typename Set::iterator::reference, const int &>::value)); + EXPECT_TRUE( + (std::is_same<typename Set::iterator::pointer, const int *>::value)); + + using MSet = absl::btree_multiset<int>; + EXPECT_TRUE( + (std::is_same<typename MSet::iterator::reference, const int &>::value)); + EXPECT_TRUE( + (std::is_same<typename MSet::iterator::pointer, const int *>::value)); +} + TEST(Btree, AllocConstructor) { using Alloc = CountingAllocator<int>; using Set = absl::btree_set<int, std::less<int>, Alloc>; @@ -3137,27 +3000,104 @@ TEST(Btree, InvalidComparatorsCaught) { absl::btree_set<int, ThreeWaySumGreaterZeroCmp> set; EXPECT_DEATH(set.insert({0, 1, 2}), "lhs_comp_rhs < 0 -> rhs_comp_lhs > 0"); } + // Verify that we detect cases of comparators that violate transitivity. + // When the comparators below check for the presence of an optional field, + // they violate transitivity because instances that have the optional field + // compare differently with each other from how they compare with instances + // that don't have the optional field. + struct ClockTime { + absl::optional<int> hour; + int minute; + }; + // `comp(a,b) && comp(b,c) && !comp(a,c)` violates transitivity. + ClockTime a = {absl::nullopt, 1}; + ClockTime b = {2, 5}; + ClockTime c = {6, 0}; + { + struct NonTransitiveTimeCmp { + bool operator()(ClockTime lhs, ClockTime rhs) const { + if (lhs.hour.has_value() && rhs.hour.has_value() && + *lhs.hour != *rhs.hour) { + return *lhs.hour < *rhs.hour; + } + return lhs.minute < rhs.minute; + } + }; + NonTransitiveTimeCmp cmp; + ASSERT_TRUE(cmp(a, b) && cmp(b, c) && !cmp(a, c)); + absl::btree_set<ClockTime, NonTransitiveTimeCmp> set; + EXPECT_DEATH(set.insert({a, b, c}), "is_ordered_correctly"); + absl::btree_multiset<ClockTime, NonTransitiveTimeCmp> mset; + EXPECT_DEATH(mset.insert({a, a, b, b, c, c}), "is_ordered_correctly"); + } + { + struct ThreeWayNonTransitiveTimeCmp { + absl::weak_ordering operator()(ClockTime lhs, ClockTime rhs) const { + if (lhs.hour.has_value() && rhs.hour.has_value() && + *lhs.hour != *rhs.hour) { + return *lhs.hour < *rhs.hour ? absl::weak_ordering::less + : absl::weak_ordering::greater; + } + return lhs.minute < rhs.minute ? absl::weak_ordering::less + : lhs.minute == rhs.minute ? absl::weak_ordering::equivalent + : absl::weak_ordering::greater; + } + }; + ThreeWayNonTransitiveTimeCmp cmp; + ASSERT_TRUE(cmp(a, b) < 0 && cmp(b, c) < 0 && cmp(a, c) > 0); + absl::btree_set<ClockTime, ThreeWayNonTransitiveTimeCmp> set; + EXPECT_DEATH(set.insert({a, b, c}), "is_ordered_correctly"); + absl::btree_multiset<ClockTime, ThreeWayNonTransitiveTimeCmp> mset; + EXPECT_DEATH(mset.insert({a, a, b, b, c, c}), "is_ordered_correctly"); + } +} + +TEST(Btree, MutatedKeysCaught) { + if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled."; + + struct IntPtrCmp { + bool operator()(int *lhs, int *rhs) const { return *lhs < *rhs; } + }; + { + absl::btree_set<int *, IntPtrCmp> set; + int arr[] = {0, 1, 2}; + set.insert({&arr[0], &arr[1], &arr[2]}); + arr[0] = 100; + EXPECT_DEATH(set.insert(&arr[0]), "is_ordered_correctly"); + } + { + absl::btree_multiset<int *, IntPtrCmp> set; + int arr[] = {0, 1, 2}; + set.insert({&arr[0], &arr[0], &arr[1], &arr[1], &arr[2], &arr[2]}); + arr[0] = 100; + EXPECT_DEATH(set.insert(&arr[0]), "is_ordered_correctly"); + } } #ifndef _MSC_VER // This test crashes on MSVC. TEST(Btree, InvalidIteratorUse) { - if (!BtreeNodePeer::UsesGenerations<absl::btree_set<int>>()) + if (!BtreeGenerationsEnabled()) GTEST_SKIP() << "Generation validation for iterators is disabled."; + // Invalid memory use can trigger use-after-free in ASan, HWASAN or + // invalidated iterator assertions. + constexpr const char *kInvalidMemoryDeathMessage = + "use-after-free|invalidated iterator"; + { absl::btree_set<int> set; for (int i = 0; i < 10; ++i) set.insert(i); auto it = set.begin(); set.erase(it++); - EXPECT_DEATH(set.erase(it++), "invalidated iterator"); + EXPECT_DEATH(set.erase(it++), kInvalidMemoryDeathMessage); } { absl::btree_set<int> set; for (int i = 0; i < 10; ++i) set.insert(i); auto it = set.insert(20).first; set.insert(30); - EXPECT_DEATH(*it, "invalidated iterator"); + EXPECT_DEATH(*it, kInvalidMemoryDeathMessage); } { absl::btree_set<int> set; @@ -3165,15 +3105,15 @@ TEST(Btree, InvalidIteratorUse) { auto it = set.find(5000); ASSERT_NE(it, set.end()); set.erase(1); - EXPECT_DEATH(*it, "invalidated iterator"); + EXPECT_DEATH(*it, kInvalidMemoryDeathMessage); } { absl::btree_set<int> set; for (int i = 0; i < 10; ++i) set.insert(i); auto it = set.insert(20).first; set.insert(30); - EXPECT_DEATH(void(it == set.begin()), "invalidated iterator"); - EXPECT_DEATH(void(set.begin() == it), "invalidated iterator"); + EXPECT_DEATH(void(it == set.begin()), kInvalidMemoryDeathMessage); + EXPECT_DEATH(void(set.begin() == it), kInvalidMemoryDeathMessage); } } #endif @@ -3464,6 +3404,57 @@ TEST(Btree, InvalidIteratorComparison) { EXPECT_DEATH(void(iter2 == iter1), kDifferentContainerDeathMessage); } +TEST(Btree, InvalidPointerUse) { + if (!kAsan) + GTEST_SKIP() << "We only detect invalid pointer use in ASan mode."; + + absl::btree_set<int> set; + set.insert(0); + const int *ptr = &*set.begin(); + set.insert(1); + EXPECT_DEATH(std::cout << *ptr, "use-after-free"); + size_t slots_per_node = BtreeNodePeer::GetNumSlotsPerNode<decltype(set)>(); + for (int i = 2; i < slots_per_node - 1; ++i) set.insert(i); + ptr = &*set.begin(); + set.insert(static_cast<int>(slots_per_node)); + EXPECT_DEATH(std::cout << *ptr, "use-after-free"); +} + +template<typename Set> +void TestBasicFunctionality(Set set) { + using value_type = typename Set::value_type; + for (int i = 0; i < 100; ++i) { set.insert(value_type(i)); } + for (int i = 50; i < 100; ++i) { set.erase(value_type(i)); } + auto it = set.begin(); + for (int i = 0; i < 50; ++i, ++it) { + ASSERT_EQ(set.find(value_type(i)), it) << i; + } +} + +template<size_t align> +struct alignas(align) OveralignedKey { + explicit OveralignedKey(int i) : key(i) {} + bool operator<(const OveralignedKey &other) const { return key < other.key; } + int key = 0; +}; + +TEST(Btree, OveralignedKey) { + // Test basic functionality with both even and odd numbers of slots per node. + // The goal here is to detect cases where alignment may be incorrect. + TestBasicFunctionality( + SizedBtreeSet<OveralignedKey<16>, /*TargetValuesPerNode=*/8>()); + TestBasicFunctionality( + SizedBtreeSet<OveralignedKey<16>, /*TargetValuesPerNode=*/9>()); +} + +TEST(Btree, FieldTypeEqualsSlotType) { + // This breaks if we try to do layout_type::Pointer<slot_type> because + // slot_type is the same as field_type. + using set_type = absl::btree_set<uint8_t>; + static_assert(BtreeNodePeer::FieldTypeEqualsSlotType<set_type>(), ""); + TestBasicFunctionality(set_type()); +} + } // namespace } // namespace container_internal ABSL_NAMESPACE_END diff --git a/absl/container/fixed_array.h b/absl/container/fixed_array.h index b67379cf..9f1c813d 100644 --- a/absl/container/fixed_array.h +++ b/absl/container/fixed_array.h @@ -117,14 +117,20 @@ class FixedArray { (N == kFixedArrayUseDefault ? kInlineBytesDefault / sizeof(value_type) : static_cast<size_type>(N)); - FixedArray( - const FixedArray& other, - const allocator_type& a = allocator_type()) noexcept(NoexceptCopyable()) + FixedArray(const FixedArray& other) noexcept(NoexceptCopyable()) + : FixedArray(other, + AllocatorTraits::select_on_container_copy_construction( + other.storage_.alloc())) {} + + FixedArray(const FixedArray& other, + const allocator_type& a) noexcept(NoexceptCopyable()) : FixedArray(other.begin(), other.end(), a) {} - FixedArray( - FixedArray&& other, - const allocator_type& a = allocator_type()) noexcept(NoexceptMovable()) + FixedArray(FixedArray&& other) noexcept(NoexceptMovable()) + : FixedArray(std::move(other), other.storage_.alloc()) {} + + FixedArray(FixedArray&& other, + const allocator_type& a) noexcept(NoexceptMovable()) : FixedArray(std::make_move_iterator(other.begin()), std::make_move_iterator(other.end()), a) {} @@ -200,18 +206,22 @@ class FixedArray { // // Returns a const T* pointer to elements of the `FixedArray`. This pointer // can be used to access (but not modify) the contained elements. - const_pointer data() const { return AsValueType(storage_.begin()); } + const_pointer data() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return AsValueType(storage_.begin()); + } // Overload of FixedArray::data() to return a T* pointer to elements of the // fixed array. This pointer can be used to access and modify the contained // elements. - pointer data() { return AsValueType(storage_.begin()); } + pointer data() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return AsValueType(storage_.begin()); + } // FixedArray::operator[] // // Returns a reference the ith element of the fixed array. // REQUIRES: 0 <= i < size() - reference operator[](size_type i) { + reference operator[](size_type i) ABSL_ATTRIBUTE_LIFETIME_BOUND { ABSL_HARDENING_ASSERT(i < size()); return data()[i]; } @@ -219,7 +229,7 @@ class FixedArray { // Overload of FixedArray::operator()[] to return a const reference to the // ith element of the fixed array. // REQUIRES: 0 <= i < size() - const_reference operator[](size_type i) const { + const_reference operator[](size_type i) const ABSL_ATTRIBUTE_LIFETIME_BOUND { ABSL_HARDENING_ASSERT(i < size()); return data()[i]; } @@ -228,7 +238,7 @@ class FixedArray { // // Bounds-checked access. Returns a reference to the ith element of the fixed // array, or throws std::out_of_range - reference at(size_type i) { + reference at(size_type i) ABSL_ATTRIBUTE_LIFETIME_BOUND { if (ABSL_PREDICT_FALSE(i >= size())) { base_internal::ThrowStdOutOfRange("FixedArray::at failed bounds check"); } @@ -237,7 +247,7 @@ class FixedArray { // Overload of FixedArray::at() to return a const reference to the ith element // of the fixed array. - const_reference at(size_type i) const { + const_reference at(size_type i) const ABSL_ATTRIBUTE_LIFETIME_BOUND { if (ABSL_PREDICT_FALSE(i >= size())) { base_internal::ThrowStdOutOfRange("FixedArray::at failed bounds check"); } @@ -247,14 +257,14 @@ class FixedArray { // FixedArray::front() // // Returns a reference to the first element of the fixed array. - reference front() { + reference front() ABSL_ATTRIBUTE_LIFETIME_BOUND { ABSL_HARDENING_ASSERT(!empty()); return data()[0]; } // Overload of FixedArray::front() to return a reference to the first element // of a fixed array of const values. - const_reference front() const { + const_reference front() const ABSL_ATTRIBUTE_LIFETIME_BOUND { ABSL_HARDENING_ASSERT(!empty()); return data()[0]; } @@ -262,14 +272,14 @@ class FixedArray { // FixedArray::back() // // Returns a reference to the last element of the fixed array. - reference back() { + reference back() ABSL_ATTRIBUTE_LIFETIME_BOUND { ABSL_HARDENING_ASSERT(!empty()); return data()[size() - 1]; } // Overload of FixedArray::back() to return a reference to the last element // of a fixed array of const values. - const_reference back() const { + const_reference back() const ABSL_ATTRIBUTE_LIFETIME_BOUND { ABSL_HARDENING_ASSERT(!empty()); return data()[size() - 1]; } @@ -277,62 +287,74 @@ class FixedArray { // FixedArray::begin() // // Returns an iterator to the beginning of the fixed array. - iterator begin() { return data(); } + iterator begin() ABSL_ATTRIBUTE_LIFETIME_BOUND { return data(); } // Overload of FixedArray::begin() to return a const iterator to the // beginning of the fixed array. - const_iterator begin() const { return data(); } + const_iterator begin() const ABSL_ATTRIBUTE_LIFETIME_BOUND { return data(); } // FixedArray::cbegin() // // Returns a const iterator to the beginning of the fixed array. - const_iterator cbegin() const { return begin(); } + const_iterator cbegin() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return begin(); + } // FixedArray::end() // // Returns an iterator to the end of the fixed array. - iterator end() { return data() + size(); } + iterator end() ABSL_ATTRIBUTE_LIFETIME_BOUND { return data() + size(); } // Overload of FixedArray::end() to return a const iterator to the end of the // fixed array. - const_iterator end() const { return data() + size(); } + const_iterator end() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return data() + size(); + } // FixedArray::cend() // // Returns a const iterator to the end of the fixed array. - const_iterator cend() const { return end(); } + const_iterator cend() const ABSL_ATTRIBUTE_LIFETIME_BOUND { return end(); } // FixedArray::rbegin() // // Returns a reverse iterator from the end of the fixed array. - reverse_iterator rbegin() { return reverse_iterator(end()); } + reverse_iterator rbegin() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return reverse_iterator(end()); + } // Overload of FixedArray::rbegin() to return a const reverse iterator from // the end of the fixed array. - const_reverse_iterator rbegin() const { + const_reverse_iterator rbegin() const ABSL_ATTRIBUTE_LIFETIME_BOUND { return const_reverse_iterator(end()); } // FixedArray::crbegin() // // Returns a const reverse iterator from the end of the fixed array. - const_reverse_iterator crbegin() const { return rbegin(); } + const_reverse_iterator crbegin() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return rbegin(); + } // FixedArray::rend() // // Returns a reverse iterator from the beginning of the fixed array. - reverse_iterator rend() { return reverse_iterator(begin()); } + reverse_iterator rend() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return reverse_iterator(begin()); + } // Overload of FixedArray::rend() for returning a const reverse iterator // from the beginning of the fixed array. - const_reverse_iterator rend() const { + const_reverse_iterator rend() const ABSL_ATTRIBUTE_LIFETIME_BOUND { return const_reverse_iterator(begin()); } // FixedArray::crend() // // Returns a reverse iterator from the beginning of the fixed array. - const_reverse_iterator crend() const { return rend(); } + const_reverse_iterator crend() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return rend(); + } // FixedArray::fill() // @@ -342,7 +364,7 @@ class FixedArray { // Relational operators. Equality operators are elementwise using // `operator==`, while order operators order FixedArrays lexicographically. friend bool operator==(const FixedArray& lhs, const FixedArray& rhs) { - return absl::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); + return std::equal(lhs.begin(), lhs.end(), rhs.begin(), rhs.end()); } friend bool operator!=(const FixedArray& lhs, const FixedArray& rhs) { @@ -464,6 +486,9 @@ class FixedArray { StorageElement* begin() const { return data_; } StorageElement* end() const { return begin() + size(); } allocator_type& alloc() { return size_alloc_.template get<1>(); } + const allocator_type& alloc() const { + return size_alloc_.template get<1>(); + } private: static bool UsingInlinedStorage(size_type n) { diff --git a/absl/container/fixed_array_test.cc b/absl/container/fixed_array_test.cc index 49598e7a..2421b5fc 100644 --- a/absl/container/fixed_array_test.cc +++ b/absl/container/fixed_array_test.cc @@ -30,7 +30,7 @@ #include "absl/base/config.h" #include "absl/base/internal/exception_testing.h" #include "absl/base/options.h" -#include "absl/container/internal/counting_allocator.h" +#include "absl/container/internal/test_allocator.h" #include "absl/hash/hash_testing.h" #include "absl/memory/memory.h" @@ -768,6 +768,22 @@ TEST(AllocatorSupportTest, SizeValAllocConstructor) { } } +TEST(AllocatorSupportTest, PropagatesStatefulAllocator) { + constexpr size_t inlined_size = 4; + using Alloc = absl::container_internal::CountingAllocator<int>; + using AllocFxdArr = absl::FixedArray<int, inlined_size, Alloc>; + + auto len = inlined_size * 2; + auto val = 0; + int64_t allocated = 0; + AllocFxdArr arr(len, val, Alloc(&allocated)); + + EXPECT_EQ(allocated, len * sizeof(int)); + + AllocFxdArr copy = arr; + EXPECT_EQ(allocated, len * sizeof(int) * 2); +} + #ifdef ABSL_HAVE_ADDRESS_SANITIZER TEST(FixedArrayTest, AddressSanitizerAnnotations1) { absl::FixedArray<int, 32> a(10); diff --git a/absl/container/flat_hash_map.h b/absl/container/flat_hash_map.h index e6bdbd9e..acd013b0 100644 --- a/absl/container/flat_hash_map.h +++ b/absl/container/flat_hash_map.h @@ -64,7 +64,7 @@ struct FlatHashMapPolicy; // `insert()`, provided that the map is provided a compatible heterogeneous // hashing function and equality operator. // * Invalidates any references and pointers to elements within the table after -// `rehash()`. +// `rehash()` and when the table is moved. // * Contains a `capacity()` member function indicating the number of element // slots (open, deleted, and empty) within the hash map. // * Returns `void` from the `erase(iterator)` overload. @@ -235,7 +235,11 @@ class flat_hash_map : public absl::container_internal::raw_hash_map< // iterator erase(const_iterator first, const_iterator last): // // Erases the elements in the open interval [`first`, `last`), returning an - // iterator pointing to `last`. + // iterator pointing to `last`. The special case of calling + // `erase(begin(), end())` resets the reserved growth such that if + // `reserve(N)` has previously been called and there has been no intervening + // call to `clear()`, then after calling `erase(begin(), end())`, it is safe + // to assume that inserting N elements will not cause a rehash. // // size_type erase(const key_type& key): // @@ -575,9 +579,9 @@ struct FlatHashMapPolicy { } template <class Allocator> - static void transfer(Allocator* alloc, slot_type* new_slot, + static auto transfer(Allocator* alloc, slot_type* new_slot, slot_type* old_slot) { - slot_policy::transfer(alloc, new_slot, old_slot); + return slot_policy::transfer(alloc, new_slot, old_slot); } template <class F, class... Args> diff --git a/absl/container/flat_hash_map_test.cc b/absl/container/flat_hash_map_test.cc index 03171f6d..d90fe9d5 100644 --- a/absl/container/flat_hash_map_test.cc +++ b/absl/container/flat_hash_map_test.cc @@ -14,14 +14,20 @@ #include "absl/container/flat_hash_map.h" +#include <cstddef> #include <memory> +#include <type_traits> +#include <utility> +#include <vector> -#include "absl/base/internal/raw_logging.h" +#include "gtest/gtest.h" #include "absl/container/internal/hash_generator_testing.h" #include "absl/container/internal/unordered_map_constructor_test.h" #include "absl/container/internal/unordered_map_lookup_test.h" #include "absl/container/internal/unordered_map_members_test.h" #include "absl/container/internal/unordered_map_modifiers_test.h" +#include "absl/log/check.h" +#include "absl/meta/type_traits.h" #include "absl/types/any.h" namespace absl { @@ -40,10 +46,10 @@ struct BeforeMain { BeforeMain() { absl::flat_hash_map<int, int> x; x.insert({1, 1}); - ABSL_RAW_CHECK(x.find(0) == x.end(), "x should not contain 0"); + CHECK(x.find(0) == x.end()) << "x should not contain 0"; auto it = x.find(1); - ABSL_RAW_CHECK(it != x.end(), "x should contain 1"); - ABSL_RAW_CHECK(it->second, "1 should map to 1"); + CHECK(it != x.end()) << "x should contain 1"; + CHECK(it->second) << "1 should map to 1"; } }; const BeforeMain before_main; @@ -102,6 +108,34 @@ TEST(FlatHashMap, StandardLayout) { } } +TEST(FlatHashMap, Relocatability) { + static_assert(absl::is_trivially_relocatable<int>::value, ""); + static_assert( + absl::is_trivially_relocatable<std::pair<const int, int>>::value, ""); + static_assert( + std::is_same<decltype(absl::container_internal::FlatHashMapPolicy< + int, int>::transfer<std::allocator<char>>(nullptr, + nullptr, + nullptr)), + std::true_type>::value, + ""); + + struct NonRelocatable { + NonRelocatable() = default; + NonRelocatable(NonRelocatable&&) {} + NonRelocatable& operator=(NonRelocatable&&) { return *this; } + void* self = nullptr; + }; + + EXPECT_FALSE(absl::is_trivially_relocatable<NonRelocatable>::value); + EXPECT_TRUE( + (std::is_same<decltype(absl::container_internal::FlatHashMapPolicy< + int, NonRelocatable>:: + transfer<std::allocator<char>>(nullptr, nullptr, + nullptr)), + std::false_type>::value)); +} + // gcc becomes unhappy if this is inside the method, so pull it out here. struct balast {}; @@ -150,9 +184,7 @@ struct Hash { struct Eq { using is_transparent = void; - bool operator()(size_t lhs, size_t rhs) const { - return lhs == rhs; - } + bool operator()(size_t lhs, size_t rhs) const { return lhs == rhs; } bool operator()(size_t lhs, const LazyInt& rhs) const { return lhs == rhs.value; } diff --git a/absl/container/flat_hash_set.h b/absl/container/flat_hash_set.h index f5376f99..a94a82a0 100644 --- a/absl/container/flat_hash_set.h +++ b/absl/container/flat_hash_set.h @@ -60,7 +60,7 @@ struct FlatHashSetPolicy; // that the set is provided a compatible heterogeneous hashing function and // equality operator. // * Invalidates any references and pointers to elements within the table after -// `rehash()`. +// `rehash()` and when the table is moved. // * Contains a `capacity()` member function indicating the number of element // slots (open, deleted, and empty) within the hash set. // * Returns `void` from the `erase(iterator)` overload. @@ -227,7 +227,11 @@ class flat_hash_set // iterator erase(const_iterator first, const_iterator last): // // Erases the elements in the open interval [`first`, `last`), returning an - // iterator pointing to `last`. + // iterator pointing to `last`. The special case of calling + // `erase(begin(), end())` resets the reserved growth such that if + // `reserve(N)` has previously been called and there has been no intervening + // call to `clear()`, then after calling `erase(begin(), end())`, it is safe + // to assume that inserting N elements will not cause a rehash. // // size_type erase(const key_type& key): // @@ -343,7 +347,7 @@ class flat_hash_set // for the past-the-end iterator, which is invalidated. // // `swap()` requires that the flat hash set's hashing and key equivalence - // functions be Swappable, and are exchaged using unqualified calls to + // functions be Swappable, and are exchanged using unqualified calls to // non-member `swap()`. If the set's allocator has // `std::allocator_traits<allocator_type>::propagate_on_container_swap::value` // set to `true`, the allocators are also exchanged using an unqualified call diff --git a/absl/container/flat_hash_set_test.cc b/absl/container/flat_hash_set_test.cc index b6a72a20..a60b4bf5 100644 --- a/absl/container/flat_hash_set_test.cc +++ b/absl/container/flat_hash_set_test.cc @@ -14,14 +14,21 @@ #include "absl/container/flat_hash_set.h" +#include <cstdint> +#include <memory> +#include <utility> #include <vector> -#include "absl/base/internal/raw_logging.h" +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/container/internal/container_memory.h" #include "absl/container/internal/hash_generator_testing.h" #include "absl/container/internal/unordered_set_constructor_test.h" #include "absl/container/internal/unordered_set_lookup_test.h" #include "absl/container/internal/unordered_set_members_test.h" #include "absl/container/internal/unordered_set_modifiers_test.h" +#include "absl/log/check.h" #include "absl/memory/memory.h" #include "absl/strings/string_view.h" @@ -42,8 +49,8 @@ struct BeforeMain { BeforeMain() { absl::flat_hash_set<int> x; x.insert(1); - ABSL_RAW_CHECK(!x.contains(0), "x should not contain 0"); - ABSL_RAW_CHECK(x.contains(1), "x should contain 1"); + CHECK(!x.contains(0)) << "x should not contain 0"; + CHECK(x.contains(1)) << "x should contain 1"; } }; const BeforeMain before_main; @@ -172,6 +179,64 @@ TEST(FlatHashSet, EraseIf) { } } +class PoisonInline { + int64_t data_; + + public: + explicit PoisonInline(int64_t d) : data_(d) { + SanitizerPoisonObject(&data_); + } + PoisonInline(const PoisonInline& that) : PoisonInline(*that) {} + ~PoisonInline() { SanitizerUnpoisonObject(&data_); } + + int64_t operator*() const { + SanitizerUnpoisonObject(&data_); + const int64_t ret = data_; + SanitizerPoisonObject(&data_); + return ret; + } + template <typename H> + friend H AbslHashValue(H h, const PoisonInline& pi) { + return H::combine(std::move(h), *pi); + } + bool operator==(const PoisonInline& rhs) const { return **this == *rhs; } +}; + +// Tests that we don't touch the poison_ member of PoisonInline. +TEST(FlatHashSet, PoisonInline) { + PoisonInline a(0), b(1); + { // basic usage + flat_hash_set<PoisonInline> set; + set.insert(a); + EXPECT_THAT(set, UnorderedElementsAre(a)); + set.insert(b); + EXPECT_THAT(set, UnorderedElementsAre(a, b)); + set.erase(a); + EXPECT_THAT(set, UnorderedElementsAre(b)); + set.rehash(0); // shrink to inline + EXPECT_THAT(set, UnorderedElementsAre(b)); + } + { // test move constructor from inline to inline + flat_hash_set<PoisonInline> set; + set.insert(a); + flat_hash_set<PoisonInline> set2(std::move(set)); + EXPECT_THAT(set2, UnorderedElementsAre(a)); + } + { // test move assignment from inline to inline + flat_hash_set<PoisonInline> set, set2; + set.insert(a); + set2 = std::move(set); + EXPECT_THAT(set2, UnorderedElementsAre(a)); + } + { // test alloc move constructor from inline to inline + flat_hash_set<PoisonInline> set; + set.insert(a); + flat_hash_set<PoisonInline> set2(std::move(set), + std::allocator<PoisonInline>()); + EXPECT_THAT(set2, UnorderedElementsAre(a)); + } +} + } // namespace } // namespace container_internal ABSL_NAMESPACE_END diff --git a/absl/container/inlined_vector.h b/absl/container/inlined_vector.h index 7058f375..04e2c385 100644 --- a/absl/container/inlined_vector.h +++ b/absl/container/inlined_vector.h @@ -77,8 +77,6 @@ class InlinedVector { template <typename TheA> 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> @@ -182,14 +180,23 @@ class InlinedVector { // provided `allocator`. InlinedVector(const InlinedVector& other, const allocator_type& allocator) : storage_(allocator) { + // Fast path: if the other vector is empty, there's nothing for us to do. if (other.empty()) { - // Empty; nothing to do. - } else if (IsMemcpyOk<A>::value && !other.storage_.GetIsAllocated()) { - // Memcpy-able and do not need allocation. + return; + } + + // Fast path: if the value type is trivially copy constructible, we know the + // allocator doesn't do anything fancy, and there is nothing on the heap + // then we know it is legal for us to simply memcpy the other vector's + // inlined bytes to form our copy of its elements. + if (absl::is_trivially_copy_constructible<value_type>::value && + std::is_same<A, std::allocator<value_type>>::value && + !other.storage_.GetIsAllocated()) { storage_.MemcpyFrom(other.storage_); - } else { - storage_.InitFrom(other.storage_); + return; } + + storage_.InitFrom(other.storage_); } // Creates an inlined vector by moving in the contents of `other` without @@ -210,26 +217,38 @@ class InlinedVector { absl::allocator_is_nothrow<allocator_type>::value || std::is_nothrow_move_constructible<value_type>::value) : storage_(other.storage_.GetAllocator()) { - if (IsMemcpyOk<A>::value) { + // Fast path: if the value type can be trivially relocated (i.e. moved from + // and destroyed), and we know the allocator doesn't do anything fancy, then + // it's safe for us to simply adopt the contents of the storage for `other` + // and remove its own reference to them. It's as if we had individually + // move-constructed each value and then destroyed the original. + if (absl::is_trivially_relocatable<value_type>::value && + std::is_same<A, std::allocator<value_type>>::value) { storage_.MemcpyFrom(other.storage_); - other.storage_.SetInlinedSize(0); - } else if (other.storage_.GetIsAllocated()) { + return; + } + + // Fast path: if the other vector is on the heap, we can simply take over + // its allocation. + if (other.storage_.GetIsAllocated()) { storage_.SetAllocation({other.storage_.GetAllocatedData(), other.storage_.GetAllocatedCapacity()}); storage_.SetAllocatedSize(other.storage_.GetSize()); other.storage_.SetInlinedSize(0); - } else { - IteratorValueAdapter<A, MoveIterator<A>> other_values( - MoveIterator<A>(other.storage_.GetInlinedData())); + return; + } - inlined_vector_internal::ConstructElements<A>( - storage_.GetAllocator(), storage_.GetInlinedData(), other_values, - other.storage_.GetSize()); + // Otherwise we must move each element individually. + IteratorValueAdapter<A, MoveIterator<A>> other_values( + MoveIterator<A>(other.storage_.GetInlinedData())); - storage_.SetInlinedSize(other.storage_.GetSize()); - } + inlined_vector_internal::ConstructElements<A>( + storage_.GetAllocator(), storage_.GetInlinedData(), other_values, + other.storage_.GetSize()); + + storage_.SetInlinedSize(other.storage_.GetSize()); } // Creates an inlined vector by moving in the contents of `other` with a copy @@ -244,22 +263,34 @@ class InlinedVector { const allocator_type& allocator) noexcept(absl::allocator_is_nothrow<allocator_type>::value) : storage_(allocator) { - if (IsMemcpyOk<A>::value) { + // Fast path: if the value type can be trivially relocated (i.e. moved from + // and destroyed), and we know the allocator doesn't do anything fancy, then + // it's safe for us to simply adopt the contents of the storage for `other` + // and remove its own reference to them. It's as if we had individually + // move-constructed each value and then destroyed the original. + if (absl::is_trivially_relocatable<value_type>::value && + std::is_same<A, std::allocator<value_type>>::value) { storage_.MemcpyFrom(other.storage_); - other.storage_.SetInlinedSize(0); - } else if ((storage_.GetAllocator() == other.storage_.GetAllocator()) && - other.storage_.GetIsAllocated()) { + return; + } + + // Fast path: if the other vector is on the heap and shared the same + // allocator, we can simply take over its allocation. + if ((storage_.GetAllocator() == other.storage_.GetAllocator()) && + other.storage_.GetIsAllocated()) { storage_.SetAllocation({other.storage_.GetAllocatedData(), other.storage_.GetAllocatedCapacity()}); storage_.SetAllocatedSize(other.storage_.GetSize()); other.storage_.SetInlinedSize(0); - } else { - storage_.Initialize(IteratorValueAdapter<A, MoveIterator<A>>( - MoveIterator<A>(other.data())), - other.size()); + return; } + + // Otherwise we must move each element individually. + storage_.Initialize( + IteratorValueAdapter<A, MoveIterator<A>>(MoveIterator<A>(other.data())), + other.size()); } ~InlinedVector() {} @@ -310,7 +341,7 @@ class InlinedVector { // can be used to access and modify the contained elements. // // NOTE: only elements within [`data()`, `data() + size()`) are valid. - pointer data() noexcept { + pointer data() noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND { return storage_.GetIsAllocated() ? storage_.GetAllocatedData() : storage_.GetInlinedData(); } @@ -320,7 +351,7 @@ class InlinedVector { // modify the contained elements. // // NOTE: only elements within [`data()`, `data() + size()`) are valid. - const_pointer data() const noexcept { + const_pointer data() const noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND { return storage_.GetIsAllocated() ? storage_.GetAllocatedData() : storage_.GetInlinedData(); } @@ -328,14 +359,14 @@ class InlinedVector { // `InlinedVector::operator[](...)` // // Returns a `reference` to the `i`th element of the inlined vector. - reference operator[](size_type i) { + reference operator[](size_type i) ABSL_ATTRIBUTE_LIFETIME_BOUND { ABSL_HARDENING_ASSERT(i < size()); return data()[i]; } // Overload of `InlinedVector::operator[](...)` that returns a // `const_reference` to the `i`th element of the inlined vector. - const_reference operator[](size_type i) const { + const_reference operator[](size_type i) const ABSL_ATTRIBUTE_LIFETIME_BOUND { ABSL_HARDENING_ASSERT(i < size()); return data()[i]; } @@ -346,7 +377,7 @@ class InlinedVector { // // NOTE: if `i` is not within the required range of `InlinedVector::at(...)`, // in both debug and non-debug builds, `std::out_of_range` will be thrown. - reference at(size_type i) { + reference at(size_type i) ABSL_ATTRIBUTE_LIFETIME_BOUND { if (ABSL_PREDICT_FALSE(i >= size())) { base_internal::ThrowStdOutOfRange( "`InlinedVector::at(size_type)` failed bounds check"); @@ -359,7 +390,7 @@ class InlinedVector { // // NOTE: if `i` is not within the required range of `InlinedVector::at(...)`, // in both debug and non-debug builds, `std::out_of_range` will be thrown. - const_reference at(size_type i) const { + const_reference at(size_type i) const ABSL_ATTRIBUTE_LIFETIME_BOUND { if (ABSL_PREDICT_FALSE(i >= size())) { base_internal::ThrowStdOutOfRange( "`InlinedVector::at(size_type) const` failed bounds check"); @@ -370,14 +401,14 @@ class InlinedVector { // `InlinedVector::front()` // // Returns a `reference` to the first element of the inlined vector. - reference front() { + reference front() ABSL_ATTRIBUTE_LIFETIME_BOUND { ABSL_HARDENING_ASSERT(!empty()); return data()[0]; } // Overload of `InlinedVector::front()` that returns a `const_reference` to // the first element of the inlined vector. - const_reference front() const { + const_reference front() const ABSL_ATTRIBUTE_LIFETIME_BOUND { ABSL_HARDENING_ASSERT(!empty()); return data()[0]; } @@ -385,14 +416,14 @@ class InlinedVector { // `InlinedVector::back()` // // Returns a `reference` to the last element of the inlined vector. - reference back() { + reference back() ABSL_ATTRIBUTE_LIFETIME_BOUND { ABSL_HARDENING_ASSERT(!empty()); return data()[size() - 1]; } // Overload of `InlinedVector::back()` that returns a `const_reference` to the // last element of the inlined vector. - const_reference back() const { + const_reference back() const ABSL_ATTRIBUTE_LIFETIME_BOUND { ABSL_HARDENING_ASSERT(!empty()); return data()[size() - 1]; } @@ -400,63 +431,82 @@ class InlinedVector { // `InlinedVector::begin()` // // Returns an `iterator` to the beginning of the inlined vector. - iterator begin() noexcept { return data(); } + iterator begin() noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND { return data(); } // Overload of `InlinedVector::begin()` that returns a `const_iterator` to // the beginning of the inlined vector. - const_iterator begin() const noexcept { return data(); } + const_iterator begin() const noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND { + return data(); + } // `InlinedVector::end()` // // Returns an `iterator` to the end of the inlined vector. - iterator end() noexcept { return data() + size(); } + iterator end() noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND { + return data() + size(); + } // Overload of `InlinedVector::end()` that returns a `const_iterator` to the // end of the inlined vector. - const_iterator end() const noexcept { return data() + size(); } + const_iterator end() const noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND { + return data() + size(); + } // `InlinedVector::cbegin()` // // Returns a `const_iterator` to the beginning of the inlined vector. - const_iterator cbegin() const noexcept { return begin(); } + const_iterator cbegin() const noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND { + return begin(); + } // `InlinedVector::cend()` // // Returns a `const_iterator` to the end of the inlined vector. - const_iterator cend() const noexcept { return end(); } + const_iterator cend() const noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND { + return end(); + } // `InlinedVector::rbegin()` // // Returns a `reverse_iterator` from the end of the inlined vector. - reverse_iterator rbegin() noexcept { return reverse_iterator(end()); } + reverse_iterator rbegin() noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND { + return reverse_iterator(end()); + } // Overload of `InlinedVector::rbegin()` that returns a // `const_reverse_iterator` from the end of the inlined vector. - const_reverse_iterator rbegin() const noexcept { + const_reverse_iterator rbegin() const noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND { return const_reverse_iterator(end()); } // `InlinedVector::rend()` // // Returns a `reverse_iterator` from the beginning of the inlined vector. - reverse_iterator rend() noexcept { return reverse_iterator(begin()); } + reverse_iterator rend() noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND { + return reverse_iterator(begin()); + } // Overload of `InlinedVector::rend()` that returns a `const_reverse_iterator` // from the beginning of the inlined vector. - const_reverse_iterator rend() const noexcept { + const_reverse_iterator rend() const noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND { return const_reverse_iterator(begin()); } // `InlinedVector::crbegin()` // // Returns a `const_reverse_iterator` from the end of the inlined vector. - const_reverse_iterator crbegin() const noexcept { return rbegin(); } + const_reverse_iterator crbegin() const noexcept + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return rbegin(); + } // `InlinedVector::crend()` // // Returns a `const_reverse_iterator` from the beginning of the inlined // vector. - const_reverse_iterator crend() const noexcept { return rend(); } + const_reverse_iterator crend() const noexcept ABSL_ATTRIBUTE_LIFETIME_BOUND { + return rend(); + } // `InlinedVector::get_allocator()` // @@ -566,20 +616,23 @@ class InlinedVector { // // Inserts a copy of `v` at `pos`, returning an `iterator` to the newly // inserted element. - iterator insert(const_iterator pos, const_reference v) { + iterator insert(const_iterator pos, + const_reference v) ABSL_ATTRIBUTE_LIFETIME_BOUND { return emplace(pos, v); } // Overload of `InlinedVector::insert(...)` that inserts `v` at `pos` using // move semantics, returning an `iterator` to the newly inserted element. - iterator insert(const_iterator pos, value_type&& v) { + iterator insert(const_iterator pos, + value_type&& v) ABSL_ATTRIBUTE_LIFETIME_BOUND { return emplace(pos, std::move(v)); } // Overload of `InlinedVector::insert(...)` that inserts `n` contiguous copies // of `v` starting at `pos`, returning an `iterator` pointing to the first of // the newly inserted elements. - iterator insert(const_iterator pos, size_type n, const_reference v) { + iterator insert(const_iterator pos, size_type n, + const_reference v) ABSL_ATTRIBUTE_LIFETIME_BOUND { ABSL_HARDENING_ASSERT(pos >= begin()); ABSL_HARDENING_ASSERT(pos <= end()); @@ -607,7 +660,8 @@ class InlinedVector { // Overload of `InlinedVector::insert(...)` that inserts copies of the // elements of `list` starting at `pos`, returning an `iterator` pointing to // the first of the newly inserted elements. - iterator insert(const_iterator pos, std::initializer_list<value_type> list) { + iterator insert(const_iterator pos, std::initializer_list<value_type> list) + ABSL_ATTRIBUTE_LIFETIME_BOUND { return insert(pos, list.begin(), list.end()); } @@ -619,7 +673,7 @@ class InlinedVector { template <typename ForwardIterator, EnableIfAtLeastForwardIterator<ForwardIterator> = 0> iterator insert(const_iterator pos, ForwardIterator first, - ForwardIterator last) { + ForwardIterator last) ABSL_ATTRIBUTE_LIFETIME_BOUND { ABSL_HARDENING_ASSERT(pos >= begin()); ABSL_HARDENING_ASSERT(pos <= end()); @@ -639,7 +693,8 @@ class InlinedVector { // NOTE: this overload is for iterators that are "input" category. template <typename InputIterator, DisableIfAtLeastForwardIterator<InputIterator> = 0> - iterator insert(const_iterator pos, InputIterator first, InputIterator last) { + iterator insert(const_iterator pos, InputIterator first, + InputIterator last) ABSL_ATTRIBUTE_LIFETIME_BOUND { ABSL_HARDENING_ASSERT(pos >= begin()); ABSL_HARDENING_ASSERT(pos <= end()); @@ -656,7 +711,8 @@ class InlinedVector { // Constructs and inserts an element using `args...` in the inlined vector at // `pos`, returning an `iterator` pointing to the newly emplaced element. template <typename... Args> - iterator emplace(const_iterator pos, Args&&... args) { + iterator emplace(const_iterator pos, + Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND { ABSL_HARDENING_ASSERT(pos >= begin()); ABSL_HARDENING_ASSERT(pos <= end()); @@ -684,7 +740,7 @@ class InlinedVector { // Constructs and inserts an element using `args...` in the inlined vector at // `end()`, returning a `reference` to the newly emplaced element. template <typename... Args> - reference emplace_back(Args&&... args) { + reference emplace_back(Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND { return storage_.EmplaceBack(std::forward<Args>(args)...); } @@ -714,8 +770,8 @@ class InlinedVector { // Erases the element at `pos`, returning an `iterator` pointing to where the // erased element was located. // - // NOTE: may return `end()`, which is not dereferencable. - iterator erase(const_iterator pos) { + // NOTE: may return `end()`, which is not dereferenceable. + iterator erase(const_iterator pos) ABSL_ATTRIBUTE_LIFETIME_BOUND { ABSL_HARDENING_ASSERT(pos >= begin()); ABSL_HARDENING_ASSERT(pos < end()); @@ -726,8 +782,9 @@ class InlinedVector { // range [`from`, `to`), returning an `iterator` pointing to where the first // erased element was located. // - // NOTE: may return `end()`, which is not dereferencable. - iterator erase(const_iterator from, const_iterator to) { + // NOTE: may return `end()`, which is not dereferenceable. + iterator erase(const_iterator from, + const_iterator to) ABSL_ATTRIBUTE_LIFETIME_BOUND { ABSL_HARDENING_ASSERT(from >= begin()); ABSL_HARDENING_ASSERT(from <= to); ABSL_HARDENING_ASSERT(to <= end()); @@ -784,39 +841,70 @@ class InlinedVector { friend H AbslHashValue(H h, const absl::InlinedVector<TheT, TheN, TheA>& a); void MoveAssignment(MemcpyPolicy, InlinedVector&& other) { + // Assumption check: we shouldn't be told to use memcpy to implement move + // assignment unless we have trivially destructible elements and an + // allocator that does nothing fancy. + static_assert(absl::is_trivially_destructible<value_type>::value, ""); + static_assert(std::is_same<A, std::allocator<value_type>>::value, ""); + + // Throw away our existing heap allocation, if any. There is no need to + // destroy the existing elements one by one because we know they are + // trivially destructible. + storage_.DeallocateIfAllocated(); + + // Adopt the other vector's inline elements or heap allocation. + storage_.MemcpyFrom(other.storage_); + other.storage_.SetInlinedSize(0); + } + + // Destroy our existing elements, if any, and adopt the heap-allocated + // elements of the other vector. + // + // REQUIRES: other.storage_.GetIsAllocated() + void DestroyExistingAndAdopt(InlinedVector&& other) { + ABSL_HARDENING_ASSERT(other.storage_.GetIsAllocated()); + inlined_vector_internal::DestroyAdapter<A>::DestroyElements( storage_.GetAllocator(), data(), size()); storage_.DeallocateIfAllocated(); - storage_.MemcpyFrom(other.storage_); + storage_.MemcpyFrom(other.storage_); other.storage_.SetInlinedSize(0); } void MoveAssignment(ElementwiseAssignPolicy, InlinedVector&& other) { + // Fast path: if the other vector is on the heap then we don't worry about + // actually move-assigning each element. Instead we only throw away our own + // existing elements and adopt the heap allocation of the other vector. if (other.storage_.GetIsAllocated()) { - MoveAssignment(MemcpyPolicy{}, std::move(other)); - } else { - storage_.Assign(IteratorValueAdapter<A, MoveIterator<A>>( - MoveIterator<A>(other.storage_.GetInlinedData())), - other.size()); + DestroyExistingAndAdopt(std::move(other)); + return; } + + storage_.Assign(IteratorValueAdapter<A, MoveIterator<A>>( + MoveIterator<A>(other.storage_.GetInlinedData())), + other.size()); } void MoveAssignment(ElementwiseConstructPolicy, InlinedVector&& other) { + // Fast path: if the other vector is on the heap then we don't worry about + // actually move-assigning each element. Instead we only throw away our own + // existing elements and adopt the heap allocation of the other vector. 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()); + DestroyExistingAndAdopt(std::move(other)); + return; } + + 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_; @@ -843,7 +931,7 @@ bool operator==(const absl::InlinedVector<T, N, A>& a, const absl::InlinedVector<T, N, A>& b) { auto a_data = a.data(); auto b_data = b.data(); - return absl::equal(a_data, a_data + a.size(), b_data, b_data + b.size()); + return std::equal(a_data, a_data + a.size(), b_data, b_data + b.size()); } // `operator!=(...)` diff --git a/absl/container/inlined_vector_benchmark.cc b/absl/container/inlined_vector_benchmark.cc index 56a6bfd2..5a04277c 100644 --- a/absl/container/inlined_vector_benchmark.cc +++ b/absl/container/inlined_vector_benchmark.cc @@ -66,7 +66,7 @@ void BM_StdVectorFill(benchmark::State& state) { BENCHMARK(BM_StdVectorFill)->Range(1, 256); // The purpose of the next two benchmarks is to verify that -// absl::InlinedVector is efficient when moving is more efficent than +// absl::InlinedVector is efficient when moving is more efficient than // copying. To do so, we use strings that are larger than the short // string optimization. bool StringRepresentedInline(std::string s) { diff --git a/absl/container/inlined_vector_test.cc b/absl/container/inlined_vector_test.cc index 898b40db..241389ae 100644 --- a/absl/container/inlined_vector_test.cc +++ b/absl/container/inlined_vector_test.cc @@ -15,6 +15,7 @@ #include "absl/container/inlined_vector.h" #include <algorithm> +#include <cstddef> #include <forward_list> #include <iterator> #include <list> @@ -30,12 +31,12 @@ #include "gtest/gtest.h" #include "absl/base/attributes.h" #include "absl/base/internal/exception_testing.h" -#include "absl/base/internal/raw_logging.h" #include "absl/base/macros.h" #include "absl/base/options.h" -#include "absl/container/internal/counting_allocator.h" +#include "absl/container/internal/test_allocator.h" #include "absl/container/internal/test_instance_tracker.h" #include "absl/hash/hash_testing.h" +#include "absl/log/check.h" #include "absl/memory/memory.h" #include "absl/strings/str_cat.h" @@ -51,15 +52,13 @@ using testing::ElementsAre; using testing::ElementsAreArray; using testing::Eq; using testing::Gt; +using testing::Pointee; using testing::Pointwise; using testing::PrintToString; +using testing::SizeIs; using IntVec = absl::InlinedVector<int, 8>; -MATCHER_P(SizeIs, n, "") { - return testing::ExplainMatchResult(n, arg.size(), result_listener); -} - MATCHER_P(CapacityIs, n, "") { return testing::ExplainMatchResult(n, arg.capacity(), result_listener); } @@ -104,13 +103,13 @@ class RefCounted { } void Ref() const { - ABSL_RAW_CHECK(count_ != nullptr, ""); + CHECK_NE(count_, nullptr); ++(*count_); } void Unref() const { --(*count_); - ABSL_RAW_CHECK(*count_ >= 0, ""); + CHECK_GE(*count_, 0); } int value_; @@ -262,6 +261,49 @@ TEST(IntVec, Hardened) { #endif } +// Move construction of a container of unique pointers should work fine, with no +// leaks, despite the fact that unique pointers are trivially relocatable but +// not trivially destructible. +TEST(UniquePtr, MoveConstruct) { + for (size_t size = 0; size < 16; ++size) { + SCOPED_TRACE(size); + + absl::InlinedVector<std::unique_ptr<size_t>, 2> a; + for (size_t i = 0; i < size; ++i) { + a.push_back(std::make_unique<size_t>(i)); + } + + absl::InlinedVector<std::unique_ptr<size_t>, 2> b(std::move(a)); + + ASSERT_THAT(b, SizeIs(size)); + for (size_t i = 0; i < size; ++i) { + ASSERT_THAT(b[i], Pointee(i)); + } + } +} + +// Move assignment of a container of unique pointers should work fine, with no +// leaks, despite the fact that unique pointers are trivially relocatable but +// not trivially destructible. +TEST(UniquePtr, MoveAssign) { + for (size_t size = 0; size < 16; ++size) { + SCOPED_TRACE(size); + + absl::InlinedVector<std::unique_ptr<size_t>, 2> a; + for (size_t i = 0; i < size; ++i) { + a.push_back(std::make_unique<size_t>(i)); + } + + absl::InlinedVector<std::unique_ptr<size_t>, 2> b; + b = std::move(a); + + ASSERT_THAT(b, SizeIs(size)); + for (size_t i = 0; i < size; ++i) { + ASSERT_THAT(b[i], Pointee(i)); + } + } +} + // At the end of this test loop, the elements between [erase_begin, erase_end) // should have reference counts == 0, and all others elements should have // reference counts == 1. @@ -1579,6 +1621,30 @@ TEST(DynamicVec, DynamicVecCompiles) { (void)v; } +TEST(DynamicVec, CreateNonEmptyDynamicVec) { + DynamicVec v(1); + EXPECT_EQ(v.size(), 1u); +} + +TEST(DynamicVec, EmplaceBack) { + DynamicVec v; + v.emplace_back(Dynamic{}); + EXPECT_EQ(v.size(), 1u); +} + +TEST(DynamicVec, EmplaceBackAfterHeapAllocation) { + DynamicVec v; + v.reserve(10); + v.emplace_back(Dynamic{}); + EXPECT_EQ(v.size(), 1u); +} + +TEST(DynamicVec, EmptyIteratorComparison) { + DynamicVec v; + EXPECT_EQ(v.begin(), v.end()); + EXPECT_EQ(v.cbegin(), v.cend()); +} + TEST(AllocatorSupportTest, Constructors) { using MyAlloc = CountingAllocator<int>; using AllocVec = absl::InlinedVector<int, 4, MyAlloc>; diff --git a/absl/container/internal/btree.h b/absl/container/internal/btree.h index d734676a..91df57a3 100644 --- a/absl/container/internal/btree.h +++ b/absl/container/internal/btree.h @@ -79,6 +79,7 @@ namespace container_internal { #ifdef ABSL_BTREE_ENABLE_GENERATIONS #error ABSL_BTREE_ENABLE_GENERATIONS cannot be directly set #elif defined(ABSL_HAVE_ADDRESS_SANITIZER) || \ + defined(ABSL_HAVE_HWADDRESS_SANITIZER) || \ defined(ABSL_HAVE_MEMORY_SANITIZER) // When compiled in sanitizer mode, we add generation integers to the nodes and // iterators. When iterators are used, we validate that the container has not @@ -86,6 +87,12 @@ namespace container_internal { #define ABSL_BTREE_ENABLE_GENERATIONS #endif +#ifdef ABSL_BTREE_ENABLE_GENERATIONS +constexpr bool BtreeGenerationsEnabled() { return true; } +#else +constexpr bool BtreeGenerationsEnabled() { return false; } +#endif + template <typename Compare, typename T, typename U> using compare_result_t = absl::result_of_t<const Compare(const T &, const U &)>; @@ -378,12 +385,6 @@ struct common_params : common_policy_traits<SlotPolicy> { std::is_same<key_compare, StringBtreeDefaultGreater>::value; static constexpr bool kIsKeyCompareTransparent = IsTransparent<original_key_compare>::value || kIsKeyCompareStringAdapted; - static constexpr bool kEnableGenerations = -#ifdef ABSL_BTREE_ENABLE_GENERATIONS - true; -#else - false; -#endif // A type which indicates if we have a key-compare-to functor or a plain old // key-compare functor. @@ -572,13 +573,6 @@ class btree_node { btree_node(btree_node const &) = delete; btree_node &operator=(btree_node const &) = delete; - // Public for EmptyNodeType. - constexpr static size_type Alignment() { - static_assert(LeafLayout(1).Alignment() == InternalLayout().Alignment(), - "Alignment of all nodes must be equal."); - return InternalLayout().Alignment(); - } - protected: btree_node() = default; @@ -589,7 +583,7 @@ class btree_node { constexpr static size_type SizeWithNSlots(size_type n) { return layout_type( /*parent*/ 1, - /*generation*/ params_type::kEnableGenerations ? 1 : 0, + /*generation*/ BtreeGenerationsEnabled() ? 1 : 0, /*position, start, finish, max_count*/ 4, /*slots*/ n, /*children*/ 0) @@ -629,23 +623,22 @@ class btree_node { // has this value. constexpr static field_type kInternalNodeMaxCount = 0; - // Leaves can have less than kNodeSlots values. - constexpr static layout_type LeafLayout( - const size_type slot_count = kNodeSlots) { + constexpr static layout_type Layout(const size_type slot_count, + const size_type child_count) { return layout_type( /*parent*/ 1, - /*generation*/ params_type::kEnableGenerations ? 1 : 0, + /*generation*/ BtreeGenerationsEnabled() ? 1 : 0, /*position, start, finish, max_count*/ 4, /*slots*/ slot_count, - /*children*/ 0); + /*children*/ child_count); + } + // Leaves can have less than kNodeSlots values. + constexpr static layout_type LeafLayout( + const size_type slot_count = kNodeSlots) { + return Layout(slot_count, 0); } constexpr static layout_type InternalLayout() { - return layout_type( - /*parent*/ 1, - /*generation*/ params_type::kEnableGenerations ? 1 : 0, - /*position, start, finish, max_count*/ 4, - /*slots*/ kNodeSlots, - /*children*/ kNodeSlots + 1); + return Layout(kNodeSlots, kNodeSlots + 1); } constexpr static size_type LeafSize(const size_type slot_count = kNodeSlots) { return LeafLayout(slot_count).AllocSize(); @@ -654,6 +647,12 @@ class btree_node { return InternalLayout().AllocSize(); } + constexpr static size_type Alignment() { + static_assert(LeafLayout(1).Alignment() == InternalLayout().Alignment(), + "Alignment of all nodes must be equal."); + return InternalLayout().Alignment(); + } + // N is the index of the type in the Layout definition. // ElementType<N> is the Nth type in the Layout definition. template <size_type N> @@ -729,7 +728,7 @@ class btree_node { // Gets the root node's generation integer, which is the one used by the tree. uint32_t *get_root_generation() const { - assert(params_type::kEnableGenerations); + assert(BtreeGenerationsEnabled()); const btree_node *curr = this; for (; !curr->is_root(); curr = curr->parent()) continue; return const_cast<uint32_t *>(&curr->GetField<1>()[0]); @@ -737,16 +736,16 @@ class btree_node { // Returns the generation for iterator validation. uint32_t generation() const { - return params_type::kEnableGenerations ? *get_root_generation() : 0; + return BtreeGenerationsEnabled() ? *get_root_generation() : 0; } // Updates generation. Should only be called on a root node or during node // initialization. void set_generation(uint32_t generation) { - if (params_type::kEnableGenerations) GetField<1>()[0] = generation; + if (BtreeGenerationsEnabled()) GetField<1>()[0] = generation; } // Updates the generation. We do this whenever the node is mutated. void next_generation() { - if (params_type::kEnableGenerations) ++*get_root_generation(); + if (BtreeGenerationsEnabled()) ++*get_root_generation(); } // Getters for the key/value at position i in the node. @@ -763,9 +762,12 @@ class btree_node { void clear_child(field_type i) { absl::container_internal::SanitizerPoisonObject(&mutable_child(i)); } - void set_child(field_type i, btree_node *c) { + void set_child_noupdate_position(field_type i, btree_node *c) { absl::container_internal::SanitizerUnpoisonObject(&mutable_child(i)); mutable_child(i) = c; + } + void set_child(field_type i, btree_node *c) { + set_child_noupdate_position(i, c); c->set_position(i); } void init_child(field_type i, btree_node *c) { @@ -892,6 +894,38 @@ class btree_node { } } + // Returns whether key i is ordered correctly with respect to the other keys + // in the node. The motivation here is to detect comparators that violate + // transitivity. Note: we only do comparisons of keys on this node rather than + // the whole tree so that this is constant time. + template <typename Compare> + bool is_ordered_correctly(field_type i, const Compare &comp) const { + if (std::is_base_of<BtreeTestOnlyCheckedCompareOptOutBase, + Compare>::value || + params_type::kIsKeyCompareStringAdapted) { + return true; + } + + const auto compare = [&](field_type a, field_type b) { + const absl::weak_ordering cmp = + compare_internal::do_three_way_comparison(comp, key(a), key(b)); + return cmp < 0 ? -1 : cmp > 0 ? 1 : 0; + }; + int cmp = -1; + constexpr bool kCanHaveEquivKeys = + params_type::template can_have_multiple_equivalent_keys<key_type>(); + for (field_type j = start(); j < finish(); ++j) { + if (j == i) { + if (cmp > 0) return false; + continue; + } + int new_cmp = compare(j, i); + if (new_cmp < cmp || (!kCanHaveEquivKeys && new_cmp == 0)) return false; + cmp = new_cmp; + } + return true; + } + // Emplaces a value at position i, shifting all existing values and // children at positions >= i to the right by 1. template <typename... Args> @@ -916,18 +950,19 @@ class btree_node { void merge(btree_node *src, allocator_type *alloc); // Node allocation/deletion routines. - void init_leaf(field_type max_count, btree_node *parent) { + void init_leaf(field_type position, field_type max_count, + btree_node *parent) { set_generation(0); set_parent(parent); - set_position(0); + set_position(position); set_start(0); set_finish(0); set_max_count(max_count); absl::container_internal::SanitizerPoisonMemoryRegion( start_slot(), max_count * sizeof(slot_type)); } - void init_internal(btree_node *parent) { - init_leaf(kNodeSlots, parent); + void init_internal(field_type position, btree_node *parent) { + init_leaf(position, kNodeSlots, parent); // Set `max_count` to a sentinel value to indicate that this node is // internal. set_max_count(kInternalNodeMaxCount); @@ -1059,9 +1094,9 @@ class btree_iterator_generation_info_enabled { class btree_iterator_generation_info_disabled { public: explicit btree_iterator_generation_info_disabled(uint32_t) {} - void update_generation(const void *) {} - uint32_t generation() const { return 0; } - void assert_valid_generation(const void *) const {} + static void update_generation(const void *) {} + static uint32_t generation() { return 0; } + static void assert_valid_generation(const void *) {} }; #ifdef ABSL_BTREE_ENABLE_GENERATIONS @@ -1087,8 +1122,11 @@ class btree_iterator : private btree_iterator_generation_info { using const_reference = typename params_type::const_reference; using slot_type = typename params_type::slot_type; - using iterator = - btree_iterator<normal_node, normal_reference, normal_pointer>; + // In sets, all iterators are const. + using iterator = absl::conditional_t< + is_map_container::value, + btree_iterator<normal_node, normal_reference, normal_pointer>, + btree_iterator<normal_node, const_reference, const_pointer>>; using const_iterator = btree_iterator<const_node, const_reference, const_pointer>; @@ -1283,7 +1321,7 @@ class btree { // We use a static empty node for the root/leftmost/rightmost of empty btrees // in order to avoid branching in begin()/end(). - struct alignas(node_type::Alignment()) EmptyNodeType : node_type { + struct EmptyNodeType : node_type { using field_type = typename node_type::field_type; node_type *parent; #ifdef ABSL_BTREE_ENABLE_GENERATIONS @@ -1296,25 +1334,12 @@ class btree { // as a leaf node). max_count() is never called when the tree is empty. field_type max_count = node_type::kInternalNodeMaxCount + 1; -#ifdef _MSC_VER - // MSVC has constexpr code generations bugs here. - EmptyNodeType() : parent(this) {} -#else - explicit constexpr EmptyNodeType(node_type *p) : parent(p) {} -#endif + constexpr EmptyNodeType() : parent(this) {} }; static node_type *EmptyNode() { -#ifdef _MSC_VER - static EmptyNodeType *empty_node = new EmptyNodeType; - // This assert fails on some other construction methods. - assert(empty_node->parent == empty_node); - return empty_node; -#else - static constexpr EmptyNodeType empty_node( - const_cast<EmptyNodeType *>(&empty_node)); + alignas(node_type::Alignment()) static constexpr EmptyNodeType empty_node; return const_cast<EmptyNodeType *>(&empty_node); -#endif } enum : uint32_t { @@ -1507,7 +1532,8 @@ class btree { // Insert a range of values into the btree. template <typename InputIterator> - void insert_iterator_multi(InputIterator b, InputIterator e); + void insert_iterator_multi(InputIterator b, + InputIterator e); // Erase the specified iterator from the btree. The iterator must be valid // (i.e. not equal to end()). Return an iterator pointing to the node after @@ -1663,19 +1689,19 @@ class btree { } // Node creation/deletion routines. - node_type *new_internal_node(node_type *parent) { + node_type *new_internal_node(field_type position, node_type *parent) { node_type *n = allocate(node_type::InternalSize()); - n->init_internal(parent); + n->init_internal(position, parent); return n; } - node_type *new_leaf_node(node_type *parent) { + node_type *new_leaf_node(field_type position, node_type *parent) { node_type *n = allocate(node_type::LeafSize()); - n->init_leaf(kNodeSlots, parent); + n->init_leaf(position, kNodeSlots, parent); return n; } node_type *new_leaf_root_node(field_type max_count) { node_type *n = allocate(node_type::LeafSize(max_count)); - n->init_leaf(max_count, /*parent=*/n); + n->init_leaf(/*position=*/0, max_count, /*parent=*/n); return n; } @@ -1914,6 +1940,8 @@ void btree_node<P>::split(const int insert_position, btree_node *dest, allocator_type *alloc) { assert(dest->count() == 0); assert(max_count() == kNodeSlots); + assert(position() + 1 == dest->position()); + assert(parent() == dest->parent()); // We bias the split based on the position being inserted. If we're // inserting at the beginning of the left node then bias the split to put @@ -1936,7 +1964,7 @@ void btree_node<P>::split(const int insert_position, btree_node *dest, --mutable_finish(); parent()->emplace_value(position(), alloc, finish_slot()); value_destroy(finish(), alloc); - parent()->init_child(position() + 1, dest); + parent()->set_child_noupdate_position(position() + 1, dest); if (is_internal()) { for (field_type i = dest->start(), j = finish() + 1; i <= dest->finish(); @@ -2180,7 +2208,7 @@ constexpr bool btree<P>::static_assert_validation() { "Key comparison must be nothrow copy constructible"); static_assert(std::is_nothrow_copy_constructible<allocator_type>::value, "Allocator must be nothrow copy constructible"); - static_assert(type_traits_internal::is_trivially_copyable<iterator>::value, + static_assert(std::is_trivially_copyable<iterator>::value, "iterator not trivially copyable."); // Note: We assert that kTargetValues, which is computed from @@ -2382,7 +2410,7 @@ auto btree<P>::operator=(btree &&other) noexcept -> btree & { using std::swap; if (absl::allocator_traits< - allocator_type>::propagate_on_container_copy_assignment::value) { + allocator_type>::propagate_on_container_move_assignment::value) { swap(root_, other.root_); // Note: `rightmost_` also contains the allocator and the key comparator. swap(rightmost_, other.rightmost_); @@ -2496,6 +2524,10 @@ auto btree<P>::rebalance_after_delete(iterator iter) -> iterator { return res; } +// Note: we tried implementing this more efficiently by erasing all of the +// elements in [begin, end) at once and then doing rebalancing once at the end +// (rather than interleaving deletion and rebalancing), but that adds a lot of +// complexity, which seems to outweigh the performance win. template <typename P> auto btree<P>::erase_range(iterator begin, iterator end) -> std::pair<size_type, iterator> { @@ -2653,16 +2685,17 @@ void btree<P>::rebalance_or_split(iterator *iter) { // value. assert(parent->max_count() == kNodeSlots); if (parent->count() == kNodeSlots) { - iterator parent_iter(node->parent(), node->position()); + iterator parent_iter(parent, node->position()); rebalance_or_split(&parent_iter); + parent = node->parent(); } } else { // Rebalancing not possible because this is the root node. // Create a new root node and set the current root node as the child of the // new root. - parent = new_internal_node(parent); + parent = new_internal_node(/*position=*/0, parent); parent->set_generation(root()->generation()); - parent->init_child(parent->start(), root()); + parent->init_child(parent->start(), node); mutable_root() = parent; // If the former root was a leaf node, then it's now the rightmost node. assert(parent->start_child()->is_internal() || @@ -2672,11 +2705,11 @@ void btree<P>::rebalance_or_split(iterator *iter) { // Split the node. node_type *split_node; if (node->is_leaf()) { - split_node = new_leaf_node(parent); + split_node = new_leaf_node(node->position() + 1, parent); node->split(insert_position, split_node, mutable_allocator()); if (rightmost() == node) mutable_rightmost() = split_node; } else { - split_node = new_internal_node(parent); + split_node = new_internal_node(node->position() + 1, parent); node->split(insert_position, split_node, mutable_allocator()); } @@ -2792,30 +2825,68 @@ inline auto btree<P>::internal_emplace(iterator iter, Args &&...args) } const field_type max_count = iter.node_->max_count(); allocator_type *alloc = mutable_allocator(); + + const auto transfer_and_delete = [&](node_type *old_node, + node_type *new_node) { + new_node->transfer_n(old_node->count(), new_node->start(), + old_node->start(), old_node, alloc); + new_node->set_finish(old_node->finish()); + old_node->set_finish(old_node->start()); + new_node->set_generation(old_node->generation()); + node_type::clear_and_delete(old_node, alloc); + }; + const auto replace_leaf_root_node = [&](field_type new_node_size) { + assert(iter.node_ == root()); + node_type *old_root = iter.node_; + node_type *new_root = iter.node_ = new_leaf_root_node(new_node_size); + transfer_and_delete(old_root, new_root); + mutable_root() = mutable_rightmost() = new_root; + }; + + bool replaced_node = false; if (iter.node_->count() == max_count) { // Make room in the leaf for the new item. if (max_count < kNodeSlots) { // Insertion into the root where the root is smaller than the full node // size. Simply grow the size of the root node. - assert(iter.node_ == root()); - iter.node_ = new_leaf_root_node(static_cast<field_type>( + replace_leaf_root_node(static_cast<field_type>( (std::min)(static_cast<int>(kNodeSlots), 2 * max_count))); - // Transfer the values from the old root to the new root. - node_type *old_root = root(); - node_type *new_root = iter.node_; - new_root->transfer_n(old_root->count(), new_root->start(), - old_root->start(), old_root, alloc); - new_root->set_finish(old_root->finish()); - old_root->set_finish(old_root->start()); - new_root->set_generation(old_root->generation()); - node_type::clear_and_delete(old_root, alloc); - mutable_root() = mutable_rightmost() = new_root; + replaced_node = true; } else { rebalance_or_split(&iter); } } + (void)replaced_node; +#if defined(ABSL_HAVE_ADDRESS_SANITIZER) || \ + defined(ABSL_HAVE_HWADDRESS_SANITIZER) + if (!replaced_node) { + assert(iter.node_->is_leaf()); + if (iter.node_->is_root()) { + replace_leaf_root_node(max_count); + } else { + node_type *old_node = iter.node_; + const bool was_rightmost = rightmost() == old_node; + const bool was_leftmost = leftmost() == old_node; + node_type *parent = old_node->parent(); + const field_type position = old_node->position(); + node_type *new_node = iter.node_ = new_leaf_node(position, parent); + parent->set_child_noupdate_position(position, new_node); + transfer_and_delete(old_node, new_node); + if (was_rightmost) mutable_rightmost() = new_node; + // The leftmost node is stored as the parent of the root node. + if (was_leftmost) root()->set_parent(new_node); + } + } +#endif iter.node_->emplace_value(static_cast<field_type>(iter.position_), alloc, std::forward<Args>(args)...); + assert( + iter.node_->is_ordered_correctly(static_cast<field_type>(iter.position_), + original_key_compare(key_comp())) && + "If this assert fails, then either (1) the comparator may violate " + "transitivity, i.e. comp(a,b) && comp(b,c) -> comp(a,c) (see " + "https://en.cppreference.com/w/cpp/named_req/Compare), or (2) a " + "key may have been mutated after it was inserted into the tree."); ++size_; iter.update_generation(); return iter; diff --git a/absl/container/internal/btree_container.h b/absl/container/internal/btree_container.h index 2bff11db..a68ce445 100644 --- a/absl/container/internal/btree_container.h +++ b/absl/container/internal/btree_container.h @@ -95,18 +95,36 @@ class btree_container { std::is_nothrow_move_assignable<Tree>::value) = default; // Iterator routines. - iterator begin() { return tree_.begin(); } - const_iterator begin() const { return tree_.begin(); } - const_iterator cbegin() const { return tree_.begin(); } - iterator end() { return tree_.end(); } - const_iterator end() const { return tree_.end(); } - const_iterator cend() const { return tree_.end(); } - reverse_iterator rbegin() { return tree_.rbegin(); } - const_reverse_iterator rbegin() const { return tree_.rbegin(); } - const_reverse_iterator crbegin() const { return tree_.rbegin(); } - reverse_iterator rend() { return tree_.rend(); } - const_reverse_iterator rend() const { return tree_.rend(); } - const_reverse_iterator crend() const { return tree_.rend(); } + iterator begin() ABSL_ATTRIBUTE_LIFETIME_BOUND { return tree_.begin(); } + const_iterator begin() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return tree_.begin(); + } + const_iterator cbegin() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return tree_.begin(); + } + iterator end() ABSL_ATTRIBUTE_LIFETIME_BOUND { return tree_.end(); } + const_iterator end() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return tree_.end(); + } + const_iterator cend() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return tree_.end(); + } + reverse_iterator rbegin() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return tree_.rbegin(); + } + const_reverse_iterator rbegin() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return tree_.rbegin(); + } + const_reverse_iterator crbegin() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return tree_.rbegin(); + } + reverse_iterator rend() ABSL_ATTRIBUTE_LIFETIME_BOUND { return tree_.rend(); } + const_reverse_iterator rend() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return tree_.rend(); + } + const_reverse_iterator crend() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return tree_.rend(); + } // Lookup routines. template <typename K = key_type> @@ -115,11 +133,12 @@ class btree_container { return equal_range.second - equal_range.first; } template <typename K = key_type> - iterator find(const key_arg<K> &key) { + iterator find(const key_arg<K> &key) ABSL_ATTRIBUTE_LIFETIME_BOUND { return tree_.find(key); } template <typename K = key_type> - const_iterator find(const key_arg<K> &key) const { + const_iterator find(const key_arg<K> &key) const + ABSL_ATTRIBUTE_LIFETIME_BOUND { return tree_.find(key); } template <typename K = key_type> @@ -127,28 +146,31 @@ class btree_container { return find(key) != end(); } template <typename K = key_type> - iterator lower_bound(const key_arg<K> &key) { + iterator lower_bound(const key_arg<K> &key) ABSL_ATTRIBUTE_LIFETIME_BOUND { return tree_.lower_bound(key); } template <typename K = key_type> - const_iterator lower_bound(const key_arg<K> &key) const { + const_iterator lower_bound(const key_arg<K> &key) const + ABSL_ATTRIBUTE_LIFETIME_BOUND { return tree_.lower_bound(key); } template <typename K = key_type> - iterator upper_bound(const key_arg<K> &key) { + iterator upper_bound(const key_arg<K> &key) ABSL_ATTRIBUTE_LIFETIME_BOUND { return tree_.upper_bound(key); } template <typename K = key_type> - const_iterator upper_bound(const key_arg<K> &key) const { + const_iterator upper_bound(const key_arg<K> &key) const + ABSL_ATTRIBUTE_LIFETIME_BOUND { return tree_.upper_bound(key); } template <typename K = key_type> - std::pair<iterator, iterator> equal_range(const key_arg<K> &key) { + std::pair<iterator, iterator> equal_range(const key_arg<K> &key) + ABSL_ATTRIBUTE_LIFETIME_BOUND { return tree_.equal_range(key); } template <typename K = key_type> std::pair<const_iterator, const_iterator> equal_range( - const key_arg<K> &key) const { + const key_arg<K> &key) const ABSL_ATTRIBUTE_LIFETIME_BOUND { return tree_.equal_range(key); } @@ -158,9 +180,14 @@ class btree_container { // Erase the specified iterator from the btree. The iterator must be valid // (i.e. not equal to end()). Return an iterator pointing to the node after // the one that was erased (or end() if none exists). - iterator erase(const_iterator iter) { return tree_.erase(iterator(iter)); } - iterator erase(iterator iter) { return tree_.erase(iter); } - iterator erase(const_iterator first, const_iterator last) { + iterator erase(const_iterator iter) ABSL_ATTRIBUTE_LIFETIME_BOUND { + return tree_.erase(iterator(iter)); + } + iterator erase(iterator iter) ABSL_ATTRIBUTE_LIFETIME_BOUND { + return tree_.erase(iter); + } + iterator erase(const_iterator first, + const_iterator last) ABSL_ATTRIBUTE_LIFETIME_BOUND { return tree_.erase_range(iterator(first), iterator(last)).second; } template <typename K = key_type> @@ -170,8 +197,8 @@ class btree_container { } // Extract routines. - extract_and_get_next_return_type extract_and_get_next( - const_iterator position) { + extract_and_get_next_return_type extract_and_get_next(const_iterator position) + ABSL_ATTRIBUTE_LIFETIME_BOUND { // Use Construct instead of Transfer because the rebalancing code will // destroy the slot later. // Note: we rely on erase() taking place after Construct(). @@ -298,32 +325,38 @@ class btree_set_container : public btree_container<Tree> { : btree_set_container(init.begin(), init.end(), alloc) {} // Insertion routines. - std::pair<iterator, bool> insert(const value_type &v) { + std::pair<iterator, bool> insert(const value_type &v) + ABSL_ATTRIBUTE_LIFETIME_BOUND { return this->tree_.insert_unique(params_type::key(v), v); } - std::pair<iterator, bool> insert(value_type &&v) { + std::pair<iterator, bool> insert(value_type &&v) + ABSL_ATTRIBUTE_LIFETIME_BOUND { return this->tree_.insert_unique(params_type::key(v), std::move(v)); } template <typename... Args> - std::pair<iterator, bool> emplace(Args &&... args) { + std::pair<iterator, bool> emplace(Args &&...args) + ABSL_ATTRIBUTE_LIFETIME_BOUND { // Use a node handle to manage a temp slot. auto node = CommonAccess::Construct<node_type>(this->get_allocator(), std::forward<Args>(args)...); auto *slot = CommonAccess::GetSlot(node); return this->tree_.insert_unique(params_type::key(slot), slot); } - iterator insert(const_iterator hint, const value_type &v) { + iterator insert(const_iterator hint, + const value_type &v) ABSL_ATTRIBUTE_LIFETIME_BOUND { return this->tree_ .insert_hint_unique(iterator(hint), params_type::key(v), v) .first; } - iterator insert(const_iterator hint, value_type &&v) { + iterator insert(const_iterator hint, + value_type &&v) ABSL_ATTRIBUTE_LIFETIME_BOUND { return this->tree_ .insert_hint_unique(iterator(hint), params_type::key(v), std::move(v)) .first; } template <typename... Args> - iterator emplace_hint(const_iterator hint, Args &&... args) { + iterator emplace_hint(const_iterator hint, + Args &&...args) ABSL_ATTRIBUTE_LIFETIME_BOUND { // Use a node handle to manage a temp slot. auto node = CommonAccess::Construct<node_type>(this->get_allocator(), std::forward<Args>(args)...); @@ -339,7 +372,7 @@ class btree_set_container : public btree_container<Tree> { void insert(std::initializer_list<init_type> init) { this->tree_.insert_iterator_unique(init.begin(), init.end(), 0); } - insert_return_type insert(node_type &&node) { + insert_return_type insert(node_type &&node) ABSL_ATTRIBUTE_LIFETIME_BOUND { if (!node) return {this->end(), false, node_type()}; std::pair<iterator, bool> res = this->tree_.insert_unique(params_type::key(CommonAccess::GetSlot(node)), @@ -351,7 +384,8 @@ class btree_set_container : public btree_container<Tree> { return {res.first, false, std::move(node)}; } } - iterator insert(const_iterator hint, node_type &&node) { + iterator insert(const_iterator hint, + node_type &&node) ABSL_ATTRIBUTE_LIFETIME_BOUND { if (!node) return this->end(); std::pair<iterator, bool> res = this->tree_.insert_hint_unique( iterator(hint), params_type::key(CommonAccess::GetSlot(node)), @@ -434,37 +468,43 @@ class btree_map_container : public btree_set_container<Tree> { // Note: the nullptr template arguments and extra `const M&` overloads allow // for supporting bitfield arguments. template <typename K = key_type, class M> - std::pair<iterator, bool> insert_or_assign(const key_arg<K> &k, - const M &obj) { + std::pair<iterator, bool> insert_or_assign(const key_arg<K> &k, const M &obj) + ABSL_ATTRIBUTE_LIFETIME_BOUND { return insert_or_assign_impl(k, obj); } template <typename K = key_type, class M, K * = nullptr> - std::pair<iterator, bool> insert_or_assign(key_arg<K> &&k, const M &obj) { + std::pair<iterator, bool> insert_or_assign(key_arg<K> &&k, const M &obj) + ABSL_ATTRIBUTE_LIFETIME_BOUND { return insert_or_assign_impl(std::forward<K>(k), obj); } template <typename K = key_type, class M, M * = nullptr> - std::pair<iterator, bool> insert_or_assign(const key_arg<K> &k, M &&obj) { + std::pair<iterator, bool> insert_or_assign(const key_arg<K> &k, M &&obj) + ABSL_ATTRIBUTE_LIFETIME_BOUND { return insert_or_assign_impl(k, std::forward<M>(obj)); } template <typename K = key_type, class M, K * = nullptr, M * = nullptr> - std::pair<iterator, bool> insert_or_assign(key_arg<K> &&k, M &&obj) { + std::pair<iterator, bool> insert_or_assign(key_arg<K> &&k, M &&obj) + ABSL_ATTRIBUTE_LIFETIME_BOUND { return insert_or_assign_impl(std::forward<K>(k), std::forward<M>(obj)); } template <typename K = key_type, class M> iterator insert_or_assign(const_iterator hint, const key_arg<K> &k, - const M &obj) { + const M &obj) ABSL_ATTRIBUTE_LIFETIME_BOUND { return insert_or_assign_hint_impl(hint, k, obj); } template <typename K = key_type, class M, K * = nullptr> - iterator insert_or_assign(const_iterator hint, key_arg<K> &&k, const M &obj) { + iterator insert_or_assign(const_iterator hint, key_arg<K> &&k, + const M &obj) ABSL_ATTRIBUTE_LIFETIME_BOUND { return insert_or_assign_hint_impl(hint, std::forward<K>(k), obj); } template <typename K = key_type, class M, M * = nullptr> - iterator insert_or_assign(const_iterator hint, const key_arg<K> &k, M &&obj) { + iterator insert_or_assign(const_iterator hint, const key_arg<K> &k, + M &&obj) ABSL_ATTRIBUTE_LIFETIME_BOUND { return insert_or_assign_hint_impl(hint, k, std::forward<M>(obj)); } template <typename K = key_type, class M, K * = nullptr, M * = nullptr> - iterator insert_or_assign(const_iterator hint, key_arg<K> &&k, M &&obj) { + iterator insert_or_assign(const_iterator hint, key_arg<K> &&k, + M &&obj) ABSL_ATTRIBUTE_LIFETIME_BOUND { return insert_or_assign_hint_impl(hint, std::forward<K>(k), std::forward<M>(obj)); } @@ -472,44 +512,48 @@ class btree_map_container : public btree_set_container<Tree> { template <typename K = key_type, typename... Args, typename absl::enable_if_t< !std::is_convertible<K, const_iterator>::value, int> = 0> - std::pair<iterator, bool> try_emplace(const key_arg<K> &k, Args &&... args) { + std::pair<iterator, bool> try_emplace(const key_arg<K> &k, Args &&...args) + ABSL_ATTRIBUTE_LIFETIME_BOUND { return try_emplace_impl(k, std::forward<Args>(args)...); } template <typename K = key_type, typename... Args, typename absl::enable_if_t< !std::is_convertible<K, const_iterator>::value, int> = 0> - std::pair<iterator, bool> try_emplace(key_arg<K> &&k, Args &&... args) { + std::pair<iterator, bool> try_emplace(key_arg<K> &&k, Args &&...args) + ABSL_ATTRIBUTE_LIFETIME_BOUND { return try_emplace_impl(std::forward<K>(k), std::forward<Args>(args)...); } template <typename K = key_type, typename... Args> iterator try_emplace(const_iterator hint, const key_arg<K> &k, - Args &&... args) { + Args &&...args) ABSL_ATTRIBUTE_LIFETIME_BOUND { return try_emplace_hint_impl(hint, k, std::forward<Args>(args)...); } template <typename K = key_type, typename... Args> - iterator try_emplace(const_iterator hint, key_arg<K> &&k, Args &&... args) { + iterator try_emplace(const_iterator hint, key_arg<K> &&k, + Args &&...args) ABSL_ATTRIBUTE_LIFETIME_BOUND { return try_emplace_hint_impl(hint, std::forward<K>(k), std::forward<Args>(args)...); } template <typename K = key_type> - mapped_type &operator[](const key_arg<K> &k) { + mapped_type &operator[](const key_arg<K> &k) ABSL_ATTRIBUTE_LIFETIME_BOUND { return try_emplace(k).first->second; } template <typename K = key_type> - mapped_type &operator[](key_arg<K> &&k) { + mapped_type &operator[](key_arg<K> &&k) ABSL_ATTRIBUTE_LIFETIME_BOUND { return try_emplace(std::forward<K>(k)).first->second; } template <typename K = key_type> - mapped_type &at(const key_arg<K> &key) { + mapped_type &at(const key_arg<K> &key) ABSL_ATTRIBUTE_LIFETIME_BOUND { auto it = this->find(key); if (it == this->end()) base_internal::ThrowStdOutOfRange("absl::btree_map::at"); return it->second; } template <typename K = key_type> - const mapped_type &at(const key_arg<K> &key) const { + const mapped_type &at(const key_arg<K> &key) const + ABSL_ATTRIBUTE_LIFETIME_BOUND { auto it = this->find(key); if (it == this->end()) base_internal::ThrowStdOutOfRange("absl::btree_map::at"); @@ -600,14 +644,18 @@ class btree_multiset_container : public btree_container<Tree> { : btree_multiset_container(init.begin(), init.end(), alloc) {} // Insertion routines. - iterator insert(const value_type &v) { return this->tree_.insert_multi(v); } - iterator insert(value_type &&v) { + iterator insert(const value_type &v) ABSL_ATTRIBUTE_LIFETIME_BOUND { + return this->tree_.insert_multi(v); + } + iterator insert(value_type &&v) ABSL_ATTRIBUTE_LIFETIME_BOUND { return this->tree_.insert_multi(std::move(v)); } - iterator insert(const_iterator hint, const value_type &v) { + iterator insert(const_iterator hint, + const value_type &v) ABSL_ATTRIBUTE_LIFETIME_BOUND { return this->tree_.insert_hint_multi(iterator(hint), v); } - iterator insert(const_iterator hint, value_type &&v) { + iterator insert(const_iterator hint, + value_type &&v) ABSL_ATTRIBUTE_LIFETIME_BOUND { return this->tree_.insert_hint_multi(iterator(hint), std::move(v)); } template <typename InputIterator> @@ -618,21 +666,22 @@ class btree_multiset_container : public btree_container<Tree> { this->tree_.insert_iterator_multi(init.begin(), init.end()); } template <typename... Args> - iterator emplace(Args &&... args) { + iterator emplace(Args &&...args) ABSL_ATTRIBUTE_LIFETIME_BOUND { // Use a node handle to manage a temp slot. auto node = CommonAccess::Construct<node_type>(this->get_allocator(), std::forward<Args>(args)...); return this->tree_.insert_multi(CommonAccess::GetSlot(node)); } template <typename... Args> - iterator emplace_hint(const_iterator hint, Args &&... args) { + iterator emplace_hint(const_iterator hint, + Args &&...args) ABSL_ATTRIBUTE_LIFETIME_BOUND { // Use a node handle to manage a temp slot. auto node = CommonAccess::Construct<node_type>(this->get_allocator(), std::forward<Args>(args)...); return this->tree_.insert_hint_multi(iterator(hint), CommonAccess::GetSlot(node)); } - iterator insert(node_type &&node) { + iterator insert(node_type &&node) ABSL_ATTRIBUTE_LIFETIME_BOUND { if (!node) return this->end(); iterator res = this->tree_.insert_multi(params_type::key(CommonAccess::GetSlot(node)), @@ -640,7 +689,8 @@ class btree_multiset_container : public btree_container<Tree> { CommonAccess::Destroy(&node); return res; } - iterator insert(const_iterator hint, node_type &&node) { + iterator insert(const_iterator hint, + node_type &&node) ABSL_ATTRIBUTE_LIFETIME_BOUND { if (!node) return this->end(); iterator res = this->tree_.insert_hint_multi( iterator(hint), diff --git a/absl/container/internal/common_policy_traits.h b/absl/container/internal/common_policy_traits.h index b42c9a48..57eac678 100644 --- a/absl/container/internal/common_policy_traits.h +++ b/absl/container/internal/common_policy_traits.h @@ -87,17 +87,19 @@ struct common_policy_traits { } private: - // To rank the overloads below for overload resoltion. Rank0 is preferred. + // To rank the overloads below for overload resolution. Rank0 is preferred. struct Rank2 {}; struct Rank1 : Rank2 {}; struct Rank0 : Rank1 {}; // Use auto -> decltype as an enabler. + // P::transfer returns std::true_type if transfer uses memcpy (e.g. in + // node_slot_policy). template <class Alloc, class P = Policy> static auto transfer_impl(Alloc* alloc, slot_type* new_slot, slot_type* old_slot, Rank0) - -> decltype((void)P::transfer(alloc, new_slot, old_slot)) { - P::transfer(alloc, new_slot, old_slot); + -> decltype(P::transfer(alloc, new_slot, old_slot)) { + return P::transfer(alloc, new_slot, old_slot); } #if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606 // This overload returns true_type for the trait below. diff --git a/absl/container/internal/common_policy_traits_test.cc b/absl/container/internal/common_policy_traits_test.cc index 5eaa4aae..faee3e7a 100644 --- a/absl/container/internal/common_policy_traits_test.cc +++ b/absl/container/internal/common_policy_traits_test.cc @@ -16,10 +16,12 @@ #include <functional> #include <memory> -#include <new> +#include <type_traits> +#include <utility> #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/base/config.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -51,9 +53,14 @@ std::function<Slot&(Slot*)> PolicyWithoutOptionalOps::element; struct PolicyWithOptionalOps : PolicyWithoutOptionalOps { static std::function<void(void*, Slot*, Slot*)> transfer; }; - std::function<void(void*, Slot*, Slot*)> PolicyWithOptionalOps::transfer; +struct PolicyWithMemcpyTransfer : PolicyWithoutOptionalOps { + static std::function<std::true_type(void*, Slot*, Slot*)> transfer; +}; +std::function<std::true_type(void*, Slot*, Slot*)> + PolicyWithMemcpyTransfer::transfer; + struct Test : ::testing::Test { Test() { PolicyWithoutOptionalOps::construct = [&](void* a1, Slot* a2, Slot a3) { @@ -114,6 +121,13 @@ TEST_F(Test, with_transfer) { common_policy_traits<PolicyWithOptionalOps>::transfer(&alloc, &a, &b); } +TEST(TransferUsesMemcpy, Basic) { + EXPECT_FALSE( + common_policy_traits<PolicyWithOptionalOps>::transfer_uses_memcpy()); + EXPECT_TRUE( + common_policy_traits<PolicyWithMemcpyTransfer>::transfer_uses_memcpy()); +} + } // namespace } // namespace container_internal ABSL_NAMESPACE_END diff --git a/absl/container/internal/compressed_tuple.h b/absl/container/internal/compressed_tuple.h index 5ebe1649..59e70eb2 100644 --- a/absl/container/internal/compressed_tuple.h +++ b/absl/container/internal/compressed_tuple.h @@ -64,19 +64,6 @@ struct Elem<CompressedTuple<B...>, I> template <typename D, size_t I> using ElemT = typename Elem<D, I>::type; -// Use the __is_final intrinsic if available. Where it's not available, classes -// declared with the 'final' specifier cannot be used as CompressedTuple -// elements. -// TODO(sbenza): Replace this with std::is_final in C++14. -template <typename T> -constexpr bool IsFinal() { -#if defined(__clang__) || defined(__GNUC__) - return __is_final(T); -#else - return false; -#endif -} - // We can't use EBCO on other CompressedTuples because that would mean that we // derive from multiple Storage<> instantiations with the same I parameter, // and potentially from multiple identical Storage<> instantiations. So anytime @@ -86,20 +73,15 @@ struct uses_inheritance {}; template <typename T> constexpr bool ShouldUseBase() { - return std::is_class<T>::value && std::is_empty<T>::value && !IsFinal<T>() && + return std::is_class<T>::value && std::is_empty<T>::value && + !std::is_final<T>::value && !std::is_base_of<uses_inheritance, T>::value; } // The storage class provides two specializations: // - For empty classes, it stores T as a base class. // - For everything else, it stores T as a member. -template <typename T, size_t I, -#if defined(_MSC_VER) - bool UseBase = - ShouldUseBase<typename std::enable_if<true, T>::type>()> -#else - bool UseBase = ShouldUseBase<T>()> -#endif +template <typename T, size_t I, bool UseBase = ShouldUseBase<T>()> struct Storage { T value; constexpr Storage() = default; diff --git a/absl/container/internal/container_memory.h b/absl/container/internal/container_memory.h index bfa4ff93..3262d4eb 100644 --- a/absl/container/internal/container_memory.h +++ b/absl/container/internal/container_memory.h @@ -122,10 +122,10 @@ auto TupleRefImpl(T&& t, absl::index_sequence<Is...>) // Returns a tuple of references to the elements of the input tuple. T must be a // tuple. template <class T> -auto TupleRef(T&& t) -> decltype( - TupleRefImpl(std::forward<T>(t), - absl::make_index_sequence< - std::tuple_size<typename std::decay<T>::type>::value>())) { +auto TupleRef(T&& t) -> decltype(TupleRefImpl( + std::forward<T>(t), + absl::make_index_sequence< + std::tuple_size<typename std::decay<T>::type>::value>())) { return TupleRefImpl( std::forward<T>(t), absl::make_index_sequence< @@ -156,8 +156,8 @@ void ConstructFromTuple(Alloc* alloc, T* ptr, Tuple&& t) { // Constructs T using the args specified in the tuple and calls F with the // constructed value. template <class T, class Tuple, class F> -decltype(std::declval<F>()(std::declval<T>())) WithConstructed( - Tuple&& t, F&& f) { +decltype(std::declval<F>()(std::declval<T>())) WithConstructed(Tuple&& t, + F&& f) { return memory_internal::WithConstructedImpl<T>( std::forward<Tuple>(t), absl::make_index_sequence< @@ -165,7 +165,7 @@ decltype(std::declval<F>()(std::declval<T>())) WithConstructed( std::forward<F>(f)); } -// Given arguments of an std::pair's consructor, PairArgs() returns a pair of +// Given arguments of an std::pair's constructor, PairArgs() returns a pair of // tuples with references to the passed arguments. The tuples contain // constructor arguments for the first and the second elements of the pair. // @@ -423,16 +423,19 @@ struct map_slot_policy { } template <class Allocator> - static void transfer(Allocator* alloc, slot_type* new_slot, + static auto transfer(Allocator* alloc, slot_type* new_slot, slot_type* old_slot) { + auto is_relocatable = + typename absl::is_trivially_relocatable<value_type>::type(); + emplace(new_slot); #if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606 - if (absl::is_trivially_relocatable<value_type>()) { + if (is_relocatable) { // TODO(b/247130232,b/251814870): remove casts after fixing warnings. std::memcpy(static_cast<void*>(std::launder(&new_slot->value)), static_cast<const void*>(&old_slot->value), sizeof(value_type)); - return; + return is_relocatable; } #endif @@ -444,6 +447,7 @@ struct map_slot_policy { std::move(old_slot->value)); } destroy(alloc, old_slot); + return is_relocatable; } }; diff --git a/absl/container/internal/container_memory_test.cc b/absl/container/internal/container_memory_test.cc index fb9c4dde..90d64bf5 100644 --- a/absl/container/internal/container_memory_test.cc +++ b/absl/container/internal/container_memory_test.cc @@ -14,15 +14,20 @@ #include "absl/container/internal/container_memory.h" +#include <cstddef> #include <cstdint> +#include <memory> #include <tuple> +#include <type_traits> #include <typeindex> #include <typeinfo> #include <utility> #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/base/no_destructor.h" #include "absl/container/internal/test_instance_tracker.h" +#include "absl/meta/type_traits.h" #include "absl/strings/string_view.h" namespace absl { @@ -54,7 +59,7 @@ TEST(Memory, AlignmentSmallerThanBase) { } std::map<std::type_index, int>& AllocationMap() { - static auto* map = new std::map<std::type_index, int>; + static absl::NoDestructor<std::map<std::type_index, int>> map; return *map; } @@ -219,8 +224,7 @@ TEST(DecomposePair, NotDecomposable) { ADD_FAILURE() << "Must not be called"; return 'A'; }; - EXPECT_STREQ("not decomposable", - TryDecomposePair(f)); + EXPECT_STREQ("not decomposable", TryDecomposePair(f)); EXPECT_STREQ("not decomposable", TryDecomposePair(f, std::piecewise_construct, std::make_tuple(), std::make_tuple(0.5))); @@ -251,6 +255,31 @@ TEST(MapSlotPolicy, ConstKeyAndValue) { EXPECT_EQ(tracker.copies(), 0); } +TEST(MapSlotPolicy, TransferReturnsTrue) { + { + using slot_policy = map_slot_policy<int, float>; + EXPECT_TRUE( + (std::is_same<decltype(slot_policy::transfer<std::allocator<char>>( + nullptr, nullptr, nullptr)), + std::true_type>::value)); + } + { + struct NonRelocatable { + NonRelocatable() = default; + NonRelocatable(NonRelocatable&&) {} + NonRelocatable& operator=(NonRelocatable&&) { return *this; } + void* self = nullptr; + }; + + EXPECT_FALSE(absl::is_trivially_relocatable<NonRelocatable>::value); + using slot_policy = map_slot_policy<int, NonRelocatable>; + EXPECT_TRUE( + (std::is_same<decltype(slot_policy::transfer<std::allocator<char>>( + nullptr, nullptr, nullptr)), + std::false_type>::value)); + } +} + } // namespace } // namespace container_internal ABSL_NAMESPACE_END diff --git a/absl/container/internal/counting_allocator.h b/absl/container/internal/counting_allocator.h deleted file mode 100644 index 66068a5a..00000000 --- a/absl/container/internal/counting_allocator.h +++ /dev/null @@ -1,122 +0,0 @@ -// 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. - -#ifndef ABSL_CONTAINER_INTERNAL_COUNTING_ALLOCATOR_H_ -#define ABSL_CONTAINER_INTERNAL_COUNTING_ALLOCATOR_H_ - -#include <cstdint> -#include <memory> - -#include "absl/base/config.h" - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace container_internal { - -// This is a stateful allocator, but the state lives outside of the -// allocator (in whatever test is using the allocator). This is odd -// but helps in tests where the allocator is propagated into nested -// containers - that chain of allocators uses the same state and is -// thus easier to query for aggregate allocation information. -template <typename T> -class CountingAllocator { - public: - using Allocator = std::allocator<T>; - using AllocatorTraits = std::allocator_traits<Allocator>; - using value_type = typename AllocatorTraits::value_type; - using pointer = typename AllocatorTraits::pointer; - using const_pointer = typename AllocatorTraits::const_pointer; - using size_type = typename AllocatorTraits::size_type; - using difference_type = typename AllocatorTraits::difference_type; - - CountingAllocator() = default; - explicit CountingAllocator(int64_t* bytes_used) : bytes_used_(bytes_used) {} - CountingAllocator(int64_t* bytes_used, int64_t* instance_count) - : bytes_used_(bytes_used), instance_count_(instance_count) {} - - template <typename U> - CountingAllocator(const CountingAllocator<U>& x) - : bytes_used_(x.bytes_used_), instance_count_(x.instance_count_) {} - - pointer allocate( - size_type n, - typename AllocatorTraits::const_void_pointer hint = nullptr) { - Allocator allocator; - pointer ptr = AllocatorTraits::allocate(allocator, n, hint); - if (bytes_used_ != nullptr) { - *bytes_used_ += n * sizeof(T); - } - return ptr; - } - - void deallocate(pointer p, size_type n) { - Allocator allocator; - AllocatorTraits::deallocate(allocator, p, n); - if (bytes_used_ != nullptr) { - *bytes_used_ -= n * sizeof(T); - } - } - - template <typename U, typename... Args> - void construct(U* p, Args&&... args) { - Allocator allocator; - AllocatorTraits::construct(allocator, p, std::forward<Args>(args)...); - if (instance_count_ != nullptr) { - *instance_count_ += 1; - } - } - - template <typename U> - void destroy(U* p) { - Allocator allocator; - // Ignore GCC warning bug. -#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wuse-after-free" -#endif - AllocatorTraits::destroy(allocator, p); -#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0) -#pragma GCC diagnostic pop -#endif - if (instance_count_ != nullptr) { - *instance_count_ -= 1; - } - } - - template <typename U> - class rebind { - public: - using other = CountingAllocator<U>; - }; - - friend bool operator==(const CountingAllocator& a, - const CountingAllocator& b) { - return a.bytes_used_ == b.bytes_used_ && - a.instance_count_ == b.instance_count_; - } - - friend bool operator!=(const CountingAllocator& a, - const CountingAllocator& b) { - return !(a == b); - } - - int64_t* bytes_used_ = nullptr; - int64_t* instance_count_ = nullptr; -}; - -} // namespace container_internal -ABSL_NAMESPACE_END -} // namespace absl - -#endif // ABSL_CONTAINER_INTERNAL_COUNTING_ALLOCATOR_H_ diff --git a/absl/container/internal/hash_function_defaults.h b/absl/container/internal/hash_function_defaults.h index 250e662c..a3613b4b 100644 --- a/absl/container/internal/hash_function_defaults.h +++ b/absl/container/internal/hash_function_defaults.h @@ -56,6 +56,10 @@ #include "absl/strings/cord.h" #include "absl/strings/string_view.h" +#ifdef ABSL_HAVE_STD_STRING_VIEW +#include <string_view> +#endif + namespace absl { ABSL_NAMESPACE_BEGIN namespace container_internal { @@ -107,6 +111,48 @@ struct HashEq<absl::string_view> : StringHashEq {}; template <> struct HashEq<absl::Cord> : StringHashEq {}; +#ifdef ABSL_HAVE_STD_STRING_VIEW + +template <typename TChar> +struct BasicStringHash { + using is_transparent = void; + + size_t operator()(std::basic_string_view<TChar> v) const { + return absl::Hash<std::basic_string_view<TChar>>{}(v); + } +}; + +template <typename TChar> +struct BasicStringEq { + using is_transparent = void; + bool operator()(std::basic_string_view<TChar> lhs, + std::basic_string_view<TChar> rhs) const { + return lhs == rhs; + } +}; + +// Supports heterogeneous lookup for w/u16/u32 string + string_view + char*. +template <typename TChar> +struct BasicStringHashEq { + using Hash = BasicStringHash<TChar>; + using Eq = BasicStringEq<TChar>; +}; + +template <> +struct HashEq<std::wstring> : BasicStringHashEq<wchar_t> {}; +template <> +struct HashEq<std::wstring_view> : BasicStringHashEq<wchar_t> {}; +template <> +struct HashEq<std::u16string> : BasicStringHashEq<char16_t> {}; +template <> +struct HashEq<std::u16string_view> : BasicStringHashEq<char16_t> {}; +template <> +struct HashEq<std::u32string> : BasicStringHashEq<char32_t> {}; +template <> +struct HashEq<std::u32string_view> : BasicStringHashEq<char32_t> {}; + +#endif // ABSL_HAVE_STD_STRING_VIEW + // Supports heterogeneous lookup for pointers and smart pointers. template <class T> struct HashEq<T*> { diff --git a/absl/container/internal/hash_function_defaults_test.cc b/absl/container/internal/hash_function_defaults_test.cc index 9f0a4c72..c31af3be 100644 --- a/absl/container/internal/hash_function_defaults_test.cc +++ b/absl/container/internal/hash_function_defaults_test.cc @@ -24,6 +24,10 @@ #include "absl/strings/cord_test_helpers.h" #include "absl/strings/string_view.h" +#ifdef ABSL_HAVE_STD_STRING_VIEW +#include <string_view> +#endif + namespace absl { ABSL_NAMESPACE_BEGIN namespace container_internal { @@ -109,6 +113,168 @@ TYPED_TEST(HashString, Works) { EXPECT_NE(h, hash(std::string("b"))); } +TEST(BasicStringViewTest, WStringEqWorks) { +#ifndef ABSL_HAVE_STD_STRING_VIEW + GTEST_SKIP(); +#else + hash_default_eq<std::wstring> eq; + EXPECT_TRUE(eq(L"a", L"a")); + EXPECT_TRUE(eq(L"a", std::wstring_view(L"a"))); + EXPECT_TRUE(eq(L"a", std::wstring(L"a"))); + EXPECT_FALSE(eq(L"a", L"b")); + EXPECT_FALSE(eq(L"a", std::wstring_view(L"b"))); + EXPECT_FALSE(eq(L"a", std::wstring(L"b"))); +#endif +} + +TEST(BasicStringViewTest, WStringViewEqWorks) { +#ifndef ABSL_HAVE_STD_STRING_VIEW + GTEST_SKIP(); +#else + hash_default_eq<std::wstring_view> eq; + EXPECT_TRUE(eq(L"a", L"a")); + EXPECT_TRUE(eq(L"a", std::wstring_view(L"a"))); + EXPECT_TRUE(eq(L"a", std::wstring(L"a"))); + EXPECT_FALSE(eq(L"a", L"b")); + EXPECT_FALSE(eq(L"a", std::wstring_view(L"b"))); + EXPECT_FALSE(eq(L"a", std::wstring(L"b"))); +#endif +} + +TEST(BasicStringViewTest, U16StringEqWorks) { +#ifndef ABSL_HAVE_STD_STRING_VIEW + GTEST_SKIP(); +#else + hash_default_eq<std::u16string> eq; + EXPECT_TRUE(eq(u"a", u"a")); + EXPECT_TRUE(eq(u"a", std::u16string_view(u"a"))); + EXPECT_TRUE(eq(u"a", std::u16string(u"a"))); + EXPECT_FALSE(eq(u"a", u"b")); + EXPECT_FALSE(eq(u"a", std::u16string_view(u"b"))); + EXPECT_FALSE(eq(u"a", std::u16string(u"b"))); +#endif +} + +TEST(BasicStringViewTest, U16StringViewEqWorks) { +#ifndef ABSL_HAVE_STD_STRING_VIEW + GTEST_SKIP(); +#else + hash_default_eq<std::u16string_view> eq; + EXPECT_TRUE(eq(u"a", u"a")); + EXPECT_TRUE(eq(u"a", std::u16string_view(u"a"))); + EXPECT_TRUE(eq(u"a", std::u16string(u"a"))); + EXPECT_FALSE(eq(u"a", u"b")); + EXPECT_FALSE(eq(u"a", std::u16string_view(u"b"))); + EXPECT_FALSE(eq(u"a", std::u16string(u"b"))); +#endif +} + +TEST(BasicStringViewTest, U32StringEqWorks) { +#ifndef ABSL_HAVE_STD_STRING_VIEW + GTEST_SKIP(); +#else + hash_default_eq<std::u32string> eq; + EXPECT_TRUE(eq(U"a", U"a")); + EXPECT_TRUE(eq(U"a", std::u32string_view(U"a"))); + EXPECT_TRUE(eq(U"a", std::u32string(U"a"))); + EXPECT_FALSE(eq(U"a", U"b")); + EXPECT_FALSE(eq(U"a", std::u32string_view(U"b"))); + EXPECT_FALSE(eq(U"a", std::u32string(U"b"))); +#endif +} + +TEST(BasicStringViewTest, U32StringViewEqWorks) { +#ifndef ABSL_HAVE_STD_STRING_VIEW + GTEST_SKIP(); +#else + hash_default_eq<std::u32string_view> eq; + EXPECT_TRUE(eq(U"a", U"a")); + EXPECT_TRUE(eq(U"a", std::u32string_view(U"a"))); + EXPECT_TRUE(eq(U"a", std::u32string(U"a"))); + EXPECT_FALSE(eq(U"a", U"b")); + EXPECT_FALSE(eq(U"a", std::u32string_view(U"b"))); + EXPECT_FALSE(eq(U"a", std::u32string(U"b"))); +#endif +} + +TEST(BasicStringViewTest, WStringHashWorks) { +#ifndef ABSL_HAVE_STD_STRING_VIEW + GTEST_SKIP(); +#else + hash_default_hash<std::wstring> hash; + auto h = hash(L"a"); + EXPECT_EQ(h, hash(std::wstring_view(L"a"))); + EXPECT_EQ(h, hash(std::wstring(L"a"))); + EXPECT_NE(h, hash(std::wstring_view(L"b"))); + EXPECT_NE(h, hash(std::wstring(L"b"))); +#endif +} + +TEST(BasicStringViewTest, WStringViewHashWorks) { +#ifndef ABSL_HAVE_STD_STRING_VIEW + GTEST_SKIP(); +#else + hash_default_hash<std::wstring_view> hash; + auto h = hash(L"a"); + EXPECT_EQ(h, hash(std::wstring_view(L"a"))); + EXPECT_EQ(h, hash(std::wstring(L"a"))); + EXPECT_NE(h, hash(std::wstring_view(L"b"))); + EXPECT_NE(h, hash(std::wstring(L"b"))); +#endif +} + +TEST(BasicStringViewTest, U16StringHashWorks) { +#ifndef ABSL_HAVE_STD_STRING_VIEW + GTEST_SKIP(); +#else + hash_default_hash<std::u16string> hash; + auto h = hash(u"a"); + EXPECT_EQ(h, hash(std::u16string_view(u"a"))); + EXPECT_EQ(h, hash(std::u16string(u"a"))); + EXPECT_NE(h, hash(std::u16string_view(u"b"))); + EXPECT_NE(h, hash(std::u16string(u"b"))); +#endif +} + +TEST(BasicStringViewTest, U16StringViewHashWorks) { +#ifndef ABSL_HAVE_STD_STRING_VIEW + GTEST_SKIP(); +#else + hash_default_hash<std::u16string_view> hash; + auto h = hash(u"a"); + EXPECT_EQ(h, hash(std::u16string_view(u"a"))); + EXPECT_EQ(h, hash(std::u16string(u"a"))); + EXPECT_NE(h, hash(std::u16string_view(u"b"))); + EXPECT_NE(h, hash(std::u16string(u"b"))); +#endif +} + +TEST(BasicStringViewTest, U32StringHashWorks) { +#ifndef ABSL_HAVE_STD_STRING_VIEW + GTEST_SKIP(); +#else + hash_default_hash<std::u32string> hash; + auto h = hash(U"a"); + EXPECT_EQ(h, hash(std::u32string_view(U"a"))); + EXPECT_EQ(h, hash(std::u32string(U"a"))); + EXPECT_NE(h, hash(std::u32string_view(U"b"))); + EXPECT_NE(h, hash(std::u32string(U"b"))); +#endif +} + +TEST(BasicStringViewTest, U32StringViewHashWorks) { +#ifndef ABSL_HAVE_STD_STRING_VIEW + GTEST_SKIP(); +#else + hash_default_hash<std::u32string_view> hash; + auto h = hash(U"a"); + EXPECT_EQ(h, hash(std::u32string_view(U"a"))); + EXPECT_EQ(h, hash(std::u32string(U"a"))); + EXPECT_NE(h, hash(std::u32string_view(U"b"))); + EXPECT_NE(h, hash(std::u32string(U"b"))); +#endif +} + struct NoDeleter { template <class T> void operator()(const T* ptr) const {} diff --git a/absl/container/internal/hash_generator_testing.cc b/absl/container/internal/hash_generator_testing.cc index 59cc5aac..e89dfdb5 100644 --- a/absl/container/internal/hash_generator_testing.cc +++ b/absl/container/internal/hash_generator_testing.cc @@ -16,6 +16,8 @@ #include <deque> +#include "absl/base/no_destructor.h" + namespace absl { ABSL_NAMESPACE_BEGIN namespace container_internal { @@ -41,11 +43,11 @@ class RandomDeviceSeedSeq { } // namespace std::mt19937_64* GetSharedRng() { - static auto* rng = [] { + static absl::NoDestructor<std::mt19937_64> rng([] { RandomDeviceSeedSeq seed_seq; - return new std::mt19937_64(seed_seq); - }(); - return rng; + return std::mt19937_64(seed_seq); + }()); + return rng.get(); } std::string Generator<std::string>::operator()() const { @@ -59,7 +61,7 @@ std::string Generator<std::string>::operator()() const { } absl::string_view Generator<absl::string_view>::operator()() const { - static auto* arena = new std::deque<std::string>(); + static absl::NoDestructor<std::deque<std::string>> arena; // NOLINTNEXTLINE(runtime/int) std::uniform_int_distribution<short> chars(0x20, 0x7E); arena->emplace_back(); diff --git a/absl/container/internal/hashtable_debug.h b/absl/container/internal/hashtable_debug.h index 19d52121..c79c1a98 100644 --- a/absl/container/internal/hashtable_debug.h +++ b/absl/container/internal/hashtable_debug.h @@ -95,14 +95,6 @@ size_t AllocatedByteSize(const C& c) { HashtableDebugAccess<C>::AllocatedByteSize(c); } -// Returns a tight lower bound for AllocatedByteSize(c) where `c` is of type `C` -// and `c.size()` is equal to `num_elements`. -template <typename C> -size_t LowerBoundAllocatedByteSize(size_t num_elements) { - return absl::container_internal::hashtable_debug_internal:: - HashtableDebugAccess<C>::LowerBoundAllocatedByteSize(num_elements); -} - } // namespace container_internal ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/container/internal/hashtablez_sampler.cc b/absl/container/internal/hashtablez_sampler.cc index 6b6d3491..79a0973a 100644 --- a/absl/container/internal/hashtablez_sampler.cc +++ b/absl/container/internal/hashtablez_sampler.cc @@ -23,11 +23,13 @@ #include "absl/base/attributes.h" #include "absl/base/config.h" +#include "absl/base/internal/raw_logging.h" #include "absl/debugging/stacktrace.h" #include "absl/memory/memory.h" #include "absl/profiling/internal/exponential_biased.h" #include "absl/profiling/internal/sample_recorder.h" #include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" #include "absl/utility/utility.h" namespace absl { diff --git a/absl/container/internal/hashtablez_sampler.h b/absl/container/internal/hashtablez_sampler.h index d8fd8f34..e41ee2d7 100644 --- a/absl/container/internal/hashtablez_sampler.h +++ b/absl/container/internal/hashtablez_sampler.h @@ -137,18 +137,7 @@ class HashtablezInfoHandle { UnsampleSlow(info_); } - HashtablezInfoHandle(const HashtablezInfoHandle&) = delete; - HashtablezInfoHandle& operator=(const HashtablezInfoHandle&) = delete; - - HashtablezInfoHandle(HashtablezInfoHandle&& o) noexcept - : info_(absl::exchange(o.info_, nullptr)) {} - HashtablezInfoHandle& operator=(HashtablezInfoHandle&& o) noexcept { - if (ABSL_PREDICT_FALSE(info_ != nullptr)) { - UnsampleSlow(info_); - } - info_ = absl::exchange(o.info_, nullptr); - return *this; - } + inline bool IsSampled() const { return ABSL_PREDICT_FALSE(info_ != nullptr); } inline void RecordStorageChanged(size_t size, size_t capacity) { if (ABSL_PREDICT_TRUE(info_ == nullptr)) return; @@ -198,6 +187,7 @@ class HashtablezInfoHandle { explicit HashtablezInfoHandle(std::nullptr_t) {} inline void Unregister() {} + inline bool IsSampled() const { return false; } inline void RecordStorageChanged(size_t /*size*/, size_t /*capacity*/) {} inline void RecordRehash(size_t /*total_probe_length*/) {} inline void RecordReservation(size_t /*target_capacity*/) {} diff --git a/absl/container/internal/hashtablez_sampler_test.cc b/absl/container/internal/hashtablez_sampler_test.cc index 665d518f..8ebb08da 100644 --- a/absl/container/internal/hashtablez_sampler_test.cc +++ b/absl/container/internal/hashtablez_sampler_test.cc @@ -42,16 +42,11 @@ namespace container_internal { #if defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE) class HashtablezInfoHandlePeer { public: - static bool IsSampled(const HashtablezInfoHandle& h) { - return h.info_ != nullptr; - } - static HashtablezInfo* GetInfo(HashtablezInfoHandle* h) { return h->info_; } }; #else class HashtablezInfoHandlePeer { public: - static bool IsSampled(const HashtablezInfoHandle&) { return false; } static HashtablezInfo* GetInfo(HashtablezInfoHandle*) { return nullptr; } }; #endif // defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE) @@ -267,7 +262,7 @@ TEST(HashtablezSamplerTest, Sample) { for (int i = 0; i < 1000000; ++i) { HashtablezInfoHandle h = Sample(test_element_size); ++total; - if (HashtablezInfoHandlePeer::IsSampled(h)) { + if (h.IsSampled()) { ++num_sampled; } sample_rate = static_cast<double>(num_sampled) / total; @@ -294,6 +289,7 @@ TEST(HashtablezSamplerTest, Handle) { }); EXPECT_TRUE(found); + h.Unregister(); h = HashtablezInfoHandle(); found = false; sampler.Iterate([&](const HashtablezInfo& h) { diff --git a/absl/container/internal/inlined_vector.h b/absl/container/internal/inlined_vector.h index 0398f530..0eb9c34d 100644 --- a/absl/container/internal/inlined_vector.h +++ b/absl/container/internal/inlined_vector.h @@ -26,6 +26,7 @@ #include <utility> #include "absl/base/attributes.h" +#include "absl/base/config.h" #include "absl/base/macros.h" #include "absl/container/internal/compressed_tuple.h" #include "absl/memory/memory.h" @@ -77,13 +78,6 @@ using IsAtLeastForwardIterator = std::is_convertible< std::forward_iterator_tag>; template <typename A> -using IsMemcpyOk = - absl::conjunction<std::is_same<A, std::allocator<ValueType<A>>>, - absl::is_trivially_copy_constructible<ValueType<A>>, - 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>>; @@ -308,11 +302,36 @@ class Storage { struct ElementwiseConstructPolicy {}; using MoveAssignmentPolicy = absl::conditional_t< - IsMemcpyOk<A>::value, MemcpyPolicy, + // Fast path: if the value type can be trivially move assigned and + // destroyed, and we know the allocator doesn't do anything fancy, then + // it's safe for us to simply adopt the contents of the storage for + // `other` and remove its own reference to them. It's as if we had + // individually move-assigned each value and then destroyed the original. + absl::conjunction<absl::is_trivially_move_assignable<ValueType<A>>, + absl::is_trivially_destructible<ValueType<A>>, + std::is_same<A, std::allocator<ValueType<A>>>>::value, + MemcpyPolicy, + // Otherwise we use move assignment if possible. If not, we simulate + // move assignment using move construction. + // + // Note that this is in contrast to e.g. std::vector and std::optional, + // which are themselves not move-assignable when their contained type is + // not. absl::conditional_t<IsMoveAssignOk<A>::value, ElementwiseAssignPolicy, ElementwiseConstructPolicy>>; - using SwapPolicy = absl::conditional_t< - IsMemcpyOk<A>::value, MemcpyPolicy, + + // The policy to be used specifically when swapping inlined elements. + using SwapInlinedElementsPolicy = absl::conditional_t< + // Fast path: if the value type can be trivially move constructed/assigned + // and destroyed, and we know the allocator doesn't do anything fancy, + // then it's safe for us to simply swap the bytes in the inline storage. + // It's as if we had move-constructed a temporary vector, move-assigned + // one to the other, then move-assigned the first from the temporary. + absl::conjunction<absl::is_trivially_move_constructible<ValueType<A>>, + absl::is_trivially_move_assignable<ValueType<A>>, + absl::is_trivially_destructible<ValueType<A>>, + std::is_same<A, std::allocator<ValueType<A>>>>::value, + MemcpyPolicy, absl::conditional_t<IsSwapOk<A>::value, ElementwiseSwapPolicy, ElementwiseConstructPolicy>>; @@ -335,14 +354,21 @@ class Storage { : metadata_(allocator, /* size and is_allocated */ 0u) {} ~Storage() { + // Fast path: if we are empty and not allocated, there's nothing to do. if (GetSizeAndIsAllocated() == 0) { - // Empty and not allocated; nothing to do. - } else if (IsMemcpyOk<A>::value) { - // No destructors need to be run; just deallocate if necessary. + return; + } + + // Fast path: if no destructors need to be run and we know the allocator + // doesn't do anything fancy, then all we need to do is deallocate (and + // maybe not even that). + if (absl::is_trivially_destructible<ValueType<A>>::value && + std::is_same<A, std::allocator<ValueType<A>>>::value) { DeallocateIfAllocated(); - } else { - DestroyContents(); + return; } + + DestroyContents(); } // --------------------------------------------------------------------------- @@ -359,20 +385,34 @@ class Storage { bool GetIsAllocated() const { return GetSizeAndIsAllocated() & 1; } - Pointer<A> GetAllocatedData() { return data_.allocated.allocated_data; } + Pointer<A> GetAllocatedData() { + // GCC 12 has a false-positive -Wmaybe-uninitialized warning here. +#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + return data_.allocated.allocated_data; +#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0) +#pragma GCC diagnostic pop +#endif + } ConstPointer<A> GetAllocatedData() const { return data_.allocated.allocated_data; } - Pointer<A> GetInlinedData() { - return reinterpret_cast<Pointer<A>>( - std::addressof(data_.inlined.inlined_data[0])); + // ABSL_ATTRIBUTE_NO_SANITIZE_CFI is used because the memory pointed to may be + // uninitialized, a common pattern in allocate()+construct() APIs. + // https://clang.llvm.org/docs/ControlFlowIntegrity.html#bad-cast-checking + // NOTE: When this was written, LLVM documentation did not explicitly + // mention that casting `char*` and using `reinterpret_cast` qualifies + // as a bad cast. + ABSL_ATTRIBUTE_NO_SANITIZE_CFI Pointer<A> GetInlinedData() { + return reinterpret_cast<Pointer<A>>(data_.inlined.inlined_data); } - ConstPointer<A> GetInlinedData() const { - return reinterpret_cast<ConstPointer<A>>( - std::addressof(data_.inlined.inlined_data[0])); + ABSL_ATTRIBUTE_NO_SANITIZE_CFI ConstPointer<A> GetInlinedData() const { + return reinterpret_cast<ConstPointer<A>>(data_.inlined.inlined_data); } SizeType<A> GetAllocatedCapacity() const { @@ -461,8 +501,32 @@ class Storage { } void MemcpyFrom(const Storage& other_storage) { - ABSL_HARDENING_ASSERT(IsMemcpyOk<A>::value || - other_storage.GetIsAllocated()); + // Assumption check: it doesn't make sense to memcpy inlined elements unless + // we know the allocator doesn't do anything fancy, and one of the following + // holds: + // + // * The elements are trivially relocatable. + // + // * It's possible to trivially assign the elements and then destroy the + // source. + // + // * It's possible to trivially copy construct/assign the elements. + // + { + using V = ValueType<A>; + ABSL_HARDENING_ASSERT( + other_storage.GetIsAllocated() || + (std::is_same<A, std::allocator<V>>::value && + ( + // First case above + absl::is_trivially_relocatable<V>::value || + // Second case above + (absl::is_trivially_move_assignable<V>::value && + absl::is_trivially_destructible<V>::value) || + // Third case above + (absl::is_trivially_copy_constructible<V>::value || + absl::is_trivially_copy_assignable<V>::value)))); + } GetSizeAndIsAllocated() = other_storage.GetSizeAndIsAllocated(); data_ = other_storage.data_; @@ -542,13 +606,19 @@ void Storage<T, N, A>::InitFrom(const Storage& other) { dst = allocation.data; src = other.GetAllocatedData(); } - if (IsMemcpyOk<A>::value) { + + // Fast path: if the value type is trivially copy constructible and we know + // the allocator doesn't do anything fancy, then we know it is legal for us to + // simply memcpy the other vector's elements. + if (absl::is_trivially_copy_constructible<ValueType<A>>::value && + std::is_same<A, std::allocator<ValueType<A>>>::value) { std::memcpy(reinterpret_cast<char*>(dst), reinterpret_cast<const char*>(src), n * sizeof(ValueType<A>)); } else { auto values = IteratorValueAdapter<A, ConstPointer<A>>(src); ConstructElements<A>(GetAllocator(), dst, values, n); } + GetSizeAndIsAllocated() = other.GetSizeAndIsAllocated(); } @@ -921,7 +991,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()) { - SwapInlinedElements(SwapPolicy{}, other_storage_ptr); + SwapInlinedElements(SwapInlinedElementsPolicy{}, other_storage_ptr); } else { Storage* allocated_ptr = this; Storage* inlined_ptr = other_storage_ptr; @@ -995,7 +1065,7 @@ 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 + // post-swap allocator. Allocators will be swapped later on outside of // `SwapInlinedElements`. Storage* small_ptr = this; Storage* large_ptr = other; diff --git a/absl/container/internal/layout.h b/absl/container/internal/layout.h index a59a2430..a4ba6101 100644 --- a/absl/container/internal/layout.h +++ b/absl/container/internal/layout.h @@ -55,7 +55,7 @@ // `Partial()` comes in handy when the array sizes are embedded into the // allocation. // -// // size_t[1] containing N, size_t[1] containing M, double[N], int[M]. +// // size_t[0] containing N, size_t[1] containing M, double[N], int[M]. // using L = Layout<size_t, size_t, double, int>; // // unsigned char* Allocate(size_t n, size_t m) { @@ -172,6 +172,7 @@ #include <utility> #include "absl/base/config.h" +#include "absl/debugging/internal/demangle.h" #include "absl/meta/type_traits.h" #include "absl/strings/str_cat.h" #include "absl/types/span.h" @@ -181,14 +182,6 @@ #include <sanitizer/asan_interface.h> #endif -#if defined(__GXX_RTTI) -#define ABSL_INTERNAL_HAS_CXA_DEMANGLE -#endif - -#ifdef ABSL_INTERNAL_HAS_CXA_DEMANGLE -#include <cxxabi.h> -#endif - namespace absl { ABSL_NAMESPACE_BEGIN namespace container_internal { @@ -294,19 +287,11 @@ constexpr size_t Max(size_t a, size_t b, Ts... rest) { template <class T> std::string TypeName() { std::string out; - int status = 0; - char* demangled = nullptr; -#ifdef ABSL_INTERNAL_HAS_CXA_DEMANGLE - demangled = abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, &status); -#endif - if (status == 0 && demangled != nullptr) { // Demangling succeeded. - absl::StrAppend(&out, "<", demangled, ">"); - free(demangled); - } else { -#if defined(__GXX_RTTI) || defined(_CPPRTTI) - absl::StrAppend(&out, "<", typeid(T).name(), ">"); +#if ABSL_INTERNAL_HAS_RTTI + absl::StrAppend(&out, "<", + absl::debugging_internal::DemangleString(typeid(T).name()), + ">"); #endif - } return out; } diff --git a/absl/container/internal/layout_benchmark.cc b/absl/container/internal/layout_benchmark.cc index d8636e8d..3af35e33 100644 --- a/absl/container/internal/layout_benchmark.cc +++ b/absl/container/internal/layout_benchmark.cc @@ -85,7 +85,7 @@ void BM_OffsetVariable(benchmark::State& state) { size_t m = 5; size_t k = 7; ABSL_RAW_CHECK(L::Partial(n, m, k).template Offset<3>() == Offset, - "Inavlid offset"); + "Invalid offset"); for (auto _ : state) { DoNotOptimize(n); DoNotOptimize(m); diff --git a/absl/container/internal/layout_test.cc b/absl/container/internal/layout_test.cc index 54e5d5bb..ae55cf7e 100644 --- a/absl/container/internal/layout_test.cc +++ b/absl/container/internal/layout_test.cc @@ -19,15 +19,20 @@ #include <stddef.h> #include <cstdint> +#include <cstring> +#include <initializer_list> #include <memory> -#include <sstream> +#include <ostream> +#include <string> +#include <tuple> #include <type_traits> #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/config.h" -#include "absl/base/internal/raw_logging.h" +#include "absl/log/check.h" #include "absl/types/span.h" +#include "absl/utility/utility.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -38,7 +43,7 @@ using ::absl::Span; using ::testing::ElementsAre; size_t Distance(const void* from, const void* to) { - ABSL_RAW_CHECK(from <= to, "Distance must be non-negative"); + CHECK_LE(from, to) << "Distance must be non-negative"; return static_cast<const char*>(to) - static_cast<const char*>(from); } @@ -366,7 +371,7 @@ TEST(Layout, Sizes) { } TEST(Layout, PointerByIndex) { - alignas(max_align_t) const unsigned char p[100] = {}; + alignas(max_align_t) const unsigned char p[100] = {0}; { using L = Layout<int32_t>; EXPECT_EQ(0, Distance(p, Type<const int32_t*>(L::Partial().Pointer<0>(p)))); @@ -447,7 +452,7 @@ TEST(Layout, PointerByIndex) { } TEST(Layout, PointerByType) { - alignas(max_align_t) const unsigned char p[100] = {}; + alignas(max_align_t) const unsigned char p[100] = {0}; { using L = Layout<int32_t>; EXPECT_EQ( @@ -526,7 +531,7 @@ TEST(Layout, PointerByType) { } TEST(Layout, MutablePointerByIndex) { - alignas(max_align_t) unsigned char p[100]; + alignas(max_align_t) unsigned char p[100] = {0}; { using L = Layout<int32_t>; EXPECT_EQ(0, Distance(p, Type<int32_t*>(L::Partial().Pointer<0>(p)))); @@ -581,7 +586,7 @@ TEST(Layout, MutablePointerByIndex) { } TEST(Layout, MutablePointerByType) { - alignas(max_align_t) unsigned char p[100]; + alignas(max_align_t) unsigned char p[100] = {0}; { using L = Layout<int32_t>; EXPECT_EQ(0, Distance(p, Type<int32_t*>(L::Partial().Pointer<int32_t>(p)))); @@ -647,7 +652,7 @@ TEST(Layout, MutablePointerByType) { } TEST(Layout, Pointers) { - alignas(max_align_t) const unsigned char p[100] = {}; + alignas(max_align_t) const unsigned char p[100] = {0}; using L = Layout<int8_t, int8_t, Int128>; { const auto x = L::Partial(); @@ -683,7 +688,7 @@ TEST(Layout, Pointers) { } TEST(Layout, MutablePointers) { - alignas(max_align_t) unsigned char p[100]; + alignas(max_align_t) unsigned char p[100] = {0}; using L = Layout<int8_t, int8_t, Int128>; { const auto x = L::Partial(); @@ -716,7 +721,7 @@ TEST(Layout, MutablePointers) { } TEST(Layout, SliceByIndexSize) { - alignas(max_align_t) const unsigned char p[100] = {}; + alignas(max_align_t) const unsigned char p[100] = {0}; { using L = Layout<int32_t>; EXPECT_EQ(0, L::Partial(0).Slice<0>(p).size()); @@ -744,7 +749,7 @@ TEST(Layout, SliceByIndexSize) { } TEST(Layout, SliceByTypeSize) { - alignas(max_align_t) const unsigned char p[100] = {}; + alignas(max_align_t) const unsigned char p[100] = {0}; { using L = Layout<int32_t>; EXPECT_EQ(0, L::Partial(0).Slice<int32_t>(p).size()); @@ -766,7 +771,7 @@ TEST(Layout, SliceByTypeSize) { } TEST(Layout, MutableSliceByIndexSize) { - alignas(max_align_t) unsigned char p[100]; + alignas(max_align_t) unsigned char p[100] = {0}; { using L = Layout<int32_t>; EXPECT_EQ(0, L::Partial(0).Slice<0>(p).size()); @@ -794,7 +799,7 @@ TEST(Layout, MutableSliceByIndexSize) { } TEST(Layout, MutableSliceByTypeSize) { - alignas(max_align_t) unsigned char p[100]; + alignas(max_align_t) unsigned char p[100] = {0}; { using L = Layout<int32_t>; EXPECT_EQ(0, L::Partial(0).Slice<int32_t>(p).size()); @@ -816,7 +821,7 @@ TEST(Layout, MutableSliceByTypeSize) { } TEST(Layout, SliceByIndexData) { - alignas(max_align_t) const unsigned char p[100] = {}; + alignas(max_align_t) const unsigned char p[100] = {0}; { using L = Layout<int32_t>; EXPECT_EQ( @@ -939,7 +944,7 @@ TEST(Layout, SliceByIndexData) { } TEST(Layout, SliceByTypeData) { - alignas(max_align_t) const unsigned char p[100] = {}; + alignas(max_align_t) const unsigned char p[100] = {0}; { using L = Layout<int32_t>; EXPECT_EQ( @@ -1037,7 +1042,7 @@ TEST(Layout, SliceByTypeData) { } TEST(Layout, MutableSliceByIndexData) { - alignas(max_align_t) unsigned char p[100]; + alignas(max_align_t) unsigned char p[100] = {0}; { using L = Layout<int32_t>; EXPECT_EQ( @@ -1122,7 +1127,7 @@ TEST(Layout, MutableSliceByIndexData) { } TEST(Layout, MutableSliceByTypeData) { - alignas(max_align_t) unsigned char p[100]; + alignas(max_align_t) unsigned char p[100] = {0}; { using L = Layout<int32_t>; EXPECT_EQ( @@ -1268,7 +1273,7 @@ testing::PolymorphicMatcher<TupleMatcher<M...>> Tuple(M... matchers) { } TEST(Layout, Slices) { - alignas(max_align_t) const unsigned char p[100] = {}; + alignas(max_align_t) const unsigned char p[100] = {0}; using L = Layout<int8_t, int8_t, Int128>; { const auto x = L::Partial(); @@ -1302,7 +1307,7 @@ TEST(Layout, Slices) { } TEST(Layout, MutableSlices) { - alignas(max_align_t) unsigned char p[100] = {}; + alignas(max_align_t) unsigned char p[100] = {0}; using L = Layout<int8_t, int8_t, Int128>; { const auto x = L::Partial(); diff --git a/absl/container/internal/node_slot_policy.h b/absl/container/internal/node_slot_policy.h index baba5743..3f1874d4 100644 --- a/absl/container/internal/node_slot_policy.h +++ b/absl/container/internal/node_slot_policy.h @@ -62,9 +62,12 @@ struct node_slot_policy { Policy::delete_element(alloc, *slot); } + // Returns true_type to indicate that transfer can use memcpy. template <class Alloc> - static void transfer(Alloc*, slot_type* new_slot, slot_type* old_slot) { + static std::true_type transfer(Alloc*, slot_type* new_slot, + slot_type* old_slot) { *new_slot = *old_slot; + return {}; } static size_t space_used(const slot_type* slot) { diff --git a/absl/container/internal/node_slot_policy_test.cc b/absl/container/internal/node_slot_policy_test.cc index 51b7467b..d4ea919d 100644 --- a/absl/container/internal/node_slot_policy_test.cc +++ b/absl/container/internal/node_slot_policy_test.cc @@ -18,6 +18,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/base/config.h" #include "absl/container/internal/hash_policy_traits.h" namespace absl { @@ -61,6 +62,7 @@ TEST_F(NodeTest, transfer) { int* b = &s; NodePolicy::transfer(&alloc, &a, &b); EXPECT_EQ(&s, a); + EXPECT_TRUE(NodePolicy::transfer_uses_memcpy()); } } // namespace diff --git a/absl/container/internal/raw_hash_map.h b/absl/container/internal/raw_hash_map.h index c7df2efc..97182bc7 100644 --- a/absl/container/internal/raw_hash_map.h +++ b/absl/container/internal/raw_hash_map.h @@ -19,6 +19,8 @@ #include <type_traits> #include <utility> +#include "absl/base/attributes.h" +#include "absl/base/config.h" #include "absl/base/internal/throw_delegate.h" #include "absl/container/internal/container_memory.h" #include "absl/container/internal/raw_hash_set.h" // IWYU pragma: export @@ -71,43 +73,51 @@ class raw_hash_map : public raw_hash_set<Policy, Hash, Eq, Alloc> { // m.insert_or_assign(n, n); template <class K = key_type, class V = mapped_type, K* = nullptr, V* = nullptr> - std::pair<iterator, bool> insert_or_assign(key_arg<K>&& k, V&& v) { + std::pair<iterator, bool> insert_or_assign(key_arg<K>&& k, V&& v) + ABSL_ATTRIBUTE_LIFETIME_BOUND { return insert_or_assign_impl(std::forward<K>(k), std::forward<V>(v)); } template <class K = key_type, class V = mapped_type, K* = nullptr> - std::pair<iterator, bool> insert_or_assign(key_arg<K>&& k, const V& v) { + std::pair<iterator, bool> insert_or_assign(key_arg<K>&& k, const V& v) + ABSL_ATTRIBUTE_LIFETIME_BOUND { return insert_or_assign_impl(std::forward<K>(k), v); } template <class K = key_type, class V = mapped_type, V* = nullptr> - std::pair<iterator, bool> insert_or_assign(const key_arg<K>& k, V&& v) { + std::pair<iterator, bool> insert_or_assign(const key_arg<K>& k, V&& v) + ABSL_ATTRIBUTE_LIFETIME_BOUND { return insert_or_assign_impl(k, std::forward<V>(v)); } template <class K = key_type, class V = mapped_type> - std::pair<iterator, bool> insert_or_assign(const key_arg<K>& k, const V& v) { + std::pair<iterator, bool> insert_or_assign(const key_arg<K>& k, const V& v) + ABSL_ATTRIBUTE_LIFETIME_BOUND { return insert_or_assign_impl(k, v); } template <class K = key_type, class V = mapped_type, K* = nullptr, V* = nullptr> - iterator insert_or_assign(const_iterator, key_arg<K>&& k, V&& v) { + iterator insert_or_assign(const_iterator, key_arg<K>&& k, + V&& v) ABSL_ATTRIBUTE_LIFETIME_BOUND { return insert_or_assign(std::forward<K>(k), std::forward<V>(v)).first; } template <class K = key_type, class V = mapped_type, K* = nullptr> - iterator insert_or_assign(const_iterator, key_arg<K>&& k, const V& v) { + iterator insert_or_assign(const_iterator, key_arg<K>&& k, + const V& v) ABSL_ATTRIBUTE_LIFETIME_BOUND { return insert_or_assign(std::forward<K>(k), v).first; } template <class K = key_type, class V = mapped_type, V* = nullptr> - iterator insert_or_assign(const_iterator, const key_arg<K>& k, V&& v) { + iterator insert_or_assign(const_iterator, const key_arg<K>& k, + V&& v) ABSL_ATTRIBUTE_LIFETIME_BOUND { return insert_or_assign(k, std::forward<V>(v)).first; } template <class K = key_type, class V = mapped_type> - iterator insert_or_assign(const_iterator, const key_arg<K>& k, const V& v) { + iterator insert_or_assign(const_iterator, const key_arg<K>& k, + const V& v) ABSL_ATTRIBUTE_LIFETIME_BOUND { return insert_or_assign(k, v).first; } @@ -118,29 +128,33 @@ class raw_hash_map : public raw_hash_set<Policy, Hash, Eq, Alloc> { typename std::enable_if< !std::is_convertible<K, const_iterator>::value, int>::type = 0, K* = nullptr> - std::pair<iterator, bool> try_emplace(key_arg<K>&& k, Args&&... args) { + std::pair<iterator, bool> try_emplace(key_arg<K>&& k, Args&&... args) + ABSL_ATTRIBUTE_LIFETIME_BOUND { return try_emplace_impl(std::forward<K>(k), std::forward<Args>(args)...); } template <class K = key_type, class... Args, typename std::enable_if< !std::is_convertible<K, const_iterator>::value, int>::type = 0> - std::pair<iterator, bool> try_emplace(const key_arg<K>& k, Args&&... args) { + std::pair<iterator, bool> try_emplace(const key_arg<K>& k, Args&&... args) + ABSL_ATTRIBUTE_LIFETIME_BOUND { return try_emplace_impl(k, std::forward<Args>(args)...); } template <class K = key_type, class... Args, K* = nullptr> - iterator try_emplace(const_iterator, key_arg<K>&& k, Args&&... args) { + iterator try_emplace(const_iterator, key_arg<K>&& k, + Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND { return try_emplace(std::forward<K>(k), std::forward<Args>(args)...).first; } template <class K = key_type, class... Args> - iterator try_emplace(const_iterator, const key_arg<K>& k, Args&&... args) { + iterator try_emplace(const_iterator, const key_arg<K>& k, + Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND { return try_emplace(k, std::forward<Args>(args)...).first; } template <class K = key_type, class P = Policy> - MappedReference<P> at(const key_arg<K>& key) { + MappedReference<P> at(const key_arg<K>& key) ABSL_ATTRIBUTE_LIFETIME_BOUND { auto it = this->find(key); if (it == this->end()) { base_internal::ThrowStdOutOfRange( @@ -150,7 +164,8 @@ class raw_hash_map : public raw_hash_set<Policy, Hash, Eq, Alloc> { } template <class K = key_type, class P = Policy> - MappedConstReference<P> at(const key_arg<K>& key) const { + MappedConstReference<P> at(const key_arg<K>& key) const + ABSL_ATTRIBUTE_LIFETIME_BOUND { auto it = this->find(key); if (it == this->end()) { base_internal::ThrowStdOutOfRange( @@ -160,18 +175,28 @@ class raw_hash_map : public raw_hash_set<Policy, Hash, Eq, Alloc> { } template <class K = key_type, class P = Policy, K* = nullptr> - MappedReference<P> operator[](key_arg<K>&& key) { - return Policy::value(&*try_emplace(std::forward<K>(key)).first); + MappedReference<P> operator[](key_arg<K>&& key) + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // It is safe to use unchecked_deref here because try_emplace + // will always return an iterator pointing to a valid item in the table, + // since it inserts if nothing is found for the given key. + return Policy::value( + &this->unchecked_deref(try_emplace(std::forward<K>(key)).first)); } template <class K = key_type, class P = Policy> - MappedReference<P> operator[](const key_arg<K>& key) { - return Policy::value(&*try_emplace(key).first); + MappedReference<P> operator[](const key_arg<K>& key) + ABSL_ATTRIBUTE_LIFETIME_BOUND { + // It is safe to use unchecked_deref here because try_emplace + // will always return an iterator pointing to a valid item in the table, + // since it inserts if nothing is found for the given key. + return Policy::value(&this->unchecked_deref(try_emplace(key).first)); } private: template <class K, class V> - std::pair<iterator, bool> insert_or_assign_impl(K&& k, V&& v) { + std::pair<iterator, bool> insert_or_assign_impl(K&& k, V&& v) + ABSL_ATTRIBUTE_LIFETIME_BOUND { auto res = this->find_or_prepare_insert(k); if (res.second) this->emplace_at(res.first, std::forward<K>(k), std::forward<V>(v)); @@ -181,7 +206,8 @@ class raw_hash_map : public raw_hash_set<Policy, Hash, Eq, Alloc> { } template <class K = key_type, class... Args> - std::pair<iterator, bool> try_emplace_impl(K&& k, Args&&... args) { + std::pair<iterator, bool> try_emplace_impl(K&& k, Args&&... args) + ABSL_ATTRIBUTE_LIFETIME_BOUND { auto res = this->find_or_prepare_insert(k); if (res.second) this->emplace_at(res.first, std::piecewise_construct, diff --git a/absl/container/internal/raw_hash_set.cc b/absl/container/internal/raw_hash_set.cc index 3677ac59..9f8ea519 100644 --- a/absl/container/internal/raw_hash_set.cc +++ b/absl/container/internal/raw_hash_set.cc @@ -15,34 +15,52 @@ #include "absl/container/internal/raw_hash_set.h" #include <atomic> +#include <cassert> #include <cstddef> +#include <cstdint> #include <cstring> +#include "absl/base/attributes.h" #include "absl/base/config.h" +#include "absl/base/dynamic_annotations.h" +#include "absl/container/internal/container_memory.h" +#include "absl/hash/hash.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace container_internal { -// A single block of empty control bytes for tables without any slots allocated. -// This enables removing a branch in the hot path of find(). -// We have 17 bytes because there may be a generation counter. Any constant is -// fine for the generation counter. -alignas(16) ABSL_CONST_INIT ABSL_DLL const ctrl_t kEmptyGroup[17] = { +// We have space for `growth_left` before a single block of control bytes. A +// single block of empty control bytes for tables without any slots allocated. +// This enables removing a branch in the hot path of find(). In order to ensure +// that the control bytes are aligned to 16, we have 16 bytes before the control +// bytes even though growth_left only needs 8. +constexpr ctrl_t ZeroCtrlT() { return static_cast<ctrl_t>(0); } +alignas(16) ABSL_CONST_INIT ABSL_DLL const ctrl_t kEmptyGroup[32] = { + ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), + ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), + ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), + ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), ctrl_t::kSentinel, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, - ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, - static_cast<ctrl_t>(0)}; + ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty}; #ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL constexpr size_t Group::kWidth; #endif +namespace { + // Returns "random" seed. inline size_t RandomSeed() { #ifdef ABSL_HAVE_THREAD_LOCAL static thread_local size_t counter = 0; + // On Linux kernels >= 5.4 the MSAN runtime has a false-positive when + // accessing thread local storage data from loaded libraries + // (https://github.com/google/sanitizers/issues/1265), for this reason counter + // needs to be annotated as initialized. + ABSL_ANNOTATE_MEMORY_IS_INITIALIZED(&counter, sizeof(size_t)); size_t value = ++counter; #else // ABSL_HAVE_THREAD_LOCAL static std::atomic<size_t> counter(0); @@ -51,6 +69,41 @@ inline size_t RandomSeed() { return value ^ static_cast<size_t>(reinterpret_cast<uintptr_t>(&counter)); } +bool ShouldRehashForBugDetection(const ctrl_t* ctrl, size_t capacity) { + // Note: we can't use the abseil-random library because abseil-random + // depends on swisstable. We want to return true with probability + // `min(1, RehashProbabilityConstant() / capacity())`. In order to do this, + // we probe based on a random hash and see if the offset is less than + // RehashProbabilityConstant(). + return probe(ctrl, capacity, absl::HashOf(RandomSeed())).offset() < + RehashProbabilityConstant(); +} + +} // namespace + +GenerationType* EmptyGeneration() { + if (SwisstableGenerationsEnabled()) { + constexpr size_t kNumEmptyGenerations = 1024; + static constexpr GenerationType kEmptyGenerations[kNumEmptyGenerations]{}; + return const_cast<GenerationType*>( + &kEmptyGenerations[RandomSeed() % kNumEmptyGenerations]); + } + return nullptr; +} + +bool CommonFieldsGenerationInfoEnabled:: + should_rehash_for_bug_detection_on_insert(const ctrl_t* ctrl, + size_t capacity) const { + if (reserved_growth_ == kReservedGrowthJustRanOut) return true; + if (reserved_growth_ > 0) return false; + return ShouldRehashForBugDetection(ctrl, capacity); +} + +bool CommonFieldsGenerationInfoEnabled::should_rehash_for_bug_detection_on_move( + const ctrl_t* ctrl, size_t capacity) const { + return ShouldRehashForBugDetection(ctrl, capacity); +} + bool ShouldInsertBackwards(size_t hash, const ctrl_t* ctrl) { // To avoid problems with weak hashes and single bit tests, we use % 13. // TODO(kfm,sbenza): revisit after we do unconditional mixing @@ -75,21 +128,14 @@ FindInfo find_first_non_full_outofline(const CommonFields& common, return find_first_non_full(common, hash); } -// Return address of the ith slot in slots where each slot occupies slot_size. -static inline void* SlotAddress(void* slot_array, size_t slot, - size_t slot_size) { - return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(slot_array) + - (slot * slot_size)); -} - -// Return the address of the slot just after slot assuming each slot -// has the specified size. +// Returns the address of the slot just after slot assuming each slot has the +// specified size. static inline void* NextSlot(void* slot, size_t slot_size) { return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(slot) + slot_size); } -// Return the address of the slot just before slot assuming each slot -// has the specified size. +// Returns the address of the slot just before slot assuming each slot has the +// specified size. static inline void* PrevSlot(void* slot, size_t slot_size) { return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(slot) - slot_size); } @@ -97,8 +143,8 @@ static inline void* PrevSlot(void* slot, size_t slot_size) { void DropDeletesWithoutResize(CommonFields& common, const PolicyFunctions& policy, void* tmp_space) { void* set = &common; - void* slot_array = common.slots_; - const size_t capacity = common.capacity_; + void* slot_array = common.slot_array(); + const size_t capacity = common.capacity(); assert(IsValidCapacity(capacity)); assert(!is_small(capacity)); // Algorithm: @@ -117,7 +163,7 @@ void DropDeletesWithoutResize(CommonFields& common, // swap current element with target element // mark target as FULL // repeat procedure for current slot with moved from element (target) - ctrl_t* ctrl = common.control_; + ctrl_t* ctrl = common.control(); ConvertDeletedToEmptyAndFullToDeleted(ctrl, capacity); auto hasher = policy.hash_slot; auto transfer = policy.transfer; @@ -175,48 +221,160 @@ void DropDeletesWithoutResize(CommonFields& common, common.infoz().RecordRehash(total_probe_length); } -void EraseMetaOnly(CommonFields& c, ctrl_t* it, size_t slot_size) { - assert(IsFull(*it) && "erasing a dangling iterator"); - --c.size_; - const auto index = static_cast<size_t>(it - c.control_); - const size_t index_before = (index - Group::kWidth) & c.capacity_; - const auto empty_after = Group(it).MaskEmpty(); - const auto empty_before = Group(c.control_ + index_before).MaskEmpty(); +static bool WasNeverFull(CommonFields& c, size_t index) { + if (is_single_group(c.capacity())) { + return true; + } + const size_t index_before = (index - Group::kWidth) & c.capacity(); + const auto empty_after = Group(c.control() + index).MaskEmpty(); + const auto empty_before = Group(c.control() + index_before).MaskEmpty(); // We count how many consecutive non empties we have to the right and to the // left of `it`. If the sum is >= kWidth then there is at least one probe // window that might have seen a full group. - bool was_never_full = empty_before && empty_after && - static_cast<size_t>(empty_after.TrailingZeros()) + - empty_before.LeadingZeros() < - Group::kWidth; - - SetCtrl(c, index, was_never_full ? ctrl_t::kEmpty : ctrl_t::kDeleted, - slot_size); - c.growth_left() += (was_never_full ? 1 : 0); + return empty_before && empty_after && + static_cast<size_t>(empty_after.TrailingZeros()) + + empty_before.LeadingZeros() < + Group::kWidth; +} + +void EraseMetaOnly(CommonFields& c, size_t index, size_t slot_size) { + assert(IsFull(c.control()[index]) && "erasing a dangling iterator"); + c.decrement_size(); c.infoz().RecordErase(); + + if (WasNeverFull(c, index)) { + SetCtrl(c, index, ctrl_t::kEmpty, slot_size); + c.set_growth_left(c.growth_left() + 1); + return; + } + + SetCtrl(c, index, ctrl_t::kDeleted, slot_size); } void ClearBackingArray(CommonFields& c, const PolicyFunctions& policy, bool reuse) { - c.size_ = 0; + c.set_size(0); if (reuse) { ResetCtrl(c, policy.slot_size); - c.infoz().RecordStorageChanged(0, c.capacity_); + ResetGrowthLeft(c); + c.infoz().RecordStorageChanged(0, c.capacity()); } else { - void* set = &c; - (*policy.dealloc)(set, policy, c.control_, c.slots_, c.capacity_); - c.control_ = EmptyGroup(); - c.set_generation_ptr(EmptyGeneration()); - c.slots_ = nullptr; - c.capacity_ = 0; - c.growth_left() = 0; + // We need to record infoz before calling dealloc, which will unregister + // infoz. c.infoz().RecordClearedReservation(); - assert(c.size_ == 0); c.infoz().RecordStorageChanged(0, 0); + (*policy.dealloc)(c, policy); + c.set_control(EmptyGroup()); + c.set_generation_ptr(EmptyGeneration()); + c.set_slots(nullptr); + c.set_capacity(0); } } +void HashSetResizeHelper::GrowIntoSingleGroupShuffleControlBytes( + ctrl_t* new_ctrl, size_t new_capacity) const { + assert(is_single_group(new_capacity)); + constexpr size_t kHalfWidth = Group::kWidth / 2; + assert(old_capacity_ < kHalfWidth); + + const size_t half_old_capacity = old_capacity_ / 2; + + // NOTE: operations are done with compile time known size = kHalfWidth. + // Compiler optimizes that into single ASM operation. + + // Copy second half of bytes to the beginning. + // We potentially copy more bytes in order to have compile time known size. + // Mirrored bytes from the old_ctrl_ will also be copied. + // In case of old_capacity_ == 3, we will copy 1st element twice. + // Examples: + // old_ctrl = 0S0EEEEEEE... + // new_ctrl = S0EEEEEEEE... + // + // old_ctrl = 01S01EEEEE... + // new_ctrl = 1S01EEEEEE... + // + // old_ctrl = 0123456S0123456EE... + // new_ctrl = 456S0123?????????... + std::memcpy(new_ctrl, old_ctrl_ + half_old_capacity + 1, kHalfWidth); + // Clean up copied kSentinel from old_ctrl. + new_ctrl[half_old_capacity] = ctrl_t::kEmpty; + + // Clean up damaged or uninitialized bytes. + + // Clean bytes after the intended size of the copy. + // Example: + // new_ctrl = 1E01EEEEEEE???? + // *new_ctrl= 1E0EEEEEEEE???? + // position / + std::memset(new_ctrl + old_capacity_ + 1, static_cast<int8_t>(ctrl_t::kEmpty), + kHalfWidth); + // Clean non-mirrored bytes that are not initialized. + // For small old_capacity that may be inside of mirrored bytes zone. + // Examples: + // new_ctrl = 1E0EEEEEEEE??????????.... + // *new_ctrl= 1E0EEEEEEEEEEEEE?????.... + // position / + // + // new_ctrl = 456E0123???????????... + // *new_ctrl= 456E0123EEEEEEEE???... + // position / + std::memset(new_ctrl + kHalfWidth, static_cast<int8_t>(ctrl_t::kEmpty), + kHalfWidth); + // Clean last mirrored bytes that are not initialized + // and will not be overwritten by mirroring. + // Examples: + // new_ctrl = 1E0EEEEEEEEEEEEE???????? + // *new_ctrl= 1E0EEEEEEEEEEEEEEEEEEEEE + // position S / + // + // new_ctrl = 456E0123EEEEEEEE??????????????? + // *new_ctrl= 456E0123EEEEEEEE???????EEEEEEEE + // position S / + std::memset(new_ctrl + new_capacity + kHalfWidth, + static_cast<int8_t>(ctrl_t::kEmpty), kHalfWidth); + + // Create mirrored bytes. old_capacity_ < kHalfWidth + // Example: + // new_ctrl = 456E0123EEEEEEEE???????EEEEEEEE + // *new_ctrl= 456E0123EEEEEEEE456E0123EEEEEEE + // position S/ + ctrl_t g[kHalfWidth]; + std::memcpy(g, new_ctrl, kHalfWidth); + std::memcpy(new_ctrl + new_capacity + 1, g, kHalfWidth); + + // Finally set sentinel to its place. + new_ctrl[new_capacity] = ctrl_t::kSentinel; +} + +void HashSetResizeHelper::GrowIntoSingleGroupShuffleTransferableSlots( + void* old_slots, void* new_slots, size_t slot_size) const { + assert(old_capacity_ > 0); + const size_t half_old_capacity = old_capacity_ / 2; + + SanitizerUnpoisonMemoryRegion(old_slots, slot_size * old_capacity_); + std::memcpy(new_slots, + SlotAddress(old_slots, half_old_capacity + 1, slot_size), + slot_size * half_old_capacity); + std::memcpy(SlotAddress(new_slots, half_old_capacity + 1, slot_size), + old_slots, slot_size * (half_old_capacity + 1)); +} + +void HashSetResizeHelper::GrowSizeIntoSingleGroupTransferable( + CommonFields& c, void* old_slots, size_t slot_size) { + assert(old_capacity_ < Group::kWidth / 2); + assert(is_single_group(c.capacity())); + assert(IsGrowingIntoSingleGroupApplicable(old_capacity_, c.capacity())); + + GrowIntoSingleGroupShuffleControlBytes(c.control(), c.capacity()); + GrowIntoSingleGroupShuffleTransferableSlots(old_slots, c.slot_array(), + slot_size); + + // We poison since GrowIntoSingleGroupShuffleTransferableSlots + // may leave empty slots unpoisoned. + PoisonSingleGroupEmptySlots(c, slot_size); +} + } // namespace container_internal ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/container/internal/raw_hash_set.h b/absl/container/internal/raw_hash_set.h index 61ef196d..3518bc34 100644 --- a/absl/container/internal/raw_hash_set.h +++ b/absl/container/internal/raw_hash_set.h @@ -62,6 +62,11 @@ // pseudo-struct: // // struct BackingArray { +// // Sampling handler. This field isn't present when the sampling is +// // disabled or this allocation hasn't been selected for sampling. +// HashtablezInfoHandle infoz_; +// // The number of elements we can insert before growing the capacity. +// size_t growth_left; // // Control bytes for the "real" slots. // ctrl_t ctrl[capacity]; // // Always `ctrl_t::kSentinel`. This is used by iterators to find when to @@ -115,7 +120,7 @@ // starting with that index and extract potential candidates: occupied slots // with a control byte equal to `H2(hash(x))`. If we find an empty slot in the // group, we stop and return an error. Each candidate slot `y` is compared with -// `x`; if `x == y`, we are done and return `&y`; otherwise we contine to the +// `x`; if `x == y`, we are done and return `&y`; otherwise we continue to the // next probe index. Tombstones effectively behave like full slots that never // match the value we're looking for. // @@ -173,9 +178,12 @@ #define ABSL_CONTAINER_INTERNAL_RAW_HASH_SET_H_ #include <algorithm> +#include <cassert> #include <cmath> +#include <cstddef> #include <cstdint> #include <cstring> +#include <initializer_list> #include <iterator> #include <limits> #include <memory> @@ -183,13 +191,16 @@ #include <type_traits> #include <utility> +#include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/internal/endian.h" -#include "absl/base/internal/prefetch.h" #include "absl/base/internal/raw_logging.h" +#include "absl/base/macros.h" #include "absl/base/optimization.h" +#include "absl/base/options.h" #include "absl/base/port.h" -#include "absl/container/internal/common.h" +#include "absl/base/prefetch.h" +#include "absl/container/internal/common.h" // IWYU pragma: export // for node_handle #include "absl/container/internal/compressed_tuple.h" #include "absl/container/internal/container_memory.h" #include "absl/container/internal/hash_policy_traits.h" @@ -223,6 +234,7 @@ namespace container_internal { #ifdef ABSL_SWISSTABLE_ENABLE_GENERATIONS #error ABSL_SWISSTABLE_ENABLE_GENERATIONS cannot be directly set #elif defined(ABSL_HAVE_ADDRESS_SANITIZER) || \ + defined(ABSL_HAVE_HWADDRESS_SANITIZER) || \ defined(ABSL_HAVE_MEMORY_SANITIZER) // When compiled in sanitizer mode, we add generation integers to the backing // array and iterators. In the backing array, we store the generation between @@ -235,6 +247,14 @@ namespace container_internal { // We use uint8_t so we don't need to worry about padding. using GenerationType = uint8_t; +// A sentinel value for empty generations. Using 0 makes it easy to constexpr +// initialize an array of this value. +constexpr GenerationType SentinelEmptyGeneration() { return 0; } + +constexpr GenerationType NextGeneration(GenerationType generation) { + return ++generation == SentinelEmptyGeneration() ? ++generation : generation; +} + #ifdef ABSL_SWISSTABLE_ENABLE_GENERATIONS constexpr bool SwisstableGenerationsEnabled() { return true; } constexpr size_t NumGenerationBytes() { return sizeof(GenerationType); } @@ -250,8 +270,21 @@ void SwapAlloc(AllocType& lhs, AllocType& rhs, swap(lhs, rhs); } template <typename AllocType> -void SwapAlloc(AllocType& /*lhs*/, AllocType& /*rhs*/, - std::false_type /* propagate_on_container_swap */) {} +void SwapAlloc(AllocType& lhs, AllocType& rhs, + std::false_type /* propagate_on_container_swap */) { + (void)lhs; + (void)rhs; + assert(lhs == rhs && + "It's UB to call swap with unequal non-propagating allocators."); +} + +template <typename AllocType> +void CopyAlloc(AllocType& lhs, AllocType& rhs, + std::true_type /* propagate_alloc */) { + lhs = rhs; +} +template <typename AllocType> +void CopyAlloc(AllocType&, AllocType&, std::false_type /* propagate_alloc */) {} // The state for a probe sequence. // @@ -349,7 +382,7 @@ uint32_t TrailingZeros(T x) { // width of an abstract bit in the representation. // This mask provides operations for any number of real bits set in an abstract // bit. To add iteration on top of that, implementation must guarantee no more -// than one real bit is set in an abstract bit. +// than the most significant real bit is set in a set abstract bit. template <class T, int SignificantBits, int Shift = 0> class NonIterableBitMask { public: @@ -367,16 +400,18 @@ class NonIterableBitMask { return static_cast<uint32_t>((bit_width(mask_) - 1) >> Shift); } - // Return the number of trailing zero *abstract* bits. + // Returns the number of trailing zero *abstract* bits. uint32_t TrailingZeros() const { return container_internal::TrailingZeros(mask_) >> Shift; } - // Return the number of leading zero *abstract* bits. + // Returns the number of leading zero *abstract* bits. uint32_t LeadingZeros() const { constexpr int total_significant_bits = SignificantBits << Shift; constexpr int extra_bits = sizeof(T) * 8 - total_significant_bits; - return static_cast<uint32_t>(countl_zero(mask_ << extra_bits)) >> Shift; + return static_cast<uint32_t>( + countl_zero(static_cast<T>(mask_ << extra_bits))) >> + Shift; } T mask_; @@ -406,6 +441,10 @@ class BitMask : public NonIterableBitMask<T, SignificantBits, Shift> { using const_iterator = BitMask; BitMask& operator++() { + if (Shift == 3) { + constexpr uint64_t msbs = 0x8080808080808080ULL; + this->mask_ &= msbs; + } this->mask_ &= (this->mask_ - 1); return *this; } @@ -475,19 +514,23 @@ static_assert(ctrl_t::kDeleted == static_cast<ctrl_t>(-2), "ctrl_t::kDeleted must be -2 to make the implementation of " "ConvertSpecialToEmptyAndFullToDeleted efficient"); -ABSL_DLL extern const ctrl_t kEmptyGroup[17]; +// See definition comment for why this is size 32. +ABSL_DLL extern const ctrl_t kEmptyGroup[32]; // Returns a pointer to a control byte group that can be used by empty tables. inline ctrl_t* EmptyGroup() { // Const must be cast away here; no uses of this function will actually write // to it, because it is only used for empty tables. - return const_cast<ctrl_t*>(kEmptyGroup); + return const_cast<ctrl_t*>(kEmptyGroup + 16); } -// Returns a pointer to the generation byte at the end of the empty group, if it -// exists. -inline GenerationType* EmptyGeneration() { - return reinterpret_cast<GenerationType*>(EmptyGroup() + 16); +// Returns a pointer to a generation to use for an empty hashtable. +GenerationType* EmptyGeneration(); + +// Returns whether `generation` is a generation for an empty hashtable that +// could be returned by EmptyGeneration(). +inline bool IsEmptyGeneration(const GenerationType* generation) { + return *generation == SentinelEmptyGeneration(); } // Mixes a randomly generated per-process seed with `hash` and `ctrl` to @@ -574,29 +617,39 @@ struct GroupSse2Impl { } // Returns a bitmask representing the positions of slots that match hash. - BitMask<uint32_t, kWidth> Match(h2_t hash) const { + BitMask<uint16_t, kWidth> Match(h2_t hash) const { auto match = _mm_set1_epi8(static_cast<char>(hash)); - return BitMask<uint32_t, kWidth>( - static_cast<uint32_t>(_mm_movemask_epi8(_mm_cmpeq_epi8(match, ctrl)))); + BitMask<uint16_t, kWidth> result = BitMask<uint16_t, kWidth>(0); + result = BitMask<uint16_t, kWidth>( + static_cast<uint16_t>(_mm_movemask_epi8(_mm_cmpeq_epi8(match, ctrl)))); + return result; } // Returns a bitmask representing the positions of empty slots. - NonIterableBitMask<uint32_t, kWidth> MaskEmpty() const { + NonIterableBitMask<uint16_t, kWidth> MaskEmpty() const { #ifdef ABSL_INTERNAL_HAVE_SSSE3 // This only works because ctrl_t::kEmpty is -128. - return NonIterableBitMask<uint32_t, kWidth>( - static_cast<uint32_t>(_mm_movemask_epi8(_mm_sign_epi8(ctrl, ctrl)))); + return NonIterableBitMask<uint16_t, kWidth>( + static_cast<uint16_t>(_mm_movemask_epi8(_mm_sign_epi8(ctrl, ctrl)))); #else auto match = _mm_set1_epi8(static_cast<char>(ctrl_t::kEmpty)); - return NonIterableBitMask<uint32_t, kWidth>( - static_cast<uint32_t>(_mm_movemask_epi8(_mm_cmpeq_epi8(match, ctrl)))); + return NonIterableBitMask<uint16_t, kWidth>( + static_cast<uint16_t>(_mm_movemask_epi8(_mm_cmpeq_epi8(match, ctrl)))); #endif } + // Returns a bitmask representing the positions of full slots. + // Note: for `is_small()` tables group may contain the "same" slot twice: + // original and mirrored. + BitMask<uint16_t, kWidth> MaskFull() const { + return BitMask<uint16_t, kWidth>( + static_cast<uint16_t>(_mm_movemask_epi8(ctrl) ^ 0xffff)); + } + // Returns a bitmask representing the positions of empty or deleted slots. - NonIterableBitMask<uint32_t, kWidth> MaskEmptyOrDeleted() const { + NonIterableBitMask<uint16_t, kWidth> MaskEmptyOrDeleted() const { auto special = _mm_set1_epi8(static_cast<char>(ctrl_t::kSentinel)); - return NonIterableBitMask<uint32_t, kWidth>(static_cast<uint32_t>( + return NonIterableBitMask<uint16_t, kWidth>(static_cast<uint16_t>( _mm_movemask_epi8(_mm_cmpgt_epi8_fixed(special, ctrl)))); } @@ -635,9 +688,8 @@ struct GroupAArch64Impl { BitMask<uint64_t, kWidth, 3> Match(h2_t hash) const { uint8x8_t dup = vdup_n_u8(hash); auto mask = vceq_u8(ctrl, dup); - constexpr uint64_t msbs = 0x8080808080808080ULL; return BitMask<uint64_t, kWidth, 3>( - vget_lane_u64(vreinterpret_u64_u8(mask), 0) & msbs); + vget_lane_u64(vreinterpret_u64_u8(mask), 0)); } NonIterableBitMask<uint64_t, kWidth, 3> MaskEmpty() const { @@ -649,6 +701,17 @@ struct GroupAArch64Impl { return NonIterableBitMask<uint64_t, kWidth, 3>(mask); } + // Returns a bitmask representing the positions of full slots. + // Note: for `is_small()` tables group may contain the "same" slot twice: + // original and mirrored. + BitMask<uint64_t, kWidth, 3> MaskFull() const { + uint64_t mask = vget_lane_u64( + vreinterpret_u64_u8(vcge_s8(vreinterpret_s8_u8(ctrl), + vdup_n_s8(static_cast<int8_t>(0)))), + 0); + return BitMask<uint64_t, kWidth, 3>(mask); + } + NonIterableBitMask<uint64_t, kWidth, 3> MaskEmptyOrDeleted() const { uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(vcgt_s8( @@ -674,9 +737,10 @@ struct GroupAArch64Impl { void ConvertSpecialToEmptyAndFullToDeleted(ctrl_t* dst) const { uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(ctrl), 0); constexpr uint64_t msbs = 0x8080808080808080ULL; - constexpr uint64_t lsbs = 0x0101010101010101ULL; - auto x = mask & msbs; - auto res = (~x + (x >> 7)) & ~lsbs; + constexpr uint64_t slsbs = 0x0202020202020202ULL; + constexpr uint64_t midbs = 0x7e7e7e7e7e7e7e7eULL; + auto x = slsbs & (mask >> 6); + auto res = (x + midbs) | msbs; little_endian::Store64(dst, res); } @@ -712,13 +776,21 @@ struct GroupPortableImpl { NonIterableBitMask<uint64_t, kWidth, 3> MaskEmpty() const { constexpr uint64_t msbs = 0x8080808080808080ULL; - return NonIterableBitMask<uint64_t, kWidth, 3>((ctrl & (~ctrl << 6)) & + return NonIterableBitMask<uint64_t, kWidth, 3>((ctrl & ~(ctrl << 6)) & msbs); } + // Returns a bitmask representing the positions of full slots. + // Note: for `is_small()` tables group may contain the "same" slot twice: + // original and mirrored. + BitMask<uint64_t, kWidth, 3> MaskFull() const { + constexpr uint64_t msbs = 0x8080808080808080ULL; + return BitMask<uint64_t, kWidth, 3>((ctrl ^ msbs) & msbs); + } + NonIterableBitMask<uint64_t, kWidth, 3> MaskEmptyOrDeleted() const { constexpr uint64_t msbs = 0x8080808080808080ULL; - return NonIterableBitMask<uint64_t, kWidth, 3>((ctrl & (~ctrl << 7)) & + return NonIterableBitMask<uint64_t, kWidth, 3>((ctrl & ~(ctrl << 7)) & msbs); } @@ -743,12 +815,32 @@ struct GroupPortableImpl { #ifdef ABSL_INTERNAL_HAVE_SSE2 using Group = GroupSse2Impl; +using GroupEmptyOrDeleted = GroupSse2Impl; #elif defined(ABSL_INTERNAL_HAVE_ARM_NEON) && defined(ABSL_IS_LITTLE_ENDIAN) using Group = GroupAArch64Impl; +// For Aarch64, we use the portable implementation for counting and masking +// empty or deleted group elements. This is to avoid the latency of moving +// between data GPRs and Neon registers when it does not provide a benefit. +// Using Neon is profitable when we call Match(), but is not when we don't, +// which is the case when we do *EmptyOrDeleted operations. It is difficult to +// make a similar approach beneficial on other architectures such as x86 since +// they have much lower GPR <-> vector register transfer latency and 16-wide +// Groups. +using GroupEmptyOrDeleted = GroupPortableImpl; #else using Group = GroupPortableImpl; +using GroupEmptyOrDeleted = GroupPortableImpl; #endif +// When there is an insertion with no reserved growth, we rehash with +// probability `min(1, RehashProbabilityConstant() / capacity())`. Using a +// constant divided by capacity ensures that inserting N elements is still O(N) +// in the average case. Using the constant 16 means that we expect to rehash ~8 +// times more often than when generations are disabled. We are adding expected +// rehash_probability * #insertions/capacity_growth = 16/capacity * ((7/8 - +// 7/16) * capacity)/capacity_growth = ~7 extra rehashes per capacity growth. +inline size_t RehashProbabilityConstant() { return 16; } + class CommonFieldsGenerationInfoEnabled { // A sentinel value for reserved_growth_ indicating that we just ran out of // reserved growth on the last insertion. When reserve is called and then @@ -760,8 +852,11 @@ class CommonFieldsGenerationInfoEnabled { public: CommonFieldsGenerationInfoEnabled() = default; CommonFieldsGenerationInfoEnabled(CommonFieldsGenerationInfoEnabled&& that) - : reserved_growth_(that.reserved_growth_), generation_(that.generation_) { + : reserved_growth_(that.reserved_growth_), + reservation_size_(that.reservation_size_), + generation_(that.generation_) { that.reserved_growth_ = 0; + that.reservation_size_ = 0; that.generation_ = EmptyGeneration(); } CommonFieldsGenerationInfoEnabled& operator=( @@ -769,26 +864,30 @@ class CommonFieldsGenerationInfoEnabled { // Whether we should rehash on insert in order to detect bugs of using invalid // references. We rehash on the first insertion after reserved_growth_ reaches - // 0 after a call to reserve. - // TODO(b/254649633): we could potentially do a rehash with low probability + // 0 after a call to reserve. We also do a rehash with low probability // whenever reserved_growth_ is zero. - bool should_rehash_for_bug_detection_on_insert() const { - return reserved_growth_ == kReservedGrowthJustRanOut; - } + bool should_rehash_for_bug_detection_on_insert(const ctrl_t* ctrl, + size_t capacity) const; + // Similar to above, except that we don't depend on reserved_growth_. + bool should_rehash_for_bug_detection_on_move(const ctrl_t* ctrl, + size_t capacity) const; void maybe_increment_generation_on_insert() { if (reserved_growth_ == kReservedGrowthJustRanOut) reserved_growth_ = 0; if (reserved_growth_ > 0) { if (--reserved_growth_ == 0) reserved_growth_ = kReservedGrowthJustRanOut; } else { - ++*generation_; + increment_generation(); } } + void increment_generation() { *generation_ = NextGeneration(*generation_); } void reset_reserved_growth(size_t reservation, size_t size) { reserved_growth_ = reservation - size; } size_t reserved_growth() const { return reserved_growth_; } void set_reserved_growth(size_t r) { reserved_growth_ = r; } + size_t reservation_size() const { return reservation_size_; } + void set_reservation_size(size_t r) { reservation_size_ = r; } GenerationType generation() const { return *generation_; } void set_generation(GenerationType g) { *generation_ = g; } GenerationType* generation_ptr() const { return generation_; } @@ -796,10 +895,14 @@ class CommonFieldsGenerationInfoEnabled { private: // The number of insertions remaining that are guaranteed to not rehash due to - // a prior call to reserve. Note: we store reserved growth rather than + // a prior call to reserve. Note: we store reserved growth in addition to // reservation size because calls to erase() decrease size_ but don't decrease // reserved growth. size_t reserved_growth_ = 0; + // The maximum argument to reserve() since the container was cleared. We need + // to keep track of this, in addition to reserved growth, because we reset + // reserved growth to this when erase(begin(), end()) is called. + size_t reservation_size_ = 0; // Pointer to the generation counter, which is used to validate iterators and // is stored in the backing array between the control bytes and the slots. // Note that we can't store the generation inside the container itself and @@ -820,11 +923,19 @@ class CommonFieldsGenerationInfoDisabled { CommonFieldsGenerationInfoDisabled& operator=( CommonFieldsGenerationInfoDisabled&&) = default; - bool should_rehash_for_bug_detection_on_insert() const { return false; } + bool should_rehash_for_bug_detection_on_insert(const ctrl_t*, size_t) const { + return false; + } + bool should_rehash_for_bug_detection_on_move(const ctrl_t*, size_t) const { + return false; + } void maybe_increment_generation_on_insert() {} + void increment_generation() {} void reset_reserved_growth(size_t, size_t) {} size_t reserved_growth() const { return 0; } void set_reserved_growth(size_t) {} + size_t reservation_size() const { return 0; } + void set_reservation_size(size_t) {} GenerationType generation() const { return 0; } void set_generation(GenerationType) {} GenerationType* generation_ptr() const { return nullptr; } @@ -867,6 +978,48 @@ using CommonFieldsGenerationInfo = CommonFieldsGenerationInfoDisabled; using HashSetIteratorGenerationInfo = HashSetIteratorGenerationInfoDisabled; #endif +// Returns whether `n` is a valid capacity (i.e., number of slots). +// +// A valid capacity is a non-zero integer `2^m - 1`. +inline bool IsValidCapacity(size_t n) { return ((n + 1) & n) == 0 && n > 0; } + +// Computes the offset from the start of the backing allocation of control. +// infoz and growth_left are stored at the beginning of the backing array. +inline size_t ControlOffset(bool has_infoz) { + return (has_infoz ? sizeof(HashtablezInfoHandle) : 0) + sizeof(size_t); +} + +// Returns the number of "cloned control bytes". +// +// This is the number of control bytes that are present both at the beginning +// of the control byte array and at the end, such that we can create a +// `Group::kWidth`-width probe window starting from any control byte. +constexpr size_t NumClonedBytes() { return Group::kWidth - 1; } + +// Given the capacity of a table, computes the offset (from the start of the +// backing allocation) of the generation counter (if it exists). +inline size_t GenerationOffset(size_t capacity, bool has_infoz) { + assert(IsValidCapacity(capacity)); + const size_t num_control_bytes = capacity + 1 + NumClonedBytes(); + return ControlOffset(has_infoz) + num_control_bytes; +} + +// Given the capacity of a table, computes the offset (from the start of the +// backing allocation) at which the slots begin. +inline size_t SlotOffset(size_t capacity, size_t slot_align, bool has_infoz) { + assert(IsValidCapacity(capacity)); + return (GenerationOffset(capacity, has_infoz) + NumGenerationBytes() + + slot_align - 1) & + (~slot_align + 1); +} + +// Given the capacity of a table, computes the total size of the backing +// array. +inline size_t AllocSize(size_t capacity, size_t slot_size, size_t slot_align, + bool has_infoz) { + return SlotOffset(capacity, slot_align, has_infoz) + capacity * slot_size; +} + // CommonFields hold the fields in raw_hash_set that do not depend // on template parameters. This allows us to conveniently pass all // of this state to helper functions as a single argument. @@ -879,77 +1032,137 @@ class CommonFields : public CommonFieldsGenerationInfo { CommonFields& operator=(const CommonFields&) = delete; // Movable - CommonFields(CommonFields&& that) - : CommonFieldsGenerationInfo( - std::move(static_cast<CommonFieldsGenerationInfo&&>(that))), - // Explicitly copying fields into "this" and then resetting "that" - // fields generates less code then calling absl::exchange per field. - control_(that.control_), - slots_(that.slots_), - size_(that.size_), - capacity_(that.capacity_), - compressed_tuple_(that.growth_left(), std::move(that.infoz())) { - that.control_ = EmptyGroup(); - that.slots_ = nullptr; - that.size_ = 0; - that.capacity_ = 0; - that.growth_left() = 0; - } + CommonFields(CommonFields&& that) = default; CommonFields& operator=(CommonFields&&) = default; + ctrl_t* control() const { return control_; } + void set_control(ctrl_t* c) { control_ = c; } + void* backing_array_start() const { + // growth_left (and maybe infoz) is stored before control bytes. + assert(reinterpret_cast<uintptr_t>(control()) % alignof(size_t) == 0); + return control() - ControlOffset(has_infoz()); + } + + // Note: we can't use slots() because Qt defines "slots" as a macro. + void* slot_array() const { return slots_; } + void set_slots(void* s) { slots_ = s; } + + // The number of filled slots. + size_t size() const { return size_ >> HasInfozShift(); } + void set_size(size_t s) { + size_ = (s << HasInfozShift()) | (size_ & HasInfozMask()); + } + void increment_size() { + assert(size() < capacity()); + size_ += size_t{1} << HasInfozShift(); + } + void decrement_size() { + assert(size() > 0); + size_ -= size_t{1} << HasInfozShift(); + } + + // The total number of available slots. + size_t capacity() const { return capacity_; } + void set_capacity(size_t c) { + assert(c == 0 || IsValidCapacity(c)); + capacity_ = c; + } + // The number of slots we can still fill without needing to rehash. - size_t& growth_left() { return compressed_tuple_.template get<0>(); } + // This is stored in the heap allocation before the control bytes. + size_t growth_left() const { + const size_t* gl_ptr = reinterpret_cast<size_t*>(control()) - 1; + assert(reinterpret_cast<uintptr_t>(gl_ptr) % alignof(size_t) == 0); + return *gl_ptr; + } + void set_growth_left(size_t gl) { + size_t* gl_ptr = reinterpret_cast<size_t*>(control()) - 1; + assert(reinterpret_cast<uintptr_t>(gl_ptr) % alignof(size_t) == 0); + *gl_ptr = gl; + } - HashtablezInfoHandle& infoz() { return compressed_tuple_.template get<1>(); } - const HashtablezInfoHandle& infoz() const { - return compressed_tuple_.template get<1>(); + bool has_infoz() const { + return ABSL_PREDICT_FALSE((size_ & HasInfozMask()) != 0); + } + void set_has_infoz(bool has_infoz) { + size_ = (size() << HasInfozShift()) | static_cast<size_t>(has_infoz); + } + + HashtablezInfoHandle infoz() { + return has_infoz() + ? *reinterpret_cast<HashtablezInfoHandle*>(backing_array_start()) + : HashtablezInfoHandle(); + } + void set_infoz(HashtablezInfoHandle infoz) { + assert(has_infoz()); + *reinterpret_cast<HashtablezInfoHandle*>(backing_array_start()) = infoz; } + bool should_rehash_for_bug_detection_on_insert() const { + return CommonFieldsGenerationInfo:: + should_rehash_for_bug_detection_on_insert(control(), capacity()); + } + bool should_rehash_for_bug_detection_on_move() const { + return CommonFieldsGenerationInfo:: + should_rehash_for_bug_detection_on_move(control(), capacity()); + } + void maybe_increment_generation_on_move() { + if (capacity() == 0) return; + increment_generation(); + } void reset_reserved_growth(size_t reservation) { - CommonFieldsGenerationInfo::reset_reserved_growth(reservation, size_); + CommonFieldsGenerationInfo::reset_reserved_growth(reservation, size()); } - // TODO(b/259599413): Investigate removing some of these fields: + // The size of the backing array allocation. + size_t alloc_size(size_t slot_size, size_t slot_align) const { + return AllocSize(capacity(), slot_size, slot_align, has_infoz()); + } + + // Returns the number of control bytes set to kDeleted. For testing only. + size_t TombstonesCount() const { + return static_cast<size_t>( + std::count(control(), control() + capacity(), ctrl_t::kDeleted)); + } + + private: + // We store the has_infoz bit in the lowest bit of size_. + static constexpr size_t HasInfozShift() { return 1; } + static constexpr size_t HasInfozMask() { + return (size_t{1} << HasInfozShift()) - 1; + } + + // TODO(b/182800944): Investigate removing some of these fields: // - control/slots can be derived from each other - // - size can be moved into the slot array - // The control bytes (and, also, a pointer to the base of the backing array). + // The control bytes (and, also, a pointer near to the base of the backing + // array). // // This contains `capacity + 1 + NumClonedBytes()` entries, even // when the table is empty (hence EmptyGroup). + // + // Note that growth_left is stored immediately before this pointer. ctrl_t* control_ = EmptyGroup(); // The beginning of the slots, located at `SlotOffset()` bytes after // `control`. May be null for empty tables. void* slots_ = nullptr; - // The number of filled slots. - size_t size_ = 0; - - // The total number of available slots. + // The number of slots in the backing array. This is always 2^N-1 for an + // integer N. NOTE: we tried experimenting with compressing the capacity and + // storing it together with size_: (a) using 6 bits to store the corresponding + // power (N in 2^N-1), and (b) storing 2^N as the most significant bit of + // size_ and storing size in the low bits. Both of these experiments were + // regressions, presumably because we need capacity to do find operations. size_t capacity_ = 0; - // Bundle together growth_left and HashtablezInfoHandle to ensure EBO for - // HashtablezInfoHandle when sampling is turned off. - absl::container_internal::CompressedTuple<size_t, HashtablezInfoHandle> - compressed_tuple_{0u, HashtablezInfoHandle{}}; + // The size and also has one bit that stores whether we have infoz. + size_t size_ = 0; }; -// Returns he number of "cloned control bytes". -// -// This is the number of control bytes that are present both at the beginning -// of the control byte array and at the end, such that we can create a -// `Group::kWidth`-width probe window starting from any control byte. -constexpr size_t NumClonedBytes() { return Group::kWidth - 1; } - template <class Policy, class Hash, class Eq, class Alloc> class raw_hash_set; -// Returns whether `n` is a valid capacity (i.e., number of slots). -// -// A valid capacity is a non-zero integer `2^m - 1`. -inline bool IsValidCapacity(size_t n) { return ((n + 1) & n) == 0 && n > 0; } - // Returns the next valid capacity after `n`. inline size_t NextCapacity(size_t n) { assert(IsValidCapacity(n) || n == 0); @@ -1021,34 +1234,79 @@ size_t SelectBucketCountForIterRange(InputIter first, InputIter last, return 0; } -#define ABSL_INTERNAL_ASSERT_IS_FULL(ctrl, generation, generation_ptr, \ - 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."); \ - if (SwisstableGenerationsEnabled() && generation != *generation_ptr) \ - ABSL_INTERNAL_LOG(FATAL, operation \ - " called on invalidated iterator. The table could " \ - "have rehashed since this iterator was initialized."); \ - ABSL_HARDENING_ASSERT( \ - (IsFull(*ctrl)) && operation \ - " called on invalid iterator. The element might have been erased or " \ - "the table might have rehashed."); \ - } while (0) +constexpr bool SwisstableDebugEnabled() { +#if defined(ABSL_SWISSTABLE_ENABLE_GENERATIONS) || \ + ABSL_OPTION_HARDENED == 1 || !defined(NDEBUG) + return true; +#else + return false; +#endif +} + +inline void AssertIsFull(const ctrl_t* ctrl, GenerationType generation, + const GenerationType* generation_ptr, + const char* operation) { + if (!SwisstableDebugEnabled()) return; + // `SwisstableDebugEnabled()` is also true for release builds with hardening + // enabled. To minimize their impact in those builds: + // - use `ABSL_PREDICT_FALSE()` to provide a compiler hint for code layout + // - use `ABSL_RAW_LOG()` with a format string to reduce code size and improve + // the chances that the hot paths will be inlined. + if (ABSL_PREDICT_FALSE(ctrl == nullptr)) { + ABSL_RAW_LOG(FATAL, "%s called on end() iterator.", operation); + } + if (ABSL_PREDICT_FALSE(ctrl == EmptyGroup())) { + ABSL_RAW_LOG(FATAL, "%s called on default-constructed iterator.", + operation); + } + if (SwisstableGenerationsEnabled()) { + if (ABSL_PREDICT_FALSE(generation != *generation_ptr)) { + ABSL_RAW_LOG(FATAL, + "%s called on invalid iterator. The table could have " + "rehashed or moved since this iterator was initialized.", + operation); + } + if (ABSL_PREDICT_FALSE(!IsFull(*ctrl))) { + ABSL_RAW_LOG( + FATAL, + "%s called on invalid iterator. The element was likely erased.", + operation); + } + } else { + if (ABSL_PREDICT_FALSE(!IsFull(*ctrl))) { + ABSL_RAW_LOG( + FATAL, + "%s called on invalid iterator. The element might have been erased " + "or the table might have rehashed. Consider running with " + "--config=asan to diagnose rehashing issues.", + operation); + } + } +} // Note that for comparisons, null/end iterators are valid. inline void AssertIsValidForComparison(const ctrl_t* ctrl, GenerationType generation, const GenerationType* generation_ptr) { - ABSL_HARDENING_ASSERT((ctrl == nullptr || IsFull(*ctrl)) && - "Invalid iterator comparison. The element might have " - "been erased or the table might have rehashed."); - if (SwisstableGenerationsEnabled() && generation != *generation_ptr) { - ABSL_INTERNAL_LOG(FATAL, - "Invalid iterator comparison. The table could have " - "rehashed since this iterator was initialized."); + if (!SwisstableDebugEnabled()) return; + const bool ctrl_is_valid_for_comparison = + ctrl == nullptr || ctrl == EmptyGroup() || IsFull(*ctrl); + if (SwisstableGenerationsEnabled()) { + if (ABSL_PREDICT_FALSE(generation != *generation_ptr)) { + ABSL_RAW_LOG(FATAL, + "Invalid iterator comparison. The table could have rehashed " + "or moved since this iterator was initialized."); + } + if (ABSL_PREDICT_FALSE(!ctrl_is_valid_for_comparison)) { + ABSL_RAW_LOG( + FATAL, "Invalid iterator comparison. The element was likely erased."); + } + } else { + ABSL_HARDENING_ASSERT( + ctrl_is_valid_for_comparison && + "Invalid iterator comparison. The element might have been erased or " + "the table might have rehashed. Consider running with --config=asan to " + "diagnose rehashing issues."); } } @@ -1074,16 +1332,59 @@ inline bool AreItersFromSameContainer(const ctrl_t* ctrl_a, // Asserts that two iterators come from the same container. // Note: we take slots by reference so that it's not UB if they're uninitialized // as long as we don't read them (when ctrl is null). -// TODO(b/254649633): when generations are enabled, we can detect more cases of -// different containers by comparing the pointers to the generations - this -// can cover cases of end iterators that we would otherwise miss. inline void AssertSameContainer(const ctrl_t* ctrl_a, const ctrl_t* ctrl_b, const void* const& slot_a, - const void* const& slot_b) { - ABSL_HARDENING_ASSERT( - AreItersFromSameContainer(ctrl_a, ctrl_b, slot_a, slot_b) && - "Invalid iterator comparison. The iterators may be from different " - "containers or the container might have rehashed."); + const void* const& slot_b, + const GenerationType* generation_ptr_a, + const GenerationType* generation_ptr_b) { + if (!SwisstableDebugEnabled()) return; + // `SwisstableDebugEnabled()` is also true for release builds with hardening + // enabled. To minimize their impact in those builds: + // - use `ABSL_PREDICT_FALSE()` to provide a compiler hint for code layout + // - use `ABSL_RAW_LOG()` with a format string to reduce code size and improve + // the chances that the hot paths will be inlined. + const bool a_is_default = ctrl_a == EmptyGroup(); + const bool b_is_default = ctrl_b == EmptyGroup(); + if (ABSL_PREDICT_FALSE(a_is_default != b_is_default)) { + ABSL_RAW_LOG( + FATAL, + "Invalid iterator comparison. Comparing default-constructed iterator " + "with non-default-constructed iterator."); + } + if (a_is_default && b_is_default) return; + + if (SwisstableGenerationsEnabled()) { + if (ABSL_PREDICT_TRUE(generation_ptr_a == generation_ptr_b)) return; + const bool a_is_empty = IsEmptyGeneration(generation_ptr_a); + const bool b_is_empty = IsEmptyGeneration(generation_ptr_b); + if (a_is_empty != b_is_empty) { + ABSL_RAW_LOG(FATAL, + "Invalid iterator comparison. Comparing iterator from a " + "non-empty hashtable with an iterator from an empty " + "hashtable."); + } + if (a_is_empty && b_is_empty) { + ABSL_RAW_LOG(FATAL, + "Invalid iterator comparison. Comparing iterators from " + "different empty hashtables."); + } + const bool a_is_end = ctrl_a == nullptr; + const bool b_is_end = ctrl_b == nullptr; + if (a_is_end || b_is_end) { + ABSL_RAW_LOG(FATAL, + "Invalid iterator comparison. Comparing iterator with an " + "end() iterator from a different hashtable."); + } + ABSL_RAW_LOG(FATAL, + "Invalid iterator comparison. Comparing non-end() iterators " + "from different hashtables."); + } else { + ABSL_HARDENING_ASSERT( + AreItersFromSameContainer(ctrl_a, ctrl_b, slot_a, slot_b) && + "Invalid iterator comparison. The iterators may be from different " + "containers or the container might have rehashed or moved. Consider " + "running with --config=asan to diagnose issues."); + } } struct FindInfo { @@ -1105,12 +1406,20 @@ struct FindInfo { // `ShouldInsertBackwards()` for small tables. inline bool is_small(size_t capacity) { return capacity < Group::kWidth - 1; } +// Whether a table fits entirely into a probing group. +// Arbitrary order of elements in such tables is correct. +inline bool is_single_group(size_t capacity) { + return capacity <= Group::kWidth; +} + // Begins a probing operation on `common.control`, using `hash`. -inline probe_seq<Group::kWidth> probe(const CommonFields& common, size_t hash) { - const ctrl_t* ctrl = common.control_; - const size_t capacity = common.capacity_; +inline probe_seq<Group::kWidth> probe(const ctrl_t* ctrl, const size_t capacity, + size_t hash) { return probe_seq<Group::kWidth>(H1(hash, ctrl), capacity); } +inline probe_seq<Group::kWidth> probe(const CommonFields& common, size_t hash) { + return probe(common.control(), common.capacity(), hash); +} // Probes an array of control bits using a probe sequence derived from `hash`, // and returns the offset corresponding to the first deleted or empty slot. @@ -1122,9 +1431,9 @@ inline probe_seq<Group::kWidth> probe(const CommonFields& common, size_t hash) { template <typename = void> inline FindInfo find_first_non_full(const CommonFields& common, size_t hash) { auto seq = probe(common, hash); - const ctrl_t* ctrl = common.control_; + const ctrl_t* ctrl = common.control(); while (true) { - Group g{ctrl + seq.offset()}; + GroupEmptyOrDeleted g{ctrl + seq.offset()}; auto mask = g.MaskEmptyOrDeleted(); if (mask) { #if !defined(NDEBUG) @@ -1132,14 +1441,14 @@ inline FindInfo find_first_non_full(const CommonFields& common, size_t hash) { // In debug build we will randomly insert in either the front or back of // the group. // TODO(kfm,sbenza): revisit after we do unconditional mixing - if (!is_small(common.capacity_) && ShouldInsertBackwards(hash, ctrl)) { + if (!is_small(common.capacity()) && ShouldInsertBackwards(hash, ctrl)) { return {seq.offset(mask.HighestBitSet()), seq.index()}; } #endif return {seq.offset(mask.LowestBitSet()), seq.index()}; } seq.next(); - assert(seq.index() <= common.capacity_ && "full table!"); + assert(seq.index() <= common.capacity() && "full table!"); } } @@ -1153,19 +1462,18 @@ extern template FindInfo find_first_non_full(const CommonFields&, size_t); FindInfo find_first_non_full_outofline(const CommonFields&, size_t); inline void ResetGrowthLeft(CommonFields& common) { - common.growth_left() = CapacityToGrowth(common.capacity_) - common.size_; + common.set_growth_left(CapacityToGrowth(common.capacity()) - common.size()); } // Sets `ctrl` to `{kEmpty, kSentinel, ..., kEmpty}`, marking the entire // array as marked as empty. inline void ResetCtrl(CommonFields& common, size_t slot_size) { - const size_t capacity = common.capacity_; - ctrl_t* ctrl = common.control_; + const size_t capacity = common.capacity(); + ctrl_t* ctrl = common.control(); std::memset(ctrl, static_cast<int8_t>(ctrl_t::kEmpty), capacity + 1 + NumClonedBytes()); ctrl[capacity] = ctrl_t::kSentinel; - SanitizerPoisonMemoryRegion(common.slots_, slot_size * capacity); - ResetGrowthLeft(common); + SanitizerPoisonMemoryRegion(common.slot_array(), slot_size * capacity); } // Sets `ctrl[i]` to `h`. @@ -1174,17 +1482,17 @@ inline void ResetCtrl(CommonFields& common, size_t slot_size) { // mirror the value to the cloned tail if necessary. inline void SetCtrl(const CommonFields& common, size_t i, ctrl_t h, size_t slot_size) { - const size_t capacity = common.capacity_; + const size_t capacity = common.capacity(); assert(i < capacity); - auto* slot_i = static_cast<const char*>(common.slots_) + i * slot_size; + auto* slot_i = static_cast<const char*>(common.slot_array()) + i * slot_size; if (IsFull(h)) { SanitizerUnpoisonMemoryRegion(slot_i, slot_size); } else { SanitizerPoisonMemoryRegion(slot_i, slot_size); } - ctrl_t* ctrl = common.control_; + ctrl_t* ctrl = common.control(); ctrl[i] = h; ctrl[((i - NumClonedBytes()) & capacity) + (NumClonedBytes() & capacity)] = h; } @@ -1195,57 +1503,267 @@ inline void SetCtrl(const CommonFields& common, size_t i, h2_t h, SetCtrl(common, i, static_cast<ctrl_t>(h), slot_size); } -// Given the capacity of a table, computes the offset (from the start of the -// backing allocation) of the generation counter (if it exists). -inline size_t GenerationOffset(size_t capacity) { - assert(IsValidCapacity(capacity)); - const size_t num_control_bytes = capacity + 1 + NumClonedBytes(); - return num_control_bytes; +// growth_left (which is a size_t) is stored with the backing array. +constexpr size_t BackingArrayAlignment(size_t align_of_slot) { + return (std::max)(align_of_slot, alignof(size_t)); } -// Given the capacity of a table, computes the offset (from the start of the -// backing allocation) at which the slots begin. -inline size_t SlotOffset(size_t capacity, size_t slot_align) { - assert(IsValidCapacity(capacity)); - const size_t num_control_bytes = capacity + 1 + NumClonedBytes(); - return (num_control_bytes + NumGenerationBytes() + slot_align - 1) & - (~slot_align + 1); +// Returns the address of the ith slot in slots where each slot occupies +// slot_size. +inline void* SlotAddress(void* slot_array, size_t slot, size_t slot_size) { + return reinterpret_cast<void*>(reinterpret_cast<char*>(slot_array) + + (slot * slot_size)); } -// Given the capacity of a table, computes the total size of the backing -// array. -inline size_t AllocSize(size_t capacity, size_t slot_size, size_t slot_align) { - return SlotOffset(capacity, slot_align) + capacity * slot_size; -} +// Helper class to perform resize of the hash set. +// +// It contains special optimizations for small group resizes. +// See GrowIntoSingleGroupShuffleControlBytes for details. +class HashSetResizeHelper { + public: + explicit HashSetResizeHelper(CommonFields& c) + : old_ctrl_(c.control()), + old_capacity_(c.capacity()), + had_infoz_(c.has_infoz()) {} + + // Optimized for small groups version of `find_first_non_full` applicable + // only right after calling `raw_hash_set::resize`. + // It has implicit assumption that `resize` will call + // `GrowSizeIntoSingleGroup*` in case `IsGrowingIntoSingleGroupApplicable`. + // Falls back to `find_first_non_full` in case of big groups, so it is + // safe to use after `rehash_and_grow_if_necessary`. + static FindInfo FindFirstNonFullAfterResize(const CommonFields& c, + size_t old_capacity, + size_t hash) { + if (!IsGrowingIntoSingleGroupApplicable(old_capacity, c.capacity())) { + return find_first_non_full(c, hash); + } + // Find a location for the new element non-deterministically. + // Note that any position is correct. + // It will located at `half_old_capacity` or one of the other + // empty slots with approximately 50% probability each. + size_t offset = probe(c, hash).offset(); + + // Note that we intentionally use unsigned int underflow. + if (offset - (old_capacity + 1) >= old_capacity) { + // Offset fall on kSentinel or into the mostly occupied first half. + offset = old_capacity / 2; + } + assert(IsEmpty(c.control()[offset])); + return FindInfo{offset, 0}; + } -template <typename Alloc, size_t SizeOfSlot, size_t AlignOfSlot> -ABSL_ATTRIBUTE_NOINLINE void InitializeSlots(CommonFields& c, Alloc alloc) { - assert(c.capacity_); - // Folks with custom allocators often make unwarranted assumptions about the - // behavior of their classes vis-a-vis trivial destructability and what - // calls they will or won't make. Avoid sampling for people with custom - // allocators to get us out of this mess. This is not a hard guarantee but - // a workaround while we plan the exact guarantee we want to provide. - const size_t sample_size = - (std::is_same<Alloc, std::allocator<char>>::value && c.slots_ == nullptr) - ? SizeOfSlot - : 0; - - const size_t cap = c.capacity_; - char* mem = static_cast<char*>( - Allocate<AlignOfSlot>(&alloc, AllocSize(cap, SizeOfSlot, AlignOfSlot))); - const GenerationType old_generation = c.generation(); - c.set_generation_ptr( - reinterpret_cast<GenerationType*>(mem + GenerationOffset(cap))); - c.set_generation(old_generation + 1); - c.control_ = reinterpret_cast<ctrl_t*>(mem); - c.slots_ = mem + SlotOffset(cap, AlignOfSlot); - ResetCtrl(c, SizeOfSlot); - if (sample_size) { - c.infoz() = Sample(sample_size); - } - c.infoz().RecordStorageChanged(c.size_, cap); -} + ctrl_t* old_ctrl() const { return old_ctrl_; } + size_t old_capacity() const { return old_capacity_; } + + // Allocates a backing array for the hashtable. + // Reads `capacity` and updates all other fields based on the result of + // the allocation. + // + // It also may do the folowing actions: + // 1. initialize control bytes + // 2. initialize slots + // 3. deallocate old slots. + // + // We are bundling a lot of functionality + // in one ABSL_ATTRIBUTE_NOINLINE function in order to minimize binary code + // duplication in raw_hash_set<>::resize. + // + // `c.capacity()` must be nonzero. + // POSTCONDITIONS: + // 1. CommonFields is initialized. + // + // if IsGrowingIntoSingleGroupApplicable && TransferUsesMemcpy + // Both control bytes and slots are fully initialized. + // old_slots are deallocated. + // infoz.RecordRehash is called. + // + // if IsGrowingIntoSingleGroupApplicable && !TransferUsesMemcpy + // Control bytes are fully initialized. + // infoz.RecordRehash is called. + // GrowSizeIntoSingleGroup must be called to finish slots initialization. + // + // if !IsGrowingIntoSingleGroupApplicable + // Control bytes are initialized to empty table via ResetCtrl. + // raw_hash_set<>::resize must insert elements regularly. + // infoz.RecordRehash is called if old_capacity == 0. + // + // Returns IsGrowingIntoSingleGroupApplicable result to avoid recomputation. + template <typename Alloc, size_t SizeOfSlot, bool TransferUsesMemcpy, + size_t AlignOfSlot> + ABSL_ATTRIBUTE_NOINLINE bool InitializeSlots(CommonFields& c, void* old_slots, + Alloc alloc) { + assert(c.capacity()); + // Folks with custom allocators often make unwarranted assumptions about the + // behavior of their classes vis-a-vis trivial destructability and what + // calls they will or won't make. Avoid sampling for people with custom + // allocators to get us out of this mess. This is not a hard guarantee but + // a workaround while we plan the exact guarantee we want to provide. + const size_t sample_size = + (std::is_same<Alloc, std::allocator<char>>::value && + c.slot_array() == nullptr) + ? SizeOfSlot + : 0; + HashtablezInfoHandle infoz = + sample_size > 0 ? Sample(sample_size) : c.infoz(); + + const bool has_infoz = infoz.IsSampled(); + const size_t cap = c.capacity(); + const size_t alloc_size = + AllocSize(cap, SizeOfSlot, AlignOfSlot, has_infoz); + char* mem = static_cast<char*>( + Allocate<BackingArrayAlignment(AlignOfSlot)>(&alloc, alloc_size)); + const GenerationType old_generation = c.generation(); + c.set_generation_ptr(reinterpret_cast<GenerationType*>( + mem + GenerationOffset(cap, has_infoz))); + c.set_generation(NextGeneration(old_generation)); + c.set_control(reinterpret_cast<ctrl_t*>(mem + ControlOffset(has_infoz))); + c.set_slots(mem + SlotOffset(cap, AlignOfSlot, has_infoz)); + ResetGrowthLeft(c); + + const bool grow_single_group = + IsGrowingIntoSingleGroupApplicable(old_capacity_, c.capacity()); + if (old_capacity_ != 0 && grow_single_group) { + if (TransferUsesMemcpy) { + GrowSizeIntoSingleGroupTransferable(c, old_slots, SizeOfSlot); + DeallocateOld<AlignOfSlot>(alloc, SizeOfSlot, old_slots); + } else { + GrowIntoSingleGroupShuffleControlBytes(c.control(), c.capacity()); + } + } else { + ResetCtrl(c, SizeOfSlot); + } + + c.set_has_infoz(has_infoz); + if (has_infoz) { + infoz.RecordStorageChanged(c.size(), cap); + if (grow_single_group || old_capacity_ == 0) { + infoz.RecordRehash(0); + } + c.set_infoz(infoz); + } + return grow_single_group; + } + + // Relocates slots into new single group consistent with + // GrowIntoSingleGroupShuffleControlBytes. + // + // PRECONDITIONS: + // 1. GrowIntoSingleGroupShuffleControlBytes was already called. + template <class PolicyTraits, class Alloc> + void GrowSizeIntoSingleGroup(CommonFields& c, Alloc& alloc_ref, + typename PolicyTraits::slot_type* old_slots) { + assert(old_capacity_ < Group::kWidth / 2); + assert(IsGrowingIntoSingleGroupApplicable(old_capacity_, c.capacity())); + using slot_type = typename PolicyTraits::slot_type; + assert(is_single_group(c.capacity())); + + auto* new_slots = reinterpret_cast<slot_type*>(c.slot_array()); + + size_t shuffle_bit = old_capacity_ / 2 + 1; + for (size_t i = 0; i < old_capacity_; ++i) { + if (IsFull(old_ctrl_[i])) { + size_t new_i = i ^ shuffle_bit; + SanitizerUnpoisonMemoryRegion(new_slots + new_i, sizeof(slot_type)); + PolicyTraits::transfer(&alloc_ref, new_slots + new_i, old_slots + i); + } + } + PoisonSingleGroupEmptySlots(c, sizeof(slot_type)); + } + + // Deallocates old backing array. + template <size_t AlignOfSlot, class CharAlloc> + void DeallocateOld(CharAlloc alloc_ref, size_t slot_size, void* old_slots) { + SanitizerUnpoisonMemoryRegion(old_slots, slot_size * old_capacity_); + Deallocate<BackingArrayAlignment(AlignOfSlot)>( + &alloc_ref, old_ctrl_ - ControlOffset(had_infoz_), + AllocSize(old_capacity_, slot_size, AlignOfSlot, had_infoz_)); + } + + private: + // Returns true if `GrowSizeIntoSingleGroup` can be used for resizing. + static bool IsGrowingIntoSingleGroupApplicable(size_t old_capacity, + size_t new_capacity) { + // NOTE that `old_capacity < new_capacity` in order to have + // `old_capacity < Group::kWidth / 2` to make faster copies of 8 bytes. + return is_single_group(new_capacity) && old_capacity < new_capacity; + } + + // Relocates control bytes and slots into new single group for + // transferable objects. + // Must be called only if IsGrowingIntoSingleGroupApplicable returned true. + void GrowSizeIntoSingleGroupTransferable(CommonFields& c, void* old_slots, + size_t slot_size); + + // Shuffle control bits deterministically to the next capacity. + // Returns offset for newly added element with given hash. + // + // PRECONDITIONs: + // 1. new_ctrl is allocated for new_capacity, + // but not initialized. + // 2. new_capacity is a single group. + // + // All elements are transferred into the first `old_capacity + 1` positions + // of the new_ctrl. Elements are rotated by `old_capacity_ / 2 + 1` positions + // in order to change an order and keep it non deterministic. + // Although rotation itself deterministic, position of the new added element + // will be based on `H1` and is not deterministic. + // + // Examples: + // S = kSentinel, E = kEmpty + // + // old_ctrl = SEEEEEEEE... + // new_ctrl = ESEEEEEEE... + // + // old_ctrl = 0SEEEEEEE... + // new_ctrl = E0ESE0EEE... + // + // old_ctrl = 012S012EEEEEEEEE... + // new_ctrl = 2E01EEES2E01EEE... + // + // old_ctrl = 0123456S0123456EEEEEEEEEEE... + // new_ctrl = 456E0123EEEEEES456E0123EEE... + void GrowIntoSingleGroupShuffleControlBytes(ctrl_t* new_ctrl, + size_t new_capacity) const; + + // Shuffle trivially transferable slots in the way consistent with + // GrowIntoSingleGroupShuffleControlBytes. + // + // PRECONDITIONs: + // 1. old_capacity must be non-zero. + // 2. new_ctrl is fully initialized using + // GrowIntoSingleGroupShuffleControlBytes. + // 3. new_slots is allocated and *not* poisoned. + // + // POSTCONDITIONS: + // 1. new_slots are transferred from old_slots_ consistent with + // GrowIntoSingleGroupShuffleControlBytes. + // 2. Empty new_slots are *not* poisoned. + void GrowIntoSingleGroupShuffleTransferableSlots(void* old_slots, + void* new_slots, + size_t slot_size) const; + + // Poison empty slots that were transferred using the deterministic algorithm + // described above. + // PRECONDITIONs: + // 1. new_ctrl is fully initialized using + // GrowIntoSingleGroupShuffleControlBytes. + // 2. new_slots is fully initialized consistent with + // GrowIntoSingleGroupShuffleControlBytes. + void PoisonSingleGroupEmptySlots(CommonFields& c, size_t slot_size) const { + // poison non full items + for (size_t i = 0; i < c.capacity(); ++i) { + if (!IsFull(c.control()[i])) { + SanitizerPoisonMemoryRegion(SlotAddress(c.slot_array(), i, slot_size), + slot_size); + } + } + } + + ctrl_t* old_ctrl_; + size_t old_capacity_; + bool had_infoz_; +}; // PolicyFunctions bundles together some information for a particular // raw_hash_set<T, ...> instantiation. This information is passed to @@ -1254,15 +1772,14 @@ ABSL_ATTRIBUTE_NOINLINE void InitializeSlots(CommonFields& c, Alloc alloc) { struct PolicyFunctions { size_t slot_size; - // Return the hash of the pointed-to slot. + // Returns the hash of the pointed-to slot. size_t (*hash_slot)(void* set, void* slot); // Transfer the contents of src_slot to dst_slot. void (*transfer)(void* set, void* dst_slot, void* src_slot); - // Deallocate the specified backing store which is sized for n slots. - void (*dealloc)(void* set, const PolicyFunctions& policy, ctrl_t* ctrl, - void* slot_array, size_t n); + // Deallocate the backing store from common. + void (*dealloc)(CommonFields& common, const PolicyFunctions& policy); }; // ClearBackingArray clears the backing array, either modifying it in place, @@ -1272,23 +1789,24 @@ void ClearBackingArray(CommonFields& c, const PolicyFunctions& policy, bool reuse); // Type-erased version of raw_hash_set::erase_meta_only. -void EraseMetaOnly(CommonFields& c, ctrl_t* it, size_t slot_size); +void EraseMetaOnly(CommonFields& c, size_t index, size_t slot_size); // Function to place in PolicyFunctions::dealloc for raw_hash_sets // that are using std::allocator. This allows us to share the same // function body for raw_hash_set instantiations that have the // same slot alignment. template <size_t AlignOfSlot> -ABSL_ATTRIBUTE_NOINLINE void DeallocateStandard(void*, - const PolicyFunctions& policy, - ctrl_t* ctrl, void* slot_array, - size_t n) { +ABSL_ATTRIBUTE_NOINLINE void DeallocateStandard(CommonFields& common, + const PolicyFunctions& policy) { // Unpoison before returning the memory to the allocator. - SanitizerUnpoisonMemoryRegion(slot_array, policy.slot_size * n); + SanitizerUnpoisonMemoryRegion(common.slot_array(), + policy.slot_size * common.capacity()); std::allocator<char> alloc; - Deallocate<AlignOfSlot>(&alloc, ctrl, - AllocSize(n, policy.slot_size, AlignOfSlot)); + common.infoz().Unregister(); + Deallocate<BackingArrayAlignment(AlignOfSlot)>( + &alloc, common.backing_array_start(), + common.alloc_size(policy.slot_size, AlignOfSlot)); } // For trivially relocatable types we use memcpy directly. This allows us to @@ -1364,6 +1882,11 @@ class raw_hash_set { using AllocTraits = absl::allocator_traits<allocator_type>; using SlotAlloc = typename absl::allocator_traits< allocator_type>::template rebind_alloc<slot_type>; + // People are often sloppy with the exact type of their allocator (sometimes + // it has an extra const or is missing the pair, but rebinds made it work + // anyway). + using CharAlloc = + typename absl::allocator_traits<Alloc>::template rebind_alloc<char>; using SlotAllocTraits = typename absl::allocator_traits< allocator_type>::template rebind_traits<slot_type>; @@ -1419,22 +1942,19 @@ class raw_hash_set { // PRECONDITION: not an end() iterator. reference operator*() const { - ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, generation(), generation_ptr(), - "operator*()"); - return PolicyTraits::element(slot_); + AssertIsFull(ctrl_, generation(), generation_ptr(), "operator*()"); + return unchecked_deref(); } // PRECONDITION: not an end() iterator. pointer operator->() const { - ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, generation(), generation_ptr(), - "operator->"); + AssertIsFull(ctrl_, generation(), generation_ptr(), "operator->"); return &operator*(); } // PRECONDITION: not an end() iterator. iterator& operator++() { - ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, generation(), generation_ptr(), - "operator++"); + AssertIsFull(ctrl_, generation(), generation_ptr(), "operator++"); ++ctrl_; ++slot_; skip_empty_or_deleted(); @@ -1448,9 +1968,10 @@ class raw_hash_set { } friend bool operator==(const iterator& a, const iterator& b) { - AssertSameContainer(a.ctrl_, b.ctrl_, a.slot_, b.slot_); AssertIsValidForComparison(a.ctrl_, a.generation(), a.generation_ptr()); AssertIsValidForComparison(b.ctrl_, b.generation(), b.generation_ptr()); + AssertSameContainer(a.ctrl_, b.ctrl_, a.slot_, b.slot_, + a.generation_ptr(), b.generation_ptr()); return a.ctrl_ == b.ctrl_; } friend bool operator!=(const iterator& a, const iterator& b) { @@ -1469,7 +1990,7 @@ class raw_hash_set { } // For end() iterators. explicit iterator(const GenerationType* generation_ptr) - : HashSetIteratorGenerationInfo(generation_ptr) {} + : HashSetIteratorGenerationInfo(generation_ptr), ctrl_(nullptr) {} // Fixes up `ctrl_` to point to a full by advancing it and `slot_` until // they reach one. @@ -1477,23 +1998,42 @@ class raw_hash_set { // If a sentinel is reached, we null `ctrl_` out instead. void skip_empty_or_deleted() { while (IsEmptyOrDeleted(*ctrl_)) { - uint32_t shift = Group{ctrl_}.CountLeadingEmptyOrDeleted(); + uint32_t shift = + GroupEmptyOrDeleted{ctrl_}.CountLeadingEmptyOrDeleted(); ctrl_ += shift; slot_ += shift; } if (ABSL_PREDICT_FALSE(*ctrl_ == ctrl_t::kSentinel)) ctrl_ = nullptr; } - ctrl_t* ctrl_ = nullptr; + ctrl_t* control() const { return ctrl_; } + slot_type* slot() const { return slot_; } + + // We use EmptyGroup() for default-constructed iterators so that they can + // be distinguished from end iterators, which have nullptr ctrl_. + ctrl_t* ctrl_ = EmptyGroup(); // To avoid uninitialized member warnings, put slot_ in an anonymous union. // The member is not initialized on singleton and end iterators. union { slot_type* slot_; }; + + // An equality check which skips ABSL Hardening iterator invalidation + // checks. + // Should be used when the lifetimes of the iterators are well-enough + // understood to prove that they cannot be invalid. + bool unchecked_equals(const iterator& b) { return ctrl_ == b.control(); } + + // Dereferences the iterator without ABSL Hardening iterator invalidation + // checks. + reference unchecked_deref() const { return PolicyTraits::element(slot_); } }; class const_iterator { friend class raw_hash_set; + template <class Container, typename Enabler> + friend struct absl::container_internal::hashtable_debug_internal:: + HashtableDebugAccess; public: using iterator_category = typename iterator::iterator_category; @@ -1527,8 +2067,14 @@ class raw_hash_set { const GenerationType* gen) : inner_(const_cast<ctrl_t*>(ctrl), const_cast<slot_type*>(slot), gen) { } + ctrl_t* control() const { return inner_.control(); } + slot_type* slot() const { return inner_.slot(); } iterator inner_; + + bool unchecked_equals(const const_iterator& b) { + return inner_.unchecked_equals(b.inner_); + } }; using node_type = node_handle<Policy, hash_policy_traits<Policy>, Alloc>; @@ -1537,9 +2083,9 @@ class raw_hash_set { // Note: can't use `= default` due to non-default noexcept (causes // problems for some compilers). NOLINTNEXTLINE raw_hash_set() noexcept( - std::is_nothrow_default_constructible<hasher>::value&& - std::is_nothrow_default_constructible<key_equal>::value&& - std::is_nothrow_default_constructible<allocator_type>::value) {} + std::is_nothrow_default_constructible<hasher>::value && + std::is_nothrow_default_constructible<key_equal>::value && + std::is_nothrow_default_constructible<allocator_type>::value) {} ABSL_ATTRIBUTE_NOINLINE explicit raw_hash_set( size_t bucket_count, const hasher& hash = hasher(), @@ -1547,8 +2093,7 @@ class raw_hash_set { const allocator_type& alloc = allocator_type()) : settings_(CommonFields{}, hash, eq, alloc) { if (bucket_count) { - common().capacity_ = NormalizeCapacity(bucket_count); - initialize_slots(); + resize(NormalizeCapacity(bucket_count)); } } @@ -1649,7 +2194,9 @@ class raw_hash_set { raw_hash_set(const raw_hash_set& that, const allocator_type& a) : raw_hash_set(0, that.hash_ref(), that.eq_ref(), a) { - reserve(that.size()); + const size_t size = that.size(); + if (size == 0) return; + reserve(size); // Because the table is guaranteed to be empty, we can do something faster // than a full `insert`. for (const auto& v : that) { @@ -1660,45 +2207,52 @@ class raw_hash_set { common().maybe_increment_generation_on_insert(); infoz().RecordInsert(hash, target.probe_length); } - common().size_ = that.size(); - growth_left() -= that.size(); + common().set_size(size); + set_growth_left(growth_left() - size); } ABSL_ATTRIBUTE_NOINLINE raw_hash_set(raw_hash_set&& that) noexcept( - std::is_nothrow_copy_constructible<hasher>::value&& - std::is_nothrow_copy_constructible<key_equal>::value&& - std::is_nothrow_copy_constructible<allocator_type>::value) + std::is_nothrow_copy_constructible<hasher>::value && + std::is_nothrow_copy_constructible<key_equal>::value && + std::is_nothrow_copy_constructible<allocator_type>::value) : // Hash, equality and allocator are copied instead of moved because // `that` must be left valid. If Hash is std::function<Key>, moving it // would create a nullptr functor that cannot be called. - settings_(absl::exchange(that.common(), CommonFields{}), - that.hash_ref(), that.eq_ref(), that.alloc_ref()) {} + // TODO(b/296061262): move instead of copying hash/eq/alloc. + // Note: we avoid using exchange for better generated code. + settings_(std::move(that.common()), that.hash_ref(), that.eq_ref(), + that.alloc_ref()) { + that.common() = CommonFields{}; + maybe_increment_generation_or_rehash_on_move(); + } raw_hash_set(raw_hash_set&& that, const allocator_type& a) : settings_(CommonFields{}, that.hash_ref(), that.eq_ref(), a) { if (a == that.alloc_ref()) { std::swap(common(), that.common()); + maybe_increment_generation_or_rehash_on_move(); } else { - reserve(that.size()); - // Note: this will copy elements of dense_set and unordered_set instead of - // moving them. This can be fixed if it ever becomes an issue. - for (auto& elem : that) insert(std::move(elem)); + move_elements_allocs_unequal(std::move(that)); } } raw_hash_set& operator=(const raw_hash_set& that) { - raw_hash_set tmp(that, - AllocTraits::propagate_on_container_copy_assignment::value - ? that.alloc_ref() - : alloc_ref()); - swap(tmp); - return *this; + if (ABSL_PREDICT_FALSE(this == &that)) return *this; + constexpr bool propagate_alloc = + AllocTraits::propagate_on_container_copy_assignment::value; + // TODO(ezb): maybe avoid allocating a new backing array if this->capacity() + // is an exact match for that.size(). If this->capacity() is too big, then + // it would make iteration very slow to reuse the allocation. Maybe we can + // do the same heuristic as clear() and reuse if it's small enough. + raw_hash_set tmp(that, propagate_alloc ? that.alloc_ref() : alloc_ref()); + // NOLINTNEXTLINE: not returning *this for performance. + return assign_impl<propagate_alloc>(std::move(tmp)); } raw_hash_set& operator=(raw_hash_set&& that) noexcept( - absl::allocator_traits<allocator_type>::is_always_equal::value&& - std::is_nothrow_move_assignable<hasher>::value&& - std::is_nothrow_move_assignable<key_equal>::value) { + absl::allocator_traits<allocator_type>::is_always_equal::value && + std::is_nothrow_move_assignable<hasher>::value && + std::is_nothrow_move_assignable<key_equal>::value) { // TODO(sbenza): We should only use the operations from the noexcept clause // to make sure we actually adhere to that contract. // NOLINTNEXTLINE: not returning *this for performance. @@ -1707,37 +2261,31 @@ class raw_hash_set { typename AllocTraits::propagate_on_container_move_assignment()); } - ~raw_hash_set() { - const size_t cap = capacity(); - if (!cap) return; - destroy_slots(); - - // Unpoison before returning the memory to the allocator. - SanitizerUnpoisonMemoryRegion(slot_array(), sizeof(slot_type) * cap); - Deallocate<alignof(slot_type)>( - &alloc_ref(), control(), - AllocSize(cap, sizeof(slot_type), alignof(slot_type))); + ~raw_hash_set() { destructor_impl(); } - infoz().Unregister(); - } - - iterator begin() { + iterator begin() ABSL_ATTRIBUTE_LIFETIME_BOUND { auto it = iterator_at(0); it.skip_empty_or_deleted(); return it; } - iterator end() { return iterator(common().generation_ptr()); } + iterator end() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return iterator(common().generation_ptr()); + } - const_iterator begin() const { + const_iterator begin() const ABSL_ATTRIBUTE_LIFETIME_BOUND { return const_cast<raw_hash_set*>(this)->begin(); } - const_iterator end() const { return iterator(common().generation_ptr()); } - const_iterator cbegin() const { return begin(); } - const_iterator cend() const { return end(); } + const_iterator end() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return iterator(common().generation_ptr()); + } + const_iterator cbegin() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return begin(); + } + const_iterator cend() const ABSL_ATTRIBUTE_LIFETIME_BOUND { return end(); } bool empty() const { return !size(); } - size_t size() const { return common().size_; } - size_t capacity() const { return common().capacity_; } + size_t size() const { return common().size(); } + size_t capacity() const { return common().capacity(); } size_t max_size() const { return (std::numeric_limits<size_t>::max)(); } ABSL_ATTRIBUTE_REINITIALIZES void clear() { @@ -1753,21 +2301,10 @@ class raw_hash_set { // Already guaranteed to be empty; so nothing to do. } else { destroy_slots(); - ClearBackingArray(common(), GetPolicyFunctions(), - /*reuse=*/cap < 128); + ClearBackingArray(common(), GetPolicyFunctions(), /*reuse=*/cap < 128); } common().set_reserved_growth(0); - } - - inline void destroy_slots() { - const size_t cap = capacity(); - const ctrl_t* ctrl = control(); - slot_type* slot = slot_array(); - for (size_t i = 0; i != cap; ++i) { - if (IsFull(ctrl[i])) { - PolicyTraits::destroy(&alloc_ref(), slot + i); - } - } + common().set_reservation_size(0); } // This overload kicks in when the argument is an rvalue of insertable and @@ -1780,7 +2317,7 @@ class raw_hash_set { template <class T, RequiresInsertable<T> = 0, class T2 = T, typename std::enable_if<IsDecomposable<T2>::value, int>::type = 0, T* = nullptr> - std::pair<iterator, bool> insert(T&& value) { + std::pair<iterator, bool> insert(T&& value) ABSL_ATTRIBUTE_LIFETIME_BOUND { return emplace(std::forward<T>(value)); } @@ -1795,13 +2332,11 @@ class raw_hash_set { // const char* p = "hello"; // s.insert(p); // - // TODO(romanp): Once we stop supporting gcc 5.1 and below, replace - // RequiresInsertable<T> with RequiresInsertable<const T&>. - // We are hitting this bug: https://godbolt.org/g/1Vht4f. template < - class T, RequiresInsertable<T> = 0, + class T, RequiresInsertable<const T&> = 0, typename std::enable_if<IsDecomposable<const T&>::value, int>::type = 0> - std::pair<iterator, bool> insert(const T& value) { + std::pair<iterator, bool> insert(const T& value) + ABSL_ATTRIBUTE_LIFETIME_BOUND { return emplace(value); } @@ -1810,7 +2345,8 @@ class raw_hash_set { // // flat_hash_map<std::string, int> s; // s.insert({"abc", 42}); - std::pair<iterator, bool> insert(init_type&& value) { + std::pair<iterator, bool> insert(init_type&& value) + ABSL_ATTRIBUTE_LIFETIME_BOUND { return emplace(std::move(value)); } @@ -1819,21 +2355,20 @@ class raw_hash_set { template <class T, RequiresInsertable<T> = 0, class T2 = T, typename std::enable_if<IsDecomposable<T2>::value, int>::type = 0, T* = nullptr> - iterator insert(const_iterator, T&& value) { + iterator insert(const_iterator, T&& value) ABSL_ATTRIBUTE_LIFETIME_BOUND { return insert(std::forward<T>(value)).first; } - // TODO(romanp): Once we stop supporting gcc 5.1 and below, replace - // RequiresInsertable<T> with RequiresInsertable<const T&>. - // We are hitting this bug: https://godbolt.org/g/1Vht4f. template < - class T, RequiresInsertable<T> = 0, + class T, RequiresInsertable<const T&> = 0, typename std::enable_if<IsDecomposable<const T&>::value, int>::type = 0> - iterator insert(const_iterator, const T& value) { + iterator insert(const_iterator, + const T& value) ABSL_ATTRIBUTE_LIFETIME_BOUND { return insert(value).first; } - iterator insert(const_iterator, init_type&& value) { + iterator insert(const_iterator, + init_type&& value) ABSL_ATTRIBUTE_LIFETIME_BOUND { return insert(std::move(value)).first; } @@ -1851,7 +2386,7 @@ class raw_hash_set { insert(ilist.begin(), ilist.end()); } - insert_return_type insert(node_type&& node) { + insert_return_type insert(node_type&& node) ABSL_ATTRIBUTE_LIFETIME_BOUND { if (!node) return {end(), false, node_type()}; const auto& elem = PolicyTraits::element(CommonAccess::GetSlot(node)); auto res = PolicyTraits::apply( @@ -1865,7 +2400,8 @@ class raw_hash_set { } } - iterator insert(const_iterator, node_type&& node) { + iterator insert(const_iterator, + node_type&& node) ABSL_ATTRIBUTE_LIFETIME_BOUND { auto res = insert(std::move(node)); node = std::move(res.node); return res.position; @@ -1882,7 +2418,8 @@ class raw_hash_set { // m.emplace("abc", "xyz"); template <class... Args, typename std::enable_if< IsDecomposable<Args...>::value, int>::type = 0> - std::pair<iterator, bool> emplace(Args&&... args) { + std::pair<iterator, bool> emplace(Args&&... args) + ABSL_ATTRIBUTE_LIFETIME_BOUND { return PolicyTraits::apply(EmplaceDecomposable{*this}, std::forward<Args>(args)...); } @@ -1892,24 +2429,27 @@ class raw_hash_set { // destroys. template <class... Args, typename std::enable_if< !IsDecomposable<Args...>::value, int>::type = 0> - std::pair<iterator, bool> emplace(Args&&... args) { + std::pair<iterator, bool> emplace(Args&&... args) + ABSL_ATTRIBUTE_LIFETIME_BOUND { alignas(slot_type) unsigned char raw[sizeof(slot_type)]; slot_type* slot = reinterpret_cast<slot_type*>(&raw); - PolicyTraits::construct(&alloc_ref(), slot, std::forward<Args>(args)...); + construct(slot, std::forward<Args>(args)...); const auto& elem = PolicyTraits::element(slot); return PolicyTraits::apply(InsertSlot<true>{*this, std::move(*slot)}, elem); } template <class... Args> - iterator emplace_hint(const_iterator, Args&&... args) { + iterator emplace_hint(const_iterator, + Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND { return emplace(std::forward<Args>(args)...).first; } // Extension API: support for lazy emplace. // // Looks up key in the table. If found, returns the iterator to the element. - // Otherwise calls `f` with one argument of type `raw_hash_set::constructor`. + // Otherwise calls `f` with one argument of type `raw_hash_set::constructor`, + // and returns an iterator to the new element. // // `f` must abide by several restrictions: // - it MUST call `raw_hash_set::constructor` with arguments as if a @@ -1952,7 +2492,8 @@ class raw_hash_set { }; template <class K = key_type, class F> - iterator lazy_emplace(const key_arg<K>& key, F&& f) { + iterator lazy_emplace(const key_arg<K>& key, + F&& f) ABSL_ATTRIBUTE_LIFETIME_BOUND { auto res = find_or_prepare_insert(key); if (res.second) { slot_type* slot = slot_array() + res.first; @@ -1997,13 +2538,25 @@ 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_, it.generation(), it.generation_ptr(), - "erase()"); - PolicyTraits::destroy(&alloc_ref(), it.slot_); + AssertIsFull(it.control(), it.generation(), it.generation_ptr(), "erase()"); + destroy(it.slot()); erase_meta_only(it); } - iterator erase(const_iterator first, const_iterator last) { + iterator erase(const_iterator first, + const_iterator last) ABSL_ATTRIBUTE_LIFETIME_BOUND { + // We check for empty first because ClearBackingArray requires that + // capacity() > 0 as a precondition. + if (empty()) return end(); + if (first == begin() && last == end()) { + // TODO(ezb): we access control bytes in destroy_slots so it could make + // sense to combine destroy_slots and ClearBackingArray to avoid cache + // misses when the table is large. Note that we also do this in clear(). + destroy_slots(); + ClearBackingArray(common(), GetPolicyFunctions(), /*reuse=*/true); + common().set_reserved_growth(common().reservation_size()); + return end(); + } while (first != last) { erase(first++); } @@ -2017,8 +2570,8 @@ class raw_hash_set { assert(this != &src); for (auto it = src.begin(), e = src.end(); it != e;) { auto next = std::next(it); - if (PolicyTraits::apply(InsertSlot<false>{*this, std::move(*it.slot_)}, - PolicyTraits::element(it.slot_)) + if (PolicyTraits::apply(InsertSlot<false>{*this, std::move(*it.slot())}, + PolicyTraits::element(it.slot())) .second) { src.erase_meta_only(it); } @@ -2032,11 +2585,9 @@ class raw_hash_set { } node_type extract(const_iterator position) { - ABSL_INTERNAL_ASSERT_IS_FULL(position.inner_.ctrl_, - position.inner_.generation(), - position.inner_.generation_ptr(), "extract()"); - auto node = - CommonAccess::Transfer<node_type>(alloc_ref(), position.inner_.slot_); + AssertIsFull(position.control(), position.inner_.generation(), + position.inner_.generation_ptr(), "extract()"); + auto node = CommonAccess::Transfer<node_type>(alloc_ref(), position.slot()); erase_meta_only(position); return node; } @@ -2064,8 +2615,7 @@ class raw_hash_set { void rehash(size_t n) { if (n == 0 && capacity() == 0) return; if (n == 0 && size() == 0) { - ClearBackingArray(common(), GetPolicyFunctions(), - /*reuse=*/false); + ClearBackingArray(common(), GetPolicyFunctions(), /*reuse=*/false); return; } @@ -2092,6 +2642,7 @@ class raw_hash_set { infoz().RecordReservation(n); } common().reset_reserved_growth(n); + common().set_reservation_size(n); } // Extension API: support for heterogeneous keys. @@ -2117,12 +2668,12 @@ class raw_hash_set { void prefetch(const key_arg<K>& key) const { (void)key; // Avoid probing if we won't be able to prefetch the addresses received. -#ifdef ABSL_INTERNAL_HAVE_PREFETCH +#ifdef ABSL_HAVE_PREFETCH prefetch_heap_block(); auto seq = probe(common(), hash_ref()(key)); - base_internal::PrefetchT0(control() + seq.offset()); - base_internal::PrefetchT0(slot_array() + seq.offset()); -#endif // ABSL_INTERNAL_HAVE_PREFETCH + PrefetchToLocalCache(control() + seq.offset()); + PrefetchToLocalCache(slot_array() + seq.offset()); +#endif // ABSL_HAVE_PREFETCH } // The API of find() has two extensions. @@ -2133,7 +2684,8 @@ class raw_hash_set { // 2. The type of the key argument doesn't have to be key_type. This is so // called heterogeneous key support. template <class K = key_type> - iterator find(const key_arg<K>& key, size_t hash) { + iterator find(const key_arg<K>& key, + size_t hash) ABSL_ATTRIBUTE_LIFETIME_BOUND { auto seq = probe(common(), hash); slot_type* slot_ptr = slot_array(); const ctrl_t* ctrl = control(); @@ -2151,35 +2703,42 @@ class raw_hash_set { } } template <class K = key_type> - iterator find(const key_arg<K>& key) { + iterator find(const key_arg<K>& key) ABSL_ATTRIBUTE_LIFETIME_BOUND { prefetch_heap_block(); return find(key, hash_ref()(key)); } template <class K = key_type> - const_iterator find(const key_arg<K>& key, size_t hash) const { + const_iterator find(const key_arg<K>& key, + size_t hash) const ABSL_ATTRIBUTE_LIFETIME_BOUND { return const_cast<raw_hash_set*>(this)->find(key, hash); } template <class K = key_type> - const_iterator find(const key_arg<K>& key) const { + const_iterator find(const key_arg<K>& key) const + ABSL_ATTRIBUTE_LIFETIME_BOUND { prefetch_heap_block(); return find(key, hash_ref()(key)); } template <class K = key_type> bool contains(const key_arg<K>& key) const { - return find(key) != end(); + // Here neither the iterator returned by `find()` nor `end()` can be invalid + // outside of potential thread-safety issues. + // `find()`'s return value is constructed, used, and then destructed + // all in this context. + return !find(key).unchecked_equals(end()); } template <class K = key_type> - std::pair<iterator, iterator> equal_range(const key_arg<K>& key) { + std::pair<iterator, iterator> equal_range(const key_arg<K>& key) + ABSL_ATTRIBUTE_LIFETIME_BOUND { auto it = find(key); if (it != end()) return {it, std::next(it)}; return {it, it}; } template <class K = key_type> std::pair<const_iterator, const_iterator> equal_range( - const key_arg<K>& key) const { + const key_arg<K>& key) const ABSL_ATTRIBUTE_LIFETIME_BOUND { auto it = find(key); if (it != end()) return {it, std::next(it)}; return {it, it}; @@ -2203,8 +2762,10 @@ class raw_hash_set { const raw_hash_set* outer = &a; const raw_hash_set* inner = &b; if (outer->capacity() > inner->capacity()) std::swap(outer, inner); - for (const value_type& elem : *outer) - if (!inner->has_element(elem)) return false; + for (const value_type& elem : *outer) { + auto it = PolicyTraits::apply(FindElement{*inner}, elem); + if (it == inner->end() || !(*it == elem)) return false; + } return true; } @@ -2274,10 +2835,9 @@ class raw_hash_set { std::pair<iterator, bool> operator()(const K& key, Args&&...) && { auto res = s.find_or_prepare_insert(key); if (res.second) { - PolicyTraits::transfer(&s.alloc_ref(), s.slot_array() + res.first, - &slot); + s.transfer(s.slot_array() + res.first, &slot); } else if (do_destroy) { - PolicyTraits::destroy(&s.alloc_ref(), &slot); + s.destroy(&slot); } return {s.iterator_at(res.first), res.second}; } @@ -2286,58 +2846,111 @@ class raw_hash_set { slot_type&& slot; }; + // TODO(b/303305702): re-enable reentrant validation. + template <typename... Args> + inline void construct(slot_type* slot, Args&&... args) { + PolicyTraits::construct(&alloc_ref(), slot, std::forward<Args>(args)...); + } + inline void destroy(slot_type* slot) { + PolicyTraits::destroy(&alloc_ref(), slot); + } + inline void transfer(slot_type* to, slot_type* from) { + PolicyTraits::transfer(&alloc_ref(), to, from); + } + + inline void destroy_slots() { + const size_t cap = capacity(); + const ctrl_t* ctrl = control(); + slot_type* slot = slot_array(); + for (size_t i = 0; i != cap; ++i) { + if (IsFull(ctrl[i])) { + destroy(slot + i); + } + } + } + + inline void dealloc() { + assert(capacity() != 0); + // Unpoison before returning the memory to the allocator. + SanitizerUnpoisonMemoryRegion(slot_array(), sizeof(slot_type) * capacity()); + infoz().Unregister(); + Deallocate<BackingArrayAlignment(alignof(slot_type))>( + &alloc_ref(), common().backing_array_start(), + common().alloc_size(sizeof(slot_type), alignof(slot_type))); + } + + inline void destructor_impl() { + if (capacity() == 0) return; + destroy_slots(); + dealloc(); + } + // Erases, but does not destroy, the value pointed to by `it`. // // This merely updates the pertinent control byte. This can be used in // conjunction with Policy::transfer to move the object to another place. void erase_meta_only(const_iterator it) { - EraseMetaOnly(common(), it.inner_.ctrl_, sizeof(slot_type)); + EraseMetaOnly(common(), static_cast<size_t>(it.control() - control()), + sizeof(slot_type)); } - // Allocates a backing array for `self` and initializes its control bytes. - // This reads `capacity` and updates all other fields based on the result of - // the allocation. + // Resizes table to the new capacity and move all elements to the new + // positions accordingly. // - // This does not free the currently held array; `capacity` must be nonzero. - inline void initialize_slots() { - // People are often sloppy with the exact type of their allocator (sometimes - // it has an extra const or is missing the pair, but rebinds made it work - // anyway). - using CharAlloc = - typename absl::allocator_traits<Alloc>::template rebind_alloc<char>; - InitializeSlots<CharAlloc, sizeof(slot_type), alignof(slot_type)>( - common(), CharAlloc(alloc_ref())); - } - + // Note that for better performance instead of + // find_first_non_full(common(), hash), + // HashSetResizeHelper::FindFirstNonFullAfterResize( + // common(), old_capacity, hash) + // can be called right after `resize`. ABSL_ATTRIBUTE_NOINLINE void resize(size_t new_capacity) { assert(IsValidCapacity(new_capacity)); - auto* old_ctrl = control(); + HashSetResizeHelper resize_helper(common()); auto* old_slots = slot_array(); - const size_t old_capacity = common().capacity_; - common().capacity_ = new_capacity; - initialize_slots(); - - auto* new_slots = slot_array(); - size_t total_probe_length = 0; - for (size_t i = 0; i != old_capacity; ++i) { - if (IsFull(old_ctrl[i])) { - size_t hash = PolicyTraits::apply(HashElement{hash_ref()}, - PolicyTraits::element(old_slots + i)); - auto target = find_first_non_full(common(), hash); - size_t new_i = target.offset; - total_probe_length += target.probe_length; - SetCtrl(common(), new_i, H2(hash), sizeof(slot_type)); - PolicyTraits::transfer(&alloc_ref(), new_slots + new_i, old_slots + i); - } + common().set_capacity(new_capacity); + // Note that `InitializeSlots` does different number initialization steps + // depending on the values of `transfer_uses_memcpy` and capacities. + // Refer to the comment in `InitializeSlots` for more details. + const bool grow_single_group = + resize_helper.InitializeSlots<CharAlloc, sizeof(slot_type), + PolicyTraits::transfer_uses_memcpy(), + alignof(slot_type)>( + common(), const_cast<std::remove_const_t<slot_type>*>(old_slots), + CharAlloc(alloc_ref())); + + if (resize_helper.old_capacity() == 0) { + // InitializeSlots did all the work including infoz().RecordRehash(). + return; } - if (old_capacity) { - SanitizerUnpoisonMemoryRegion(old_slots, - sizeof(slot_type) * old_capacity); - Deallocate<alignof(slot_type)>( - &alloc_ref(), old_ctrl, - AllocSize(old_capacity, sizeof(slot_type), alignof(slot_type))); + + if (grow_single_group) { + if (PolicyTraits::transfer_uses_memcpy()) { + // InitializeSlots did all the work. + return; + } + // We want GrowSizeIntoSingleGroup to be called here in order to make + // InitializeSlots not depend on PolicyTraits. + resize_helper.GrowSizeIntoSingleGroup<PolicyTraits>(common(), alloc_ref(), + old_slots); + } else { + // InitializeSlots prepares control bytes to correspond to empty table. + auto* new_slots = slot_array(); + size_t total_probe_length = 0; + for (size_t i = 0; i != resize_helper.old_capacity(); ++i) { + if (IsFull(resize_helper.old_ctrl()[i])) { + size_t hash = PolicyTraits::apply( + HashElement{hash_ref()}, PolicyTraits::element(old_slots + i)); + auto target = find_first_non_full(common(), hash); + size_t new_i = target.offset; + total_probe_length += target.probe_length; + SetCtrl(common(), new_i, H2(hash), sizeof(slot_type)); + transfer(new_slots + new_i, old_slots + i); + } + } + infoz().RecordRehash(total_probe_length); } - infoz().RecordRehash(total_probe_length); + resize_helper.DeallocateOld<alignof(slot_type)>( + CharAlloc(alloc_ref()), sizeof(slot_type), + const_cast<std::remove_const_t<slot_type>*>(old_slots)); } // Prunes control bytes to remove as many tombstones as possible. @@ -2357,8 +2970,8 @@ class raw_hash_set { void rehash_and_grow_if_necessary() { const size_t cap = capacity(); if (cap > Group::kWidth && - // Do these calcuations in 64-bit to avoid overflow. - size() * uint64_t{32} <= cap* uint64_t{25}) { + // Do these calculations in 64-bit to avoid overflow. + size() * uint64_t{32} <= cap * uint64_t{25}) { // Squash DELETED without growing if there is enough capacity. // // Rehash in place if the current size is <= 25/32 of capacity. @@ -2407,36 +3020,64 @@ class raw_hash_set { } } - bool has_element(const value_type& elem) const { - size_t hash = PolicyTraits::apply(HashElement{hash_ref()}, elem); - auto seq = probe(common(), hash); - const ctrl_t* ctrl = control(); - while (true) { - Group g{ctrl + seq.offset()}; - for (uint32_t i : g.Match(H2(hash))) { - if (ABSL_PREDICT_TRUE( - PolicyTraits::element(slot_array() + seq.offset(i)) == elem)) - return true; - } - if (ABSL_PREDICT_TRUE(g.MaskEmpty())) return false; - seq.next(); - assert(seq.index() <= capacity() && "full table!"); + void maybe_increment_generation_or_rehash_on_move() { + common().maybe_increment_generation_on_move(); + if (!empty() && common().should_rehash_for_bug_detection_on_move()) { + resize(capacity()); } - return false; } - // TODO(alkis): Optimize this assuming *this and that don't overlap. - raw_hash_set& move_assign(raw_hash_set&& that, std::true_type) { - raw_hash_set tmp(std::move(that)); - swap(tmp); + template<bool propagate_alloc> + raw_hash_set& assign_impl(raw_hash_set&& that) { + // We don't bother checking for this/that aliasing. We just need to avoid + // breaking the invariants in that case. + destructor_impl(); + common() = std::move(that.common()); + // TODO(b/296061262): move instead of copying hash/eq/alloc. + hash_ref() = that.hash_ref(); + eq_ref() = that.eq_ref(); + CopyAlloc(alloc_ref(), that.alloc_ref(), + std::integral_constant<bool, propagate_alloc>()); + that.common() = CommonFields{}; + maybe_increment_generation_or_rehash_on_move(); return *this; } - raw_hash_set& move_assign(raw_hash_set&& that, std::false_type) { - raw_hash_set tmp(std::move(that), alloc_ref()); - swap(tmp); + + raw_hash_set& move_elements_allocs_unequal(raw_hash_set&& that) { + const size_t size = that.size(); + if (size == 0) return *this; + reserve(size); + for (iterator it = that.begin(); it != that.end(); ++it) { + insert(std::move(PolicyTraits::element(it.slot()))); + that.destroy(it.slot()); + } + that.dealloc(); + that.common() = CommonFields{}; + maybe_increment_generation_or_rehash_on_move(); return *this; } + raw_hash_set& move_assign(raw_hash_set&& that, + std::true_type /*propagate_alloc*/) { + return assign_impl<true>(std::move(that)); + } + raw_hash_set& move_assign(raw_hash_set&& that, + std::false_type /*propagate_alloc*/) { + if (alloc_ref() == that.alloc_ref()) { + return assign_impl<false>(std::move(that)); + } + // Aliasing can't happen here because allocs would compare equal above. + assert(this != &that); + destructor_impl(); + // We can't take over that's memory so we need to move each element. + // While moving elements, this should have that's hash/eq so copy hash/eq + // before moving elements. + // TODO(b/296061262): move instead of copying hash/eq. + hash_ref() = that.hash_ref(); + eq_ref() = that.eq_ref(); + return move_elements_allocs_unequal(std::move(that)); + } + protected: // Attempts to find `key` in the table; if it isn't found, returns a slot that // the value can be inserted into, with the control byte already set to @@ -2478,11 +3119,20 @@ class raw_hash_set { if (!rehash_for_bug_detection && ABSL_PREDICT_FALSE(growth_left() == 0 && !IsDeleted(control()[target.offset]))) { + size_t old_capacity = capacity(); rehash_and_grow_if_necessary(); - target = find_first_non_full(common(), hash); + // NOTE: It is safe to use `FindFirstNonFullAfterResize`. + // `FindFirstNonFullAfterResize` must be called right after resize. + // `rehash_and_grow_if_necessary` may *not* call `resize` + // and perform `drop_deletes_without_resize` instead. But this + // could happen only on big tables. + // For big tables `FindFirstNonFullAfterResize` will always + // fallback to normal `find_first_non_full`, so it is safe to use it. + target = HashSetResizeHelper::FindFirstNonFullAfterResize( + common(), old_capacity, hash); } - ++common().size_; - growth_left() -= IsEmpty(control()[target.offset]); + common().increment_size(); + set_growth_left(growth_left() - IsEmpty(control()[target.offset])); SetCtrl(common(), target.offset, H2(hash), sizeof(slot_type)); common().maybe_increment_generation_on_insert(); infoz().RecordInsert(hash, target.probe_length); @@ -2499,21 +3149,22 @@ class raw_hash_set { // POSTCONDITION: *m.iterator_at(i) == value_type(forward<Args>(args)...). template <class... Args> void emplace_at(size_t i, Args&&... args) { - PolicyTraits::construct(&alloc_ref(), slot_array() + i, - std::forward<Args>(args)...); + construct(slot_array() + i, std::forward<Args>(args)...); assert(PolicyTraits::apply(FindElement{*this}, *iterator_at(i)) == iterator_at(i) && "constructed value does not match the lookup key"); } - iterator iterator_at(size_t i) { + iterator iterator_at(size_t i) ABSL_ATTRIBUTE_LIFETIME_BOUND { return {control() + i, slot_array() + i, common().generation_ptr()}; } - const_iterator iterator_at(size_t i) const { + const_iterator iterator_at(size_t i) const ABSL_ATTRIBUTE_LIFETIME_BOUND { return {control() + i, slot_array() + i, common().generation_ptr()}; } + reference unchecked_deref(iterator it) { return it.unchecked_deref(); } + private: friend struct RawHashSetTestOnlyAccess; @@ -2527,21 +3178,26 @@ class raw_hash_set { // side-effect. // // See `CapacityToGrowth()`. - size_t& growth_left() { return common().growth_left(); } - - // Prefetch the heap-allocated memory region to resolve potential TLB misses. - // This is intended to overlap with execution of calculating the hash for a - // key. - void prefetch_heap_block() const { base_internal::PrefetchT2(control()); } + size_t growth_left() const { return common().growth_left(); } + void set_growth_left(size_t gl) { return common().set_growth_left(gl); } + + // Prefetch the heap-allocated memory region to resolve potential TLB and + // cache misses. This is intended to overlap with execution of calculating the + // hash for a key. + void prefetch_heap_block() const { +#if ABSL_HAVE_BUILTIN(__builtin_prefetch) || defined(__GNUC__) + __builtin_prefetch(control(), 0, 1); +#endif + } CommonFields& common() { return settings_.template get<0>(); } const CommonFields& common() const { return settings_.template get<0>(); } - ctrl_t* control() const { return common().control_; } + ctrl_t* control() const { return common().control(); } slot_type* slot_array() const { - return static_cast<slot_type*>(common().slots_); + return static_cast<slot_type*>(common().slot_array()); } - HashtablezInfoHandle& infoz() { return common().infoz(); } + HashtablezInfoHandle infoz() { return common().infoz(); } hasher& hash_ref() { return settings_.template get<1>(); } const hasher& hash_ref() const { return settings_.template get<1>(); } @@ -2561,20 +3217,20 @@ class raw_hash_set { } static void transfer_slot_fn(void* set, void* dst, void* src) { auto* h = static_cast<raw_hash_set*>(set); - PolicyTraits::transfer(&h->alloc_ref(), static_cast<slot_type*>(dst), - static_cast<slot_type*>(src)); + h->transfer(static_cast<slot_type*>(dst), static_cast<slot_type*>(src)); } // Note: dealloc_fn will only be used if we have a non-standard allocator. - static void dealloc_fn(void* set, const PolicyFunctions&, ctrl_t* ctrl, - void* slot_mem, size_t n) { - auto* h = static_cast<raw_hash_set*>(set); + static void dealloc_fn(CommonFields& common, const PolicyFunctions&) { + auto* set = reinterpret_cast<raw_hash_set*>(&common); // Unpoison before returning the memory to the allocator. - SanitizerUnpoisonMemoryRegion(slot_mem, sizeof(slot_type) * n); + SanitizerUnpoisonMemoryRegion(common.slot_array(), + sizeof(slot_type) * common.capacity()); - Deallocate<alignof(slot_type)>( - &h->alloc_ref(), ctrl, - AllocSize(n, sizeof(slot_type), alignof(slot_type))); + common.infoz().Unregister(); + Deallocate<BackingArrayAlignment(alignof(slot_type))>( + &set->alloc_ref(), common.backing_array_start(), + common.alloc_size(sizeof(slot_type), alignof(slot_type))); } static const PolicyFunctions& GetPolicyFunctions() { @@ -2645,33 +3301,18 @@ struct HashtableDebugAccess<Set, absl::void_t<typename Set::raw_hash_set>> { static size_t AllocatedByteSize(const Set& c) { size_t capacity = c.capacity(); if (capacity == 0) return 0; - size_t m = AllocSize(capacity, sizeof(Slot), alignof(Slot)); + size_t m = c.common().alloc_size(sizeof(Slot), alignof(Slot)); size_t per_slot = Traits::space_used(static_cast<const Slot*>(nullptr)); if (per_slot != ~size_t{}) { m += per_slot * c.size(); } else { - const ctrl_t* ctrl = c.control(); - for (size_t i = 0; i != capacity; ++i) { - if (container_internal::IsFull(ctrl[i])) { - m += Traits::space_used(c.slot_array() + i); - } + for (auto it = c.begin(); it != c.end(); ++it) { + m += Traits::space_used(it.slot()); } } return m; } - - static size_t LowerBoundAllocatedByteSize(size_t size) { - size_t capacity = GrowthToLowerboundCapacity(size); - if (capacity == 0) return 0; - size_t m = - AllocSize(NormalizeCapacity(capacity), sizeof(Slot), alignof(Slot)); - size_t per_slot = Traits::space_used(static_cast<const Slot*>(nullptr)); - if (per_slot != ~size_t{}) { - m += per_slot * size; - } - return m; - } }; } // namespace hashtable_debug_internal @@ -2680,6 +3321,5 @@ ABSL_NAMESPACE_END } // namespace absl #undef ABSL_SWISSTABLE_ENABLE_GENERATIONS -#undef ABSL_INTERNAL_ASSERT_IS_FULL #endif // ABSL_CONTAINER_INTERNAL_RAW_HASH_SET_H_ diff --git a/absl/container/internal/raw_hash_set_allocator_test.cc b/absl/container/internal/raw_hash_set_allocator_test.cc index e73f53fd..05dcfaa6 100644 --- a/absl/container/internal/raw_hash_set_allocator_test.cc +++ b/absl/container/internal/raw_hash_set_allocator_test.cc @@ -12,10 +12,19 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include <cstddef> +#include <cstdint> +#include <functional> #include <limits> -#include <scoped_allocator> +#include <memory> +#include <ostream> +#include <set> +#include <type_traits> +#include <utility> +#include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/base/config.h" #include "absl/container/internal/raw_hash_set.h" #include "absl/container/internal/tracked.h" @@ -23,6 +32,7 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace container_internal { namespace { +using ::testing::AnyOf; enum AllocSpec { kPropagateOnCopy = 1, @@ -276,34 +286,38 @@ TEST_F(NoPropagateOnCopy, CopyConstructorWithDifferentAlloc) { } TEST_F(PropagateOnAll, MoveConstructor) { - auto it = t1.insert(0).first; + t1.insert(0); Table u(std::move(t1)); - EXPECT_EQ(1, a1.num_allocs()); - EXPECT_EQ(0, it->num_moves()); + auto it = u.begin(); + EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2)); + EXPECT_THAT(it->num_moves(), AnyOf(0, 1)); EXPECT_EQ(0, it->num_copies()); } TEST_F(NoPropagateOnMove, MoveConstructor) { - auto it = t1.insert(0).first; + t1.insert(0); Table u(std::move(t1)); - EXPECT_EQ(1, a1.num_allocs()); - EXPECT_EQ(0, it->num_moves()); + auto it = u.begin(); + EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2)); + EXPECT_THAT(it->num_moves(), AnyOf(0, 1)); EXPECT_EQ(0, it->num_copies()); } TEST_F(PropagateOnAll, MoveConstructorWithSameAlloc) { - auto it = t1.insert(0).first; + t1.insert(0); Table u(std::move(t1), a1); - EXPECT_EQ(1, a1.num_allocs()); - EXPECT_EQ(0, it->num_moves()); + auto it = u.begin(); + EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2)); + EXPECT_THAT(it->num_moves(), AnyOf(0, 1)); EXPECT_EQ(0, it->num_copies()); } TEST_F(NoPropagateOnMove, MoveConstructorWithSameAlloc) { - auto it = t1.insert(0).first; + t1.insert(0); Table u(std::move(t1), a1); - EXPECT_EQ(1, a1.num_allocs()); - EXPECT_EQ(0, it->num_moves()); + auto it = u.begin(); + EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2)); + EXPECT_THAT(it->num_moves(), AnyOf(0, 1)); EXPECT_EQ(0, it->num_copies()); } @@ -313,8 +327,8 @@ TEST_F(PropagateOnAll, MoveConstructorWithDifferentAlloc) { it = u.find(0); EXPECT_EQ(a2, u.get_allocator()); EXPECT_EQ(1, a1.num_allocs()); - EXPECT_EQ(1, a2.num_allocs()); - EXPECT_EQ(1, it->num_moves()); + EXPECT_THAT(a2.num_allocs(), AnyOf(1, 2)); + EXPECT_THAT(it->num_moves(), AnyOf(1, 2)); EXPECT_EQ(0, it->num_copies()); } @@ -324,8 +338,8 @@ TEST_F(NoPropagateOnMove, MoveConstructorWithDifferentAlloc) { it = u.find(0); EXPECT_EQ(a2, u.get_allocator()); EXPECT_EQ(1, a1.num_allocs()); - EXPECT_EQ(1, a2.num_allocs()); - EXPECT_EQ(1, it->num_moves()); + EXPECT_THAT(a2.num_allocs(), AnyOf(1, 2)); + EXPECT_THAT(it->num_moves(), AnyOf(1, 2)); EXPECT_EQ(0, it->num_copies()); } @@ -333,8 +347,8 @@ TEST_F(PropagateOnAll, CopyAssignmentWithSameAlloc) { auto it = t1.insert(0).first; Table u(0, a1); u = t1; - EXPECT_EQ(2, a1.num_allocs()); - EXPECT_EQ(0, it->num_moves()); + EXPECT_THAT(a1.num_allocs(), AnyOf(2, 3)); + EXPECT_THAT(it->num_moves(), AnyOf(0, 1)); EXPECT_EQ(1, it->num_copies()); } @@ -342,8 +356,8 @@ TEST_F(NoPropagateOnCopy, CopyAssignmentWithSameAlloc) { auto it = t1.insert(0).first; Table u(0, a1); u = t1; - EXPECT_EQ(2, a1.num_allocs()); - EXPECT_EQ(0, it->num_moves()); + EXPECT_THAT(a1.num_allocs(), AnyOf(2, 3)); + EXPECT_THAT(it->num_moves(), AnyOf(0, 1)); EXPECT_EQ(1, it->num_copies()); } @@ -352,9 +366,9 @@ TEST_F(PropagateOnAll, CopyAssignmentWithDifferentAlloc) { Table u(0, a2); u = t1; EXPECT_EQ(a1, u.get_allocator()); - EXPECT_EQ(2, a1.num_allocs()); + EXPECT_THAT(a1.num_allocs(), AnyOf(2, 3)); EXPECT_EQ(0, a2.num_allocs()); - EXPECT_EQ(0, it->num_moves()); + EXPECT_THAT(it->num_moves(), AnyOf(0, 1)); EXPECT_EQ(1, it->num_copies()); } @@ -364,51 +378,54 @@ TEST_F(NoPropagateOnCopy, CopyAssignmentWithDifferentAlloc) { u = t1; EXPECT_EQ(a2, u.get_allocator()); EXPECT_EQ(1, a1.num_allocs()); - EXPECT_EQ(1, a2.num_allocs()); - EXPECT_EQ(0, it->num_moves()); + EXPECT_THAT(a2.num_allocs(), AnyOf(1, 2)); + EXPECT_THAT(it->num_moves(), AnyOf(0, 1)); EXPECT_EQ(1, it->num_copies()); } TEST_F(PropagateOnAll, MoveAssignmentWithSameAlloc) { - auto it = t1.insert(0).first; + t1.insert(0); Table u(0, a1); u = std::move(t1); + auto it = u.begin(); EXPECT_EQ(a1, u.get_allocator()); - EXPECT_EQ(1, a1.num_allocs()); - EXPECT_EQ(0, it->num_moves()); + EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2)); + EXPECT_THAT(it->num_moves(), AnyOf(0, 1)); EXPECT_EQ(0, it->num_copies()); } TEST_F(NoPropagateOnMove, MoveAssignmentWithSameAlloc) { - auto it = t1.insert(0).first; + t1.insert(0); Table u(0, a1); u = std::move(t1); + auto it = u.begin(); EXPECT_EQ(a1, u.get_allocator()); - EXPECT_EQ(1, a1.num_allocs()); - EXPECT_EQ(0, it->num_moves()); + EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2)); + EXPECT_THAT(it->num_moves(), AnyOf(0, 1)); EXPECT_EQ(0, it->num_copies()); } TEST_F(PropagateOnAll, MoveAssignmentWithDifferentAlloc) { - auto it = t1.insert(0).first; + t1.insert(0); Table u(0, a2); u = std::move(t1); + auto it = u.begin(); EXPECT_EQ(a1, u.get_allocator()); - EXPECT_EQ(1, a1.num_allocs()); + EXPECT_THAT(a1.num_allocs(), AnyOf(1, 2)); EXPECT_EQ(0, a2.num_allocs()); - EXPECT_EQ(0, it->num_moves()); + EXPECT_THAT(it->num_moves(), AnyOf(0, 1)); EXPECT_EQ(0, it->num_copies()); } TEST_F(NoPropagateOnMove, MoveAssignmentWithDifferentAlloc) { - auto it = t1.insert(0).first; + t1.insert(0); Table u(0, a2); u = std::move(t1); - it = u.find(0); + auto it = u.find(0); EXPECT_EQ(a2, u.get_allocator()); EXPECT_EQ(1, a1.num_allocs()); - EXPECT_EQ(1, a2.num_allocs()); - EXPECT_EQ(1, it->num_moves()); + EXPECT_THAT(a2.num_allocs(), AnyOf(1, 2)); + EXPECT_THAT(it->num_moves(), AnyOf(1, 2)); EXPECT_EQ(0, it->num_copies()); } @@ -435,9 +452,6 @@ class PAlloc { // types using value_type = T; - // traits - using propagate_on_container_swap = std::false_type; - PAlloc() noexcept = default; explicit PAlloc(size_t id) noexcept : id_(id) {} PAlloc(const PAlloc&) noexcept = default; @@ -466,37 +480,32 @@ class PAlloc { size_t id_ = std::numeric_limits<size_t>::max(); }; -// This doesn't compile with GCC 5.4 and 5.5 due to a bug in noexcept handing. -#if !defined(__GNUC__) || __GNUC__ != 5 || (__GNUC_MINOR__ != 4 && \ - __GNUC_MINOR__ != 5) -TEST(NoPropagateOn, Swap) { +TEST(NoPropagateDeletedAssignment, CopyConstruct) { using PA = PAlloc<char>; using Table = raw_hash_set<Policy, Identity, std::equal_to<int32_t>, PA>; - Table t1(PA{1}), t2(PA{2}); - swap(t1, t2); + Table t1(PA{1}), t2(t1); EXPECT_EQ(t1.get_allocator(), PA(1)); - EXPECT_EQ(t2.get_allocator(), PA(2)); + EXPECT_EQ(t2.get_allocator(), PA()); } -#endif -TEST(NoPropagateOn, CopyConstruct) { +TEST(NoPropagateDeletedAssignment, CopyAssignment) { using PA = PAlloc<char>; using Table = raw_hash_set<Policy, Identity, std::equal_to<int32_t>, PA>; - Table t1(PA{1}), t2(t1); + Table t1(PA{1}), t2(PA{2}); + t1 = t2; EXPECT_EQ(t1.get_allocator(), PA(1)); - EXPECT_EQ(t2.get_allocator(), PA()); + EXPECT_EQ(t2.get_allocator(), PA(2)); } -TEST(NoPropagateOn, Assignment) { +TEST(NoPropagateDeletedAssignment, MoveAssignment) { using PA = PAlloc<char>; using Table = raw_hash_set<Policy, Identity, std::equal_to<int32_t>, PA>; Table t1(PA{1}), t2(PA{2}); - t1 = t2; + t1 = std::move(t2); EXPECT_EQ(t1.get_allocator(), PA(1)); - EXPECT_EQ(t2.get_allocator(), PA(2)); } } // namespace diff --git a/absl/container/internal/raw_hash_set_benchmark.cc b/absl/container/internal/raw_hash_set_benchmark.cc index f77f2a7b..88b07373 100644 --- a/absl/container/internal/raw_hash_set_benchmark.cc +++ b/absl/container/internal/raw_hash_set_benchmark.cc @@ -14,6 +14,8 @@ #include <array> #include <cmath> +#include <cstddef> +#include <cstdint> #include <numeric> #include <random> #include <tuple> @@ -142,7 +144,7 @@ struct string_generator { template <class RNG> std::string operator()(RNG& rng) const { std::string res; - res.resize(12); + res.resize(size); std::uniform_int_distribution<uint32_t> printable_ascii(0x20, 0x7E); std::generate(res.begin(), res.end(), [&] { return printable_ascii(rng); }); return res; @@ -205,6 +207,22 @@ void CacheInSteadyStateArgs(Benchmark* bm) { } BENCHMARK(BM_CacheInSteadyState)->Apply(CacheInSteadyStateArgs); +void BM_EraseEmplace(benchmark::State& state) { + IntTable t; + int64_t size = state.range(0); + for (int64_t i = 0; i < size; ++i) { + t.emplace(i); + } + while (state.KeepRunningBatch(size)) { + for (int64_t i = 0; i < size; ++i) { + benchmark::DoNotOptimize(t); + t.erase(i); + t.emplace(i); + } + } +} +BENCHMARK(BM_EraseEmplace)->Arg(1)->Arg(2)->Arg(4)->Arg(8)->Arg(16)->Arg(100); + void BM_EndComparison(benchmark::State& state) { StringTable t = {{"a", "a"}, {"b", "b"}}; auto it = t.begin(); @@ -369,28 +387,42 @@ void BM_NoOpReserveStringTable(benchmark::State& state) { BENCHMARK(BM_NoOpReserveStringTable); void BM_ReserveIntTable(benchmark::State& state) { - int reserve_size = state.range(0); - for (auto _ : state) { + constexpr size_t kBatchSize = 1024; + size_t reserve_size = static_cast<size_t>(state.range(0)); + + std::vector<IntTable> tables; + while (state.KeepRunningBatch(kBatchSize)) { state.PauseTiming(); - IntTable t; + tables.clear(); + tables.resize(kBatchSize); state.ResumeTiming(); - benchmark::DoNotOptimize(t); - t.reserve(reserve_size); + for (auto& t : tables) { + benchmark::DoNotOptimize(t); + t.reserve(reserve_size); + benchmark::DoNotOptimize(t); + } } } -BENCHMARK(BM_ReserveIntTable)->Range(128, 4096); +BENCHMARK(BM_ReserveIntTable)->Range(1, 64); void BM_ReserveStringTable(benchmark::State& state) { - int reserve_size = state.range(0); - for (auto _ : state) { + constexpr size_t kBatchSize = 1024; + size_t reserve_size = static_cast<size_t>(state.range(0)); + + std::vector<StringTable> tables; + while (state.KeepRunningBatch(kBatchSize)) { state.PauseTiming(); - StringTable t; + tables.clear(); + tables.resize(kBatchSize); state.ResumeTiming(); - benchmark::DoNotOptimize(t); - t.reserve(reserve_size); + for (auto& t : tables) { + benchmark::DoNotOptimize(t); + t.reserve(reserve_size); + benchmark::DoNotOptimize(t); + } } } -BENCHMARK(BM_ReserveStringTable)->Range(128, 4096); +BENCHMARK(BM_ReserveStringTable)->Range(1, 64); // Like std::iota, except that ctrl_t doesn't support operator++. template <typename CtrlIter> diff --git a/absl/container/internal/raw_hash_set_probe_benchmark.cc b/absl/container/internal/raw_hash_set_probe_benchmark.cc index 7169a2e2..5d4184b2 100644 --- a/absl/container/internal/raw_hash_set_probe_benchmark.cc +++ b/absl/container/internal/raw_hash_set_probe_benchmark.cc @@ -19,6 +19,7 @@ #include <regex> // NOLINT #include <vector> +#include "absl/base/no_destructor.h" #include "absl/container/flat_hash_map.h" #include "absl/container/internal/hash_function_defaults.h" #include "absl/container/internal/hashtable_debug.h" @@ -29,6 +30,7 @@ #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" #include "absl/strings/strip.h" +#include "absl/types/optional.h" namespace { @@ -71,7 +73,7 @@ struct Policy { }; absl::BitGen& GlobalBitGen() { - static auto* value = new absl::BitGen; + static absl::NoDestructor<absl::BitGen> value; return *value; } @@ -112,7 +114,7 @@ class RandomizedAllocator { static constexpr size_t kRandomPool = 20; static std::vector<T*>& GetPointers(size_t n) { - static auto* m = new absl::flat_hash_map<size_t, std::vector<T*>>(); + static absl::NoDestructor<absl::flat_hash_map<size_t, std::vector<T*>>> m; return (*m)[n]; } }; @@ -460,12 +462,12 @@ constexpr int kNameWidth = 15; constexpr int kDistWidth = 16; bool CanRunBenchmark(absl::string_view name) { - static std::regex* const filter = []() -> std::regex* { + static const absl::NoDestructor<absl::optional<std::regex>> filter([] { return benchmarks.empty() || benchmarks == "all" - ? nullptr - : new std::regex(std::string(benchmarks)); - }(); - return filter == nullptr || std::regex_search(std::string(name), *filter); + ? absl::nullopt + : absl::make_optional(std::regex(std::string(benchmarks))); + }()); + return !filter->has_value() || std::regex_search(std::string(name), **filter); } struct Result { diff --git a/absl/container/internal/raw_hash_set_test.cc b/absl/container/internal/raw_hash_set_test.cc index 3d3b089c..f9797f56 100644 --- a/absl/container/internal/raw_hash_set_test.cc +++ b/absl/container/internal/raw_hash_set_test.cc @@ -17,6 +17,7 @@ #include <algorithm> #include <atomic> #include <cmath> +#include <cstddef> #include <cstdint> #include <deque> #include <functional> @@ -29,6 +30,7 @@ #include <ostream> #include <random> #include <string> +#include <tuple> #include <type_traits> #include <unordered_map> #include <unordered_set> @@ -40,15 +42,19 @@ #include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/internal/cycleclock.h" -#include "absl/base/internal/prefetch.h" -#include "absl/base/internal/raw_logging.h" +#include "absl/base/prefetch.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/container/internal/hashtablez_sampler.h" +#include "absl/container/internal/test_allocator.h" +#include "absl/hash/hash.h" #include "absl/log/log.h" +#include "absl/memory/memory.h" +#include "absl/meta/type_traits.h" #include "absl/strings/string_view.h" namespace absl { @@ -60,6 +66,10 @@ struct RawHashSetTestOnlyAccess { static auto GetSlots(const C& c) -> decltype(c.slot_array()) { return c.slot_array(); } + template <typename C> + static size_t CountTombstones(const C& c) { + return c.common().TombstonesCount(); + } }; namespace { @@ -226,6 +236,25 @@ TEST(Group, MaskEmpty) { } } +TEST(Group, MaskFull) { + if (Group::kWidth == 16) { + ctrl_t group[] = { + ctrl_t::kEmpty, CtrlT(1), ctrl_t::kDeleted, CtrlT(3), + ctrl_t::kEmpty, CtrlT(5), ctrl_t::kSentinel, CtrlT(7), + CtrlT(7), CtrlT(5), ctrl_t::kDeleted, CtrlT(1), + CtrlT(1), ctrl_t::kSentinel, ctrl_t::kEmpty, CtrlT(1)}; + EXPECT_THAT(Group{group}.MaskFull(), + ElementsAre(1, 3, 5, 7, 8, 9, 11, 12, 15)); + } else if (Group::kWidth == 8) { + ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), ctrl_t::kEmpty, + ctrl_t::kDeleted, CtrlT(2), ctrl_t::kSentinel, + ctrl_t::kSentinel, CtrlT(1)}; + EXPECT_THAT(Group{group}.MaskFull(), ElementsAre(1, 4, 7)); + } else { + FAIL() << "No test coverage for Group::kWidth==" << Group::kWidth; + } +} + TEST(Group, MaskEmptyOrDeleted) { if (Group::kWidth == 16) { ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), ctrl_t::kEmpty, CtrlT(3), @@ -288,13 +317,13 @@ TEST(Group, CountLeadingEmptyOrDeleted) { std::vector<ctrl_t> f(Group::kWidth, empty); f[Group::kWidth * 2 / 3] = full; f[Group::kWidth / 2] = full; - EXPECT_EQ( - Group::kWidth / 2, Group{f.data()}.CountLeadingEmptyOrDeleted()); + EXPECT_EQ(Group::kWidth / 2, + Group{f.data()}.CountLeadingEmptyOrDeleted()); } } } -template <class T> +template <class T, bool kTransferable = false> struct ValuePolicy { using slot_type = T; using key_type = T; @@ -312,10 +341,11 @@ struct ValuePolicy { } template <class Allocator> - static void transfer(Allocator* alloc, slot_type* new_slot, - slot_type* old_slot) { + static std::integral_constant<bool, kTransferable> transfer( + Allocator* alloc, slot_type* new_slot, slot_type* old_slot) { construct(alloc, new_slot, std::move(*old_slot)); destroy(alloc, old_slot); + return {}; } static T& element(slot_type* slot) { return *slot; } @@ -332,6 +362,8 @@ struct ValuePolicy { using IntPolicy = ValuePolicy<int64_t>; using Uint8Policy = ValuePolicy<uint8_t>; +using TranferableIntPolicy = ValuePolicy<int64_t, /*kTransferable=*/true>; + class StringPolicy { template <class F, class K, class V, class = typename std::enable_if< @@ -404,19 +436,18 @@ struct StringTable using Base::Base; }; -struct IntTable - : raw_hash_set<IntPolicy, container_internal::hash_default_hash<int64_t>, - std::equal_to<int64_t>, std::allocator<int64_t>> { - using Base = typename IntTable::raw_hash_set; +template <typename T, bool kTransferable = false> +struct ValueTable + : raw_hash_set<ValuePolicy<T, kTransferable>, hash_default_hash<T>, + std::equal_to<T>, std::allocator<T>> { + using Base = typename ValueTable::raw_hash_set; using Base::Base; }; -struct Uint8Table - : raw_hash_set<Uint8Policy, container_internal::hash_default_hash<uint8_t>, - std::equal_to<uint8_t>, std::allocator<uint8_t>> { - using Base = typename Uint8Table::raw_hash_set; - using Base::Base; -}; +using IntTable = ValueTable<int64_t>; +using Uint8Table = ValueTable<uint8_t>; + +using TransferableIntTable = ValueTable<int64_t, /*kTransferable=*/true>; template <typename T> struct CustomAlloc : std::allocator<T> { @@ -425,18 +456,48 @@ struct CustomAlloc : std::allocator<T> { template <typename U> explicit CustomAlloc(const CustomAlloc<U>& /*other*/) {} - template<class U> struct rebind { + template <class U> + struct rebind { using other = CustomAlloc<U>; }; }; struct CustomAllocIntTable - : raw_hash_set<IntPolicy, container_internal::hash_default_hash<int64_t>, + : raw_hash_set<IntPolicy, hash_default_hash<int64_t>, std::equal_to<int64_t>, CustomAlloc<int64_t>> { using Base = typename CustomAllocIntTable::raw_hash_set; using Base::Base; }; +struct MinimumAlignmentUint8Table + : raw_hash_set<Uint8Policy, hash_default_hash<uint8_t>, + std::equal_to<uint8_t>, MinimumAlignmentAlloc<uint8_t>> { + using Base = typename MinimumAlignmentUint8Table::raw_hash_set; + using Base::Base; +}; + +// Allows for freezing the allocator to expect no further allocations. +template <typename T> +struct FreezableAlloc : std::allocator<T> { + explicit FreezableAlloc(bool* f) : frozen(f) {} + + template <typename U> + explicit FreezableAlloc(const FreezableAlloc<U>& other) + : frozen(other.frozen) {} + + template <class U> + struct rebind { + using other = FreezableAlloc<U>; + }; + + T* allocate(size_t n) { + EXPECT_FALSE(*frozen); + return std::allocator<T>::allocate(n); + } + + bool* frozen; +}; + struct BadFastHash { template <class T> size_t operator()(const T&) const { @@ -444,6 +505,13 @@ struct BadFastHash { } }; +struct BadHashFreezableIntTable + : raw_hash_set<IntPolicy, BadFastHash, std::equal_to<int64_t>, + FreezableAlloc<int64_t>> { + using Base = typename BadHashFreezableIntTable::raw_hash_set; + using Base::Base; +}; + struct BadTable : raw_hash_set<IntPolicy, BadFastHash, std::equal_to<int>, std::allocator<int>> { using Base = typename BadTable::raw_hash_set; @@ -456,19 +524,10 @@ TEST(Table, EmptyFunctorOptimization) { static_assert(std::is_empty<std::allocator<int>>::value, ""); struct MockTable { - void* infoz; void* ctrl; void* slots; size_t size; size_t capacity; - size_t growth_left; - }; - struct MockTableInfozDisabled { - void* ctrl; - void* slots; - size_t size; - size_t capacity; - size_t growth_left; }; struct StatelessHash { size_t operator()(absl::string_view) const { return 0; } @@ -479,6 +538,7 @@ TEST(Table, EmptyFunctorOptimization) { struct GenerationData { size_t reserved_growth; + size_t reservation_size; GenerationType* generation; }; @@ -488,9 +548,7 @@ TEST(Table, EmptyFunctorOptimization) { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wunreachable-code" #endif - constexpr size_t mock_size = std::is_empty<HashtablezInfoHandle>() - ? sizeof(MockTableInfozDisabled) - : sizeof(MockTable); + constexpr size_t mock_size = sizeof(MockTable); constexpr size_t generation_size = SwisstableGenerationsEnabled() ? sizeof(GenerationData) : 0; #if defined(__clang__) @@ -595,6 +653,23 @@ TEST(Table, InsertCollisionAndFindAfterDelete) { EXPECT_TRUE(t.empty()); } +TEST(Table, EraseInSmallTables) { + for (int64_t size = 0; size < 64; ++size) { + IntTable t; + for (int64_t i = 0; i < size; ++i) { + t.insert(i); + } + for (int64_t i = 0; i < size; ++i) { + t.erase(i); + EXPECT_EQ(t.size(), size - i - 1); + for (int64_t j = i + 1; j < size; ++j) { + EXPECT_THAT(*t.find(j), j); + } + } + EXPECT_TRUE(t.empty()); + } +} + TEST(Table, InsertWithinCapacity) { IntTable t; t.reserve(10); @@ -626,6 +701,68 @@ TEST(Table, InsertWithinCapacity) { EXPECT_THAT(addr(0), original_addr_0); } +template <class TableType> +class SmallTableResizeTest : public testing::Test {}; + +TYPED_TEST_SUITE_P(SmallTableResizeTest); + +TYPED_TEST_P(SmallTableResizeTest, InsertIntoSmallTable) { + TypeParam t; + for (int i = 0; i < 32; ++i) { + t.insert(i); + ASSERT_EQ(t.size(), i + 1); + for (int j = 0; j < i + 1; ++j) { + EXPECT_TRUE(t.find(j) != t.end()); + EXPECT_EQ(*t.find(j), j); + } + } +} + +TYPED_TEST_P(SmallTableResizeTest, ResizeGrowSmallTables) { + TypeParam t; + for (size_t source_size = 0; source_size < 32; ++source_size) { + for (size_t target_size = source_size; target_size < 32; ++target_size) { + for (bool rehash : {false, true}) { + for (size_t i = 0; i < source_size; ++i) { + t.insert(static_cast<int>(i)); + } + if (rehash) { + t.rehash(target_size); + } else { + t.reserve(target_size); + } + for (size_t i = 0; i < source_size; ++i) { + EXPECT_TRUE(t.find(static_cast<int>(i)) != t.end()); + EXPECT_EQ(*t.find(static_cast<int>(i)), static_cast<int>(i)); + } + } + } + } +} + +TYPED_TEST_P(SmallTableResizeTest, ResizeReduceSmallTables) { + TypeParam t; + for (size_t source_size = 0; source_size < 32; ++source_size) { + for (size_t target_size = 0; target_size <= source_size; ++target_size) { + size_t inserted_count = std::min<size_t>(source_size, 5); + for (size_t i = 0; i < inserted_count; ++i) { + t.insert(static_cast<int>(i)); + } + t.rehash(target_size); + for (size_t i = 0; i < inserted_count; ++i) { + EXPECT_TRUE(t.find(static_cast<int>(i)) != t.end()); + EXPECT_EQ(*t.find(static_cast<int>(i)), static_cast<int>(i)); + } + } + } +} + +REGISTER_TYPED_TEST_SUITE_P(SmallTableResizeTest, InsertIntoSmallTable, + ResizeGrowSmallTables, ResizeReduceSmallTables); +using SmallTableTypes = ::testing::Types<IntTable, TransferableIntTable>; +INSTANTIATE_TYPED_TEST_SUITE_P(InstanceSmallTableResizeTest, + SmallTableResizeTest, SmallTableTypes); + TEST(Table, LazyEmplace) { StringTable t; bool called = false; @@ -865,6 +1002,10 @@ void TestDecompose(bool construct_three) { } TEST(Table, Decompose) { + if (SwisstableGenerationsEnabled()) { + GTEST_SKIP() << "Generations being enabled causes extra rehashes."; + } + TestDecompose<DecomposeHash, DecomposeEq>(false); struct TransparentHashIntOverload { @@ -903,6 +1044,10 @@ struct Modulo1000HashTable // Test that rehash with no resize happen in case of many deleted slots. TEST(Table, RehashWithNoResize) { + if (SwisstableGenerationsEnabled()) { + GTEST_SKIP() << "Generations being enabled causes extra rehashes."; + } + Modulo1000HashTable t; // Adding the same length (and the same hash) strings // to have at least kMinFullGroups groups @@ -996,6 +1141,10 @@ TEST(Table, EnsureNonQuadraticAsInRust) { } TEST(Table, ClearBug) { + if (SwisstableGenerationsEnabled()) { + GTEST_SKIP() << "Generations being enabled causes extra rehashes."; + } + IntTable t; constexpr size_t capacity = container_internal::Group::kWidth - 1; constexpr size_t max_size = capacity / 2 + 1; @@ -1032,7 +1181,7 @@ TEST(Table, Erase) { TEST(Table, EraseMaintainsValidIterator) { IntTable t; const int kNumElements = 100; - for (int i = 0; i < kNumElements; i ++) { + for (int i = 0; i < kNumElements; i++) { EXPECT_TRUE(t.emplace(i).second); } EXPECT_EQ(t.size(), kNumElements); @@ -1048,6 +1197,14 @@ TEST(Table, EraseMaintainsValidIterator) { EXPECT_EQ(num_erase_calls, kNumElements); } +TEST(Table, EraseBeginEnd) { + IntTable t; + for (int i = 0; i < 10; ++i) t.insert(i); + EXPECT_EQ(t.size(), 10); + t.erase(t.begin(), t.end()); + EXPECT_EQ(t.size(), 0); +} + // Collect N bad keys by following algorithm: // 1. Create an empty table and reserve it to 2 * N. // 2. Insert N random elements. @@ -1245,15 +1402,15 @@ ExpectedStats XorSeedExpectedStats() { switch (container_internal::Group::kWidth) { case 8: if (kRandomizesInserts) { - return {0.05, - 1.0, - {{0.95, 0.5}}, - {{0.95, 0}, {0.99, 2}, {0.999, 4}, {0.9999, 10}}}; + return {0.05, + 1.0, + {{0.95, 0.5}}, + {{0.95, 0}, {0.99, 2}, {0.999, 4}, {0.9999, 10}}}; } else { - return {0.05, - 2.0, - {{0.95, 0.1}}, - {{0.95, 0}, {0.99, 2}, {0.999, 4}, {0.9999, 10}}}; + return {0.05, + 2.0, + {{0.95, 0.1}}, + {{0.95, 0}, {0.99, 2}, {0.999, 4}, {0.9999, 10}}}; } case 16: if (kRandomizesInserts) { @@ -1268,7 +1425,7 @@ ExpectedStats XorSeedExpectedStats() { {{0.95, 0}, {0.99, 1}, {0.999, 4}, {0.9999, 10}}}; } } - ABSL_RAW_LOG(FATAL, "%s", "Unknown Group width"); + LOG(FATAL) << "Unknown Group width"; return {}; } @@ -1299,7 +1456,7 @@ ProbeStats CollectProbeStatsOnLinearlyTransformedKeys( std::random_device rd; std::mt19937 rng(rd()); auto linear_transform = [](size_t x, size_t y) { return x * 17 + y * 13; }; - std::uniform_int_distribution<size_t> dist(0, keys.size()-1); + std::uniform_int_distribution<size_t> dist(0, keys.size() - 1); while (num_iters--) { IntTable t1; size_t num_keys = keys.size() / 10; @@ -1364,7 +1521,7 @@ ExpectedStats LinearTransformExpectedStats() { {{0.95, 0}, {0.99, 1}, {0.999, 6}, {0.9999, 10}}}; } } - ABSL_RAW_LOG(FATAL, "%s", "Unknown Group width"); + LOG(FATAL) << "Unknown Group width"; return {}; } @@ -1551,7 +1708,7 @@ TEST(Table, CopyConstructWithAlloc) { } struct ExplicitAllocIntTable - : raw_hash_set<IntPolicy, container_internal::hash_default_hash<int64_t>, + : raw_hash_set<IntPolicy, hash_default_hash<int64_t>, std::equal_to<int64_t>, Alloc<int64_t>> { ExplicitAllocIntTable() = default; }; @@ -1629,6 +1786,14 @@ TEST(Table, MoveAssign) { EXPECT_THAT(*u.find("a"), Pair("a", "b")); } +TEST(Table, MoveSelfAssign) { + StringTable t; + t.emplace("a", "b"); + EXPECT_EQ(1, t.size()); + t = std::move(*&t); + // As long as we don't crash, it's fine. +} + TEST(Table, Equality) { StringTable t; std::vector<std::pair<std::string, std::string>> v = {{"a", "b"}, @@ -1807,11 +1972,9 @@ TEST(Table, HeterogeneousLookupOverloads) { EXPECT_FALSE((VerifyResultOf<CallPrefetch, NonTransparentTable>())); EXPECT_FALSE((VerifyResultOf<CallCount, NonTransparentTable>())); - using TransparentTable = raw_hash_set< - StringPolicy, - absl::container_internal::hash_default_hash<absl::string_view>, - absl::container_internal::hash_default_eq<absl::string_view>, - std::allocator<int>>; + using TransparentTable = + raw_hash_set<StringPolicy, hash_default_hash<absl::string_view>, + hash_default_eq<absl::string_view>, std::allocator<int>>; EXPECT_TRUE((VerifyResultOf<CallFind, TransparentTable>())); EXPECT_TRUE((VerifyResultOf<CallErase, TransparentTable>())); @@ -2033,41 +2196,44 @@ TEST(Table, UnstablePointers) { EXPECT_NE(old_ptr, addr(0)); } -bool IsAssertEnabled() { - // Use an assert with side-effects to figure out if they are actually enabled. - bool assert_enabled = false; - assert([&]() { // NOLINT - assert_enabled = true; - return true; - }()); - return assert_enabled; -} - TEST(TableDeathTest, InvalidIteratorAsserts) { - if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled."; + if (!IsAssertEnabled() && !SwisstableGenerationsEnabled()) + 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."); + EXPECT_DEATH_IF_SUPPORTED(t.erase(t.end()), + "erase.* called on end.. iterator."); 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."); + ++iter, "operator.* called on default-constructed iterator."); 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."); + const char* const kErasedDeathMessage = + SwisstableGenerationsEnabled() + ? "operator.* called on invalid iterator.*was likely erased" + : "operator.* called on invalid iterator.*might have been " + "erased.*config=asan"; + EXPECT_DEATH_IF_SUPPORTED(++iter, kErasedDeathMessage); } +// Invalid iterator use can trigger use-after-free in asan/hwasan, +// use-of-uninitialized-value in msan, or invalidated iterator assertions. +constexpr const char* kInvalidIteratorDeathMessage = + "use-after-free|use-of-uninitialized-value|invalidated " + "iterator|Invalid iterator|invalid iterator"; + +// MSVC doesn't support | in regex. +#if defined(_MSC_VER) +constexpr bool kMsvc = true; +#else +constexpr bool kMsvc = false; +#endif + TEST(TableDeathTest, IteratorInvalidAssertsEqualityOperator) { - if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled."; + if (!IsAssertEnabled() && !SwisstableGenerationsEnabled()) + GTEST_SKIP() << "Assertions not enabled."; IntTable t; t.insert(1); @@ -2080,8 +2246,9 @@ TEST(TableDeathTest, IteratorInvalidAssertsEqualityOperator) { t.erase(iter1); // Extra simple "regexp" as regexp support is highly varied across platforms. const char* const kErasedDeathMessage = - "Invalid iterator comparison. The element might have .*been erased or " - "the table might have rehashed."; + SwisstableGenerationsEnabled() + ? "Invalid iterator comparison.*was likely erased" + : "Invalid iterator comparison.*might have been erased.*config=asan"; EXPECT_DEATH_IF_SUPPORTED(void(iter1 == iter2), kErasedDeathMessage); EXPECT_DEATH_IF_SUPPORTED(void(iter2 != iter1), kErasedDeathMessage); t.erase(iter2); @@ -2093,15 +2260,25 @@ TEST(TableDeathTest, IteratorInvalidAssertsEqualityOperator) { iter1 = t1.begin(); iter2 = t2.begin(); const char* const kContainerDiffDeathMessage = - "Invalid iterator comparison. The iterators may be from different " - ".*containers or the container might have rehashed."; + SwisstableGenerationsEnabled() + ? "Invalid iterator comparison.*iterators from different hashtables" + : "Invalid iterator comparison.*may be from different " + ".*containers.*config=asan"; EXPECT_DEATH_IF_SUPPORTED(void(iter1 == iter2), kContainerDiffDeathMessage); EXPECT_DEATH_IF_SUPPORTED(void(iter2 == iter1), kContainerDiffDeathMessage); for (int i = 0; i < 10; ++i) t1.insert(i); // There should have been a rehash in t1. - EXPECT_DEATH_IF_SUPPORTED(void(iter1 == t1.begin()), - kContainerDiffDeathMessage); + if (kMsvc) return; // MSVC doesn't support | in regex. + + // NOTE(b/293887834): After rehashing, iterators will contain pointers to + // freed memory, which may be detected by ThreadSanitizer. + const char* const kRehashedDeathMessage = + SwisstableGenerationsEnabled() + ? kInvalidIteratorDeathMessage + : "Invalid iterator comparison.*might have rehashed.*config=asan" + "|ThreadSanitizer: heap-use-after-free"; + EXPECT_DEATH_IF_SUPPORTED(void(iter1 == t1.begin()), kRehashedDeathMessage); } #if defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE) @@ -2191,21 +2368,34 @@ TEST(RawHashSamplerTest, DoNotSampleCustomAllocators) { } #ifdef ABSL_HAVE_ADDRESS_SANITIZER -TEST(Sanitizer, PoisoningUnused) { - IntTable t; - t.reserve(5); - // Insert something to force an allocation. - int64_t& v1 = *t.insert(0).first; +template <class TableType> +class SanitizerTest : public testing::Test {}; + +TYPED_TEST_SUITE_P(SanitizerTest); + +TYPED_TEST_P(SanitizerTest, PoisoningUnused) { + TypeParam t; + for (size_t reserve_size = 2; reserve_size < 1024; + reserve_size = reserve_size * 3 / 2) { + t.reserve(reserve_size); + // Insert something to force an allocation. + int64_t& v = *t.insert(0).first; - // Make sure there is something to test. - ASSERT_GT(t.capacity(), 1); + // Make sure there is something to test. + ASSERT_GT(t.capacity(), 1); - int64_t* slots = RawHashSetTestOnlyAccess::GetSlots(t); - for (size_t i = 0; i < t.capacity(); ++i) { - EXPECT_EQ(slots + i != &v1, __asan_address_is_poisoned(slots + i)); + int64_t* slots = RawHashSetTestOnlyAccess::GetSlots(t); + for (size_t i = 0; i < t.capacity(); ++i) { + EXPECT_EQ(slots + i != &v, __asan_address_is_poisoned(slots + i)) << i; + } } } +REGISTER_TYPED_TEST_SUITE_P(SanitizerTest, PoisoningUnused); +using SanitizerTableTypes = ::testing::Types<IntTable, TransferableIntTable>; +INSTANTIATE_TYPED_TEST_SUITE_P(InstanceSanitizerTest, SanitizerTest, + SanitizerTableTypes); + TEST(Sanitizer, PoisoningOnErase) { IntTable t; int64_t& v = *t.insert(0).first; @@ -2216,11 +2406,17 @@ TEST(Sanitizer, PoisoningOnErase) { } #endif // ABSL_HAVE_ADDRESS_SANITIZER -TEST(Table, AlignOne) { +template <typename T> +class AlignOneTest : public ::testing::Test {}; +using AlignOneTestTypes = + ::testing::Types<Uint8Table, MinimumAlignmentUint8Table>; +TYPED_TEST_SUITE(AlignOneTest, AlignOneTestTypes); + +TYPED_TEST(AlignOneTest, AlignOne) { // We previously had a bug in which we were copying a control byte over the // first slot when alignof(value_type) is 1. We test repeated // insertions/erases and verify that the behavior is correct. - Uint8Table t; + TypeParam t; std::unordered_set<uint8_t> verifier; // NOLINT // Do repeated insertions/erases from the table. @@ -2246,21 +2442,9 @@ TEST(Table, AlignOne) { } } -// Invalid iterator use can trigger heap-use-after-free in asan, -// use-of-uninitialized-value in msan, or invalidated iterator assertions. -constexpr const char* kInvalidIteratorDeathMessage = - "heap-use-after-free|use-of-uninitialized-value|invalidated " - "iterator|Invalid iterator"; - -#if defined(__clang__) && defined(_MSC_VER) -constexpr bool kLexan = true; -#else -constexpr bool kLexan = false; -#endif - TEST(Iterator, InvalidUseCrashesWithSanitizers) { if (!SwisstableGenerationsEnabled()) GTEST_SKIP() << "Generations disabled."; - if (kLexan) GTEST_SKIP() << "Lexan doesn't support | in regexp."; + if (kMsvc) GTEST_SKIP() << "MSVC doesn't support | in regexp."; IntTable t; // Start with 1 element so that `it` is never an end iterator. @@ -2276,7 +2460,7 @@ TEST(Iterator, InvalidUseCrashesWithSanitizers) { TEST(Iterator, InvalidUseWithReserveCrashesWithSanitizers) { if (!SwisstableGenerationsEnabled()) GTEST_SKIP() << "Generations disabled."; - if (kLexan) GTEST_SKIP() << "Lexan doesn't support | in regexp."; + if (kMsvc) GTEST_SKIP() << "MSVC doesn't support | in regexp."; IntTable t; t.reserve(10); @@ -2289,6 +2473,7 @@ TEST(Iterator, InvalidUseWithReserveCrashesWithSanitizers) { } // ptr will become invalidated on rehash. const int64_t* ptr = &*it; + (void)ptr; // erase decreases size but does not decrease reserved growth so the next // insertion still invalidates iterators. @@ -2299,8 +2484,29 @@ TEST(Iterator, InvalidUseWithReserveCrashesWithSanitizers) { EXPECT_DEATH_IF_SUPPORTED(*it, kInvalidIteratorDeathMessage); EXPECT_DEATH_IF_SUPPORTED(void(it == t.begin()), kInvalidIteratorDeathMessage); - EXPECT_DEATH_IF_SUPPORTED(std::cout << *ptr, - "heap-use-after-free|use-of-uninitialized-value"); +#ifdef ABSL_HAVE_ADDRESS_SANITIZER + EXPECT_DEATH_IF_SUPPORTED(std::cout << *ptr, "heap-use-after-free"); +#endif +} + +TEST(Iterator, InvalidUseWithMoveCrashesWithSanitizers) { + if (!SwisstableGenerationsEnabled()) GTEST_SKIP() << "Generations disabled."; + if (kMsvc) GTEST_SKIP() << "MSVC doesn't support | in regexp."; + + IntTable t1, t2; + t1.insert(1); + auto it = t1.begin(); + // ptr will become invalidated on rehash. + const int64_t* ptr = &*it; + (void)ptr; + + t2 = std::move(t1); + EXPECT_DEATH_IF_SUPPORTED(*it, kInvalidIteratorDeathMessage); + EXPECT_DEATH_IF_SUPPORTED(void(it == t2.begin()), + kInvalidIteratorDeathMessage); +#ifdef ABSL_HAVE_ADDRESS_SANITIZER + EXPECT_DEATH_IF_SUPPORTED(std::cout << *ptr, "heap-use-after-free"); +#endif } TEST(Table, ReservedGrowthUpdatesWhenTableDoesntGrow) { @@ -2318,6 +2524,160 @@ TEST(Table, ReservedGrowthUpdatesWhenTableDoesntGrow) { EXPECT_EQ(*it, 0); } +TEST(Table, EraseBeginEndResetsReservedGrowth) { + bool frozen = false; + BadHashFreezableIntTable t{FreezableAlloc<int64_t>(&frozen)}; + t.reserve(100); + const size_t cap = t.capacity(); + frozen = true; // no further allocs allowed + + for (int i = 0; i < 10; ++i) { + // Create a long run (hash function returns constant). + for (int j = 0; j < 100; ++j) t.insert(j); + // Erase elements from the middle of the long run, which creates tombstones. + for (int j = 30; j < 60; ++j) t.erase(j); + EXPECT_EQ(t.size(), 70); + EXPECT_EQ(t.capacity(), cap); + ASSERT_EQ(RawHashSetTestOnlyAccess::CountTombstones(t), 30); + + t.erase(t.begin(), t.end()); + + EXPECT_EQ(t.size(), 0); + EXPECT_EQ(t.capacity(), cap); + ASSERT_EQ(RawHashSetTestOnlyAccess::CountTombstones(t), 0); + } +} + +TEST(Table, GenerationInfoResetsOnClear) { + if (!SwisstableGenerationsEnabled()) GTEST_SKIP() << "Generations disabled."; + if (kMsvc) GTEST_SKIP() << "MSVC doesn't support | in regexp."; + + IntTable t; + for (int i = 0; i < 1000; ++i) t.insert(i); + t.reserve(t.size() + 100); + + t.clear(); + + t.insert(0); + auto it = t.begin(); + t.insert(1); + EXPECT_DEATH_IF_SUPPORTED(*it, kInvalidIteratorDeathMessage); +} + +TEST(Table, InvalidReferenceUseCrashesWithSanitizers) { + if (!SwisstableGenerationsEnabled()) GTEST_SKIP() << "Generations disabled."; +#ifdef ABSL_HAVE_MEMORY_SANITIZER + GTEST_SKIP() << "MSan fails to detect some of these rehashes."; +#endif + + IntTable t; + t.insert(0); + // Rehashing is guaranteed on every insertion while capacity is less than + // RehashProbabilityConstant(). + int64_t i = 0; + while (t.capacity() <= RehashProbabilityConstant()) { + // ptr will become invalidated on rehash. + const int64_t* ptr = &*t.begin(); + t.insert(++i); + EXPECT_DEATH_IF_SUPPORTED(std::cout << *ptr, "use-after-free") << i; + } +} + +TEST(Iterator, InvalidComparisonDifferentTables) { + if (!SwisstableGenerationsEnabled()) GTEST_SKIP() << "Generations disabled."; + + IntTable t1, t2; + IntTable::iterator default_constructed_iter; + // We randomly use one of N empty generations for generations from empty + // hashtables. In general, we won't always detect when iterators from + // different empty hashtables are compared, but in this test case, we + // should deterministically detect the error due to our randomness yielding + // consecutive random generations. + EXPECT_DEATH_IF_SUPPORTED(void(t1.end() == t2.end()), + "Invalid iterator comparison.*empty hashtables"); + EXPECT_DEATH_IF_SUPPORTED(void(t1.end() == default_constructed_iter), + "Invalid iterator comparison.*default-constructed"); + t1.insert(0); + EXPECT_DEATH_IF_SUPPORTED(void(t1.begin() == t2.end()), + "Invalid iterator comparison.*empty hashtable"); + EXPECT_DEATH_IF_SUPPORTED(void(t1.begin() == default_constructed_iter), + "Invalid iterator comparison.*default-constructed"); + t2.insert(0); + EXPECT_DEATH_IF_SUPPORTED(void(t1.begin() == t2.end()), + "Invalid iterator comparison.*end.. iterator"); + EXPECT_DEATH_IF_SUPPORTED(void(t1.begin() == t2.begin()), + "Invalid iterator comparison.*non-end"); +} + +template <typename Alloc> +using RawHashSetAlloc = raw_hash_set<IntPolicy, hash_default_hash<int64_t>, + std::equal_to<int64_t>, Alloc>; + +TEST(Table, AllocatorPropagation) { TestAllocPropagation<RawHashSetAlloc>(); } + +struct CountedHash { + size_t operator()(int value) const { + ++count; + return static_cast<size_t>(value); + } + mutable int count = 0; +}; + +struct CountedHashIntTable + : raw_hash_set<IntPolicy, CountedHash, std::equal_to<int>, + std::allocator<int>> { + using Base = typename CountedHashIntTable::raw_hash_set; + using Base::Base; +}; + +TEST(Table, CountedHash) { + // Verify that raw_hash_set does not compute redundant hashes. +#ifdef NDEBUG + constexpr bool kExpectMinimumHashes = true; +#else + constexpr bool kExpectMinimumHashes = false; +#endif + if (!kExpectMinimumHashes) { + GTEST_SKIP() << "Only run under NDEBUG: `assert` statements may cause " + "redundant hashing."; + } + + using Table = CountedHashIntTable; + auto HashCount = [](const Table& t) { return t.hash_function().count; }; + { + Table t; + EXPECT_EQ(HashCount(t), 0); + } + { + Table t; + t.insert(1); + EXPECT_EQ(HashCount(t), 1); + t.erase(1); + EXPECT_EQ(HashCount(t), 2); + } + { + Table t; + t.insert(3); + EXPECT_EQ(HashCount(t), 1); + auto node = t.extract(3); + EXPECT_EQ(HashCount(t), 2); + t.insert(std::move(node)); + EXPECT_EQ(HashCount(t), 3); + } + { + Table t; + t.emplace(5); + EXPECT_EQ(HashCount(t), 1); + } + { + Table src; + src.insert(7); + Table dst; + dst.merge(src); + EXPECT_EQ(HashCount(dst), 1); + } +} + } // namespace } // namespace container_internal ABSL_NAMESPACE_END diff --git a/absl/container/internal/test_allocator.h b/absl/container/internal/test_allocator.h new file mode 100644 index 00000000..8e365a3c --- /dev/null +++ b/absl/container/internal/test_allocator.h @@ -0,0 +1,387 @@ +// 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. + +#ifndef ABSL_CONTAINER_INTERNAL_TEST_ALLOCATOR_H_ +#define ABSL_CONTAINER_INTERNAL_TEST_ALLOCATOR_H_ + +#include <cassert> +#include <cstddef> +#include <cstdint> +#include <memory> +#include <type_traits> + +#include "gtest/gtest.h" +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace container_internal { + +// This is a stateful allocator, but the state lives outside of the +// allocator (in whatever test is using the allocator). This is odd +// but helps in tests where the allocator is propagated into nested +// containers - that chain of allocators uses the same state and is +// thus easier to query for aggregate allocation information. +template <typename T> +class CountingAllocator { + public: + using Allocator = std::allocator<T>; + using AllocatorTraits = std::allocator_traits<Allocator>; + using value_type = typename AllocatorTraits::value_type; + using pointer = typename AllocatorTraits::pointer; + using const_pointer = typename AllocatorTraits::const_pointer; + using size_type = typename AllocatorTraits::size_type; + using difference_type = typename AllocatorTraits::difference_type; + + CountingAllocator() = default; + explicit CountingAllocator(int64_t* bytes_used) : bytes_used_(bytes_used) {} + CountingAllocator(int64_t* bytes_used, int64_t* instance_count) + : bytes_used_(bytes_used), instance_count_(instance_count) {} + + template <typename U> + CountingAllocator(const CountingAllocator<U>& x) + : bytes_used_(x.bytes_used_), instance_count_(x.instance_count_) {} + + pointer allocate( + size_type n, + typename AllocatorTraits::const_void_pointer hint = nullptr) { + Allocator allocator; + pointer ptr = AllocatorTraits::allocate(allocator, n, hint); + if (bytes_used_ != nullptr) { + *bytes_used_ += n * sizeof(T); + } + return ptr; + } + + void deallocate(pointer p, size_type n) { + Allocator allocator; + AllocatorTraits::deallocate(allocator, p, n); + if (bytes_used_ != nullptr) { + *bytes_used_ -= n * sizeof(T); + } + } + + template <typename U, typename... Args> + void construct(U* p, Args&&... args) { + Allocator allocator; + AllocatorTraits::construct(allocator, p, std::forward<Args>(args)...); + if (instance_count_ != nullptr) { + *instance_count_ += 1; + } + } + + template <typename U> + void destroy(U* p) { + Allocator allocator; + // Ignore GCC warning bug. +#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wuse-after-free" +#endif + AllocatorTraits::destroy(allocator, p); +#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0) +#pragma GCC diagnostic pop +#endif + if (instance_count_ != nullptr) { + *instance_count_ -= 1; + } + } + + template <typename U> + class rebind { + public: + using other = CountingAllocator<U>; + }; + + friend bool operator==(const CountingAllocator& a, + const CountingAllocator& b) { + return a.bytes_used_ == b.bytes_used_ && + a.instance_count_ == b.instance_count_; + } + + friend bool operator!=(const CountingAllocator& a, + const CountingAllocator& b) { + return !(a == b); + } + + int64_t* bytes_used_ = nullptr; + int64_t* instance_count_ = nullptr; +}; + +template <typename T> +struct CopyAssignPropagatingCountingAlloc : public CountingAllocator<T> { + using propagate_on_container_copy_assignment = std::true_type; + + using Base = CountingAllocator<T>; + using Base::Base; + + template <typename U> + explicit CopyAssignPropagatingCountingAlloc( + const CopyAssignPropagatingCountingAlloc<U>& other) + : Base(other.bytes_used_, other.instance_count_) {} + + template <typename U> + struct rebind { + using other = CopyAssignPropagatingCountingAlloc<U>; + }; +}; + +template <typename T> +struct MoveAssignPropagatingCountingAlloc : public CountingAllocator<T> { + using propagate_on_container_move_assignment = std::true_type; + + using Base = CountingAllocator<T>; + using Base::Base; + + template <typename U> + explicit MoveAssignPropagatingCountingAlloc( + const MoveAssignPropagatingCountingAlloc<U>& other) + : Base(other.bytes_used_, other.instance_count_) {} + + template <typename U> + struct rebind { + using other = MoveAssignPropagatingCountingAlloc<U>; + }; +}; + +template <typename T> +struct SwapPropagatingCountingAlloc : public CountingAllocator<T> { + using propagate_on_container_swap = std::true_type; + + using Base = CountingAllocator<T>; + using Base::Base; + + template <typename U> + explicit SwapPropagatingCountingAlloc( + const SwapPropagatingCountingAlloc<U>& other) + : Base(other.bytes_used_, other.instance_count_) {} + + template <typename U> + struct rebind { + using other = SwapPropagatingCountingAlloc<U>; + }; +}; + +// Tries to allocate memory at the minimum alignment even when the default +// allocator uses a higher alignment. +template <typename T> +struct MinimumAlignmentAlloc : std::allocator<T> { + MinimumAlignmentAlloc() = default; + + template <typename U> + explicit MinimumAlignmentAlloc(const MinimumAlignmentAlloc<U>& /*other*/) {} + + template <class U> + struct rebind { + using other = MinimumAlignmentAlloc<U>; + }; + + T* allocate(size_t n) { + T* ptr = std::allocator<T>::allocate(n + 1); + char* cptr = reinterpret_cast<char*>(ptr); + cptr += alignof(T); + return reinterpret_cast<T*>(cptr); + } + + void deallocate(T* ptr, size_t n) { + char* cptr = reinterpret_cast<char*>(ptr); + cptr -= alignof(T); + std::allocator<T>::deallocate(reinterpret_cast<T*>(cptr), n + 1); + } +}; + +inline bool IsAssertEnabled() { + // Use an assert with side-effects to figure out if they are actually enabled. + bool assert_enabled = false; + assert([&]() { // NOLINT + assert_enabled = true; + return true; + }()); + return assert_enabled; +} + +template <template <class Alloc> class Container> +void TestCopyAssignAllocPropagation() { + int64_t bytes1 = 0, instances1 = 0, bytes2 = 0, instances2 = 0; + CopyAssignPropagatingCountingAlloc<int> allocator1(&bytes1, &instances1); + CopyAssignPropagatingCountingAlloc<int> allocator2(&bytes2, &instances2); + + // Test propagating allocator_type. + { + Container<CopyAssignPropagatingCountingAlloc<int>> c1(allocator1); + Container<CopyAssignPropagatingCountingAlloc<int>> c2(allocator2); + + for (int i = 0; i < 100; ++i) c1.insert(i); + + EXPECT_NE(c2.get_allocator(), allocator1); + EXPECT_EQ(instances1, 100); + EXPECT_EQ(instances2, 0); + + c2 = c1; + + EXPECT_EQ(c2.get_allocator(), allocator1); + EXPECT_EQ(instances1, 200); + EXPECT_EQ(instances2, 0); + } + // Test non-propagating allocator_type with different allocators. + { + Container<CountingAllocator<int>> c1(allocator1), c2(allocator2); + + for (int i = 0; i < 100; ++i) c1.insert(i); + + EXPECT_EQ(c2.get_allocator(), allocator2); + EXPECT_EQ(instances1, 100); + EXPECT_EQ(instances2, 0); + + c2 = c1; + + EXPECT_EQ(c2.get_allocator(), allocator2); + EXPECT_EQ(instances1, 100); + EXPECT_EQ(instances2, 100); + } + EXPECT_EQ(bytes1, 0); + EXPECT_EQ(instances1, 0); + EXPECT_EQ(bytes2, 0); + EXPECT_EQ(instances2, 0); +} + +template <template <class Alloc> class Container> +void TestMoveAssignAllocPropagation() { + int64_t bytes1 = 0, instances1 = 0, bytes2 = 0, instances2 = 0; + MoveAssignPropagatingCountingAlloc<int> allocator1(&bytes1, &instances1); + MoveAssignPropagatingCountingAlloc<int> allocator2(&bytes2, &instances2); + + // Test propagating allocator_type. + { + Container<MoveAssignPropagatingCountingAlloc<int>> c1(allocator1); + Container<MoveAssignPropagatingCountingAlloc<int>> c2(allocator2); + + for (int i = 0; i < 100; ++i) c1.insert(i); + + EXPECT_NE(c2.get_allocator(), allocator1); + EXPECT_EQ(instances1, 100); + EXPECT_EQ(instances2, 0); + + c2 = std::move(c1); + + EXPECT_EQ(c2.get_allocator(), allocator1); + EXPECT_EQ(instances1, 100); + EXPECT_EQ(instances2, 0); + } + // Test non-propagating allocator_type with equal allocators. + { + Container<CountingAllocator<int>> c1(allocator1), c2(allocator1); + + for (int i = 0; i < 100; ++i) c1.insert(i); + + EXPECT_EQ(c2.get_allocator(), allocator1); + EXPECT_EQ(instances1, 100); + EXPECT_EQ(instances2, 0); + + c2 = std::move(c1); + + EXPECT_EQ(c2.get_allocator(), allocator1); + EXPECT_EQ(instances1, 100); + EXPECT_EQ(instances2, 0); + } + // Test non-propagating allocator_type with different allocators. + { + Container<CountingAllocator<int>> c1(allocator1), c2(allocator2); + + for (int i = 0; i < 100; ++i) c1.insert(i); + + EXPECT_NE(c2.get_allocator(), allocator1); + EXPECT_EQ(instances1, 100); + EXPECT_EQ(instances2, 0); + + c2 = std::move(c1); + + EXPECT_EQ(c2.get_allocator(), allocator2); + EXPECT_LE(instances1, 100); // The values in c1 may or may not have been + // destroyed at this point. + EXPECT_EQ(instances2, 100); + } + EXPECT_EQ(bytes1, 0); + EXPECT_EQ(instances1, 0); + EXPECT_EQ(bytes2, 0); + EXPECT_EQ(instances2, 0); +} + +template <template <class Alloc> class Container> +void TestSwapAllocPropagation() { + int64_t bytes1 = 0, instances1 = 0, bytes2 = 0, instances2 = 0; + SwapPropagatingCountingAlloc<int> allocator1(&bytes1, &instances1); + SwapPropagatingCountingAlloc<int> allocator2(&bytes2, &instances2); + + // Test propagating allocator_type. + { + Container<SwapPropagatingCountingAlloc<int>> c1(allocator1), c2(allocator2); + + for (int i = 0; i < 100; ++i) c1.insert(i); + + EXPECT_NE(c2.get_allocator(), allocator1); + EXPECT_EQ(instances1, 100); + EXPECT_EQ(instances2, 0); + + c2.swap(c1); + + EXPECT_EQ(c2.get_allocator(), allocator1); + EXPECT_EQ(instances1, 100); + EXPECT_EQ(instances2, 0); + } + // Test non-propagating allocator_type with equal allocators. + { + Container<CountingAllocator<int>> c1(allocator1), c2(allocator1); + + for (int i = 0; i < 100; ++i) c1.insert(i); + + EXPECT_EQ(c2.get_allocator(), allocator1); + EXPECT_EQ(instances1, 100); + EXPECT_EQ(instances2, 0); + + c2.swap(c1); + + EXPECT_EQ(c2.get_allocator(), allocator1); + EXPECT_EQ(instances1, 100); + EXPECT_EQ(instances2, 0); + } + // Test non-propagating allocator_type with different allocators. + { + Container<CountingAllocator<int>> c1(allocator1), c2(allocator2); + + for (int i = 0; i < 100; ++i) c1.insert(i); + + EXPECT_NE(c1.get_allocator(), c2.get_allocator()); + if (IsAssertEnabled()) { + EXPECT_DEATH_IF_SUPPORTED(c2.swap(c1), ""); + } + } + EXPECT_EQ(bytes1, 0); + EXPECT_EQ(instances1, 0); + EXPECT_EQ(bytes2, 0); + EXPECT_EQ(instances2, 0); +} + +template <template <class Alloc> class Container> +void TestAllocPropagation() { + TestCopyAssignAllocPropagation<Container>(); + TestMoveAssignAllocPropagation<Container>(); + TestSwapAllocPropagation<Container>(); +} + +} // namespace container_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CONTAINER_INTERNAL_TEST_ALLOCATOR_H_ diff --git a/absl/container/node_hash_map.h b/absl/container/node_hash_map.h index 6868e63a..a396de2e 100644 --- a/absl/container/node_hash_map.h +++ b/absl/container/node_hash_map.h @@ -226,7 +226,11 @@ class node_hash_map // iterator erase(const_iterator first, const_iterator last): // // Erases the elements in the open interval [`first`, `last`), returning an - // iterator pointing to `last`. + // iterator pointing to `last`. The special case of calling + // `erase(begin(), end())` resets the reserved growth such that if + // `reserve(N)` has previously been called and there has been no intervening + // call to `clear()`, then after calling `erase(begin(), end())`, it is safe + // to assume that inserting N elements will not cause a rehash. // // size_type erase(const key_type& key): // @@ -404,7 +408,7 @@ class node_hash_map // for the past-the-end iterator, which is invalidated. // // `swap()` requires that the node hash map's hashing and key equivalence - // functions be Swappable, and are exchaged using unqualified calls to + // functions be Swappable, and are exchanged using unqualified calls to // non-member `swap()`. If the map's allocator has // `std::allocator_traits<allocator_type>::propagate_on_container_swap::value` // set to `true`, the allocators are also exchanged using an unqualified call diff --git a/absl/container/node_hash_set.h b/absl/container/node_hash_set.h index f2cc70c3..421ff460 100644 --- a/absl/container/node_hash_set.h +++ b/absl/container/node_hash_set.h @@ -218,7 +218,11 @@ class node_hash_set // iterator erase(const_iterator first, const_iterator last): // // Erases the elements in the open interval [`first`, `last`), returning an - // iterator pointing to `last`. + // iterator pointing to `last`. The special case of calling + // `erase(begin(), end())` resets the reserved growth such that if + // `reserve(N)` has previously been called and there has been no intervening + // call to `clear()`, then after calling `erase(begin(), end())`, it is safe + // to assume that inserting N elements will not cause a rehash. // // size_type erase(const key_type& key): // @@ -334,7 +338,7 @@ class node_hash_set // for the past-the-end iterator, which is invalidated. // // `swap()` requires that the node hash set's hashing and key equivalence - // functions be Swappable, and are exchaged using unqualified calls to + // functions be Swappable, and are exchanged using unqualified calls to // non-member `swap()`. If the set's allocator has // `std::allocator_traits<allocator_type>::propagate_on_container_swap::value` // set to `true`, the allocators are also exchanged using an unqualified call diff --git a/absl/copts/AbseilConfigureCopts.cmake b/absl/copts/AbseilConfigureCopts.cmake index 8209b262..3f737c81 100644 --- a/absl/copts/AbseilConfigureCopts.cmake +++ b/absl/copts/AbseilConfigureCopts.cmake @@ -83,6 +83,16 @@ elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") # MATCHES so we get both Clang an set(ABSL_DEFAULT_COPTS "${ABSL_LLVM_FLAGS}") set(ABSL_TEST_COPTS "${ABSL_LLVM_TEST_FLAGS}") endif() +elseif(CMAKE_CXX_COMPILER_ID STREQUAL "IntelLLVM") + # IntelLLVM is similar to Clang, with some additional flags. + if(MSVC) + # clang-cl is half MSVC, half LLVM + set(ABSL_DEFAULT_COPTS "${ABSL_CLANG_CL_FLAGS}") + set(ABSL_TEST_COPTS "${ABSL_CLANG_CL_TEST_FLAGS}") + else() + set(ABSL_DEFAULT_COPTS "${ABSL_LLVM_FLAGS}") + set(ABSL_TEST_COPTS "${ABSL_LLVM_TEST_FLAGS}") + endif() elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") set(ABSL_DEFAULT_COPTS "${ABSL_MSVC_FLAGS}") set(ABSL_TEST_COPTS "${ABSL_MSVC_TEST_FLAGS}") diff --git a/absl/crc/BUILD.bazel b/absl/crc/BUILD.bazel index 1c58f46c..f44c3f6b 100644 --- a/absl/crc/BUILD.bazel +++ b/absl/crc/BUILD.bazel @@ -19,7 +19,14 @@ load( "ABSL_TEST_COPTS", ) -package(default_visibility = ["//visibility:private"]) +package( + default_visibility = ["//visibility:private"], + features = [ + "header_modules", + "layering_check", + "parse_headers", + ], +) licenses(["notice"]) @@ -54,10 +61,8 @@ cc_library( visibility = ["//visibility:private"], deps = [ ":cpu_detect", - "//absl/base", "//absl/base:config", "//absl/base:core_headers", - "//absl/base:dynamic_annotations", "//absl/base:endian", "//absl/base:prefetch", "//absl/base:raw_logging_internal", @@ -72,7 +77,7 @@ cc_library( "crc32c.cc", "internal/crc32c_inline.h", "internal/crc_memcpy_fallback.cc", - "internal/crc_memcpy_x86_64.cc", + "internal/crc_memcpy_x86_arm_combined.cc", "internal/crc_non_temporal_memcpy.cc", ], hdrs = [ @@ -89,10 +94,10 @@ cc_library( ":non_temporal_memcpy", "//absl/base:config", "//absl/base:core_headers", - "//absl/base:dynamic_annotations", "//absl/base:endian", "//absl/base:prefetch", "//absl/strings", + "//absl/strings:str_format", ], ) @@ -105,6 +110,8 @@ cc_test( deps = [ ":crc32c", "//absl/strings", + "//absl/strings:str_format", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -147,6 +154,7 @@ cc_test( "//absl/random", "//absl/random:distributions", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -159,6 +167,7 @@ cc_test( visibility = ["//visibility:private"], deps = [ ":non_temporal_memcpy", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -187,6 +196,7 @@ cc_test( deps = [ ":crc32c", ":crc_cord_state", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/crc/CMakeLists.txt b/absl/crc/CMakeLists.txt index 72ea2094..ec7b4512 100644 --- a/absl/crc/CMakeLists.txt +++ b/absl/crc/CMakeLists.txt @@ -42,10 +42,8 @@ absl_cc_library( ${ABSL_DEFAULT_COPTS} DEPS absl::crc_cpu_detect - absl::base absl::config absl::core_headers - absl::dynamic_annotations absl::endian absl::prefetch absl::raw_logging_internal @@ -64,7 +62,7 @@ absl_cc_library( "crc32c.cc" "internal/crc32c_inline.h" "internal/crc_memcpy_fallback.cc" - "internal/crc_memcpy_x86_64.cc" + "internal/crc_memcpy_x86_arm_combined.cc" "internal/crc_non_temporal_memcpy.cc" COPTS ${ABSL_DEFAULT_COPTS} @@ -74,9 +72,9 @@ absl_cc_library( absl::non_temporal_memcpy absl::config absl::core_headers - absl::dynamic_annotations absl::endian absl::prefetch + absl::str_format absl::strings ) @@ -90,6 +88,7 @@ absl_cc_test( DEPS absl::crc32c absl::strings + absl::str_format GTest::gtest_main ) diff --git a/absl/crc/crc32c.h b/absl/crc/crc32c.h index ba09e52a..362861e4 100644 --- a/absl/crc/crc32c.h +++ b/absl/crc/crc32c.h @@ -29,6 +29,7 @@ #include <ostream> #include "absl/crc/internal/crc32c_inline.h" +#include "absl/strings/str_format.h" #include "absl/strings/string_view.h" namespace absl { @@ -61,10 +62,16 @@ class crc32c_t final { friend bool operator!=(crc32c_t lhs, crc32c_t rhs) { return !(lhs == rhs); } + template <typename Sink> + friend void AbslStringify(Sink& sink, crc32c_t crc) { + absl::Format(&sink, "%08x", static_cast<uint32_t>(crc)); + } + private: uint32_t crc_; }; + namespace crc_internal { // Non-inline code path for `absl::ExtendCrc32c()`. Do not call directly. // Call `absl::ExtendCrc32c()` (defined below) instead. @@ -174,7 +181,7 @@ crc32c_t RemoveCrc32cSuffix(crc32c_t full_string_crc, crc32c_t suffix_crc, // // Streams the CRC32C value `crc` to the stream `os`. inline std::ostream& operator<<(std::ostream& os, crc32c_t crc) { - return os << static_cast<uint32_t>(crc); + return os << absl::StreamFormat("%08x", static_cast<uint32_t>(crc)); } ABSL_NAMESPACE_END diff --git a/absl/crc/crc32c_test.cc b/absl/crc/crc32c_test.cc index 72d422a1..df0afb3e 100644 --- a/absl/crc/crc32c_test.cc +++ b/absl/crc/crc32c_test.cc @@ -18,11 +18,13 @@ #include <cstddef> #include <cstdint> #include <cstring> +#include <sstream> #include <string> #include "gtest/gtest.h" #include "absl/crc/internal/crc32c.h" #include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" #include "absl/strings/string_view.h" namespace { @@ -191,4 +193,35 @@ TEST(CRC32C, RemoveSuffix) { EXPECT_EQ(absl::RemoveCrc32cSuffix(crc_ab, crc_b, world.size()), crc_a); } + +TEST(CRC32C, InsertionOperator) { + { + std::ostringstream buf; + buf << absl::crc32c_t{0xc99465aa}; + EXPECT_EQ(buf.str(), "c99465aa"); + } + { + std::ostringstream buf; + buf << absl::crc32c_t{0}; + EXPECT_EQ(buf.str(), "00000000"); + } + { + std::ostringstream buf; + buf << absl::crc32c_t{17}; + EXPECT_EQ(buf.str(), "00000011"); + } +} + +TEST(CRC32C, AbslStringify) { + // StrFormat + EXPECT_EQ(absl::StrFormat("%v", absl::crc32c_t{0xc99465aa}), "c99465aa"); + EXPECT_EQ(absl::StrFormat("%v", absl::crc32c_t{0}), "00000000"); + EXPECT_EQ(absl::StrFormat("%v", absl::crc32c_t{17}), "00000011"); + + // StrCat + EXPECT_EQ(absl::StrCat(absl::crc32c_t{0xc99465aa}), "c99465aa"); + EXPECT_EQ(absl::StrCat(absl::crc32c_t{0}), "00000000"); + EXPECT_EQ(absl::StrCat(absl::crc32c_t{17}), "00000011"); +} + } // namespace diff --git a/absl/crc/internal/cpu_detect.cc b/absl/crc/internal/cpu_detect.cc index d61b7018..d7eedd1c 100644 --- a/absl/crc/internal/cpu_detect.cc +++ b/absl/crc/internal/cpu_detect.cc @@ -28,15 +28,12 @@ #include <intrin.h> #endif -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace crc_internal { - #if defined(__x86_64__) || defined(_M_X64) - -namespace { - -#if !defined(_WIN32) && !defined(_WIN64) +#if ABSL_HAVE_BUILTIN(__cpuid) +// MSVC-equivalent __cpuid intrinsic declaration for clang-like compilers +// for non-Windows build environments. +extern void __cpuid(int[4], int); +#elif !defined(_WIN32) && !defined(_WIN64) // MSVC defines this function for us. // https://learn.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex static void __cpuid(int cpu_info[4], int info_type) { @@ -46,6 +43,15 @@ static void __cpuid(int cpu_info[4], int info_type) { : "a"(info_type), "c"(0)); } #endif // !defined(_WIN32) && !defined(_WIN64) +#endif // defined(__x86_64__) || defined(_M_X64) + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +#if defined(__x86_64__) || defined(_M_X64) + +namespace { enum class Vendor { kUnknown, @@ -183,8 +189,14 @@ CpuType GetAmdCpuType() { break; case 0x19: switch (model_num) { + case 0x0: // Stepping Ax case 0x1: // Stepping B0 return CpuType::kAmdMilan; + case 0x10: // Stepping A0 + case 0x11: // Stepping B0 + return CpuType::kAmdGenoa; + case 0x44: // Stepping A0 + return CpuType::kAmdRyzenV3000; default: return CpuType::kUnknown; } @@ -231,8 +243,26 @@ CpuType GetCpuType() { ABSL_INTERNAL_AARCH64_ID_REG_READ(MIDR_EL1, midr); uint32_t implementer = (midr >> 24) & 0xff; uint32_t part_number = (midr >> 4) & 0xfff; - if (implementer == 0x41 && part_number == 0xd0c) { - return CpuType::kArmNeoverseN1; + switch (implementer) { + case 0x41: + switch (part_number) { + case 0xd0c: return CpuType::kArmNeoverseN1; + case 0xd40: return CpuType::kArmNeoverseV1; + case 0xd49: return CpuType::kArmNeoverseN2; + case 0xd4f: return CpuType::kArmNeoverseV2; + default: + return CpuType::kUnknown; + } + break; + case 0xc0: + switch (part_number) { + case 0xac3: return CpuType::kAmpereSiryn; + default: + return CpuType::kUnknown; + } + break; + default: + return CpuType::kUnknown; } } return CpuType::kUnknown; diff --git a/absl/crc/internal/cpu_detect.h b/absl/crc/internal/cpu_detect.h index 6054f696..01e19590 100644 --- a/absl/crc/internal/cpu_detect.h +++ b/absl/crc/internal/cpu_detect.h @@ -29,6 +29,8 @@ enum class CpuType { kAmdRome, kAmdNaples, kAmdMilan, + kAmdGenoa, + kAmdRyzenV3000, kIntelCascadelakeXeon, kIntelSkylakeXeon, kIntelBroadwell, @@ -37,6 +39,10 @@ enum class CpuType { kIntelSandybridge, kIntelWestmere, kArmNeoverseN1, + kArmNeoverseV1, + kAmpereSiryn, + kArmNeoverseN2, + kArmNeoverseV2 }; // Returns the type of host CPU this code is running on. Returns kUnknown if diff --git a/absl/crc/internal/crc.cc b/absl/crc/internal/crc.cc index bb8936e3..22e91c53 100644 --- a/absl/crc/internal/crc.cc +++ b/absl/crc/internal/crc.cc @@ -44,8 +44,8 @@ #include <cstdint> #include "absl/base/internal/endian.h" -#include "absl/base/internal/prefetch.h" #include "absl/base/internal/raw_logging.h" +#include "absl/base/prefetch.h" #include "absl/crc/internal/crc_internal.h" namespace absl { @@ -176,9 +176,6 @@ CRCImpl* CRCImpl::NewInternal() { return result; } -// The CRC of the empty string is always the CRC polynomial itself. -void CRCImpl::Empty(uint32_t* crc) const { *crc = kCrc32cPoly; } - // The 32-bit implementation void CRC32::InitTables() { @@ -261,7 +258,7 @@ void CRC32::Extend(uint32_t* crc, const void* bytes, size_t length) const { const uint8_t* e = p + length; uint32_t l = *crc; - auto step_one_byte = [this, &p, &l] () { + auto step_one_byte = [this, &p, &l]() { int c = (l & 0xff) ^ *p++; l = this->table0_[c] ^ (l >> 8); }; @@ -309,7 +306,7 @@ void CRC32::Extend(uint32_t* crc, const void* bytes, size_t length) const { // Process kStride interleaved swaths through the data in parallel. while ((e - p) > kPrefetchHorizon) { - base_internal::PrefetchNta( + PrefetchToLocalCacheNta( reinterpret_cast<const void*>(p + kPrefetchHorizon)); // Process 64 bytes at a time step_stride(); @@ -359,7 +356,7 @@ void CRC32::Extend(uint32_t* crc, const void* bytes, size_t length) const { void CRC32::ExtendByZeroesImpl(uint32_t* crc, size_t length, const uint32_t zeroes_table[256], - const uint32_t poly_table[256]) const { + const uint32_t poly_table[256]) { if (length != 0) { uint32_t l = *crc; // For each ZEROES_BASE_LG bits in length @@ -435,34 +432,6 @@ CRC* CRC::Crc32c() { return singleton; } -// This Concat implementation works for arbitrary polynomials. -void CRC::Concat(uint32_t* px, uint32_t y, size_t ylen) { - // https://en.wikipedia.org/wiki/Mathematics_of_cyclic_redundancy_checks - // The CRC of a message M is the remainder of polynomial divison modulo G, - // where the coefficient arithmetic is performed modulo 2 (so +/- are XOR): - // R(x) = M(x) x**n (mod G) - // (n is the degree of G) - // In practice, we use an initial value A and a bitmask B to get - // R = (A ^ B)x**|M| ^ Mx**n ^ B (mod G) - // If M is the concatenation of two strings S and T, and Z is the string of - // len(T) 0s, then the remainder CRC(ST) can be expressed as: - // R = (A ^ B)x**|ST| ^ STx**n ^ B - // = (A ^ B)x**|SZ| ^ SZx**n ^ B ^ Tx**n - // = CRC(SZ) ^ Tx**n - // CRC(Z) = (A ^ B)x**|T| ^ B - // CRC(T) = (A ^ B)x**|T| ^ Tx**n ^ B - // So R = CRC(SZ) ^ CRC(Z) ^ CRC(T) - // - // And further, since CRC(SZ) = Extend(CRC(S), Z), - // CRC(SZ) ^ CRC(Z) = Extend(CRC(S) ^ CRC(''), Z). - uint32_t z; - uint32_t t; - Empty(&z); - t = *px ^ z; - ExtendByZeroes(&t, ylen); - *px = t ^ y; -} - } // namespace crc_internal ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/crc/internal/crc.h b/absl/crc/internal/crc.h index 72515b06..4efdd032 100644 --- a/absl/crc/internal/crc.h +++ b/absl/crc/internal/crc.h @@ -40,9 +40,6 @@ class CRC { public: virtual ~CRC(); - // Place the CRC of the empty string in "*crc" - virtual void Empty(uint32_t* crc) const = 0; - // If "*crc" is the CRC of bytestring A, place the CRC of // the bytestring formed from the concatenation of A and the "length" // bytes at "bytes" into "*crc". @@ -53,22 +50,17 @@ class CRC { // points to an array of "length" zero bytes. virtual void ExtendByZeroes(uint32_t* crc, size_t length) const = 0; - // Inverse opration of ExtendByZeroes. If `crc` is the CRC value of a string + // Inverse operation of ExtendByZeroes. If `crc` is the CRC value of a string // ending in `length` zero bytes, this returns a CRC value of that string // with those zero bytes removed. virtual void UnextendByZeroes(uint32_t* crc, size_t length) const = 0; - // If *px is the CRC (as defined by *crc) of some string X, - // and y is the CRC of some string Y that is ylen bytes long, set - // *px to the CRC of the concatenation of X followed by Y. - virtual void Concat(uint32_t* px, uint32_t y, size_t ylen); - // Apply a non-linear transformation to "*crc" so that // it is safe to CRC the result with the same polynomial without // any reduction of error-detection ability in the outer CRC. // Unscramble() performs the inverse transformation. // It is strongly recommended that CRCs be scrambled before storage or - // transmission, and unscrambled at the other end before futher manipulation. + // transmission, and unscrambled at the other end before further manipulation. virtual void Scramble(uint32_t* crc) const = 0; virtual void Unscramble(uint32_t* crc) const = 0; diff --git a/absl/crc/internal/crc32_x86_arm_combined_simd.h b/absl/crc/internal/crc32_x86_arm_combined_simd.h index fb643986..59995ae3 100644 --- a/absl/crc/internal/crc32_x86_arm_combined_simd.h +++ b/absl/crc/internal/crc32_x86_arm_combined_simd.h @@ -33,7 +33,7 @@ #include <x86intrin.h> #define ABSL_CRC_INTERNAL_HAVE_X86_SIMD -#elif defined(_MSC_VER) && defined(__AVX__) +#elif defined(_MSC_VER) && !defined(__clang__) && defined(__AVX__) // MSVC AVX (/arch:AVX) implies SSE 4.2 and PCLMULQDQ. #include <intrin.h> @@ -59,6 +59,8 @@ namespace crc_internal { #if defined(ABSL_CRC_INTERNAL_HAVE_ARM_SIMD) using V128 = uint64x2_t; #else +// Note: Do not use __m128i_u, it is not portable. +// Use V128_LoadU() perform an unaligned load from __m128i*. using V128 = __m128i; #endif @@ -78,6 +80,9 @@ V128 V128_Load(const V128* src); // Load 128 bits of integer data. |src| does not need to be aligned. V128 V128_LoadU(const V128* src); +// Store 128 bits of integer data. |src| must be 16-byte aligned. +void V128_Store(V128* dst, V128 data); + // Polynomially multiplies the high 64 bits of |l| and |r|. V128 V128_PMulHi(const V128 l, const V128 r); @@ -109,6 +114,10 @@ V128 V128_ShiftRight(const V128 l); template <int imm> int V128_Extract32(const V128 l); +// Extracts a 64-bit integer from |l|, selected with |imm|. +template <int imm> +uint64_t V128_Extract64(const V128 l); + // Extracts the low 64 bits from V128. int64_t V128_Low64(const V128 l); @@ -139,6 +148,8 @@ inline V128 V128_Load(const V128* src) { return _mm_load_si128(src); } inline V128 V128_LoadU(const V128* src) { return _mm_loadu_si128(src); } +inline void V128_Store(V128* dst, V128 data) { _mm_store_si128(dst, data); } + inline V128 V128_PMulHi(const V128 l, const V128 r) { return _mm_clmulepi64_si128(l, r, 0x11); } @@ -173,6 +184,11 @@ inline int V128_Extract32(const V128 l) { return _mm_extract_epi32(l, imm); } +template <int imm> +inline uint64_t V128_Extract64(const V128 l) { + return static_cast<uint64_t>(_mm_extract_epi64(l, imm)); +} + inline int64_t V128_Low64(const V128 l) { return _mm_cvtsi128_si64(l); } inline V128 V128_ShiftLeft64(const V128 l, const V128 r) { @@ -203,10 +219,14 @@ inline V128 V128_LoadU(const V128* src) { return vld1q_u64(reinterpret_cast<const uint64_t*>(src)); } +inline void V128_Store(V128* dst, V128 data) { + vst1q_u64(reinterpret_cast<uint64_t*>(dst), data); +} + // Using inline assembly as clang does not generate the pmull2 instruction and // performance drops by 15-20%. -// TODO(b/193678732): Investigate why the compiler decides not to generate -// such instructions and why it becomes so much worse. +// TODO(b/193678732): Investigate why there is a slight performance hit when +// using intrinsics instead of inline assembly. inline V128 V128_PMulHi(const V128 l, const V128 r) { uint64x2_t res; __asm__ __volatile__("pmull2 %0.1q, %1.2d, %2.2d \n\t" @@ -215,10 +235,14 @@ inline V128 V128_PMulHi(const V128 l, const V128 r) { return res; } +// TODO(b/193678732): Investigate why the compiler decides to move the constant +// loop multiplicands from GPR to Neon registers every loop iteration. inline V128 V128_PMulLow(const V128 l, const V128 r) { - return reinterpret_cast<V128>(vmull_p64( - reinterpret_cast<poly64_t>(vget_low_p64(vreinterpretq_p64_u64(l))), - reinterpret_cast<poly64_t>(vget_low_p64(vreinterpretq_p64_u64(r))))); + uint64x2_t res; + __asm__ __volatile__("pmull %0.1q, %1.1d, %2.1d \n\t" + : "=w"(res) + : "w"(l), "w"(r)); + return res; } inline V128 V128_PMul01(const V128 l, const V128 r) { @@ -252,6 +276,11 @@ inline int V128_Extract32(const V128 l) { return vgetq_lane_s32(vreinterpretq_s32_u64(l), imm); } +template <int imm> +inline uint64_t V128_Extract64(const V128 l) { + return vgetq_lane_u64(l, imm); +} + inline int64_t V128_Low64(const V128 l) { return vgetq_lane_s64(vreinterpretq_s64_u64(l), 0); } diff --git a/absl/crc/internal/crc_cord_state.cc b/absl/crc/internal/crc_cord_state.cc index d0be0ddd..28d04dc4 100644 --- a/absl/crc/internal/crc_cord_state.cc +++ b/absl/crc/internal/crc_cord_state.cc @@ -121,7 +121,7 @@ void CrcCordState::Poison() { } } else { // Add a fake corrupt chunk. - rep->prefix_crc.push_back(PrefixCrc(0, crc32c_t{1})); + rep->prefix_crc.emplace_back(0, crc32c_t{1}); } } diff --git a/absl/crc/internal/crc_cord_state.h b/absl/crc/internal/crc_cord_state.h index d305424c..fbbb8c00 100644 --- a/absl/crc/internal/crc_cord_state.h +++ b/absl/crc/internal/crc_cord_state.h @@ -71,9 +71,9 @@ class CrcCordState { struct Rep { // `removed_prefix` is the crc and length of any prefix that has been // removed from the Cord (for example, by calling - // `CrcCord::RemovePrefix()`). To get the checkum of any prefix of the cord, - // this value must be subtracted from `prefix_crc`. See `Checksum()` for an - // example. + // `CrcCord::RemovePrefix()`). To get the checksum of any prefix of the + // cord, this value must be subtracted from `prefix_crc`. See `Checksum()` + // for an example. // // CrcCordState is said to be "normalized" if removed_prefix.length == 0. PrefixCrc removed_prefix; @@ -109,7 +109,7 @@ class CrcCordState { // Returns true if the chunked CRC32C cached is normalized. bool IsNormalized() const { return rep().removed_prefix.length == 0; } - // Normalizes the chunked CRC32C checksum cache by substracting any removed + // Normalizes the chunked CRC32C checksum cache by subtracting any removed // prefix from the chunks. void Normalize(); diff --git a/absl/crc/internal/crc_internal.h b/absl/crc/internal/crc_internal.h index 0611b383..4d3582d9 100644 --- a/absl/crc/internal/crc_internal.h +++ b/absl/crc/internal/crc_internal.h @@ -60,18 +60,16 @@ constexpr uint64_t kScrambleHi = (static_cast<uint64_t>(0x4f1bbcdcU) << 32) | constexpr uint64_t kScrambleLo = (static_cast<uint64_t>(0xf9ce6030U) << 32) | static_cast<uint64_t>(0x2e76e41bU); -class CRCImpl : public CRC { // Implemention of the abstract class CRC +class CRCImpl : public CRC { // Implementation of the abstract class CRC public: using Uint32By256 = uint32_t[256]; - CRCImpl() {} + CRCImpl() = default; ~CRCImpl() override = default; // The internal version of CRC::New(). static CRCImpl* NewInternal(); - void Empty(uint32_t* crc) const override; - // Fill in a table for updating a CRC by one word of 'word_size' bytes // [last_lo, last_hi] contains the answer if the last bit in the word // is set. @@ -96,8 +94,8 @@ class CRCImpl : public CRC { // Implemention of the abstract class CRC // This is the 32-bit implementation. It handles all sizes from 8 to 32. class CRC32 : public CRCImpl { public: - CRC32() {} - ~CRC32() override {} + CRC32() = default; + ~CRC32() override = default; void Extend(uint32_t* crc, const void* bytes, size_t length) const override; void ExtendByZeroes(uint32_t* crc, size_t length) const override; @@ -111,16 +109,16 @@ class CRC32 : public CRCImpl { // Common implementation guts for ExtendByZeroes and UnextendByZeroes(). // // zeroes_table is a table as returned by FillZeroesTable(), containing - // polynomials representing CRCs of strings-of-zeros of various lenghts, + // polynomials representing CRCs of strings-of-zeros of various lengths, // and which can be combined by polynomial multiplication. poly_table is // a table of CRC byte extension values. These tables are determined by // the generator polynomial. // // These will be set to reverse_zeroes_ and reverse_table0_ for Unextend, and // CRC32::zeroes_ and CRC32::table0_ for Extend. - void ExtendByZeroesImpl(uint32_t* crc, size_t length, - const uint32_t zeroes_table[256], - const uint32_t poly_table[256]) const; + static void ExtendByZeroesImpl(uint32_t* crc, size_t length, + const uint32_t zeroes_table[256], + const uint32_t poly_table[256]); uint32_t table0_[256]; // table of byte extensions uint32_t zeroes_[256]; // table of zero extensions diff --git a/absl/crc/internal/crc_memcpy.h b/absl/crc/internal/crc_memcpy.h index 4909d433..a0fed65a 100644 --- a/absl/crc/internal/crc_memcpy.h +++ b/absl/crc/internal/crc_memcpy.h @@ -20,12 +20,15 @@ #include "absl/base/config.h" #include "absl/crc/crc32c.h" +#include "absl/crc/internal/crc32_x86_arm_combined_simd.h" // Defined if the class AcceleratedCrcMemcpyEngine exists. -#if defined(__x86_64__) && defined(__SSE4_2__) -#define ABSL_INTERNAL_HAVE_X86_64_ACCELERATED_CRC_MEMCPY_ENGINE 1 -#elif defined(_MSC_VER) && defined(__AVX__) +// TODO(b/299127771): Consider relaxing the pclmul requirement once the other +// intrinsics are conditionally compiled without it. +#if defined(ABSL_CRC_INTERNAL_HAVE_X86_SIMD) #define ABSL_INTERNAL_HAVE_X86_64_ACCELERATED_CRC_MEMCPY_ENGINE 1 +#elif defined(ABSL_CRC_INTERNAL_HAVE_ARM_SIMD) +#define ABSL_INTERNAL_HAVE_ARM_ACCELERATED_CRC_MEMCPY_ENGINE 1 #endif namespace absl { diff --git a/absl/crc/internal/crc_memcpy_fallback.cc b/absl/crc/internal/crc_memcpy_fallback.cc index 15b4b055..07795504 100644 --- a/absl/crc/internal/crc_memcpy_fallback.cc +++ b/absl/crc/internal/crc_memcpy_fallback.cc @@ -54,7 +54,8 @@ absl::crc32c_t FallbackCrcMemcpyEngine::Compute(void* __restrict dst, } // Compile the following only if we don't have -#ifndef ABSL_INTERNAL_HAVE_X86_64_ACCELERATED_CRC_MEMCPY_ENGINE +#if !defined(ABSL_INTERNAL_HAVE_X86_64_ACCELERATED_CRC_MEMCPY_ENGINE) && \ + !defined(ABSL_INTERNAL_HAVE_ARM_ACCELERATED_CRC_MEMCPY_ENGINE) CrcMemcpy::ArchSpecificEngines CrcMemcpy::GetArchSpecificEngines() { CrcMemcpy::ArchSpecificEngines engines; @@ -68,7 +69,8 @@ std::unique_ptr<CrcMemcpyEngine> CrcMemcpy::GetTestEngine(int /*vector*/, return std::make_unique<FallbackCrcMemcpyEngine>(); } -#endif // ABSL_INTERNAL_HAVE_X86_64_ACCELERATED_CRC_MEMCPY_ENGINE +#endif // !ABSL_INTERNAL_HAVE_X86_64_ACCELERATED_CRC_MEMCPY_ENGINE && + // !ABSL_INTERNAL_HAVE_ARM_ACCELERATED_CRC_MEMCPY_ENGINE } // namespace crc_internal ABSL_NAMESPACE_END diff --git a/absl/crc/internal/crc_memcpy_test.cc b/absl/crc/internal/crc_memcpy_test.cc index bbdcd205..045a1e5e 100644 --- a/absl/crc/internal/crc_memcpy_test.cc +++ b/absl/crc/internal/crc_memcpy_test.cc @@ -33,7 +33,7 @@ namespace { enum CrcEngine { - X86 = 0, + ACCELERATED = 0, NONTEMPORAL = 1, FALLBACK = 2, }; @@ -66,10 +66,10 @@ typedef CrcMemcpyTest<4500> CrcSmallTest; typedef CrcMemcpyTest<(1 << 24)> CrcLargeTest; // Parametrize the small test so that it can be done with all configurations. template <typename ParamsT> -class x86ParamTestTemplate : public CrcSmallTest, - public ::testing::WithParamInterface<ParamsT> { +class EngineParamTestTemplate : public CrcSmallTest, + public ::testing::WithParamInterface<ParamsT> { protected: - x86ParamTestTemplate() { + EngineParamTestTemplate() { if (GetParam().crc_engine_selector == FALLBACK) { engine_ = std::make_unique<absl::crc_internal::FallbackCrcMemcpyEngine>(); } else if (GetParam().crc_engine_selector == NONTEMPORAL) { @@ -89,14 +89,14 @@ class x86ParamTestTemplate : public CrcSmallTest, std::unique_ptr<absl::crc_internal::CrcMemcpyEngine> engine_; }; struct TestParams { - CrcEngine crc_engine_selector = X86; + CrcEngine crc_engine_selector = ACCELERATED; int vector_lanes = 0; int integer_lanes = 0; }; -using x86ParamTest = x86ParamTestTemplate<TestParams>; +using EngineParamTest = EngineParamTestTemplate<TestParams>; // SmallCorrectness is designed to exercise every possible set of code paths // in the memcpy code, not including the loop. -TEST_P(x86ParamTest, SmallCorrectnessCheckSourceAlignment) { +TEST_P(EngineParamTest, SmallCorrectnessCheckSourceAlignment) { constexpr size_t kTestSizes[] = {0, 100, 255, 512, 1024, 4000, kMaxCopySize}; for (size_t source_alignment = 0; source_alignment < kAlignment; @@ -107,6 +107,9 @@ TEST_P(x86ParamTest, SmallCorrectnessCheckSourceAlignment) { *(base_data + i) = static_cast<char>(absl::Uniform<unsigned char>(gen_)); } + SCOPED_TRACE(absl::StrCat("engine=<", GetParam().vector_lanes, ",", + GetParam().integer_lanes, ">, ", "size=", size, + ", source_alignment=", source_alignment)); absl::crc32c_t initial_crc = absl::crc32c_t{absl::Uniform<uint32_t>(gen_)}; absl::crc32c_t experiment_crc = @@ -127,7 +130,7 @@ TEST_P(x86ParamTest, SmallCorrectnessCheckSourceAlignment) { } } -TEST_P(x86ParamTest, SmallCorrectnessCheckDestAlignment) { +TEST_P(EngineParamTest, SmallCorrectnessCheckDestAlignment) { constexpr size_t kTestSizes[] = {0, 100, 255, 512, 1024, 4000, kMaxCopySize}; for (size_t dest_alignment = 0; dest_alignment < kAlignment; @@ -138,6 +141,9 @@ TEST_P(x86ParamTest, SmallCorrectnessCheckDestAlignment) { *(base_data + i) = static_cast<char>(absl::Uniform<unsigned char>(gen_)); } + SCOPED_TRACE(absl::StrCat("engine=<", GetParam().vector_lanes, ",", + GetParam().integer_lanes, ">, ", "size=", size, + ", destination_alignment=", dest_alignment)); absl::crc32c_t initial_crc = absl::crc32c_t{absl::Uniform<uint32_t>(gen_)}; absl::crc32c_t experiment_crc = @@ -157,10 +163,12 @@ TEST_P(x86ParamTest, SmallCorrectnessCheckDestAlignment) { } } -INSTANTIATE_TEST_SUITE_P(x86ParamTest, x86ParamTest, +INSTANTIATE_TEST_SUITE_P(EngineParamTest, EngineParamTest, ::testing::Values( // Tests for configurations that may occur in prod. - TestParams{X86, 3, 0}, TestParams{X86, 1, 2}, + TestParams{ACCELERATED, 3, 0}, + TestParams{ACCELERATED, 1, 2}, + TestParams{ACCELERATED, 1, 0}, // Fallback test. TestParams{FALLBACK, 0, 0}, // Non Temporal diff --git a/absl/crc/internal/crc_memcpy_x86_64.cc b/absl/crc/internal/crc_memcpy_x86_arm_combined.cc index 66f784de..968e9ae3 100644 --- a/absl/crc/internal/crc_memcpy_x86_64.cc +++ b/absl/crc/internal/crc_memcpy_x86_arm_combined.cc @@ -12,9 +12,9 @@ // See the License for the specific language governing permissions and // limitations under the License. -// Simultaneous memcopy and CRC-32C for x86-64. Uses integer registers because -// XMM registers do not support the CRC instruction (yet). While copying, -// compute the running CRC of the data being copied. +// Simultaneous memcopy and CRC-32C for x86-64 and ARM 64. Uses integer +// registers because XMM registers do not support the CRC instruction (yet). +// While copying, compute the running CRC of the data being copied. // // It is assumed that any CPU running this code has SSE4.2 instructions // available (for CRC32C). This file will do nothing if that is not true. @@ -49,17 +49,20 @@ #include <array> #include <cstddef> #include <cstdint> -#include <type_traits> +#include <cstring> +#include <memory> -#include "absl/base/dynamic_annotations.h" -#include "absl/base/internal/prefetch.h" +#include "absl/base/config.h" #include "absl/base/optimization.h" +#include "absl/base/prefetch.h" #include "absl/crc/crc32c.h" #include "absl/crc/internal/cpu_detect.h" +#include "absl/crc/internal/crc32_x86_arm_combined_simd.h" #include "absl/crc/internal/crc_memcpy.h" #include "absl/strings/string_view.h" -#ifdef ABSL_INTERNAL_HAVE_X86_64_ACCELERATED_CRC_MEMCPY_ENGINE +#if defined(ABSL_INTERNAL_HAVE_X86_64_ACCELERATED_CRC_MEMCPY_ENGINE) || \ + defined(ABSL_INTERNAL_HAVE_ARM_ACCELERATED_CRC_MEMCPY_ENGINE) namespace absl { ABSL_NAMESPACE_BEGIN @@ -74,7 +77,7 @@ inline crc32c_t ShortCrcCopy(char* dst, const char* src, std::size_t length, uint32_t crc_uint32 = static_cast<uint32_t>(crc); for (std::size_t i = 0; i < length; i++) { uint8_t data = *reinterpret_cast<const uint8_t*>(src); - crc_uint32 = _mm_crc32_u8(crc_uint32, data); + crc_uint32 = CRC32_u8(crc_uint32, data); *reinterpret_cast<uint8_t*>(dst) = data; ++src; ++dst; @@ -82,36 +85,35 @@ inline crc32c_t ShortCrcCopy(char* dst, const char* src, std::size_t length, return crc32c_t{crc_uint32}; } -constexpr size_t kIntLoadsPerVec = sizeof(__m128i) / sizeof(uint64_t); +constexpr size_t kIntLoadsPerVec = sizeof(V128) / sizeof(uint64_t); // Common function for copying the tails of multiple large regions. template <size_t vec_regions, size_t int_regions> inline void LargeTailCopy(crc32c_t* crcs, char** dst, const char** src, size_t region_size, size_t copy_rounds) { - std::array<__m128i, vec_regions> data; + std::array<V128, vec_regions> data; std::array<uint64_t, kIntLoadsPerVec * int_regions> int_data; while (copy_rounds > 0) { for (size_t i = 0; i < vec_regions; i++) { size_t region = i; - auto* vsrc = - reinterpret_cast<const __m128i*>(*src + region_size * region); - auto* vdst = reinterpret_cast<__m128i*>(*dst + region_size * region); + auto* vsrc = reinterpret_cast<const V128*>(*src + region_size * region); + auto* vdst = reinterpret_cast<V128*>(*dst + region_size * region); // Load the blocks, unaligned - data[i] = _mm_loadu_si128(vsrc); + data[i] = V128_LoadU(vsrc); // Store the blocks, aligned - _mm_store_si128(vdst, data[i]); + V128_Store(vdst, data[i]); // Compute the running CRC crcs[region] = crc32c_t{static_cast<uint32_t>( - _mm_crc32_u64(static_cast<uint32_t>(crcs[region]), - static_cast<uint64_t>(_mm_extract_epi64(data[i], 0))))}; + CRC32_u64(static_cast<uint32_t>(crcs[region]), + static_cast<uint64_t>(V128_Extract64<0>(data[i]))))}; crcs[region] = crc32c_t{static_cast<uint32_t>( - _mm_crc32_u64(static_cast<uint32_t>(crcs[region]), - static_cast<uint64_t>(_mm_extract_epi64(data[i], 1))))}; + CRC32_u64(static_cast<uint32_t>(crcs[region]), + static_cast<uint64_t>(V128_Extract64<1>(data[i]))))}; } for (size_t i = 0; i < int_regions; i++) { @@ -125,7 +127,7 @@ inline void LargeTailCopy(crc32c_t* crcs, char** dst, const char** src, size_t data_index = i * kIntLoadsPerVec + j; int_data[data_index] = *(usrc + j); - crcs[region] = crc32c_t{static_cast<uint32_t>(_mm_crc32_u64( + crcs[region] = crc32c_t{static_cast<uint32_t>(CRC32_u64( static_cast<uint32_t>(crcs[region]), int_data[data_index]))}; *(udst + j) = int_data[data_index]; @@ -133,8 +135,8 @@ inline void LargeTailCopy(crc32c_t* crcs, char** dst, const char** src, } // Increment pointers - *src += sizeof(__m128i); - *dst += sizeof(__m128i); + *src += sizeof(V128); + *dst += sizeof(V128); --copy_rounds; } } @@ -158,8 +160,9 @@ crc32c_t AcceleratedCrcMemcpyEngine<vec_regions, int_regions>::Compute( void* __restrict dst, const void* __restrict src, std::size_t length, crc32c_t initial_crc) const { constexpr std::size_t kRegions = vec_regions + int_regions; + static_assert(kRegions > 0, "Must specify at least one region."); constexpr uint32_t kCrcDataXor = uint32_t{0xffffffff}; - constexpr std::size_t kBlockSize = sizeof(__m128i); + constexpr std::size_t kBlockSize = sizeof(V128); constexpr std::size_t kCopyRoundSize = kRegions * kBlockSize; // Number of blocks per cacheline. @@ -235,17 +238,18 @@ crc32c_t AcceleratedCrcMemcpyEngine<vec_regions, int_regions>::Compute( const std::size_t tail_size = length - (kRegions * region_size); // Holding registers for data in each region. - std::array<__m128i, vec_regions> vec_data; + std::array<V128, vec_regions> vec_data; std::array<uint64_t, int_regions * kIntLoadsPerVec> int_data; // Main loop. while (copy_rounds > kBlocksPerCacheLine) { // Prefetch kPrefetchAhead bytes ahead of each pointer. for (size_t i = 0; i < kRegions; i++) { - absl::base_internal::PrefetchT0(src_bytes + kPrefetchAhead + - region_size * i); - absl::base_internal::PrefetchT0(dst_bytes + kPrefetchAhead + - region_size * i); + absl::PrefetchToLocalCache(src_bytes + kPrefetchAhead + region_size * i); +#ifdef ABSL_INTERNAL_HAVE_X86_64_ACCELERATED_CRC_MEMCPY_ENGINE + // TODO(b/297082454): investigate dropping prefetch on x86. + absl::PrefetchToLocalCache(dst_bytes + kPrefetchAhead + region_size * i); +#endif } // Load and store data, computing CRC on the way. @@ -258,21 +262,20 @@ crc32c_t AcceleratedCrcMemcpyEngine<vec_regions, int_regions>::Compute( size_t region = (j + i) % kRegions; auto* vsrc = - reinterpret_cast<const __m128i*>(src_bytes + region_size * region); - auto* vdst = - reinterpret_cast<__m128i*>(dst_bytes + region_size * region); + reinterpret_cast<const V128*>(src_bytes + region_size * region); + auto* vdst = reinterpret_cast<V128*>(dst_bytes + region_size * region); // Load and CRC data. - vec_data[j] = _mm_loadu_si128(vsrc + i); - crcs[region] = crc32c_t{static_cast<uint32_t>(_mm_crc32_u64( - static_cast<uint32_t>(crcs[region]), - static_cast<uint64_t>(_mm_extract_epi64(vec_data[j], 0))))}; - crcs[region] = crc32c_t{static_cast<uint32_t>(_mm_crc32_u64( - static_cast<uint32_t>(crcs[region]), - static_cast<uint64_t>(_mm_extract_epi64(vec_data[j], 1))))}; + vec_data[j] = V128_LoadU(vsrc + i); + crcs[region] = crc32c_t{static_cast<uint32_t>( + CRC32_u64(static_cast<uint32_t>(crcs[region]), + static_cast<uint64_t>(V128_Extract64<0>(vec_data[j]))))}; + crcs[region] = crc32c_t{static_cast<uint32_t>( + CRC32_u64(static_cast<uint32_t>(crcs[region]), + static_cast<uint64_t>(V128_Extract64<1>(vec_data[j]))))}; // Store the data. - _mm_store_si128(vdst + i, vec_data[j]); + V128_Store(vdst + i, vec_data[j]); } // Preload the partial CRCs for the CLMUL subregions. @@ -292,7 +295,7 @@ crc32c_t AcceleratedCrcMemcpyEngine<vec_regions, int_regions>::Compute( // Load and CRC the data. int_data[data_index] = *(usrc + i * kIntLoadsPerVec + k); - crcs[region] = crc32c_t{static_cast<uint32_t>(_mm_crc32_u64( + crcs[region] = crc32c_t{static_cast<uint32_t>(CRC32_u64( static_cast<uint32_t>(crcs[region]), int_data[data_index]))}; // Store the data. @@ -315,6 +318,21 @@ crc32c_t AcceleratedCrcMemcpyEngine<vec_regions, int_regions>::Compute( src_bytes += region_size * (kRegions - 1); dst_bytes += region_size * (kRegions - 1); + // Copy and CRC the tail through the XMM registers. + std::size_t tail_blocks = tail_size / kBlockSize; + LargeTailCopy<0, 1>(&crcs[kRegions - 1], &dst_bytes, &src_bytes, 0, + tail_blocks); + + // Final tail copy for under 16 bytes. + crcs[kRegions - 1] = + ShortCrcCopy(dst_bytes, src_bytes, tail_size - tail_blocks * kBlockSize, + crcs[kRegions - 1]); + + if (kRegions == 1) { + // If there is only one region, finalize and return its CRC. + return crc32c_t{static_cast<uint32_t>(crcs[0]) ^ kCrcDataXor}; + } + // Finalize the first CRCs: XOR the internal CRCs by the XOR mask to undo the // XOR done before doing block copy + CRCs. for (size_t i = 0; i + 1 < kRegions; i++) { @@ -327,16 +345,6 @@ crc32c_t AcceleratedCrcMemcpyEngine<vec_regions, int_regions>::Compute( full_crc = ConcatCrc32c(full_crc, crcs[i], region_size); } - // Copy and CRC the tail through the XMM registers. - std::size_t tail_blocks = tail_size / kBlockSize; - LargeTailCopy<0, 1>(&crcs[kRegions - 1], &dst_bytes, &src_bytes, 0, - tail_blocks); - - // Final tail copy for under 16 bytes. - crcs[kRegions - 1] = - ShortCrcCopy(dst_bytes, src_bytes, tail_size - tail_blocks * kBlockSize, - crcs[kRegions - 1]); - // Finalize and concatenate the final CRC, then return. crcs[kRegions - 1] = crc32c_t{static_cast<uint32_t>(crcs[kRegions - 1]) ^ kCrcDataXor}; @@ -349,9 +357,11 @@ CrcMemcpy::ArchSpecificEngines CrcMemcpy::GetArchSpecificEngines() { // Get the underlying architecture. CpuType cpu_type = GetCpuType(); switch (cpu_type) { - case CpuType::kUnknown: case CpuType::kAmdRome: case CpuType::kAmdNaples: + case CpuType::kAmdMilan: + case CpuType::kAmdGenoa: + case CpuType::kAmdRyzenV3000: case CpuType::kIntelCascadelakeXeon: case CpuType::kIntelSkylakeXeon: case CpuType::kIntelSkylake: @@ -359,18 +369,18 @@ CrcMemcpy::ArchSpecificEngines CrcMemcpy::GetArchSpecificEngines() { case CpuType::kIntelHaswell: case CpuType::kIntelIvybridge: return { - .temporal = new FallbackCrcMemcpyEngine(), - .non_temporal = new CrcNonTemporalMemcpyAVXEngine(), + /*.temporal=*/new FallbackCrcMemcpyEngine(), + /*.non_temporal=*/new CrcNonTemporalMemcpyAVXEngine(), }; // INTEL_SANDYBRIDGE performs better with SSE than AVX. case CpuType::kIntelSandybridge: return { - .temporal = new FallbackCrcMemcpyEngine(), - .non_temporal = new CrcNonTemporalMemcpyEngine(), + /*.temporal=*/new FallbackCrcMemcpyEngine(), + /*.non_temporal=*/new CrcNonTemporalMemcpyEngine(), }; default: - return {.temporal = new FallbackCrcMemcpyEngine(), - .non_temporal = new FallbackCrcMemcpyEngine()}; + return {/*.temporal=*/new FallbackCrcMemcpyEngine(), + /*.non_temporal=*/new FallbackCrcMemcpyEngine()}; } #else // Get the underlying architecture. @@ -387,9 +397,12 @@ CrcMemcpy::ArchSpecificEngines CrcMemcpy::GetArchSpecificEngines() { // strided access to each region, and do the right thing. case CpuType::kAmdRome: case CpuType::kAmdNaples: + case CpuType::kAmdMilan: + case CpuType::kAmdGenoa: + case CpuType::kAmdRyzenV3000: return { - .temporal = new AcceleratedCrcMemcpyEngine<1, 2>(), - .non_temporal = new CrcNonTemporalMemcpyAVXEngine(), + /*.temporal=*/new AcceleratedCrcMemcpyEngine<1, 2>(), + /*.non_temporal=*/new CrcNonTemporalMemcpyAVXEngine(), }; // PCLMULQDQ is slow and we don't have wide enough issue width to take // advantage of it. For an unknown architecture, don't risk using CLMULs. @@ -400,18 +413,18 @@ CrcMemcpy::ArchSpecificEngines CrcMemcpy::GetArchSpecificEngines() { case CpuType::kIntelHaswell: case CpuType::kIntelIvybridge: return { - .temporal = new AcceleratedCrcMemcpyEngine<3, 0>(), - .non_temporal = new CrcNonTemporalMemcpyAVXEngine(), + /*.temporal=*/new AcceleratedCrcMemcpyEngine<3, 0>(), + /*.non_temporal=*/new CrcNonTemporalMemcpyAVXEngine(), }; // INTEL_SANDYBRIDGE performs better with SSE than AVX. case CpuType::kIntelSandybridge: return { - .temporal = new AcceleratedCrcMemcpyEngine<3, 0>(), - .non_temporal = new CrcNonTemporalMemcpyEngine(), + /*.temporal=*/new AcceleratedCrcMemcpyEngine<3, 0>(), + /*.non_temporal=*/new CrcNonTemporalMemcpyEngine(), }; default: - return {.temporal = new FallbackCrcMemcpyEngine(), - .non_temporal = new FallbackCrcMemcpyEngine()}; + return {/*.temporal=*/new FallbackCrcMemcpyEngine(), + /*.non_temporal=*/new FallbackCrcMemcpyEngine()}; } #endif // UNDEFINED_BEHAVIOR_SANITIZER } @@ -423,6 +436,8 @@ std::unique_ptr<CrcMemcpyEngine> CrcMemcpy::GetTestEngine(int vector, return std::make_unique<AcceleratedCrcMemcpyEngine<3, 0>>(); } else if (vector == 1 && integer == 2) { return std::make_unique<AcceleratedCrcMemcpyEngine<1, 2>>(); + } else if (vector == 1 && integer == 0) { + return std::make_unique<AcceleratedCrcMemcpyEngine<1, 0>>(); } return nullptr; } @@ -431,4 +446,5 @@ std::unique_ptr<CrcMemcpyEngine> CrcMemcpy::GetTestEngine(int vector, ABSL_NAMESPACE_END } // namespace absl -#endif // ABSL_INTERNAL_HAVE_X86_64_ACCELERATED_CRC_MEMCPY_ENGINE +#endif // ABSL_INTERNAL_HAVE_X86_64_ACCELERATED_CRC_MEMCPY_ENGINE || + // ABSL_INTERNAL_HAVE_ARM_ACCELERATED_CRC_MEMCPY_ENGINE diff --git a/absl/crc/internal/crc_x86_arm_combined.cc b/absl/crc/internal/crc_x86_arm_combined.cc index d71191e3..51eff4ed 100644 --- a/absl/crc/internal/crc_x86_arm_combined.cc +++ b/absl/crc/internal/crc_x86_arm_combined.cc @@ -16,14 +16,14 @@ #include <cstddef> #include <cstdint> +#include <memory> +#include <vector> #include "absl/base/attributes.h" #include "absl/base/config.h" -#include "absl/base/dynamic_annotations.h" #include "absl/base/internal/endian.h" -#include "absl/base/internal/prefetch.h" +#include "absl/base/prefetch.h" #include "absl/crc/internal/cpu_detect.h" -#include "absl/crc/internal/crc.h" #include "absl/crc/internal/crc32_x86_arm_combined_simd.h" #include "absl/crc/internal/crc_internal.h" #include "absl/memory/memory.h" @@ -429,11 +429,11 @@ class CRC32AcceleratedX86ARMCombinedMultipleStreams ABSL_INTERNAL_STEP8BY3(l64, l641, l642, p, p1, p2); ABSL_INTERNAL_STEP8BY3(l64, l641, l642, p, p1, p2); ABSL_INTERNAL_STEP8BY3(l64, l641, l642, p, p1, p2); - base_internal::PrefetchT0( + PrefetchToLocalCache( reinterpret_cast<const char*>(p + kPrefetchHorizonMedium)); - base_internal::PrefetchT0( + PrefetchToLocalCache( reinterpret_cast<const char*>(p1 + kPrefetchHorizonMedium)); - base_internal::PrefetchT0( + PrefetchToLocalCache( reinterpret_cast<const char*>(p2 + kPrefetchHorizonMedium)); } // Don't run crc on last 8 bytes. @@ -515,14 +515,14 @@ class CRC32AcceleratedX86ARMCombinedMultipleStreams } for (size_t i = 1; i < bs; i++) { - // Prefetch data for next itterations. + // Prefetch data for next iterations. for (size_t j = 0; j < num_crc_streams; j++) { - base_internal::PrefetchT0( + PrefetchToLocalCache( reinterpret_cast<const char*>(crc_streams[j] + kPrefetchHorizon)); } for (size_t j = 0; j < num_pclmul_streams; j++) { - base_internal::PrefetchT0(reinterpret_cast<const char*>( - pclmul_streams[j] + kPrefetchHorizon)); + PrefetchToLocalCache(reinterpret_cast<const char*>(pclmul_streams[j] + + kPrefetchHorizon)); } // We process each stream in 64 byte blocks. This can be written as @@ -634,8 +634,16 @@ CRCImpl* TryNewCRC32AcceleratedX86ARMCombined() { return new CRC32AcceleratedX86ARMCombinedMultipleStreams< 3, 0, CutoffStrategy::Fold3>(); case CpuType::kArmNeoverseN1: + case CpuType::kArmNeoverseN2: + case CpuType::kArmNeoverseV1: return new CRC32AcceleratedX86ARMCombinedMultipleStreams< 1, 1, CutoffStrategy::Unroll64CRC>(); + case CpuType::kAmpereSiryn: + return new CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 2, CutoffStrategy::Fold3>(); + case CpuType::kArmNeoverseV2: + return new CRC32AcceleratedX86ARMCombinedMultipleStreams< + 1, 2, CutoffStrategy::Unroll64CRC>(); #if defined(__aarch64__) default: // Not all ARM processors support the needed instructions, so check here diff --git a/absl/debugging/BUILD.bazel b/absl/debugging/BUILD.bazel index edbb3698..5baff7a1 100644 --- a/absl/debugging/BUILD.bazel +++ b/absl/debugging/BUILD.bazel @@ -23,6 +23,11 @@ load( package( default_visibility = ["//visibility:public"], + features = [ + "header_modules", + "layering_check", + "parse_headers", + ], ) licenses(["notice"]) @@ -49,6 +54,7 @@ cc_library( ":debugging_internal", "//absl/base:config", "//absl/base:core_headers", + "//absl/base:dynamic_annotations", "//absl/base:raw_logging_internal", ], ) @@ -61,6 +67,7 @@ cc_test( deps = [ ":stacktrace", "//absl/base:core_headers", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -83,6 +90,10 @@ cc_library( linkopts = ABSL_DEFAULT_LINKOPTS + select({ "//absl:msvc_compiler": ["-DEFAULTLIB:dbghelp.lib"], "//absl:clang-cl_compiler": ["-DEFAULTLIB:dbghelp.lib"], + "//absl:mingw_compiler": [ + "-DEFAULTLIB:dbghelp.lib", + "-ldbghelp", + ], "//conditions:default": [], }), deps = [ @@ -117,7 +128,8 @@ cc_test( "//absl/base", "//absl/base:config", "//absl/base:core_headers", - "//absl/base:raw_logging_internal", + "//absl/log", + "//absl/log:check", "//absl/memory", "//absl/strings", "@com_google_googletest//:gtest", @@ -175,6 +187,7 @@ cc_test( ":stacktrace", ":symbolize", "//absl/base:raw_logging_internal", + "//absl/log:check", "//absl/strings", "@com_google_googletest//:gtest", ], @@ -210,7 +223,10 @@ cc_library( hdrs = ["internal/demangle.h"], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, - visibility = ["//visibility:private"], + visibility = [ + "//absl/container:__pkg__", + "//absl/debugging:__pkg__", + ], deps = [ "//absl/base", "//absl/base:config", @@ -228,8 +244,9 @@ cc_test( ":stack_consumption", "//absl/base:config", "//absl/base:core_headers", - "//absl/base:raw_logging_internal", + "//absl/log", "//absl/memory", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -255,7 +272,8 @@ cc_test( deps = [ ":leak_check", "//absl/base:config", - "//absl/base:raw_logging_internal", + "//absl/log", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -272,7 +290,8 @@ cc_binary( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":leak_check", - "//absl/base:raw_logging_internal", + "//absl/log", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -301,7 +320,8 @@ cc_test( deps = [ ":stack_consumption", "//absl/base:core_headers", - "//absl/base:raw_logging_internal", + "//absl/log", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/debugging/CMakeLists.txt b/absl/debugging/CMakeLists.txt index 8f29cc07..65e2af88 100644 --- a/absl/debugging/CMakeLists.txt +++ b/absl/debugging/CMakeLists.txt @@ -41,6 +41,7 @@ absl_cc_library( absl::debugging_internal absl::config absl::core_headers + absl::dynamic_annotations absl::raw_logging_internal PUBLIC ) @@ -100,14 +101,15 @@ absl_cc_test( LINKOPTS $<$<BOOL:${MSVC}>:-DEBUG> DEPS - absl::stack_consumption - absl::symbolize absl::base + absl::check absl::config absl::core_headers + absl::log absl::memory - absl::raw_logging_internal + absl::stack_consumption absl::strings + absl::symbolize GTest::gmock ) @@ -156,6 +158,7 @@ absl_cc_test( COPTS ${ABSL_TEST_COPTS} DEPS + absl::check absl::failure_signal_handler absl::stacktrace absl::symbolize @@ -215,8 +218,8 @@ absl_cc_test( absl::stack_consumption absl::config absl::core_headers + absl::log absl::memory - absl::raw_logging_internal GTest::gmock_main ) @@ -247,6 +250,7 @@ absl_cc_test( DEPS absl::leak_check absl::base + absl::log GTest::gmock_main ) @@ -277,7 +281,7 @@ absl_cc_test( DEPS absl::stack_consumption absl::core_headers - absl::raw_logging_internal + absl::log GTest::gmock_main ) diff --git a/absl/debugging/failure_signal_handler.cc b/absl/debugging/failure_signal_handler.cc index ef8ab9e5..570d1e50 100644 --- a/absl/debugging/failure_signal_handler.cc +++ b/absl/debugging/failure_signal_handler.cc @@ -31,6 +31,13 @@ #ifdef ABSL_HAVE_MMAP #include <sys/mman.h> +#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS) +#define MAP_ANONYMOUS MAP_ANON +#endif +#endif + +#ifdef __linux__ +#include <sys/prctl.h> #endif #include <algorithm> @@ -47,7 +54,7 @@ #include "absl/debugging/internal/examine_stack.h" #include "absl/debugging/stacktrace.h" -#ifndef _WIN32 +#if !defined(_WIN32) && !defined(__wasi__) #define ABSL_HAVE_SIGACTION // Apple WatchOS and TVOS don't allow sigaltstack // Apple macOS has sigaltstack, but using it makes backtrace() unusable. @@ -77,10 +84,10 @@ struct FailureSignalData { struct sigaction previous_action; // StructSigaction is used to silence -Wmissing-field-initializers. using StructSigaction = struct sigaction; - #define FSD_PREVIOUS_INIT FailureSignalData::StructSigaction() +#define FSD_PREVIOUS_INIT FailureSignalData::StructSigaction() #else void (*previous_handler)(int); - #define FSD_PREVIOUS_INIT SIG_DFL +#define FSD_PREVIOUS_INIT SIG_DFL #endif }; @@ -132,7 +139,7 @@ const char* FailureSignalToString(int signo) { #ifdef ABSL_HAVE_SIGALTSTACK static bool SetupAlternateStackOnce() { -#if defined(__wasm__) || defined (__asjms__) +#if defined(__wasm__) || defined(__asjms__) const size_t page_mask = getpagesize() - 1; #else const size_t page_mask = static_cast<size_t>(sysconf(_SC_PAGESIZE)) - 1; @@ -154,9 +161,6 @@ static bool SetupAlternateStackOnce() { #ifndef MAP_STACK #define MAP_STACK 0 #endif -#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS) -#define MAP_ANONYMOUS MAP_ANON -#endif sigstk.ss_sp = mmap(nullptr, sigstk.ss_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS | MAP_STACK, -1, 0); if (sigstk.ss_sp == MAP_FAILED) { @@ -172,6 +176,20 @@ static bool SetupAlternateStackOnce() { if (sigaltstack(&sigstk, nullptr) != 0) { ABSL_RAW_LOG(FATAL, "sigaltstack() failed with errno=%d", errno); } + +#ifdef __linux__ +#if defined(PR_SET_VMA) && defined(PR_SET_VMA_ANON_NAME) + // Make a best-effort attempt to name the allocated region in + // /proc/$PID/smaps. + // + // The call to prctl() may fail if the kernel was not configured with the + // CONFIG_ANON_VMA_NAME kernel option. This is OK since the call is + // primarily a debugging aid. + prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, sigstk.ss_sp, sigstk.ss_size, + "absl-signalstack"); +#endif +#endif // __linux__ + return true; } @@ -218,10 +236,6 @@ static void InstallOneFailureHandler(FailureSignalData* data, #endif -static void WriteToStderr(const char* data) { - absl::raw_log_internal::AsyncSignalSafeWriteToStderr(data, strlen(data)); -} - static void WriteSignalMessage(int signo, int cpu, void (*writerfn)(const char*)) { char buf[96]; @@ -234,7 +248,7 @@ static void WriteSignalMessage(int signo, int cpu, if (signal_string != nullptr && signal_string[0] != '\0') { snprintf(buf, sizeof(buf), "*** %s received at time=%ld%s ***\n", signal_string, - static_cast<long>(time(nullptr)), // NOLINT(runtime/int) + static_cast<long>(time(nullptr)), // NOLINT(runtime/int) on_cpu); } else { snprintf(buf, sizeof(buf), "*** Signal %d received at time=%ld%s ***\n", @@ -297,7 +311,8 @@ static void PortableSleepForSeconds(int seconds) { struct timespec sleep_time; sleep_time.tv_sec = seconds; sleep_time.tv_nsec = 0; - while (nanosleep(&sleep_time, &sleep_time) != 0 && errno == EINTR) {} + while (nanosleep(&sleep_time, &sleep_time) != 0 && errno == EINTR) { + } #endif } @@ -307,9 +322,7 @@ static void PortableSleepForSeconds(int seconds) { // set amount of time. If AbslFailureSignalHandler() hangs for more than // the alarm timeout, ImmediateAbortSignalHandler() will abort the // program. -static void ImmediateAbortSignalHandler(int) { - RaiseToDefaultHandler(SIGABRT); -} +static void ImmediateAbortSignalHandler(int) { RaiseToDefaultHandler(SIGABRT); } #endif // absl::base_internal::GetTID() returns pid_t on most platforms, but @@ -362,7 +375,10 @@ static void AbslFailureSignalHandler(int signo, siginfo_t*, void* ucontext) { #endif // First write to stderr. - WriteFailureInfo(signo, ucontext, my_cpu, WriteToStderr); + WriteFailureInfo( + signo, ucontext, my_cpu, +[](const char* data) { + absl::raw_log_internal::AsyncSignalSafeWriteError(data, strlen(data)); + }); // Riskier code (because it is less likely to be async-signal-safe) // goes after this point. diff --git a/absl/debugging/failure_signal_handler.h b/absl/debugging/failure_signal_handler.h index 500115c0..5e034785 100644 --- a/absl/debugging/failure_signal_handler.h +++ b/absl/debugging/failure_signal_handler.h @@ -62,7 +62,7 @@ struct FailureSignalHandlerOptions { // If true, try to run signal handlers on an alternate stack (if supported on // the given platform). An alternate stack is useful for program crashes due // to a stack overflow; by running on a alternate stack, the signal handler - // may run even when normal stack space has been exausted. The downside of + // may run even when normal stack space has been exhausted. The downside of // using an alternate stack is that extra memory for the alternate stack needs // to be pre-allocated. bool use_alternate_stack = true; diff --git a/absl/debugging/failure_signal_handler_test.cc b/absl/debugging/failure_signal_handler_test.cc index 6a62428b..72816a3e 100644 --- a/absl/debugging/failure_signal_handler_test.cc +++ b/absl/debugging/failure_signal_handler_test.cc @@ -22,11 +22,12 @@ #include <cstring> #include <fstream> -#include "gtest/gtest.h" #include "gmock/gmock.h" +#include "gtest/gtest.h" #include "absl/base/internal/raw_logging.h" #include "absl/debugging/stacktrace.h" #include "absl/debugging/symbolize.h" +#include "absl/log/check.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" @@ -87,7 +88,7 @@ std::string GetTmpDir() { // This function runs in a fork()ed process on most systems. void InstallHandlerWithWriteToFileAndRaise(const char* file, int signo) { error_file = fopen(file, "w"); - ABSL_RAW_CHECK(error_file != nullptr, "Failed create error_file"); + CHECK_NE(error_file, nullptr) << "Failed create error_file"; absl::FailureSignalHandlerOptions options; options.writerfn = WriteToErrorFile; absl::InstallFailureSignalHandler(options); diff --git a/absl/debugging/internal/address_is_readable.cc b/absl/debugging/internal/address_is_readable.cc index 91eaa76f..be17a105 100644 --- a/absl/debugging/internal/address_is_readable.cc +++ b/absl/debugging/internal/address_is_readable.cc @@ -50,8 +50,10 @@ namespace debugging_internal { // NOTE: any new system calls here may also require sandbox reconfiguration. // bool AddressIsReadable(const void *addr) { - // Align address on 8-byte boundary. On aarch64, checking last - // byte before inaccessible page returned unexpected EFAULT. + // rt_sigprocmask below checks 8 contiguous bytes. If addr resides in the + // last 7 bytes of a page (unaligned), rt_sigprocmask would additionally + // check the readability of the next page, which is not desired. Align + // address on 8-byte boundary to check only the current page. const uintptr_t u_addr = reinterpret_cast<uintptr_t>(addr) & ~uintptr_t{7}; addr = reinterpret_cast<const void *>(u_addr); diff --git a/absl/debugging/internal/demangle.cc b/absl/debugging/internal/demangle.cc index f2832915..381a2b50 100644 --- a/absl/debugging/internal/demangle.cc +++ b/absl/debugging/internal/demangle.cc @@ -21,7 +21,15 @@ #include <cstdint> #include <cstdio> +#include <cstdlib> #include <limits> +#include <string> + +#include "absl/base/config.h" + +#if ABSL_INTERNAL_HAS_CXA_DEMANGLE +#include <cxxabi.h> +#endif namespace absl { ABSL_NAMESPACE_BEGIN @@ -1983,6 +1991,22 @@ bool Demangle(const char* mangled, char* out, size_t out_size) { state.parse_state.out_cur_idx > 0; } +std::string DemangleString(const char* mangled) { + std::string out; + int status = 0; + char* demangled = nullptr; +#if ABSL_INTERNAL_HAS_CXA_DEMANGLE + demangled = abi::__cxa_demangle(mangled, nullptr, nullptr, &status); +#endif + if (status == 0 && demangled != nullptr) { + out.append(demangled); + free(demangled); + } else { + out.append(mangled); + } + return out; +} + } // namespace debugging_internal ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/debugging/internal/demangle.h b/absl/debugging/internal/demangle.h index e1f15698..146d1150 100644 --- a/absl/debugging/internal/demangle.h +++ b/absl/debugging/internal/demangle.h @@ -12,13 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -// An async-signal-safe and thread-safe demangler for Itanium C++ ABI -// (aka G++ V3 ABI). +#ifndef ABSL_DEBUGGING_INTERNAL_DEMANGLE_H_ +#define ABSL_DEBUGGING_INTERNAL_DEMANGLE_H_ + +#include <string> +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace debugging_internal { + +// Demangle `mangled`. On success, return true and write the +// demangled symbol name to `out`. Otherwise, return false. +// `out` is modified even if demangling is unsuccessful. // -// The demangler is implemented to be used in async signal handlers to -// symbolize stack traces. We cannot use libstdc++'s -// abi::__cxa_demangle() in such signal handlers since it's not async -// signal safe (it uses malloc() internally). +// This function provides an alternative to libstdc++'s abi::__cxa_demangle, +// which is not async signal safe (it uses malloc internally). It's intended to +// be used in async signal handlers to symbolize stack traces. // // Note that this demangler doesn't support full demangling. More // specifically, it doesn't print types of function parameters and @@ -30,40 +40,32 @@ // // Example: // -// | Mangled Name | The Demangler | abi::__cxa_demangle() -// |---------------|---------------|----------------------- -// | _Z1fv | f() | f() -// | _Z1fi | f() | f(int) -// | _Z3foo3bar | foo() | foo(bar) -// | _Z1fIiEvi | f<>() | void f<int>(int) -// | _ZN1N1fE | N::f | N::f -// | _ZN3Foo3BarEv | Foo::Bar() | Foo::Bar() -// | _Zrm1XS_" | operator%() | operator%(X, X) -// | _ZN3FooC1Ev | Foo::Foo() | Foo::Foo() -// | _Z1fSs | f() | f(std::basic_string<char, -// | | | std::char_traits<char>, -// | | | std::allocator<char> >) +// | Mangled Name | Demangle | DemangleString +// |---------------|-------------|----------------------- +// | _Z1fv | f() | f() +// | _Z1fi | f() | f(int) +// | _Z3foo3bar | foo() | foo(bar) +// | _Z1fIiEvi | f<>() | void f<int>(int) +// | _ZN1N1fE | N::f | N::f +// | _ZN3Foo3BarEv | Foo::Bar() | Foo::Bar() +// | _Zrm1XS_" | operator%() | operator%(X, X) +// | _ZN3FooC1Ev | Foo::Foo() | Foo::Foo() +// | _Z1fSs | f() | f(std::basic_string<char, +// | | | std::char_traits<char>, +// | | | std::allocator<char> >) // // See the unit test for more examples. // // Note: we might want to write demanglers for ABIs other than Itanium // C++ ABI in the future. -// - -#ifndef ABSL_DEBUGGING_INTERNAL_DEMANGLE_H_ -#define ABSL_DEBUGGING_INTERNAL_DEMANGLE_H_ - -#include "absl/base/config.h" - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace debugging_internal { - -// Demangle `mangled`. On success, return true and write the -// demangled symbol name to `out`. Otherwise, return false. -// `out` is modified even if demangling is unsuccessful. bool Demangle(const char* mangled, char* out, size_t out_size); +// A wrapper around `abi::__cxa_demangle()`. On success, returns the demangled +// name. On failure, returns the input mangled name. +// +// This function is not async-signal-safe. +std::string DemangleString(const char* mangled); + } // namespace debugging_internal ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/debugging/internal/demangle_test.cc b/absl/debugging/internal/demangle_test.cc index 8463a2b7..a16ab75e 100644 --- a/absl/debugging/internal/demangle_test.cc +++ b/absl/debugging/internal/demangle_test.cc @@ -17,10 +17,11 @@ #include <cstdlib> #include <string> +#include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/config.h" -#include "absl/base/internal/raw_logging.h" #include "absl/debugging/internal/stack_consumption.h" +#include "absl/log/log.h" #include "absl/memory/memory.h" namespace absl { @@ -28,17 +29,9 @@ ABSL_NAMESPACE_BEGIN namespace debugging_internal { namespace { -// A wrapper function for Demangle() to make the unit test simple. -static const char *DemangleIt(const char * const mangled) { - static char demangled[4096]; - if (Demangle(mangled, demangled, sizeof(demangled))) { - return demangled; - } else { - return mangled; - } -} +using ::testing::ContainsRegex; -// Test corner cases of bounary conditions. +// Test corner cases of boundary conditions. TEST(Demangle, CornerCases) { char tmp[10]; EXPECT_TRUE(Demangle("_Z6foobarv", tmp, sizeof(tmp))); @@ -151,7 +144,7 @@ static const char *DemangleStackConsumption(const char *mangled, int *stack_consumed) { g_mangled = mangled; *stack_consumed = GetSignalHandlerStackConsumption(DemangleSignalHandler); - ABSL_RAW_LOG(INFO, "Stack consumption of Demangle: %d", *stack_consumed); + LOG(INFO) << "Stack consumption of Demangle: " << *stack_consumed; return g_demangle_result; } @@ -237,6 +230,25 @@ TEST(DemangleRegression, DeeplyNestedArrayType) { TestOnInput(data.c_str()); } +struct Base { + virtual ~Base() = default; +}; + +struct Derived : public Base {}; + +TEST(DemangleStringTest, SupportsSymbolNameReturnedByTypeId) { + EXPECT_EQ(DemangleString(typeid(int).name()), "int"); + // We want to test that `DemangleString` can demangle the symbol names + // returned by `typeid`, but without hard-coding the actual demangled values + // (because they are platform-specific). + EXPECT_THAT( + DemangleString(typeid(Base).name()), + ContainsRegex("absl.*debugging_internal.*anonymous namespace.*::Base")); + EXPECT_THAT(DemangleString(typeid(Derived).name()), + ContainsRegex( + "absl.*debugging_internal.*anonymous namespace.*::Derived")); +} + } // namespace } // namespace debugging_internal ABSL_NAMESPACE_END diff --git a/absl/debugging/internal/elf_mem_image.h b/absl/debugging/internal/elf_mem_image.h index 113071a9..e7fe6ab0 100644 --- a/absl/debugging/internal/elf_mem_image.h +++ b/absl/debugging/internal/elf_mem_image.h @@ -33,7 +33,8 @@ #if defined(__ELF__) && !defined(__OpenBSD__) && !defined(__QNX__) && \ !defined(__native_client__) && !defined(__asmjs__) && \ - !defined(__wasm__) && !defined(__HAIKU__) + !defined(__wasm__) && !defined(__HAIKU__) && !defined(__sun) && \ + !defined(__VXWORKS__) && !defined(__hexagon__) #define ABSL_HAVE_ELF_MEM_IMAGE 1 #endif diff --git a/absl/debugging/internal/examine_stack.cc b/absl/debugging/internal/examine_stack.cc index 57863228..3dd6ba1a 100644 --- a/absl/debugging/internal/examine_stack.cc +++ b/absl/debugging/internal/examine_stack.cc @@ -24,6 +24,9 @@ #ifdef ABSL_HAVE_MMAP #include <sys/mman.h> +#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS) +#define MAP_ANONYMOUS MAP_ANON +#endif #endif #if defined(__linux__) || defined(__APPLE__) diff --git a/absl/debugging/internal/stack_consumption.cc b/absl/debugging/internal/stack_consumption.cc index 51348649..b54a1b28 100644 --- a/absl/debugging/internal/stack_consumption.cc +++ b/absl/debugging/internal/stack_consumption.cc @@ -18,14 +18,17 @@ #ifdef ABSL_INTERNAL_HAVE_DEBUGGING_STACK_CONSUMPTION #include <signal.h> +#include <string.h> #include <sys/mman.h> #include <unistd.h> -#include <string.h> - #include "absl/base/attributes.h" #include "absl/base/internal/raw_logging.h" +#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS) +#define MAP_ANONYMOUS MAP_ANON +#endif + namespace absl { ABSL_NAMESPACE_BEGIN namespace debugging_internal { @@ -162,7 +165,7 @@ int GetSignalHandlerStackConsumption(void (*signal_handler)(int)) { // versions of musl have a bug that rejects ss_size==0. Work around this by // setting ss_size to MINSIGSTKSZ, which should be ignored by the kernel // when SS_DISABLE is set. - old_sigstk.ss_size = MINSIGSTKSZ; + old_sigstk.ss_size = static_cast<size_t>(MINSIGSTKSZ); } ABSL_RAW_CHECK(sigaltstack(&old_sigstk, nullptr) == 0, "sigaltstack() failed"); @@ -182,4 +185,22 @@ int GetSignalHandlerStackConsumption(void (*signal_handler)(int)) { ABSL_NAMESPACE_END } // namespace absl +#else + +// https://github.com/abseil/abseil-cpp/issues/1465 +// CMake builds on Apple platforms error when libraries are empty. +// Our CMake configuration can avoid this error on header-only libraries, +// but since this library is conditionally empty, including a single +// variable is an easy workaround. +#ifdef __APPLE__ +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace debugging_internal { +extern const char kAvoidEmptyStackConsumptionLibraryWarning; +const char kAvoidEmptyStackConsumptionLibraryWarning = 0; +} // namespace debugging_internal +ABSL_NAMESPACE_END +} // namespace absl +#endif // __APPLE__ + #endif // ABSL_INTERNAL_HAVE_DEBUGGING_STACK_CONSUMPTION diff --git a/absl/debugging/internal/stack_consumption_test.cc b/absl/debugging/internal/stack_consumption_test.cc index 80445bf4..0255ac8f 100644 --- a/absl/debugging/internal/stack_consumption_test.cc +++ b/absl/debugging/internal/stack_consumption_test.cc @@ -20,7 +20,7 @@ #include <string.h> #include "gtest/gtest.h" -#include "absl/base/internal/raw_logging.h" +#include "absl/log/log.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -33,7 +33,7 @@ static void SimpleSignalHandler(int signo) { // Never true, but prevents compiler from optimizing buf out. if (signo == 0) { - ABSL_RAW_LOG(INFO, "%p", static_cast<void*>(buf)); + LOG(INFO) << static_cast<void*>(buf); } } diff --git a/absl/debugging/internal/stacktrace_aarch64-inl.inc b/absl/debugging/internal/stacktrace_aarch64-inl.inc index 71cdaf09..1caf7bbe 100644 --- a/absl/debugging/internal/stacktrace_aarch64-inl.inc +++ b/absl/debugging/internal/stacktrace_aarch64-inl.inc @@ -4,6 +4,7 @@ // Generate stack tracer for aarch64 #if defined(__linux__) +#include <signal.h> #include <sys/mman.h> #include <ucontext.h> #include <unistd.h> @@ -13,6 +14,7 @@ #include <cassert> #include <cstdint> #include <iostream> +#include <limits> #include "absl/base/attributes.h" #include "absl/debugging/internal/address_is_readable.h" @@ -20,6 +22,10 @@ #include "absl/debugging/stacktrace.h" static const size_t kUnknownFrameSize = 0; +// Stack end to use when we don't know the actual stack end +// (effectively just the end of address space). +constexpr uintptr_t kUnknownStackEnd = + std::numeric_limits<size_t>::max() - sizeof(void *); #if defined(__linux__) // Returns the address of the VDSO __kernel_rt_sigreturn function, if present. @@ -65,7 +71,7 @@ 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 size_t ComputeStackFrameSize(const T* low, +static 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); @@ -73,16 +79,30 @@ static inline size_t ComputeStackFrameSize(const T* low, : kUnknownFrameSize; } +// Saves stack info that is expensive to calculate to avoid recalculating per frame. +struct StackInfo { + uintptr_t stack_low; + uintptr_t stack_high; + uintptr_t sig_stack_low; + uintptr_t sig_stack_high; +}; + +static bool InsideSignalStack(void** ptr, const StackInfo* stack_info) { + uintptr_t comparable_ptr = reinterpret_cast<uintptr_t>(ptr); + return (comparable_ptr >= stack_info->sig_stack_low && + comparable_ptr < stack_info->sig_stack_high); +} + // Given a pointer to a stack frame, locate and return the calling // stackframe, or return null if no stackframe can be found. Perform sanity // checks (the strictness of which is controlled by the boolean parameter // "STRICT_UNWINDING") to reduce the chance that a bad pointer is returned. template<bool STRICT_UNWINDING, bool WITH_CONTEXT> ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS // May read random elements from stack. -ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY // May read random elements from stack. -static void **NextStackFrame(void **old_frame_pointer, const void *uc) { +ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY // May read random elements from stack. +static void **NextStackFrame(void **old_frame_pointer, const void *uc, + const StackInfo *stack_info) { void **new_frame_pointer = reinterpret_cast<void**>(*old_frame_pointer); - bool check_frame_size = true; #if defined(__linux__) if (WITH_CONTEXT && uc != nullptr) { @@ -94,19 +114,20 @@ static void **NextStackFrame(void **old_frame_pointer, const void *uc) { void **const pre_signal_frame_pointer = reinterpret_cast<void **>(ucv->uc_mcontext.regs[29]); + // The most recent signal always needs special handling to find the frame + // pointer, but a nested signal does not. If pre_signal_frame_pointer is + // earlier in the stack than the old_frame_pointer, then use it. If it is + // later, then we have already unwound through it and it needs no special + // handling. + if (pre_signal_frame_pointer >= old_frame_pointer) { + new_frame_pointer = pre_signal_frame_pointer; + } // Check that alleged frame pointer is actually readable. This is to // prevent "double fault" in case we hit the first fault due to e.g. // stack corruption. if (!absl::debugging_internal::AddressIsReadable( - pre_signal_frame_pointer)) + new_frame_pointer)) return nullptr; - - // Alleged frame pointer is readable, use it for further unwinding. - new_frame_pointer = pre_signal_frame_pointer; - - // Skip frame size check if we return from a signal. We may be using a - // an alternate stack for signals. - check_frame_size = false; } } #endif @@ -115,20 +136,49 @@ static void **NextStackFrame(void **old_frame_pointer, const void *uc) { if ((reinterpret_cast<uintptr_t>(new_frame_pointer) & 7) != 0) return nullptr; - // 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) { + // Only check the size if both frames are in the same stack. + if (InsideSignalStack(new_frame_pointer, stack_info) == + InsideSignalStack(old_frame_pointer, stack_info)) { + // 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. 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; + if (frame_size == kUnknownFrameSize) + return nullptr; + // A very large frame may mean corrupt memory or an erroneous frame + // pointer. But also maybe just a plain-old large frame. Assume that if the + // frame is within a known stack, then it is valid. + if (frame_size > max_size) { + size_t stack_low = stack_info->stack_low; + size_t stack_high = stack_info->stack_high; + if (InsideSignalStack(new_frame_pointer, stack_info)) { + stack_low = stack_info->sig_stack_low; + stack_high = stack_info->sig_stack_high; + } + if (stack_high < kUnknownStackEnd && + static_cast<size_t>(getpagesize()) < stack_low) { + const uintptr_t new_fp_u = + reinterpret_cast<uintptr_t>(new_frame_pointer); + // Stack bounds are known. + if (!(stack_low < new_fp_u && new_fp_u <= stack_high)) { + // new_frame_pointer is not within a known stack. + return nullptr; + } + } else { + // Stack bounds are unknown, prefer truncated stack to possible crash. + return nullptr; + } + } } return new_frame_pointer; } template <bool IS_STACK_FRAMES, bool IS_WITH_CONTEXT> +// We count on the bottom frame being this one. See the comment +// at prev_return_address +ABSL_ATTRIBUTE_NOINLINE ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS // May read random elements from stack. ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY // May read random elements from stack. static int UnwindImpl(void** result, int* sizes, int max_depth, int skip_count, @@ -138,42 +188,52 @@ static int UnwindImpl(void** result, int* sizes, int max_depth, int skip_count, #else # error reading stack point not yet supported on this platform. #endif - skip_count++; // Skip the frame for this function. int n = 0; + // Assume that the first page is not stack. + StackInfo stack_info; + stack_info.stack_low = static_cast<uintptr_t>(getpagesize()); + stack_info.stack_high = kUnknownStackEnd; + stack_info.sig_stack_low = stack_info.stack_low; + stack_info.sig_stack_high = kUnknownStackEnd; + // The frame pointer points to low address of a frame. The first 64-bit // word of a frame points to the next frame up the call chain, which normally // is just after the high address of the current frame. The second word of - // a frame contains return adress of to the caller. To find a pc value + // a frame contains return address of to the caller. To find a pc value // associated with the current frame, we need to go down a level in the call // chain. So we remember return the address of the last frame seen. This // does not work for the first stack frame, which belongs to UnwindImp() but // we skip the frame for UnwindImp() anyway. void* prev_return_address = nullptr; + // The nth frame size is the difference between the nth frame pointer and the + // the frame pointer below it in the call chain. There is no frame below the + // leaf frame, but this function is the leaf anyway, and we skip it. + void** prev_frame_pointer = nullptr; - while (frame_pointer && n < max_depth) { - // The absl::GetStackFrames routine is called when we are in some - // informational context (the failure signal handler for example). - // Use the non-strict unwinding rules to produce a stack trace - // that is as complete as possible (even if it contains a few bogus - // entries in some rare cases). - void **next_frame_pointer = - NextStackFrame<!IS_STACK_FRAMES, IS_WITH_CONTEXT>(frame_pointer, ucp); - + while (frame_pointer && n < max_depth) { if (skip_count > 0) { skip_count--; } else { result[n] = prev_return_address; if (IS_STACK_FRAMES) { sizes[n] = static_cast<int>( - ComputeStackFrameSize(frame_pointer, next_frame_pointer)); + ComputeStackFrameSize(prev_frame_pointer, frame_pointer)); } n++; } prev_return_address = frame_pointer[1]; - frame_pointer = next_frame_pointer; + prev_frame_pointer = frame_pointer; + // The absl::GetStackFrames routine is called when we are in some + // informational context (the failure signal handler for example). + // Use the non-strict unwinding rules to produce a stack trace + // that is as complete as possible (even if it contains a few bogus + // entries in some rare cases). + frame_pointer = NextStackFrame<!IS_STACK_FRAMES, IS_WITH_CONTEXT>( + frame_pointer, ucp, &stack_info); } + if (min_dropped_frames != nullptr) { // Implementation detail: we clamp the max of frames we are willing to // count, so as not to spend too much time in the loop below. @@ -185,8 +245,8 @@ static int UnwindImpl(void** result, int* sizes, int max_depth, int skip_count, } else { num_dropped_frames++; } - frame_pointer = - NextStackFrame<!IS_STACK_FRAMES, IS_WITH_CONTEXT>(frame_pointer, ucp); + frame_pointer = NextStackFrame<!IS_STACK_FRAMES, IS_WITH_CONTEXT>( + frame_pointer, ucp, &stack_info); } *min_dropped_frames = num_dropped_frames; } diff --git a/absl/debugging/internal/stacktrace_powerpc-inl.inc b/absl/debugging/internal/stacktrace_powerpc-inl.inc index 085cef67..a49ed2f7 100644 --- a/absl/debugging/internal/stacktrace_powerpc-inl.inc +++ b/absl/debugging/internal/stacktrace_powerpc-inl.inc @@ -57,7 +57,7 @@ static inline void *StacktracePowerPCGetLR(void **sp) { // This check is in case the compiler doesn't define _CALL_SYSV. return *(sp+1); #else -#error Need to specify the PPC ABI for your archiecture. +#error Need to specify the PPC ABI for your architecture. #endif } diff --git a/absl/debugging/internal/stacktrace_x86-inl.inc b/absl/debugging/internal/stacktrace_x86-inl.inc index 7b26464e..1975ba74 100644 --- a/absl/debugging/internal/stacktrace_x86-inl.inc +++ b/absl/debugging/internal/stacktrace_x86-inl.inc @@ -40,7 +40,7 @@ using absl::debugging_internal::AddressIsReadable; #if defined(__linux__) && defined(__i386__) // Count "push %reg" instructions in VDSO __kernel_vsyscall(), -// preceeding "syscall" or "sysenter". +// preceding "syscall" or "sysenter". // If __kernel_vsyscall uses frame pointer, answer 0. // // kMaxBytes tells how many instruction bytes of __kernel_vsyscall diff --git a/absl/debugging/internal/symbolize.h b/absl/debugging/internal/symbolize.h index 27d5e652..5593fde6 100644 --- a/absl/debugging/internal/symbolize.h +++ b/absl/debugging/internal/symbolize.h @@ -115,7 +115,7 @@ bool RemoveSymbolDecorator(int ticket); // Remove all installed decorators. Returns true if successful, false if // symbolization is currently in progress. -bool RemoveAllSymbolDecorators(void); +bool RemoveAllSymbolDecorators(); // Registers an address range to a file mapping. // diff --git a/absl/debugging/leak_check.cc b/absl/debugging/leak_check.cc index 195e82bf..fdb8798b 100644 --- a/absl/debugging/leak_check.cc +++ b/absl/debugging/leak_check.cc @@ -65,8 +65,8 @@ bool LeakCheckerIsActive() { return false; } void DoIgnoreLeak(const void*) { } void RegisterLivePointers(const void*, size_t) { } void UnRegisterLivePointers(const void*, size_t) { } -LeakCheckDisabler::LeakCheckDisabler() { } -LeakCheckDisabler::~LeakCheckDisabler() { } +LeakCheckDisabler::LeakCheckDisabler() = default; +LeakCheckDisabler::~LeakCheckDisabler() = default; ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/debugging/leak_check_fail_test.cc b/absl/debugging/leak_check_fail_test.cc index c49b81a9..46e9fb62 100644 --- a/absl/debugging/leak_check_fail_test.cc +++ b/absl/debugging/leak_check_fail_test.cc @@ -13,9 +13,10 @@ // limitations under the License. #include <memory> + #include "gtest/gtest.h" -#include "absl/base/internal/raw_logging.h" #include "absl/debugging/leak_check.h" +#include "absl/log/log.h" namespace { @@ -25,7 +26,7 @@ TEST(LeakCheckTest, LeakMemory) { // failed exit code. char* foo = strdup("lsan should complain about this leaked string"); - ABSL_RAW_LOG(INFO, "Should detect leaked string %s", foo); + LOG(INFO) << "Should detect leaked string " << foo; } TEST(LeakCheckTest, LeakMemoryAfterDisablerScope) { @@ -34,8 +35,7 @@ TEST(LeakCheckTest, LeakMemoryAfterDisablerScope) { // failed exit code. { absl::LeakCheckDisabler disabler; } char* foo = strdup("lsan should also complain about this leaked string"); - ABSL_RAW_LOG(INFO, "Re-enabled leak detection.Should detect leaked string %s", - foo); + LOG(INFO) << "Re-enabled leak detection.Should detect leaked string " << foo; } } // namespace diff --git a/absl/debugging/leak_check_test.cc b/absl/debugging/leak_check_test.cc index 6a42e31b..6f0135ed 100644 --- a/absl/debugging/leak_check_test.cc +++ b/absl/debugging/leak_check_test.cc @@ -16,8 +16,8 @@ #include "gtest/gtest.h" #include "absl/base/config.h" -#include "absl/base/internal/raw_logging.h" #include "absl/debugging/leak_check.h" +#include "absl/log/log.h" namespace { @@ -26,7 +26,7 @@ TEST(LeakCheckTest, IgnoreLeakSuppressesLeakedMemoryErrors) { GTEST_SKIP() << "LeakChecker is not active"; } auto foo = absl::IgnoreLeak(new std::string("some ignored leaked string")); - ABSL_RAW_LOG(INFO, "Ignoring leaked string %s", foo->c_str()); + LOG(INFO) << "Ignoring leaked string " << foo; } TEST(LeakCheckTest, LeakCheckDisablerIgnoresLeak) { @@ -35,7 +35,7 @@ TEST(LeakCheckTest, LeakCheckDisablerIgnoresLeak) { } absl::LeakCheckDisabler disabler; auto foo = new std::string("some string leaked while checks are disabled"); - ABSL_RAW_LOG(INFO, "Ignoring leaked string %s", foo->c_str()); + LOG(INFO) << "Ignoring leaked string " << foo; } } // namespace diff --git a/absl/debugging/stacktrace_test.cc b/absl/debugging/stacktrace_test.cc index 78ce7ad0..31f7723c 100644 --- a/absl/debugging/stacktrace_test.cc +++ b/absl/debugging/stacktrace_test.cc @@ -20,8 +20,8 @@ namespace { -// This test is currently only known to pass on linux/x86_64. -#if defined(__linux__) && defined(__x86_64__) +// This test is currently only known to pass on Linux x86_64/aarch64. +#if defined(__linux__) && (defined(__x86_64__) || defined(__aarch64__)) ABSL_ATTRIBUTE_NOINLINE void Unwind(void* p) { ABSL_ATTRIBUTE_UNUSED static void* volatile sink = p; constexpr int kSize = 16; diff --git a/absl/debugging/symbolize_elf.inc b/absl/debugging/symbolize_elf.inc index ffb4eecf..ae75cd41 100644 --- a/absl/debugging/symbolize_elf.inc +++ b/absl/debugging/symbolize_elf.inc @@ -289,6 +289,30 @@ ObjFile *AddrMap::Add() { return new (&obj_[size_++]) ObjFile; } +class CachingFile { + public: + // Setup reader for fd that uses buf[0, buf_size-1] as a cache. + CachingFile(int fd, char *buf, size_t buf_size) + : fd_(fd), + cache_(buf), + cache_size_(buf_size), + cache_start_(0), + cache_limit_(0) {} + + int fd() const { return fd_; } + ssize_t ReadFromOffset(void *buf, size_t count, off_t offset); + bool ReadFromOffsetExact(void *buf, size_t count, off_t offset); + + private: + // Bytes [cache_start_, cache_limit_-1] from fd_ are stored in + // a prefix of cache_[0, cache_size_-1]. + int fd_; + char *cache_; + size_t cache_size_; + off_t cache_start_; + off_t cache_limit_; +}; + // --------------------------------------------------------------- enum FindSymbolResult { SYMBOL_NOT_FOUND = 1, SYMBOL_TRUNCATED, SYMBOL_FOUND }; @@ -330,6 +354,7 @@ class Symbolizer { SYMBOL_BUF_SIZE = 3072, TMP_BUF_SIZE = 1024, SYMBOL_CACHE_LINES = 128, + FILE_CACHE_SIZE = 8192, }; AddrMap addr_map_; @@ -338,6 +363,7 @@ class Symbolizer { bool addr_map_read_; char symbol_buf_[SYMBOL_BUF_SIZE]; + char file_cache_[FILE_CACHE_SIZE]; // tmp_buf_ will be used to store arrays of ElfW(Shdr) and ElfW(Sym) // so we ensure that tmp_buf_ is properly aligned to store either. @@ -436,34 +462,58 @@ static ssize_t ReadPersistent(int fd, void *buf, size_t count) { return static_cast<ssize_t>(num_bytes); } -// Read up to "count" bytes from "offset" in the file pointed by file -// descriptor "fd" into the buffer starting at "buf". On success, -// return the number of bytes read. Otherwise, return -1. -static ssize_t ReadFromOffset(const int fd, void *buf, const size_t count, - const off_t offset) { - off_t off = lseek(fd, offset, SEEK_SET); - if (off == (off_t)-1) { - ABSL_RAW_LOG(WARNING, "lseek(%d, %jd, SEEK_SET) failed: errno=%d", fd, - static_cast<intmax_t>(offset), errno); - return -1; +// Read up to "count" bytes from "offset" into the buffer starting at "buf", +// while handling short reads and EINTR. On success, return the number of bytes +// read. Otherwise, return -1. +ssize_t CachingFile::ReadFromOffset(void *buf, size_t count, off_t offset) { + char *dst = static_cast<char *>(buf); + size_t read = 0; + while (read < count) { + // Look in cache first. + if (offset >= cache_start_ && offset < cache_limit_) { + const char *hit_start = &cache_[offset - cache_start_]; + const size_t n = + std::min(count - read, static_cast<size_t>(cache_limit_ - offset)); + memcpy(dst, hit_start, n); + dst += n; + read += static_cast<size_t>(n); + offset += static_cast<off_t>(n); + continue; + } + + cache_start_ = 0; + cache_limit_ = 0; + ssize_t n = pread(fd_, cache_, cache_size_, offset); + if (n < 0) { + if (errno == EINTR) { + continue; + } + ABSL_RAW_LOG(WARNING, "read failed: errno=%d", errno); + return -1; + } + if (n == 0) { // Reached EOF. + break; + } + + cache_start_ = offset; + cache_limit_ = offset + static_cast<off_t>(n); + // Next iteration will copy from cache into dst. } - return ReadPersistent(fd, buf, count); + return static_cast<ssize_t>(read); } -// Try reading exactly "count" bytes from "offset" bytes in a file -// pointed by "fd" into the buffer starting at "buf" while handling -// short reads and EINTR. On success, return true. Otherwise, return -// false. -static bool ReadFromOffsetExact(const int fd, void *buf, const size_t count, - const off_t offset) { - ssize_t len = ReadFromOffset(fd, buf, count, offset); +// Try reading exactly "count" bytes from "offset" bytes into the buffer +// starting at "buf" while handling short reads and EINTR. On success, return +// true. Otherwise, return false. +bool CachingFile::ReadFromOffsetExact(void *buf, size_t count, off_t offset) { + ssize_t len = ReadFromOffset(buf, count, offset); return len >= 0 && static_cast<size_t>(len) == count; } // Returns elf_header.e_type if the file pointed by fd is an ELF binary. -static int FileGetElfType(const int fd) { +static int FileGetElfType(CachingFile *file) { ElfW(Ehdr) elf_header; - if (!ReadFromOffsetExact(fd, &elf_header, sizeof(elf_header), 0)) { + if (!file->ReadFromOffsetExact(&elf_header, sizeof(elf_header), 0)) { return -1; } if (memcmp(elf_header.e_ident, ELFMAG, SELFMAG) != 0) { @@ -478,8 +528,8 @@ static int FileGetElfType(const int fd) { // To keep stack consumption low, we would like this function to not get // inlined. static ABSL_ATTRIBUTE_NOINLINE bool GetSectionHeaderByType( - const int fd, ElfW(Half) sh_num, const off_t sh_offset, ElfW(Word) type, - ElfW(Shdr) * out, char *tmp_buf, size_t tmp_buf_size) { + CachingFile *file, ElfW(Half) sh_num, const off_t sh_offset, + ElfW(Word) type, ElfW(Shdr) * out, char *tmp_buf, size_t tmp_buf_size) { ElfW(Shdr) *buf = reinterpret_cast<ElfW(Shdr) *>(tmp_buf); const size_t buf_entries = tmp_buf_size / sizeof(buf[0]); const size_t buf_bytes = buf_entries * sizeof(buf[0]); @@ -490,7 +540,7 @@ static ABSL_ATTRIBUTE_NOINLINE bool GetSectionHeaderByType( const size_t num_bytes_to_read = (buf_bytes > num_bytes_left) ? num_bytes_left : buf_bytes; const off_t offset = sh_offset + static_cast<off_t>(i * sizeof(buf[0])); - const ssize_t len = ReadFromOffset(fd, buf, num_bytes_to_read, offset); + const ssize_t len = file->ReadFromOffset(buf, num_bytes_to_read, offset); if (len < 0) { ABSL_RAW_LOG( WARNING, @@ -524,18 +574,29 @@ static ABSL_ATTRIBUTE_NOINLINE bool GetSectionHeaderByType( // but there has (as yet) been no need for anything longer either. const int kMaxSectionNameLen = 64; +// Small cache to use for miscellaneous file reads. +const int kSmallFileCacheSize = 100; + bool ForEachSection(int fd, const std::function<bool(absl::string_view name, const ElfW(Shdr) &)> &callback) { + char buf[kSmallFileCacheSize]; + CachingFile file(fd, buf, sizeof(buf)); + ElfW(Ehdr) elf_header; - if (!ReadFromOffsetExact(fd, &elf_header, sizeof(elf_header), 0)) { + if (!file.ReadFromOffsetExact(&elf_header, sizeof(elf_header), 0)) { + return false; + } + + // Technically it can be larger, but in practice this never happens. + if (elf_header.e_shentsize != sizeof(ElfW(Shdr))) { return false; } ElfW(Shdr) shstrtab; off_t shstrtab_offset = static_cast<off_t>(elf_header.e_shoff) + elf_header.e_shentsize * elf_header.e_shstrndx; - if (!ReadFromOffsetExact(fd, &shstrtab, sizeof(shstrtab), shstrtab_offset)) { + if (!file.ReadFromOffsetExact(&shstrtab, sizeof(shstrtab), shstrtab_offset)) { return false; } @@ -543,13 +604,13 @@ bool ForEachSection(int fd, ElfW(Shdr) out; off_t section_header_offset = static_cast<off_t>(elf_header.e_shoff) + elf_header.e_shentsize * i; - if (!ReadFromOffsetExact(fd, &out, sizeof(out), section_header_offset)) { + if (!file.ReadFromOffsetExact(&out, sizeof(out), section_header_offset)) { return false; } off_t name_offset = static_cast<off_t>(shstrtab.sh_offset) + out.sh_name; char header_name[kMaxSectionNameLen]; ssize_t n_read = - ReadFromOffset(fd, &header_name, kMaxSectionNameLen, name_offset); + file.ReadFromOffset(&header_name, kMaxSectionNameLen, name_offset); if (n_read < 0) { return false; } else if (n_read > kMaxSectionNameLen) { @@ -579,26 +640,33 @@ bool GetSectionHeaderByName(int fd, const char *name, size_t name_len, return false; } + char buf[kSmallFileCacheSize]; + CachingFile file(fd, buf, sizeof(buf)); ElfW(Ehdr) elf_header; - if (!ReadFromOffsetExact(fd, &elf_header, sizeof(elf_header), 0)) { + if (!file.ReadFromOffsetExact(&elf_header, sizeof(elf_header), 0)) { + return false; + } + + // Technically it can be larger, but in practice this never happens. + if (elf_header.e_shentsize != sizeof(ElfW(Shdr))) { return false; } ElfW(Shdr) shstrtab; off_t shstrtab_offset = static_cast<off_t>(elf_header.e_shoff) + elf_header.e_shentsize * elf_header.e_shstrndx; - if (!ReadFromOffsetExact(fd, &shstrtab, sizeof(shstrtab), shstrtab_offset)) { + if (!file.ReadFromOffsetExact(&shstrtab, sizeof(shstrtab), shstrtab_offset)) { return false; } for (int i = 0; i < elf_header.e_shnum; ++i) { off_t section_header_offset = static_cast<off_t>(elf_header.e_shoff) + elf_header.e_shentsize * i; - if (!ReadFromOffsetExact(fd, out, sizeof(*out), section_header_offset)) { + if (!file.ReadFromOffsetExact(out, sizeof(*out), section_header_offset)) { return false; } off_t name_offset = static_cast<off_t>(shstrtab.sh_offset) + out->sh_name; - ssize_t n_read = ReadFromOffset(fd, &header_name, name_len, name_offset); + ssize_t n_read = file.ReadFromOffset(&header_name, name_len, name_offset); if (n_read < 0) { return false; } else if (static_cast<size_t>(n_read) != name_len) { @@ -648,8 +716,10 @@ static bool ShouldPickFirstSymbol(const ElfW(Sym) & symbol1, } // Return true if an address is inside a section. -static bool InSection(const void *address, const ElfW(Shdr) * section) { - const char *start = reinterpret_cast<const char *>(section->sh_addr); +static bool InSection(const void *address, ptrdiff_t relocation, + const ElfW(Shdr) * section) { + const char *start = reinterpret_cast<const char *>( + section->sh_addr + static_cast<ElfW(Addr)>(relocation)); size_t size = static_cast<size_t>(section->sh_size); return start <= address && address < (start + size); } @@ -671,7 +741,7 @@ static const char *ComputeOffset(const char *base, ptrdiff_t offset) { // To keep stack consumption low, we would like this function to not get // inlined. static ABSL_ATTRIBUTE_NOINLINE FindSymbolResult FindSymbol( - const void *const pc, const int fd, char *out, size_t out_size, + const void *const pc, CachingFile *file, char *out, size_t out_size, ptrdiff_t relocation, const ElfW(Shdr) * strtab, const ElfW(Shdr) * symtab, const ElfW(Shdr) * opd, char *tmp_buf, size_t tmp_buf_size) { if (symtab == nullptr) { @@ -689,8 +759,8 @@ static ABSL_ATTRIBUTE_NOINLINE FindSymbolResult FindSymbol( // starting address. However, we do not always want to use the real // starting address because we sometimes want to symbolize a function // pointer into the .opd section, e.g. FindSymbol(&foo,...). - const bool pc_in_opd = - kPlatformUsesOPDSections && opd != nullptr && InSection(pc, opd); + const bool pc_in_opd = kPlatformUsesOPDSections && opd != nullptr && + InSection(pc, relocation, opd); const bool deref_function_descriptor_pointer = kPlatformUsesOPDSections && opd != nullptr && !pc_in_opd; @@ -704,7 +774,7 @@ static ABSL_ATTRIBUTE_NOINLINE FindSymbolResult FindSymbol( const size_t entries_in_chunk = std::min(num_remaining_symbols, buf_entries); const size_t bytes_in_chunk = entries_in_chunk * sizeof(buf[0]); - const ssize_t len = ReadFromOffset(fd, buf, bytes_in_chunk, offset); + const ssize_t len = file->ReadFromOffset(buf, bytes_in_chunk, offset); SAFE_ASSERT(len >= 0); SAFE_ASSERT(static_cast<size_t>(len) % sizeof(buf[0]) == 0); const size_t num_symbols_in_buf = static_cast<size_t>(len) / sizeof(buf[0]); @@ -730,7 +800,7 @@ static ABSL_ATTRIBUTE_NOINLINE FindSymbolResult FindSymbol( #endif if (deref_function_descriptor_pointer && - InSection(original_start_address, opd)) { + InSection(original_start_address, /*relocation=*/0, opd)) { // The opd section is mapped into memory. Just dereference // start_address to get the first double word, which points to the // function entry. @@ -760,12 +830,12 @@ static ABSL_ATTRIBUTE_NOINLINE FindSymbolResult FindSymbol( if (found_match) { const off_t off = static_cast<off_t>(strtab->sh_offset) + best_match.st_name; - const ssize_t n_read = ReadFromOffset(fd, out, out_size, off); + const ssize_t n_read = file->ReadFromOffset(out, out_size, off); if (n_read <= 0) { // This should never happen. ABSL_RAW_LOG(WARNING, - "Unable to read from fd %d at offset %lld: n_read = %zd", fd, - static_cast<long long>(off), n_read); + "Unable to read from fd %d at offset %lld: n_read = %zd", + file->fd(), static_cast<long long>(off), n_read); return SYMBOL_NOT_FOUND; } ABSL_RAW_CHECK(static_cast<size_t>(n_read) <= out_size, @@ -815,22 +885,24 @@ FindSymbolResult Symbolizer::GetSymbolFromObjectFile( } } + CachingFile file(obj.fd, file_cache_, sizeof(file_cache_)); + // Consult a regular symbol table, then fall back to the dynamic symbol table. for (const auto symbol_table_type : {SHT_SYMTAB, SHT_DYNSYM}) { - if (!GetSectionHeaderByType(obj.fd, obj.elf_header.e_shnum, + if (!GetSectionHeaderByType(&file, obj.elf_header.e_shnum, static_cast<off_t>(obj.elf_header.e_shoff), static_cast<ElfW(Word)>(symbol_table_type), &symtab, tmp_buf, tmp_buf_size)) { continue; } - if (!ReadFromOffsetExact( - obj.fd, &strtab, sizeof(strtab), + if (!file.ReadFromOffsetExact( + &strtab, sizeof(strtab), static_cast<off_t>(obj.elf_header.e_shoff + symtab.sh_link * sizeof(symtab)))) { continue; } const FindSymbolResult rc = - FindSymbol(pc, obj.fd, out, out_size, relocation, &strtab, &symtab, + FindSymbol(pc, &file, out, out_size, relocation, &strtab, &symtab, opd_ptr, tmp_buf, tmp_buf_size); if (rc != SYMBOL_NOT_FOUND) { return rc; @@ -1311,47 +1383,63 @@ static bool MaybeInitializeObjFile(ObjFile *obj) { ABSL_RAW_LOG(WARNING, "%s: open failed: errno=%d", obj->filename, errno); return false; } - obj->elf_type = FileGetElfType(obj->fd); + + char buf[kSmallFileCacheSize]; + CachingFile file(obj->fd, buf, sizeof(buf)); + + obj->elf_type = FileGetElfType(&file); if (obj->elf_type < 0) { ABSL_RAW_LOG(WARNING, "%s: wrong elf type: %d", obj->filename, obj->elf_type); return false; } - if (!ReadFromOffsetExact(obj->fd, &obj->elf_header, sizeof(obj->elf_header), - 0)) { + if (!file.ReadFromOffsetExact(&obj->elf_header, sizeof(obj->elf_header), + 0)) { ABSL_RAW_LOG(WARNING, "%s: failed to read elf header", obj->filename); return false; } const int phnum = obj->elf_header.e_phnum; const int phentsize = obj->elf_header.e_phentsize; auto phoff = static_cast<off_t>(obj->elf_header.e_phoff); - size_t num_executable_load_segments = 0; + size_t num_interesting_load_segments = 0; for (int j = 0; j < phnum; j++) { ElfW(Phdr) phdr; - if (!ReadFromOffsetExact(obj->fd, &phdr, sizeof(phdr), phoff)) { + if (!file.ReadFromOffsetExact(&phdr, sizeof(phdr), phoff)) { ABSL_RAW_LOG(WARNING, "%s: failed to read program header %d", obj->filename, j); return false; } phoff += phentsize; - constexpr int rx = PF_X | PF_R; - if (phdr.p_type != PT_LOAD || (phdr.p_flags & rx) != rx) { - // Not a LOAD segment, or not executable code. + +#if defined(__powerpc__) && !(_CALL_ELF > 1) + // On the PowerPC ELF v1 ABI, function pointers actually point to function + // descriptors. These descriptors are stored in an .opd section, which is + // mapped read-only. We thus need to look at all readable segments, not + // just the executable ones. + constexpr int interesting = PF_R; +#else + constexpr int interesting = PF_X | PF_R; +#endif + + if (phdr.p_type != PT_LOAD + || (phdr.p_flags & interesting) != interesting) { + // Not a LOAD segment, not executable code, and not a function + // descriptor. continue; } - if (num_executable_load_segments < obj->phdr.size()) { - memcpy(&obj->phdr[num_executable_load_segments++], &phdr, sizeof(phdr)); + if (num_interesting_load_segments < obj->phdr.size()) { + memcpy(&obj->phdr[num_interesting_load_segments++], &phdr, sizeof(phdr)); } else { ABSL_RAW_LOG( - WARNING, "%s: too many executable LOAD segments: %zu >= %zu", - obj->filename, num_executable_load_segments, obj->phdr.size()); + WARNING, "%s: too many interesting LOAD segments: %zu >= %zu", + obj->filename, num_interesting_load_segments, obj->phdr.size()); break; } } - if (num_executable_load_segments == 0) { - // This object has no "r-x" LOAD segments. That's unexpected. - ABSL_RAW_LOG(WARNING, "%s: no executable LOAD segments", obj->filename); + if (num_interesting_load_segments == 0) { + // This object has no interesting LOAD segments. That's unexpected. + ABSL_RAW_LOG(WARNING, "%s: no interesting LOAD segments", obj->filename); return false; } } @@ -1379,8 +1467,8 @@ const char *Symbolizer::GetUncachedSymbol(const void *pc) { // X in the file will have a start address of [true relocation]+X. relocation = static_cast<ptrdiff_t>(start_addr - obj->offset); - // Note: some binaries have multiple "rx" LOAD segments. We must - // find the right one. + // Note: some binaries have multiple LOAD segments that can contain + // function pointers. We must find the right one. ElfW(Phdr) *phdr = nullptr; for (size_t j = 0; j < obj->phdr.size(); j++) { ElfW(Phdr) &p = obj->phdr[j]; @@ -1390,7 +1478,7 @@ const char *Symbolizer::GetUncachedSymbol(const void *pc) { ABSL_RAW_CHECK(p.p_type == PT_NULL, "unexpected p_type"); break; } - if (pc < reinterpret_cast<void *>(start_addr + p.p_memsz)) { + if (pc < reinterpret_cast<void *>(start_addr + p.p_vaddr + p.p_memsz)) { phdr = &p; break; } diff --git a/absl/debugging/symbolize_emscripten.inc b/absl/debugging/symbolize_emscripten.inc index c226c456..a0f344dd 100644 --- a/absl/debugging/symbolize_emscripten.inc +++ b/absl/debugging/symbolize_emscripten.inc @@ -50,6 +50,9 @@ bool Symbolize(const void* pc, char* out, int out_size) { if (!HaveOffsetConverter()) { return false; } + if (pc == nullptr || out_size <= 0) { + return false; + } const char* func_name = emscripten_pc_get_function(pc); if (func_name == nullptr) { return false; diff --git a/absl/debugging/symbolize_test.cc b/absl/debugging/symbolize_test.cc index 3165c6ed..d0feab2f 100644 --- a/absl/debugging/symbolize_test.cc +++ b/absl/debugging/symbolize_test.cc @@ -14,6 +14,10 @@ #include "absl/debugging/symbolize.h" +#ifdef __EMSCRIPTEN__ +#include <emscripten.h> +#endif + #ifndef _WIN32 #include <fcntl.h> #include <sys/mman.h> @@ -29,12 +33,17 @@ #include "absl/base/casts.h" #include "absl/base/config.h" #include "absl/base/internal/per_thread_tls.h" -#include "absl/base/internal/raw_logging.h" #include "absl/base/optimization.h" #include "absl/debugging/internal/stack_consumption.h" +#include "absl/log/check.h" +#include "absl/log/log.h" #include "absl/memory/memory.h" #include "absl/strings/string_view.h" +#if defined(MAP_ANON) && !defined(MAP_ANONYMOUS) +#define MAP_ANONYMOUS MAP_ANON +#endif + using testing::Contains; #ifdef _WIN32 @@ -81,21 +90,13 @@ int ABSL_ATTRIBUTE_SECTION_VARIABLE(.text.unlikely) unlikely_func() { return 0; } -int ABSL_ATTRIBUTE_SECTION_VARIABLE(.text.hot) hot_func() { - return 0; -} +int ABSL_ATTRIBUTE_SECTION_VARIABLE(.text.hot) hot_func() { return 0; } -int ABSL_ATTRIBUTE_SECTION_VARIABLE(.text.startup) startup_func() { - return 0; -} +int ABSL_ATTRIBUTE_SECTION_VARIABLE(.text.startup) startup_func() { return 0; } -int ABSL_ATTRIBUTE_SECTION_VARIABLE(.text.exit) exit_func() { - return 0; -} +int ABSL_ATTRIBUTE_SECTION_VARIABLE(.text.exit) exit_func() { return 0; } -int /*ABSL_ATTRIBUTE_SECTION_VARIABLE(.text)*/ regular_func() { - return 0; -} +int /*ABSL_ATTRIBUTE_SECTION_VARIABLE(.text)*/ regular_func() { return 0; } // Thread-local data may confuse the symbolizer, ensure that it does not. // Variable sizes and order are important. @@ -106,6 +107,8 @@ static ABSL_PER_THREAD_TLS_KEYWORD char #endif #if !defined(__EMSCRIPTEN__) +static void *GetPCFromFnPtr(void *ptr) { return ptr; } + // Used below to hopefully inhibit some compiler/linker optimizations // that may remove kHpageTextPadding, kPadding0, and kPadding1 from // the binary. @@ -114,7 +117,14 @@ static volatile bool volatile_bool = false; // Force the binary to be large enough that a THP .text remap will succeed. static constexpr size_t kHpageSize = 1 << 21; const char kHpageTextPadding[kHpageSize * 4] ABSL_ATTRIBUTE_SECTION_VARIABLE( - .text) = ""; + .text) = ""; + +#else +static void *GetPCFromFnPtr(void *ptr) { + return EM_ASM_PTR( + { return wasmOffsetConverter.convert(wasmTable.get($0).name, 0); }, ptr); +} + #endif // !defined(__EMSCRIPTEN__) static char try_symbolize_buffer[4096]; @@ -124,15 +134,17 @@ static char try_symbolize_buffer[4096]; // absl::Symbolize() returns false, otherwise returns try_symbolize_buffer with // the result of absl::Symbolize(). static const char *TrySymbolizeWithLimit(void *pc, int limit) { - ABSL_RAW_CHECK(limit <= sizeof(try_symbolize_buffer), - "try_symbolize_buffer is too small"); + CHECK_LE(limit, sizeof(try_symbolize_buffer)) + << "try_symbolize_buffer is too small"; // Use the heap to facilitate heap and buffer sanitizer tools. auto heap_buffer = absl::make_unique<char[]>(sizeof(try_symbolize_buffer)); bool found = absl::Symbolize(pc, heap_buffer.get(), limit); if (found) { - ABSL_RAW_CHECK(strnlen(heap_buffer.get(), limit) < limit, - "absl::Symbolize() did not properly terminate the string"); + CHECK_LT(static_cast<int>( + strnlen(heap_buffer.get(), static_cast<size_t>(limit))), + limit) + << "absl::Symbolize() did not properly terminate the string"; strncpy(try_symbolize_buffer, heap_buffer.get(), sizeof(try_symbolize_buffer) - 1); try_symbolize_buffer[sizeof(try_symbolize_buffer) - 1] = '\0'; @@ -155,21 +167,20 @@ void ABSL_ATTRIBUTE_NOINLINE TestWithReturnAddress() { #if defined(ABSL_HAVE_ATTRIBUTE_NOINLINE) void *return_address = __builtin_return_address(0); const char *symbol = TrySymbolize(return_address); - ABSL_RAW_CHECK(symbol != nullptr, "TestWithReturnAddress failed"); - ABSL_RAW_CHECK(strcmp(symbol, "main") == 0, "TestWithReturnAddress failed"); + CHECK_NE(symbol, nullptr) << "TestWithReturnAddress failed"; + CHECK_STREQ(symbol, "main") << "TestWithReturnAddress failed"; std::cout << "TestWithReturnAddress passed" << std::endl; #endif } -#ifndef ABSL_INTERNAL_HAVE_EMSCRIPTEN_SYMBOLIZE - TEST(Symbolize, Cached) { // Compilers should give us pointers to them. - EXPECT_STREQ("nonstatic_func", TrySymbolize((void *)(&nonstatic_func))); - + EXPECT_STREQ("nonstatic_func", + TrySymbolize(GetPCFromFnPtr((void *)(&nonstatic_func)))); // The name of an internal linkage symbol is not specified; allow either a // mangled or an unmangled name here. - const char *static_func_symbol = TrySymbolize((void *)(&static_func)); + const char *static_func_symbol = + TrySymbolize(GetPCFromFnPtr((void *)(&static_func))); EXPECT_TRUE(strcmp("static_func", static_func_symbol) == 0 || strcmp("static_func()", static_func_symbol) == 0); @@ -179,33 +190,50 @@ TEST(Symbolize, Cached) { TEST(Symbolize, Truncation) { constexpr char kNonStaticFunc[] = "nonstatic_func"; EXPECT_STREQ("nonstatic_func", - TrySymbolizeWithLimit((void *)(&nonstatic_func), + TrySymbolizeWithLimit(GetPCFromFnPtr((void *)(&nonstatic_func)), strlen(kNonStaticFunc) + 1)); EXPECT_STREQ("nonstatic_...", - TrySymbolizeWithLimit((void *)(&nonstatic_func), + TrySymbolizeWithLimit(GetPCFromFnPtr((void *)(&nonstatic_func)), strlen(kNonStaticFunc) + 0)); EXPECT_STREQ("nonstatic...", - TrySymbolizeWithLimit((void *)(&nonstatic_func), + TrySymbolizeWithLimit(GetPCFromFnPtr((void *)(&nonstatic_func)), strlen(kNonStaticFunc) - 1)); - EXPECT_STREQ("n...", TrySymbolizeWithLimit((void *)(&nonstatic_func), 5)); - EXPECT_STREQ("...", TrySymbolizeWithLimit((void *)(&nonstatic_func), 4)); - EXPECT_STREQ("..", TrySymbolizeWithLimit((void *)(&nonstatic_func), 3)); - EXPECT_STREQ(".", TrySymbolizeWithLimit((void *)(&nonstatic_func), 2)); - EXPECT_STREQ("", TrySymbolizeWithLimit((void *)(&nonstatic_func), 1)); - EXPECT_EQ(nullptr, TrySymbolizeWithLimit((void *)(&nonstatic_func), 0)); + EXPECT_STREQ("n...", TrySymbolizeWithLimit( + GetPCFromFnPtr((void *)(&nonstatic_func)), 5)); + EXPECT_STREQ("...", TrySymbolizeWithLimit( + GetPCFromFnPtr((void *)(&nonstatic_func)), 4)); + EXPECT_STREQ("..", TrySymbolizeWithLimit( + GetPCFromFnPtr((void *)(&nonstatic_func)), 3)); + EXPECT_STREQ( + ".", TrySymbolizeWithLimit(GetPCFromFnPtr((void *)(&nonstatic_func)), 2)); + EXPECT_STREQ( + "", TrySymbolizeWithLimit(GetPCFromFnPtr((void *)(&nonstatic_func)), 1)); + EXPECT_EQ(nullptr, TrySymbolizeWithLimit( + GetPCFromFnPtr((void *)(&nonstatic_func)), 0)); } TEST(Symbolize, SymbolizeWithDemangling) { Foo::func(100); - EXPECT_STREQ("Foo::func()", TrySymbolize((void *)(&Foo::func))); +#ifdef __EMSCRIPTEN__ + // Emscripten's online symbolizer is more precise with arguments. + EXPECT_STREQ("Foo::func(int)", + TrySymbolize(GetPCFromFnPtr((void *)(&Foo::func)))); +#else + EXPECT_STREQ("Foo::func()", + TrySymbolize(GetPCFromFnPtr((void *)(&Foo::func)))); +#endif } TEST(Symbolize, SymbolizeSplitTextSections) { - EXPECT_STREQ("unlikely_func()", TrySymbolize((void *)(&unlikely_func))); - EXPECT_STREQ("hot_func()", TrySymbolize((void *)(&hot_func))); - EXPECT_STREQ("startup_func()", TrySymbolize((void *)(&startup_func))); - EXPECT_STREQ("exit_func()", TrySymbolize((void *)(&exit_func))); - EXPECT_STREQ("regular_func()", TrySymbolize((void *)(®ular_func))); + EXPECT_STREQ("unlikely_func()", + TrySymbolize(GetPCFromFnPtr((void *)(&unlikely_func)))); + EXPECT_STREQ("hot_func()", TrySymbolize(GetPCFromFnPtr((void *)(&hot_func)))); + EXPECT_STREQ("startup_func()", + TrySymbolize(GetPCFromFnPtr((void *)(&startup_func)))); + EXPECT_STREQ("exit_func()", + TrySymbolize(GetPCFromFnPtr((void *)(&exit_func)))); + EXPECT_STREQ("regular_func()", + TrySymbolize(GetPCFromFnPtr((void *)(®ular_func)))); } // Tests that verify that Symbolize stack footprint is within some limit. @@ -275,15 +303,14 @@ TEST(Symbolize, SymbolizeWithDemanglingStackConsumption) { #endif // ABSL_INTERNAL_HAVE_DEBUGGING_STACK_CONSUMPTION -#ifndef ABSL_INTERNAL_HAVE_DARWIN_SYMBOLIZE +#if !defined(ABSL_INTERNAL_HAVE_DARWIN_SYMBOLIZE) && \ + !defined(ABSL_INTERNAL_HAVE_EMSCRIPTEN_SYMBOLIZE) // Use a 64K page size for PPC. const size_t kPageSize = 64 << 10; // We place a read-only symbols into the .text section and verify that we can // symbolize them and other symbols after remapping them. -const char kPadding0[kPageSize * 4] ABSL_ATTRIBUTE_SECTION_VARIABLE(.text) = - ""; -const char kPadding1[kPageSize * 4] ABSL_ATTRIBUTE_SECTION_VARIABLE(.text) = - ""; +const char kPadding0[kPageSize * 4] ABSL_ATTRIBUTE_SECTION_VARIABLE(.text) = ""; +const char kPadding1[kPageSize * 4] ABSL_ATTRIBUTE_SECTION_VARIABLE(.text) = ""; static int FilterElfHeader(struct dl_phdr_info *info, size_t size, void *data) { for (int i = 0; i < info->dlpi_phnum; i++) { @@ -314,8 +341,8 @@ static int FilterElfHeader(struct dl_phdr_info *info, size_t size, void *data) { TEST(Symbolize, SymbolizeWithMultipleMaps) { // Force kPadding0 and kPadding1 to be linked in. if (volatile_bool) { - ABSL_RAW_LOG(INFO, "%s", kPadding0); - ABSL_RAW_LOG(INFO, "%s", kPadding1); + LOG(INFO) << kPadding0; + LOG(INFO) << kPadding1; } // Verify we can symbolize everything. @@ -433,17 +460,17 @@ TEST(Symbolize, ForEachSection) { close(fd); } -#endif // !ABSL_INTERNAL_HAVE_DARWIN_SYMBOLIZE -#endif // !ABSL_INTERNAL_HAVE_EMSCRIPTEN_SYMBOLIZE +#endif // !ABSL_INTERNAL_HAVE_DARWIN_SYMBOLIZE && + // !ABSL_INTERNAL_HAVE_EMSCRIPTEN_SYMBOLIZE // x86 specific tests. Uses some inline assembler. extern "C" { inline void *ABSL_ATTRIBUTE_ALWAYS_INLINE inline_func() { void *pc = nullptr; #if defined(__i386__) - __asm__ __volatile__("call 1f;\n 1: pop %[PC]" : [ PC ] "=r"(pc)); + __asm__ __volatile__("call 1f;\n 1: pop %[PC]" : [PC] "=r"(pc)); #elif defined(__x86_64__) - __asm__ __volatile__("leaq 0(%%rip),%[PC];\n" : [ PC ] "=r"(pc)); + __asm__ __volatile__("leaq 0(%%rip),%[PC];\n" : [PC] "=r"(pc)); #endif return pc; } @@ -451,9 +478,9 @@ inline void *ABSL_ATTRIBUTE_ALWAYS_INLINE inline_func() { void *ABSL_ATTRIBUTE_NOINLINE non_inline_func() { void *pc = nullptr; #if defined(__i386__) - __asm__ __volatile__("call 1f;\n 1: pop %[PC]" : [ PC ] "=r"(pc)); + __asm__ __volatile__("call 1f;\n 1: pop %[PC]" : [PC] "=r"(pc)); #elif defined(__x86_64__) - __asm__ __volatile__("leaq 0(%%rip),%[PC];\n" : [ PC ] "=r"(pc)); + __asm__ __volatile__("leaq 0(%%rip),%[PC];\n" : [PC] "=r"(pc)); #endif return pc; } @@ -463,9 +490,9 @@ void ABSL_ATTRIBUTE_NOINLINE TestWithPCInsideNonInlineFunction() { (defined(__i386__) || defined(__x86_64__)) void *pc = non_inline_func(); const char *symbol = TrySymbolize(pc); - ABSL_RAW_CHECK(symbol != nullptr, "TestWithPCInsideNonInlineFunction failed"); - ABSL_RAW_CHECK(strcmp(symbol, "non_inline_func") == 0, - "TestWithPCInsideNonInlineFunction failed"); + CHECK_NE(symbol, nullptr) << "TestWithPCInsideNonInlineFunction failed"; + CHECK_STREQ(symbol, "non_inline_func") + << "TestWithPCInsideNonInlineFunction failed"; std::cout << "TestWithPCInsideNonInlineFunction passed" << std::endl; #endif } @@ -475,9 +502,8 @@ void ABSL_ATTRIBUTE_NOINLINE TestWithPCInsideInlineFunction() { (defined(__i386__) || defined(__x86_64__)) void *pc = inline_func(); // Must be inlined. const char *symbol = TrySymbolize(pc); - ABSL_RAW_CHECK(symbol != nullptr, "TestWithPCInsideInlineFunction failed"); - ABSL_RAW_CHECK(strcmp(symbol, __FUNCTION__) == 0, - "TestWithPCInsideInlineFunction failed"); + CHECK_NE(symbol, nullptr) << "TestWithPCInsideInlineFunction failed"; + CHECK_STREQ(symbol, __FUNCTION__) << "TestWithPCInsideInlineFunction failed"; std::cout << "TestWithPCInsideInlineFunction passed" << std::endl; #endif } @@ -519,9 +545,8 @@ __attribute__((target("arm"))) int ArmThumbOverlapArm(int x) { void ABSL_ATTRIBUTE_NOINLINE TestArmThumbOverlap() { #if defined(ABSL_HAVE_ATTRIBUTE_NOINLINE) const char *symbol = TrySymbolize((void *)&ArmThumbOverlapArm); - ABSL_RAW_CHECK(symbol != nullptr, "TestArmThumbOverlap failed"); - ABSL_RAW_CHECK(strcmp("ArmThumbOverlapArm()", symbol) == 0, - "TestArmThumbOverlap failed"); + CHECK_NE(symbol, nullptr) << "TestArmThumbOverlap failed"; + CHECK_STREQ("ArmThumbOverlapArm()", symbol) << "TestArmThumbOverlap failed"; std::cout << "TestArmThumbOverlap passed" << std::endl; #endif } @@ -570,7 +595,7 @@ TEST(Symbolize, SymbolizeWithDemangling) { } #endif // !defined(ABSL_CONSUME_DLL) -#else // Symbolizer unimplemented +#else // Symbolizer unimplemented TEST(Symbolize, Unimplemented) { char buf[64]; EXPECT_FALSE(absl::Symbolize((void *)(&nonstatic_func), buf, sizeof(buf))); @@ -584,7 +609,7 @@ int main(int argc, char **argv) { #if !defined(__EMSCRIPTEN__) // Make sure kHpageTextPadding is linked into the binary. if (volatile_bool) { - ABSL_RAW_LOG(INFO, "%s", kHpageTextPadding); + LOG(INFO) << kHpageTextPadding; } #endif // !defined(__EMSCRIPTEN__) @@ -597,7 +622,8 @@ int main(int argc, char **argv) { absl::InitializeSymbolizer(argv[0]); testing::InitGoogleTest(&argc, argv); -#if defined(ABSL_INTERNAL_HAVE_ELF_SYMBOLIZE) || \ +#if defined(ABSL_INTERNAL_HAVE_ELF_SYMBOLIZE) || \ + defined(ABSL_INTERNAL_HAVE_EMSCRIPTEN_SYMBOLIZE) || \ defined(ABSL_INTERNAL_HAVE_DARWIN_SYMBOLIZE) TestWithPCInsideInlineFunction(); TestWithPCInsideNonInlineFunction(); diff --git a/absl/flags/BUILD.bazel b/absl/flags/BUILD.bazel index f5b3e033..d3b06227 100644 --- a/absl/flags/BUILD.bazel +++ b/absl/flags/BUILD.bazel @@ -21,7 +21,14 @@ load( "ABSL_TEST_COPTS", ) -package(default_visibility = ["//visibility:public"]) +package( + default_visibility = ["//visibility:public"], + features = [ + "header_modules", + "layering_check", + "parse_headers", + ], +) licenses(["notice"]) @@ -99,6 +106,7 @@ cc_library( "//absl/base:config", "//absl/base:core_headers", "//absl/base:log_severity", + "//absl/numeric:int128", "//absl/strings", "//absl/strings:str_format", "//absl/types:optional", @@ -182,6 +190,7 @@ cc_library( ":private_handle_accessor", "//absl/base:config", "//absl/base:core_headers", + "//absl/base:no_destructor", "//absl/container:flat_hash_map", "//absl/strings", "//absl/synchronization", @@ -220,10 +229,6 @@ cc_library( cc_library( name = "flag", - srcs = [ - "flag.cc", - "internal/flag_msvc.inc", - ], hdrs = [ "declare.h", "flag.h", @@ -265,8 +270,8 @@ cc_library( ":reflection", "//absl/base:config", "//absl/base:core_headers", - "//absl/container:flat_hash_map", "//absl/strings", + "//absl/synchronization", ], ) @@ -284,6 +289,7 @@ cc_library( ":usage_internal", "//absl/base:config", "//absl/base:core_headers", + "//absl/base:raw_logging_internal", "//absl/strings", "//absl/synchronization", ], @@ -344,6 +350,7 @@ cc_test( ":reflection", "//absl/memory", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -358,6 +365,7 @@ cc_test( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":config", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -386,8 +394,10 @@ cc_test( ":reflection", "//absl/base:core_headers", "//absl/base:malloc_internal", + "//absl/numeric:int128", "//absl/strings", "//absl/time", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -427,6 +437,7 @@ cc_test( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":marshalling", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -434,6 +445,7 @@ cc_test( cc_test( name = "parse_test", size = "small", + timeout = "moderate", srcs = [ "parse_test.cc", ], @@ -451,10 +463,11 @@ cc_test( ":parse", ":reflection", ":usage_internal", - "//absl/base:raw_logging_internal", "//absl/base:scoped_set_env", + "//absl/log", "//absl/strings", "//absl/types:span", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -469,6 +482,7 @@ cc_test( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":path_util", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -485,6 +499,7 @@ cc_test( deps = [ ":program_name", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -512,6 +527,7 @@ cc_test( ":usage_internal", "//absl/memory", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -532,6 +548,7 @@ cc_test( "//absl/base", "//absl/container:fixed_array", "//absl/time", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -549,6 +566,7 @@ cc_test( ":path_util", ":program_name", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/flags/CMakeLists.txt b/absl/flags/CMakeLists.txt index 2f234aa4..44953124 100644 --- a/absl/flags/CMakeLists.txt +++ b/absl/flags/CMakeLists.txt @@ -87,6 +87,7 @@ absl_cc_library( absl::config absl::core_headers absl::log_severity + absl::int128 absl::optional absl::strings absl::str_format @@ -168,6 +169,7 @@ absl_cc_library( absl::strings absl::synchronization absl::flat_hash_map + absl::no_destructor ) # Internal-only target, do not depend on directly. @@ -199,12 +201,9 @@ absl_cc_library( absl_cc_library( NAME flags - SRCS - "flag.cc" HDRS "declare.h" "flag.h" - "internal/flag_msvc.inc" COPTS ${ABSL_DEFAULT_COPTS} LINKOPTS @@ -242,7 +241,6 @@ absl_cc_library( absl::flags_private_handle_accessor absl::flags_program_name absl::flags_reflection - absl::flat_hash_map absl::strings absl::synchronization ) @@ -262,6 +260,7 @@ absl_cc_library( absl::config absl::core_headers absl::flags_usage_internal + absl::raw_logging_internal absl::strings absl::synchronization ) @@ -296,7 +295,7 @@ absl_cc_library( ) ############################################################################ -# Unit tests in alpahabetical order. +# Unit tests in alphabetical order. absl_cc_test( NAME @@ -344,6 +343,7 @@ absl_cc_test( absl::flags_internal absl::flags_marshalling absl::flags_reflection + absl::int128 absl::strings absl::time GTest::gtest_main @@ -373,7 +373,7 @@ absl_cc_test( absl::flags_parse absl::flags_reflection absl::flags_usage_internal - absl::raw_logging_internal + absl::log absl::scoped_set_env absl::span absl::strings diff --git a/absl/flags/commandlineflag.h b/absl/flags/commandlineflag.h index f2fa0897..c30aa609 100644 --- a/absl/flags/commandlineflag.h +++ b/absl/flags/commandlineflag.h @@ -186,7 +186,7 @@ class CommandLineFlag { // command line. virtual bool IsSpecifiedOnCommandLine() const = 0; - // Validates supplied value usign validator or parseflag routine + // Validates supplied value using validator or parseflag routine virtual bool ValidateInputValue(absl::string_view value) const = 0; // Checks that flags default value can be converted to string and back to the diff --git a/absl/flags/declare.h b/absl/flags/declare.h index d1437bb9..8d2a856e 100644 --- a/absl/flags/declare.h +++ b/absl/flags/declare.h @@ -40,13 +40,8 @@ class Flag; // Flag // // Forward declaration of the `absl::Flag` type for use in defining the macro. -#if defined(_MSC_VER) && !defined(__clang__) -template <typename T> -class Flag; -#else template <typename T> using Flag = flags_internal::Flag<T>; -#endif ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/flags/flag.h b/absl/flags/flag.h index b7f94be7..06ea6932 100644 --- a/absl/flags/flag.h +++ b/absl/flags/flag.h @@ -71,12 +71,9 @@ ABSL_NAMESPACE_BEGIN // For type support of Abseil Flags, see the marshalling.h header file, which // discusses supported standard types, optional flags, and additional Abseil // type support. -#if !defined(_MSC_VER) || defined(__clang__) + template <typename T> using Flag = flags_internal::Flag<T>; -#else -#include "absl/flags/internal/flag_msvc.inc" -#endif // GetFlag() // @@ -196,18 +193,12 @@ ABSL_NAMESPACE_END // ----------------------------------------------------------------------------- // ABSL_FLAG_IMPL macro definition conditional on ABSL_FLAGS_STRIP_NAMES -#if !defined(_MSC_VER) || defined(__clang__) #define ABSL_FLAG_IMPL_FLAG_PTR(flag) flag #define ABSL_FLAG_IMPL_HELP_ARG(name) \ absl::flags_internal::HelpArg<AbslFlagHelpGenFor##name>( \ FLAGS_help_storage_##name) #define ABSL_FLAG_IMPL_DEFAULT_ARG(Type, name) \ absl::flags_internal::DefaultArg<Type, AbslFlagDefaultGenFor##name>(0) -#else -#define ABSL_FLAG_IMPL_FLAG_PTR(flag) flag.GetImpl() -#define ABSL_FLAG_IMPL_HELP_ARG(name) &AbslFlagHelpGenFor##name::NonConst -#define ABSL_FLAG_IMPL_DEFAULT_ARG(Type, name) &AbslFlagDefaultGenFor##name::Gen -#endif #if ABSL_FLAGS_STRIP_NAMES #define ABSL_FLAG_IMPL_FLAGNAME(txt) "" diff --git a/absl/flags/flag_test.cc b/absl/flags/flag_test.cc index 845b4eba..8d14ba8d 100644 --- a/absl/flags/flag_test.cc +++ b/absl/flags/flag_test.cc @@ -34,6 +34,7 @@ #include "absl/flags/marshalling.h" #include "absl/flags/reflection.h" #include "absl/flags/usage_config.h" +#include "absl/numeric/int128.h" #include "absl/strings/match.h" #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" @@ -127,6 +128,11 @@ TEST_F(FlagTest, Traits) { flags::FlagValueStorageKind::kAlignedBuffer); EXPECT_EQ(flags::StorageKind<std::vector<std::string>>(), flags::FlagValueStorageKind::kAlignedBuffer); + + EXPECT_EQ(flags::StorageKind<absl::int128>(), + flags::FlagValueStorageKind::kSequenceLocked); + EXPECT_EQ(flags::StorageKind<absl::uint128>(), + flags::FlagValueStorageKind::kSequenceLocked); } // -------------------------------------------------------------------- @@ -135,8 +141,9 @@ constexpr flags::FlagHelpArg help_arg{flags::FlagHelpMsg("literal help"), flags::FlagHelpKind::kLiteral}; using String = std::string; +using int128 = absl::int128; +using uint128 = absl::uint128; -#if !defined(_MSC_VER) || defined(__clang__) #define DEFINE_CONSTRUCTED_FLAG(T, dflt, dflt_kind) \ constexpr flags::FlagDefaultArg f1default##T{ \ flags::FlagDefaultSrc{dflt}, flags::FlagDefaultKind::dflt_kind}; \ @@ -149,16 +156,6 @@ using String = std::string; flags::FlagDefaultKind::kGenFunc \ } \ } -#else -#define DEFINE_CONSTRUCTED_FLAG(T, dflt, dflt_kind) \ - constexpr flags::FlagDefaultArg f1default##T{ \ - flags::FlagDefaultSrc{dflt}, flags::FlagDefaultKind::dflt_kind}; \ - constexpr absl::Flag<T> f1##T{"f1", "file", &TestLiteralHelpMsg, \ - &TestMakeDflt<T>}; \ - ABSL_CONST_INIT absl::Flag<T> f2##T { \ - "f2", "file", &TestHelpMsg, &TestMakeDflt<T> \ - } -#endif DEFINE_CONSTRUCTED_FLAG(bool, true, kOneWord); DEFINE_CONSTRUCTED_FLAG(int16_t, 1, kOneWord); @@ -171,6 +168,8 @@ DEFINE_CONSTRUCTED_FLAG(float, 7.8, kOneWord); DEFINE_CONSTRUCTED_FLAG(double, 9.10, kOneWord); DEFINE_CONSTRUCTED_FLAG(String, &TestMakeDflt<String>, kGenFunc); DEFINE_CONSTRUCTED_FLAG(UDT, &TestMakeDflt<UDT>, kGenFunc); +DEFINE_CONSTRUCTED_FLAG(int128, 13, kGenFunc); +DEFINE_CONSTRUCTED_FLAG(uint128, 14, kGenFunc); template <typename T> bool TestConstructionFor(const absl::Flag<T>& f1, absl::Flag<T>& f2) { @@ -202,6 +201,8 @@ TEST_F(FlagTest, TestConstruction) { TEST_CONSTRUCTED_FLAG(double); TEST_CONSTRUCTED_FLAG(String); TEST_CONSTRUCTED_FLAG(UDT); + TEST_CONSTRUCTED_FLAG(int128); + TEST_CONSTRUCTED_FLAG(uint128); } // -------------------------------------------------------------------- @@ -220,6 +221,8 @@ ABSL_DECLARE_FLAG(double, test_flag_09); ABSL_DECLARE_FLAG(float, test_flag_10); ABSL_DECLARE_FLAG(std::string, test_flag_11); ABSL_DECLARE_FLAG(absl::Duration, test_flag_12); +ABSL_DECLARE_FLAG(absl::int128, test_flag_13); +ABSL_DECLARE_FLAG(absl::uint128, test_flag_14); namespace { @@ -251,6 +254,10 @@ TEST_F(FlagTest, TestFlagDeclaration) { "test_flag_11"); EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_12).Name(), "test_flag_12"); + EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_13).Name(), + "test_flag_13"); + EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_14).Name(), + "test_flag_14"); } #endif // !ABSL_FLAGS_STRIP_NAMES @@ -270,6 +277,9 @@ ABSL_FLAG(double, test_flag_09, -9.876e-50, "test flag 09"); ABSL_FLAG(float, test_flag_10, 1.234e12f, "test flag 10"); ABSL_FLAG(std::string, test_flag_11, "", "test flag 11"); ABSL_FLAG(absl::Duration, test_flag_12, absl::Minutes(10), "test flag 12"); +ABSL_FLAG(absl::int128, test_flag_13, absl::MakeInt128(-1, 0), "test flag 13"); +ABSL_FLAG(absl::uint128, test_flag_14, absl::MakeUint128(0, 0xFFFAAABBBCCCDDD), + "test flag 14"); namespace { @@ -384,6 +394,24 @@ TEST_F(FlagTest, TestFlagDefinition) { absl::GetFlagReflectionHandle(FLAGS_test_flag_12).Filename(), expected_file_name)) << absl::GetFlagReflectionHandle(FLAGS_test_flag_12).Filename(); + + EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_13).Name(), + "test_flag_13"); + EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_13).Help(), + "test flag 13"); + EXPECT_TRUE(absl::EndsWith( + absl::GetFlagReflectionHandle(FLAGS_test_flag_13).Filename(), + expected_file_name)) + << absl::GetFlagReflectionHandle(FLAGS_test_flag_13).Filename(); + + EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_14).Name(), + "test_flag_14"); + EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_14).Help(), + "test flag 14"); + EXPECT_TRUE(absl::EndsWith( + absl::GetFlagReflectionHandle(FLAGS_test_flag_14).Filename(), + expected_file_name)) + << absl::GetFlagReflectionHandle(FLAGS_test_flag_14).Filename(); } #endif // !ABSL_FLAGS_STRIP_NAMES @@ -414,6 +442,10 @@ TEST_F(FlagTest, TestDefault) { ""); EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_12).DefaultValue(), "10m"); + EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_13).DefaultValue(), + "-18446744073709551616"); + EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_14).DefaultValue(), + "1152827684197027293"); EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_01).CurrentValue(), "true"); @@ -439,6 +471,10 @@ TEST_F(FlagTest, TestDefault) { ""); EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_12).CurrentValue(), "10m"); + EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_13).CurrentValue(), + "-18446744073709551616"); + EXPECT_EQ(absl::GetFlagReflectionHandle(FLAGS_test_flag_14).CurrentValue(), + "1152827684197027293"); EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_01), true); EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_02), 1234); @@ -452,6 +488,9 @@ TEST_F(FlagTest, TestDefault) { EXPECT_NEAR(absl::GetFlag(FLAGS_test_flag_10), 1.234e12f, 1e5f); EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_11), ""); EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_12), absl::Minutes(10)); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_13), absl::MakeInt128(-1, 0)); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_14), + absl::MakeUint128(0, 0xFFFAAABBBCCCDDD)); } // -------------------------------------------------------------------- @@ -553,6 +592,13 @@ TEST_F(FlagTest, TestGetSet) { absl::SetFlag(&FLAGS_test_flag_12, absl::Seconds(110)); EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_12), absl::Seconds(110)); + + absl::SetFlag(&FLAGS_test_flag_13, absl::MakeInt128(-1, 0)); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_13), absl::MakeInt128(-1, 0)); + + absl::SetFlag(&FLAGS_test_flag_14, absl::MakeUint128(0, 0xFFFAAABBBCCCDDD)); + EXPECT_EQ(absl::GetFlag(FLAGS_test_flag_14), + absl::MakeUint128(0, 0xFFFAAABBBCCCDDD)); } // -------------------------------------------------------------------- @@ -582,6 +628,11 @@ TEST_F(FlagTest, TestGetViaReflection) { EXPECT_EQ(*handle->TryGet<std::string>(), ""); handle = absl::FindCommandLineFlag("test_flag_12"); EXPECT_EQ(*handle->TryGet<absl::Duration>(), absl::Minutes(10)); + handle = absl::FindCommandLineFlag("test_flag_13"); + EXPECT_EQ(*handle->TryGet<absl::int128>(), absl::MakeInt128(-1, 0)); + handle = absl::FindCommandLineFlag("test_flag_14"); + EXPECT_EQ(*handle->TryGet<absl::uint128>(), + absl::MakeUint128(0, 0xFFFAAABBBCCCDDD)); } // -------------------------------------------------------------------- @@ -980,7 +1031,7 @@ TEST_F(FlagTest, TesTypeWrappingEnum) { // This is a compile test to ensure macros are expanded within ABSL_FLAG and // ABSL_DECLARE_FLAG. -#define FLAG_NAME_MACRO(name) prefix_ ## name +#define FLAG_NAME_MACRO(name) prefix_##name ABSL_DECLARE_FLAG(int, FLAG_NAME_MACRO(test_macro_named_flag)); ABSL_FLAG(int, FLAG_NAME_MACRO(test_macro_named_flag), 0, "Testing macro expansion within ABSL_FLAG"); diff --git a/absl/flags/internal/commandlineflag.cc b/absl/flags/internal/commandlineflag.cc index 4482955c..3c114d10 100644 --- a/absl/flags/internal/commandlineflag.cc +++ b/absl/flags/internal/commandlineflag.cc @@ -19,7 +19,7 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace flags_internal { -FlagStateInterface::~FlagStateInterface() {} +FlagStateInterface::~FlagStateInterface() = default; } // namespace flags_internal ABSL_NAMESPACE_END diff --git a/absl/flags/internal/flag.cc b/absl/flags/internal/flag.cc index cc656f9d..65d0e58f 100644 --- a/absl/flags/internal/flag.cc +++ b/absl/flags/internal/flag.cc @@ -197,7 +197,7 @@ void FlagImpl::AssertValidType(FlagFastTypeId rhs_type_id, FlagFastTypeId lhs_type_id = flags_internal::FastTypeId(op_); // `rhs_type_id` is the fast type id corresponding to the declaration - // visibile at the call site. `lhs_type_id` is the fast type id + // visible at the call site. `lhs_type_id` is the fast type id // corresponding to the type specified in flag definition. They must match // for this operation to be well-defined. if (ABSL_PREDICT_TRUE(lhs_type_id == rhs_type_id)) return; @@ -238,7 +238,7 @@ void FlagImpl::StoreValue(const void* src) { switch (ValueStorageKind()) { case FlagValueStorageKind::kValueAndInitBit: case FlagValueStorageKind::kOneWordAtomic: { - // Load the current value to avoid setting 'init' bit manualy. + // Load the current value to avoid setting 'init' bit manually. int64_t one_word_val = OneWordValue().load(std::memory_order_acquire); std::memcpy(&one_word_val, src, Sizeof(op_)); OneWordValue().store(one_word_val, std::memory_order_release); diff --git a/absl/flags/internal/flag.h b/absl/flags/internal/flag.h index 6154638c..2e6e6b87 100644 --- a/absl/flags/internal/flag.h +++ b/absl/flags/internal/flag.h @@ -54,13 +54,8 @@ template <typename T> class Flag; } // namespace flags_internal -#if defined(_MSC_VER) && !defined(__clang__) -template <typename T> -class Flag; -#else template <typename T> using Flag = flags_internal::Flag<T>; -#endif template <typename T> ABSL_MUST_USE_RESULT T GetFlag(const absl::Flag<T>& flag); @@ -121,7 +116,7 @@ inline void* Clone(FlagOpFn op, const void* obj) { flags_internal::CopyConstruct(op, obj, res); return res; } -// Returns true if parsing of input text is successfull. +// Returns true if parsing of input text is successful. inline bool Parse(FlagOpFn op, absl::string_view text, void* dst, std::string* error) { return op(FlagOp::kParse, &text, dst, error) != nullptr; @@ -139,12 +134,12 @@ inline size_t Sizeof(FlagOpFn op) { return static_cast<size_t>(reinterpret_cast<intptr_t>( op(FlagOp::kSizeof, nullptr, nullptr, nullptr))); } -// Returns fast type id coresponding to the value type. +// Returns fast type id corresponding to the value type. inline FlagFastTypeId FastTypeId(FlagOpFn op) { return reinterpret_cast<FlagFastTypeId>( op(FlagOp::kFastTypeId, nullptr, nullptr, nullptr)); } -// Returns fast type id coresponding to the value type. +// Returns fast type id corresponding to the value type. inline const std::type_info* RuntimeTypeId(FlagOpFn op) { return reinterpret_cast<const std::type_info*>( op(FlagOp::kRuntimeTypeId, nullptr, nullptr, nullptr)); @@ -223,12 +218,12 @@ extern const char kStrippedFlagHelp[]; // first overload if possible. If help message is evaluatable on constexpr // context We'll be able to make FixedCharArray out of it and we'll choose first // overload. In this case the help message expression is immediately evaluated -// and is used to construct the absl::Flag. No additionl code is generated by +// and is used to construct the absl::Flag. No additional code is generated by // ABSL_FLAG Otherwise SFINAE kicks in and first overload is dropped from the // consideration, in which case the second overload will be used. The second // overload does not attempt to evaluate the help message expression -// immediately and instead delays the evaluation by returing the function -// pointer (&T::NonConst) genering the help message when necessary. This is +// immediately and instead delays the evaluation by returning the function +// pointer (&T::NonConst) generating the help message when necessary. This is // evaluatable in constexpr context, but the cost is an extra function being // generated in the ABSL_FLAG code. template <typename Gen, size_t N> @@ -308,19 +303,20 @@ constexpr int64_t UninitializedFlagValue() { } template <typename T> -using FlagUseValueAndInitBitStorage = std::integral_constant< - bool, absl::type_traits_internal::is_trivially_copyable<T>::value && - std::is_default_constructible<T>::value && (sizeof(T) < 8)>; +using FlagUseValueAndInitBitStorage = + std::integral_constant<bool, std::is_trivially_copyable<T>::value && + std::is_default_constructible<T>::value && + (sizeof(T) < 8)>; template <typename T> -using FlagUseOneWordStorage = std::integral_constant< - bool, absl::type_traits_internal::is_trivially_copyable<T>::value && - (sizeof(T) <= 8)>; +using FlagUseOneWordStorage = + std::integral_constant<bool, std::is_trivially_copyable<T>::value && + (sizeof(T) <= 8)>; template <class T> -using FlagUseSequenceLockStorage = std::integral_constant< - bool, absl::type_traits_internal::is_trivially_copyable<T>::value && - (sizeof(T) > 8)>; +using FlagUseSequenceLockStorage = + std::integral_constant<bool, std::is_trivially_copyable<T>::value && + (sizeof(T) > 8)>; enum class FlagValueStorageKind : uint8_t { kValueAndInitBit = 0, diff --git a/absl/flags/internal/flag_msvc.inc b/absl/flags/internal/flag_msvc.inc deleted file mode 100644 index c31bd27f..00000000 --- a/absl/flags/internal/flag_msvc.inc +++ /dev/null @@ -1,116 +0,0 @@ -// -// Copyright 2021 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. - -// Do not include this file directly. -// Include absl/flags/flag.h instead. - -// MSVC debug builds do not implement initialization with constexpr constructors -// correctly. To work around this we add a level of indirection, so that the -// class `absl::Flag` contains an `internal::Flag*` (instead of being an alias -// to that class) and dynamically allocates an instance when necessary. We also -// forward all calls to internal::Flag methods via trampoline methods. In this -// setup the `absl::Flag` class does not have constructor and virtual methods, -// all the data members are public and thus MSVC is able to initialize it at -// link time. To deal with multiple threads accessing the flag for the first -// time concurrently we use an atomic boolean indicating if flag object is -// initialized. We also employ the double-checked locking pattern where the -// second level of protection is a global Mutex, so if two threads attempt to -// construct the flag concurrently only one wins. -// -// This solution is based on a recomendation here: -// https://developercommunity.visualstudio.com/content/problem/336946/class-with-constexpr-constructor-not-using-static.html?childToView=648454#comment-648454 - -namespace flags_internal { -absl::Mutex* GetGlobalConstructionGuard(); -} // namespace flags_internal - -// Public methods of `absl::Flag<T>` are NOT part of the Abseil Flags API. -// See https://abseil.io/docs/cpp/guides/flags -template <typename T> -class Flag { - public: - // No constructor and destructor to ensure this is an aggregate type. - // Visual Studio 2015 still requires the constructor for class to be - // constexpr initializable. -#if _MSC_VER <= 1900 - constexpr Flag(const char* name, const char* filename, - const flags_internal::HelpGenFunc help_gen, - const flags_internal::FlagDfltGenFunc default_value_gen) - : name_(name), - filename_(filename), - help_gen_(help_gen), - default_value_gen_(default_value_gen), - inited_(false), - impl_(nullptr) {} -#endif - - flags_internal::Flag<T>& GetImpl() const { - if (!inited_.load(std::memory_order_acquire)) { - absl::MutexLock l(flags_internal::GetGlobalConstructionGuard()); - - if (inited_.load(std::memory_order_acquire)) { - return *impl_; - } - - impl_ = new flags_internal::Flag<T>( - name_, filename_, - {flags_internal::FlagHelpMsg(help_gen_), - flags_internal::FlagHelpKind::kGenFunc}, - {flags_internal::FlagDefaultSrc(default_value_gen_), - flags_internal::FlagDefaultKind::kGenFunc}); - inited_.store(true, std::memory_order_release); - } - - return *impl_; - } - - // Public methods of `absl::Flag<T>` are NOT part of the Abseil Flags API. - // See https://abseil.io/docs/cpp/guides/flags - bool IsRetired() const { return GetImpl().IsRetired(); } - absl::string_view Name() const { return GetImpl().Name(); } - std::string Help() const { return GetImpl().Help(); } - bool IsModified() const { return GetImpl().IsModified(); } - bool IsSpecifiedOnCommandLine() const { - return GetImpl().IsSpecifiedOnCommandLine(); - } - std::string Filename() const { return GetImpl().Filename(); } - std::string DefaultValue() const { return GetImpl().DefaultValue(); } - std::string CurrentValue() const { return GetImpl().CurrentValue(); } - template <typename U> - inline bool IsOfType() const { - return GetImpl().template IsOfType<U>(); - } - T Get() const { - return flags_internal::FlagImplPeer::InvokeGet<T>(GetImpl()); - } - void Set(const T& v) { - flags_internal::FlagImplPeer::InvokeSet(GetImpl(), v); - } - void InvokeCallback() { GetImpl().InvokeCallback(); } - - const CommandLineFlag& Reflect() const { - return flags_internal::FlagImplPeer::InvokeReflect(GetImpl()); - } - - // The data members are logically private, but they need to be public for - // this to be an aggregate type. - const char* name_; - const char* filename_; - const flags_internal::HelpGenFunc help_gen_; - const flags_internal::FlagDfltGenFunc default_value_gen_; - - mutable std::atomic<bool> inited_; - mutable flags_internal::Flag<T>* impl_; -}; diff --git a/absl/flags/internal/parse.h b/absl/flags/internal/parse.h index 0a7012fc..10c531b8 100644 --- a/absl/flags/internal/parse.h +++ b/absl/flags/internal/parse.h @@ -16,11 +16,14 @@ #ifndef ABSL_FLAGS_INTERNAL_PARSE_H_ #define ABSL_FLAGS_INTERNAL_PARSE_H_ +#include <iostream> +#include <ostream> #include <string> #include <vector> #include "absl/base/config.h" #include "absl/flags/declare.h" +#include "absl/flags/internal/usage.h" #include "absl/strings/string_view.h" ABSL_DECLARE_FLAG(std::vector<std::string>, flagfile); @@ -32,7 +35,6 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace flags_internal { -enum class ArgvListAction { kRemoveParsedArgs, kKeepParsedArgs }; enum class UsageFlagsAction { kHandleUsage, kIgnoreUsage }; enum class OnUndefinedFlag { kIgnoreUndefined, @@ -40,10 +42,15 @@ enum class OnUndefinedFlag { kAbortIfUndefined }; -std::vector<char*> ParseCommandLineImpl(int argc, char* argv[], - ArgvListAction arg_list_act, - UsageFlagsAction usage_flag_act, - OnUndefinedFlag on_undef_flag); +// This is not a public interface. This interface exists to expose the ability +// to change help output stream in case of parsing errors. This is used by +// internal unit tests to validate expected outputs. +// When this was written, `EXPECT_EXIT` only supported matchers on stderr, +// but not on stdout. +std::vector<char*> ParseCommandLineImpl( + int argc, char* argv[], UsageFlagsAction usage_flag_action, + OnUndefinedFlag undef_flag_action, + std::ostream& error_help_output = std::cout); // -------------------------------------------------------------------- // Inspect original command line diff --git a/absl/flags/internal/usage.cc b/absl/flags/internal/usage.cc index 5efc7b07..8b169bcd 100644 --- a/absl/flags/internal/usage.cc +++ b/absl/flags/internal/usage.cc @@ -18,6 +18,7 @@ #include <stdint.h> #include <algorithm> +#include <cstdlib> #include <functional> #include <iterator> #include <map> @@ -26,7 +27,10 @@ #include <utility> #include <vector> +#include "absl/base/attributes.h" #include "absl/base/config.h" +#include "absl/base/const_init.h" +#include "absl/base/thread_annotations.h" #include "absl/flags/commandlineflag.h" #include "absl/flags/flag.h" #include "absl/flags/internal/flag.h" @@ -39,6 +43,8 @@ #include "absl/strings/str_cat.h" #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" +#include "absl/strings/strip.h" +#include "absl/synchronization/mutex.h" // Dummy global variables to prevent anyone else defining these. bool FLAGS_help = false; @@ -91,8 +97,16 @@ class XMLElement { case '>': out << ">"; break; + case '\n': + case '\v': + case '\f': + case '\t': + out << " "; + break; default: - out << c; + if (IsValidXmlCharacter(static_cast<unsigned char>(c))) { + out << c; + } break; } } @@ -101,6 +115,7 @@ class XMLElement { } private: + static bool IsValidXmlCharacter(unsigned char c) { return c >= 0x20; } absl::string_view tag_; absl::string_view txt_; }; @@ -130,7 +145,7 @@ class FlagHelpPrettyPrinter { for (auto line : absl::StrSplit(str, absl::ByAnyChar("\n\r"))) { if (!tokens.empty()) { // Keep line separators in the input string. - tokens.push_back("\n"); + tokens.emplace_back("\n"); } for (auto token : absl::StrSplit(line, absl::ByAnyChar(" \t"), absl::SkipEmpty())) { @@ -354,8 +369,8 @@ void FlagsHelp(std::ostream& out, absl::string_view filter, HelpFormat format, // -------------------------------------------------------------------- // Checks all the 'usage' command line flags to see if any have been set. // If so, handles them appropriately. -int HandleUsageFlags(std::ostream& out, - absl::string_view program_usage_message) { +HelpMode HandleUsageFlags(std::ostream& out, + absl::string_view program_usage_message) { switch (GetFlagsHelpMode()) { case HelpMode::kNone: break; @@ -363,25 +378,24 @@ int HandleUsageFlags(std::ostream& out, flags_internal::FlagsHelpImpl( out, flags_internal::GetUsageConfig().contains_help_flags, GetFlagsHelpFormat(), program_usage_message); - return 1; + break; case HelpMode::kShort: flags_internal::FlagsHelpImpl( out, flags_internal::GetUsageConfig().contains_helpshort_flags, GetFlagsHelpFormat(), program_usage_message); - return 1; + break; case HelpMode::kFull: flags_internal::FlagsHelp(out, "", GetFlagsHelpFormat(), program_usage_message); - return 1; + break; case HelpMode::kPackage: flags_internal::FlagsHelpImpl( out, flags_internal::GetUsageConfig().contains_helppackage_flags, GetFlagsHelpFormat(), program_usage_message); - - return 1; + break; case HelpMode::kMatch: { std::string substr = GetFlagsHelpMatchSubstr(); @@ -400,20 +414,19 @@ int HandleUsageFlags(std::ostream& out, flags_internal::FlagsHelpImpl( out, filter_cb, HelpFormat::kHumanReadable, program_usage_message); } - - return 1; + break; } case HelpMode::kVersion: if (flags_internal::GetUsageConfig().version_string) out << flags_internal::GetUsageConfig().version_string(); // Unlike help, we may be asking for version in a script, so return 0 - return 0; + break; case HelpMode::kOnlyCheckArgs: - return 0; + break; } - return -1; + return GetFlagsHelpMode(); } // -------------------------------------------------------------------- @@ -521,6 +534,22 @@ bool DeduceUsageFlags(absl::string_view name, absl::string_view value) { return false; } +// -------------------------------------------------------------------- + +void MaybeExit(HelpMode mode) { + switch (mode) { + case flags_internal::HelpMode::kNone: + return; + case flags_internal::HelpMode::kOnlyCheckArgs: + case flags_internal::HelpMode::kVersion: + std::exit(0); + default: // For all the other modes we exit with 1 + std::exit(1); + } +} + +// -------------------------------------------------------------------- + } // namespace flags_internal ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/flags/internal/usage.h b/absl/flags/internal/usage.h index c0bcac57..a96cbf38 100644 --- a/absl/flags/internal/usage.h +++ b/absl/flags/internal/usage.h @@ -17,11 +17,11 @@ #define ABSL_FLAGS_INTERNAL_USAGE_H_ #include <iosfwd> +#include <ostream> #include <string> #include "absl/base/config.h" #include "absl/flags/commandlineflag.h" -#include "absl/flags/declare.h" #include "absl/strings/string_view.h" // -------------------------------------------------------------------- @@ -36,6 +36,18 @@ enum class HelpFormat { kHumanReadable, }; +// The kind of usage help requested. +enum class HelpMode { + kNone, + kImportant, + kShort, + kFull, + kPackage, + kMatch, + kVersion, + kOnlyCheckArgs +}; + // Streams the help message describing `flag` to `out`. // The default value for `flag` is included in the output. void FlagHelp(std::ostream& out, const CommandLineFlag& flag, @@ -57,28 +69,18 @@ void FlagsHelp(std::ostream& out, absl::string_view filter, // If any of the 'usage' related command line flags (listed on the bottom of // this file) has been set this routine produces corresponding help message in -// the specified output stream and returns: -// 0 - if "version" or "only_check_flags" flags were set and handled. -// 1 - if some other 'usage' related flag was set and handled. -// -1 - if no usage flags were set on a commmand line. -// Non negative return values are expected to be used as an exit code for a -// binary. -int HandleUsageFlags(std::ostream& out, - absl::string_view program_usage_message); +// the specified output stream and returns HelpMode that was handled. Otherwise +// it returns HelpMode::kNone. +HelpMode HandleUsageFlags(std::ostream& out, + absl::string_view program_usage_message); // -------------------------------------------------------------------- -// Globals representing usage reporting flags +// Encapsulates the logic of exiting the binary depending on handled help mode. -enum class HelpMode { - kNone, - kImportant, - kShort, - kFull, - kPackage, - kMatch, - kVersion, - kOnlyCheckArgs -}; +void MaybeExit(HelpMode mode); + +// -------------------------------------------------------------------- +// Globals representing usage reporting flags // Returns substring to filter help output (--help=substr argument) std::string GetFlagsHelpMatchSubstr(); diff --git a/absl/flags/internal/usage_test.cc b/absl/flags/internal/usage_test.cc index 209a7be9..6847386f 100644 --- a/absl/flags/internal/usage_test.cc +++ b/absl/flags/internal/usage_test.cc @@ -24,7 +24,6 @@ #include "gtest/gtest.h" #include "absl/flags/flag.h" #include "absl/flags/internal/parse.h" -#include "absl/flags/internal/path_util.h" #include "absl/flags/internal/program_name.h" #include "absl/flags/reflection.h" #include "absl/flags/usage.h" @@ -40,6 +39,8 @@ ABSL_FLAG(double, usage_reporting_test_flag_03, 1.03, "usage_reporting_test_flag_03 help message"); ABSL_FLAG(int64_t, usage_reporting_test_flag_04, 1000000000000004L, "usage_reporting_test_flag_04 help message"); +ABSL_FLAG(std::string, usage_reporting_test_flag_07, "\r\n\f\v\a\b\t ", + "usage_reporting_test_flag_07 help \r\n\f\v\a\b\t "); static const char kTestUsageMessage[] = "Custom usage message"; @@ -204,8 +205,12 @@ TEST_F(UsageReportingTest, TestFlagsHelpHRF) { Some more help. Even more long long long long long long long long long long long long help - message.); default: ""; + message.); default: "";)" + + "\n --usage_reporting_test_flag_07 (usage_reporting_test_flag_07 " + "help\n\n \f\v\a\b ); default: \"\r\n\f\v\a\b\t \";\n" + R"( Try --helpfull to get a list of all flags or --help=substring shows help for flags which include specified substring in either in the name, or description or path. @@ -256,7 +261,8 @@ path. TEST_F(UsageReportingTest, TestNoUsageFlags) { std::stringstream test_buf; - EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), -1); + EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), + flags::HelpMode::kNone); } // -------------------------------------------------------------------- @@ -265,9 +271,11 @@ TEST_F(UsageReportingTest, TestUsageFlag_helpshort) { flags::SetFlagsHelpMode(flags::HelpMode::kShort); std::stringstream test_buf; - EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), 1); - EXPECT_EQ(test_buf.str(), - R"(usage_test: Custom usage message + EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), + flags::HelpMode::kShort); + EXPECT_EQ( + test_buf.str(), + R"(usage_test: Custom usage message Flags from absl/flags/internal/usage_test.cc: --usage_reporting_test_flag_01 (usage_reporting_test_flag_01 help message); @@ -284,8 +292,12 @@ TEST_F(UsageReportingTest, TestUsageFlag_helpshort) { Some more help. Even more long long long long long long long long long long long long help - message.); default: ""; + message.); default: "";)" + + "\n --usage_reporting_test_flag_07 (usage_reporting_test_flag_07 " + "help\n\n \f\v\a\b ); default: \"\r\n\f\v\a\b\t \";\n" + R"( Try --helpfull to get a list of all flags or --help=substring shows help for flags which include specified substring in either in the name, or description or path. @@ -298,9 +310,11 @@ TEST_F(UsageReportingTest, TestUsageFlag_help_simple) { flags::SetFlagsHelpMode(flags::HelpMode::kImportant); std::stringstream test_buf; - EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), 1); - EXPECT_EQ(test_buf.str(), - R"(usage_test: Custom usage message + EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), + flags::HelpMode::kImportant); + EXPECT_EQ( + test_buf.str(), + R"(usage_test: Custom usage message Flags from absl/flags/internal/usage_test.cc: --usage_reporting_test_flag_01 (usage_reporting_test_flag_01 help message); @@ -317,8 +331,12 @@ TEST_F(UsageReportingTest, TestUsageFlag_help_simple) { Some more help. Even more long long long long long long long long long long long long help - message.); default: ""; + message.); default: "";)" + + "\n --usage_reporting_test_flag_07 (usage_reporting_test_flag_07 " + "help\n\n \f\v\a\b ); default: \"\r\n\f\v\a\b\t \";\n" + R"( Try --helpfull to get a list of all flags or --help=substring shows help for flags which include specified substring in either in the name, or description or path. @@ -332,7 +350,8 @@ TEST_F(UsageReportingTest, TestUsageFlag_help_one_flag) { flags::SetFlagsHelpMatchSubstr("usage_reporting_test_flag_06"); std::stringstream test_buf; - EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), 1); + EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), + flags::HelpMode::kMatch); EXPECT_EQ(test_buf.str(), R"(usage_test: Custom usage message @@ -356,9 +375,11 @@ TEST_F(UsageReportingTest, TestUsageFlag_help_multiple_flag) { flags::SetFlagsHelpMatchSubstr("test_flag"); std::stringstream test_buf; - EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), 1); - EXPECT_EQ(test_buf.str(), - R"(usage_test: Custom usage message + EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), + flags::HelpMode::kMatch); + EXPECT_EQ( + test_buf.str(), + R"(usage_test: Custom usage message Flags from absl/flags/internal/usage_test.cc: --usage_reporting_test_flag_01 (usage_reporting_test_flag_01 help message); @@ -375,8 +396,12 @@ TEST_F(UsageReportingTest, TestUsageFlag_help_multiple_flag) { Some more help. Even more long long long long long long long long long long long long help - message.); default: ""; + message.); default: "";)" + + "\n --usage_reporting_test_flag_07 (usage_reporting_test_flag_07 " + "help\n\n \f\v\a\b ); default: \"\r\n\f\v\a\b\t \";\n" + R"( Try --helpfull to get a list of all flags or --help=substring shows help for flags which include specified substring in either in the name, or description or path. @@ -389,9 +414,11 @@ TEST_F(UsageReportingTest, TestUsageFlag_helppackage) { flags::SetFlagsHelpMode(flags::HelpMode::kPackage); std::stringstream test_buf; - EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), 1); - EXPECT_EQ(test_buf.str(), - R"(usage_test: Custom usage message + EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), + flags::HelpMode::kPackage); + EXPECT_EQ( + test_buf.str(), + R"(usage_test: Custom usage message Flags from absl/flags/internal/usage_test.cc: --usage_reporting_test_flag_01 (usage_reporting_test_flag_01 help message); @@ -408,8 +435,12 @@ TEST_F(UsageReportingTest, TestUsageFlag_helppackage) { Some more help. Even more long long long long long long long long long long long long help - message.); default: ""; + message.); default: "";)" + "\n --usage_reporting_test_flag_07 (usage_reporting_test_flag_07 " + "help\n\n \f\v\a\b ); default: \"\r\n\f\v\a\b\t \";\n" + + R"( Try --helpfull to get a list of all flags or --help=substring shows help for flags which include specified substring in either in the name, or description or path. @@ -422,7 +453,8 @@ TEST_F(UsageReportingTest, TestUsageFlag_version) { flags::SetFlagsHelpMode(flags::HelpMode::kVersion); std::stringstream test_buf; - EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), 0); + EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), + flags::HelpMode::kVersion); #ifndef NDEBUG EXPECT_EQ(test_buf.str(), "usage_test\nDebug build (NDEBUG not #defined)\n"); #else @@ -436,7 +468,8 @@ TEST_F(UsageReportingTest, TestUsageFlag_only_check_args) { flags::SetFlagsHelpMode(flags::HelpMode::kOnlyCheckArgs); std::stringstream test_buf; - EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), 0); + EXPECT_EQ(flags::HandleUsageFlags(test_buf, kTestUsageMessage), + flags::HelpMode::kOnlyCheckArgs); EXPECT_EQ(test_buf.str(), ""); } @@ -447,7 +480,8 @@ TEST_F(UsageReportingTest, TestUsageFlag_helpon) { flags::SetFlagsHelpMatchSubstr("/bla-bla."); std::stringstream test_buf_01; - EXPECT_EQ(flags::HandleUsageFlags(test_buf_01, kTestUsageMessage), 1); + EXPECT_EQ(flags::HandleUsageFlags(test_buf_01, kTestUsageMessage), + flags::HelpMode::kMatch); EXPECT_EQ(test_buf_01.str(), R"(usage_test: Custom usage message @@ -461,9 +495,11 @@ path. flags::SetFlagsHelpMatchSubstr("/usage_test."); std::stringstream test_buf_02; - EXPECT_EQ(flags::HandleUsageFlags(test_buf_02, kTestUsageMessage), 1); - EXPECT_EQ(test_buf_02.str(), - R"(usage_test: Custom usage message + EXPECT_EQ(flags::HandleUsageFlags(test_buf_02, kTestUsageMessage), + flags::HelpMode::kMatch); + EXPECT_EQ( + test_buf_02.str(), + R"(usage_test: Custom usage message Flags from absl/flags/internal/usage_test.cc: --usage_reporting_test_flag_01 (usage_reporting_test_flag_01 help message); @@ -480,8 +516,12 @@ path. Some more help. Even more long long long long long long long long long long long long help - message.); default: ""; + message.); default: "";)" + + "\n --usage_reporting_test_flag_07 (usage_reporting_test_flag_07 " + "help\n\n \f\v\a\b ); default: \"\r\n\f\v\a\b\t \";\n" + R"( Try --helpfull to get a list of all flags or --help=substring shows help for flags which include specified substring in either in the name, or description or path. diff --git a/absl/flags/marshalling.cc b/absl/flags/marshalling.cc index 81f9cebd..ca4a1305 100644 --- a/absl/flags/marshalling.cc +++ b/absl/flags/marshalling.cc @@ -19,6 +19,7 @@ #include <cmath> #include <limits> +#include <sstream> #include <string> #include <type_traits> #include <vector> @@ -26,6 +27,7 @@ #include "absl/base/config.h" #include "absl/base/log_severity.h" #include "absl/base/macros.h" +#include "absl/numeric/int128.h" #include "absl/strings/ascii.h" #include "absl/strings/match.h" #include "absl/strings/numbers.h" @@ -68,8 +70,10 @@ bool AbslParseFlag(absl::string_view text, bool* dst, std::string*) { // puts us in base 16. But leading 0 does not put us in base 8. It // caused too many bugs when we had that behavior. static int NumericBase(absl::string_view text) { - const bool hex = (text.size() >= 2 && text[0] == '0' && - (text[1] == 'x' || text[1] == 'X')); + if (text.empty()) return 0; + size_t num_start = (text[0] == '-' || text[0] == '+') ? 1 : 0; + const bool hex = (text.size() >= num_start + 2 && text[num_start] == '0' && + (text[num_start + 1] == 'x' || text[num_start + 1] == 'X')); return hex ? 16 : 10; } @@ -125,6 +129,32 @@ bool AbslParseFlag(absl::string_view text, unsigned long long* dst, return ParseFlagImpl(text, *dst); } +bool AbslParseFlag(absl::string_view text, absl::int128* dst, std::string*) { + text = absl::StripAsciiWhitespace(text); + + // check hex + int base = NumericBase(text); + if (!absl::numbers_internal::safe_strto128_base(text, dst, base)) { + return false; + } + + return base == 16 ? absl::SimpleHexAtoi(text, dst) + : absl::SimpleAtoi(text, dst); +} + +bool AbslParseFlag(absl::string_view text, absl::uint128* dst, std::string*) { + text = absl::StripAsciiWhitespace(text); + + // check hex + int base = NumericBase(text); + if (!absl::numbers_internal::safe_strtou128_base(text, dst, base)) { + return false; + } + + return base == 16 ? absl::SimpleHexAtoi(text, dst) + : absl::SimpleAtoi(text, dst); +} + // -------------------------------------------------------------------- // AbslParseFlag for floating point types. @@ -171,6 +201,17 @@ std::string Unparse(long v) { return absl::StrCat(v); } std::string Unparse(unsigned long v) { return absl::StrCat(v); } std::string Unparse(long long v) { return absl::StrCat(v); } std::string Unparse(unsigned long long v) { return absl::StrCat(v); } +std::string Unparse(absl::int128 v) { + std::stringstream ss; + ss << v; + return ss.str(); +} +std::string Unparse(absl::uint128 v) { + std::stringstream ss; + ss << v; + return ss.str(); +} + template <typename T> std::string UnparseFloatingPointVal(T v) { // digits10 is guaranteed to roundtrip correctly in string -> value -> string @@ -206,6 +247,14 @@ bool AbslParseFlag(absl::string_view text, absl::LogSeverity* dst, *err = "no value provided"; return false; } + if (absl::EqualsIgnoreCase(text, "dfatal")) { + *dst = absl::kLogDebugFatal; + return true; + } + if (absl::EqualsIgnoreCase(text, "klogdebugfatal")) { + *dst = absl::kLogDebugFatal; + return true; + } if (text.front() == 'k' || text.front() == 'K') text.remove_prefix(1); if (absl::EqualsIgnoreCase(text, "info")) { *dst = absl::LogSeverity::kInfo; @@ -228,7 +277,8 @@ bool AbslParseFlag(absl::string_view text, absl::LogSeverity* dst, *dst = static_cast<absl::LogSeverity>(numeric_value); return true; } - *err = "only integers and absl::LogSeverity enumerators are accepted"; + *err = + "only integers, absl::LogSeverity enumerators, and DFATAL are accepted"; return false; } diff --git a/absl/flags/marshalling.h b/absl/flags/marshalling.h index 325e75e5..301213a9 100644 --- a/absl/flags/marshalling.h +++ b/absl/flags/marshalling.h @@ -200,6 +200,7 @@ #define ABSL_FLAGS_MARSHALLING_H_ #include "absl/base/config.h" +#include "absl/numeric/int128.h" #if defined(ABSL_HAVE_STD_OPTIONAL) && !defined(ABSL_USES_STD_OPTIONAL) #include <optional> @@ -233,6 +234,8 @@ bool AbslParseFlag(absl::string_view, unsigned long*, std::string*); // NOLINT bool AbslParseFlag(absl::string_view, long long*, std::string*); // NOLINT bool AbslParseFlag(absl::string_view, unsigned long long*, // NOLINT std::string*); +bool AbslParseFlag(absl::string_view, absl::int128*, std::string*); // NOLINT +bool AbslParseFlag(absl::string_view, absl::uint128*, std::string*); // NOLINT bool AbslParseFlag(absl::string_view, float*, std::string*); bool AbslParseFlag(absl::string_view, double*, std::string*); bool AbslParseFlag(absl::string_view, std::string*, std::string*); @@ -310,6 +313,8 @@ std::string Unparse(long v); // NOLINT std::string Unparse(unsigned long v); // NOLINT std::string Unparse(long long v); // NOLINT std::string Unparse(unsigned long long v); // NOLINT +std::string Unparse(absl::int128 v); +std::string Unparse(absl::uint128 v); std::string Unparse(float v); std::string Unparse(double v); diff --git a/absl/flags/marshalling_test.cc b/absl/flags/marshalling_test.cc index 7b6d2ad5..b0e055f5 100644 --- a/absl/flags/marshalling_test.cc +++ b/absl/flags/marshalling_test.cc @@ -137,11 +137,10 @@ TEST(MarshallingTest, TestInt16Parsing) { EXPECT_EQ(value, 16); EXPECT_TRUE(absl::ParseFlag("0X234", &value, &err)); EXPECT_EQ(value, 564); - // TODO(rogeeff): fix below validations - EXPECT_FALSE(absl::ParseFlag("-0x7FFD", &value, &err)); - EXPECT_NE(value, -3); - EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err)); - EXPECT_NE(value, 49); + EXPECT_TRUE(absl::ParseFlag("-0x7FFD", &value, &err)); + EXPECT_EQ(value, -32765); + EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err)); + EXPECT_EQ(value, 49); // Whitespace handling EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err)); @@ -194,9 +193,8 @@ TEST(MarshallingTest, TestUint16Parsing) { EXPECT_EQ(value, 16); EXPECT_TRUE(absl::ParseFlag("0X234", &value, &err)); EXPECT_EQ(value, 564); - // TODO(rogeeff): fix below validations - EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err)); - EXPECT_NE(value, 49); + EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err)); + EXPECT_EQ(value, 49); // Whitespace handling EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err)); @@ -254,11 +252,11 @@ TEST(MarshallingTest, TestInt32Parsing) { EXPECT_EQ(value, 16); EXPECT_TRUE(absl::ParseFlag("0X234", &value, &err)); EXPECT_EQ(value, 564); - // TODO(rogeeff): fix below validations - EXPECT_FALSE(absl::ParseFlag("-0x7FFFFFFD", &value, &err)); - EXPECT_NE(value, -3); - EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err)); - EXPECT_NE(value, 49); + + EXPECT_TRUE(absl::ParseFlag("-0x7FFFFFFD", &value, &err)); + EXPECT_EQ(value, -2147483645); + EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err)); + EXPECT_EQ(value, 49); // Whitespace handling EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err)); @@ -311,9 +309,8 @@ TEST(MarshallingTest, TestUint32Parsing) { EXPECT_EQ(value, 564); EXPECT_TRUE(absl::ParseFlag("0xFFFFFFFD", &value, &err)); EXPECT_EQ(value, 4294967293); - // TODO(rogeeff): fix below validations - EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err)); - EXPECT_NE(value, 49); + EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err)); + EXPECT_EQ(value, 49); // Whitespace handling EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err)); @@ -371,11 +368,12 @@ TEST(MarshallingTest, TestInt64Parsing) { EXPECT_EQ(value, 16); EXPECT_TRUE(absl::ParseFlag("0XFFFAAABBBCCCDDD", &value, &err)); EXPECT_EQ(value, 1152827684197027293); - // TODO(rogeeff): fix below validation - EXPECT_FALSE(absl::ParseFlag("-0x7FFFFFFFFFFFFFFE", &value, &err)); - EXPECT_NE(value, -2); - EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err)); - EXPECT_NE(value, 49); + EXPECT_TRUE(absl::ParseFlag("-0x7FFFFFFFFFFFFFFE", &value, &err)); + EXPECT_EQ(value, -9223372036854775806); + EXPECT_TRUE(absl::ParseFlag("-0x02", &value, &err)); + EXPECT_EQ(value, -2); + EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err)); + EXPECT_EQ(value, 49); // Whitespace handling EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err)); @@ -428,9 +426,8 @@ TEST(MarshallingTest, TestUInt64Parsing) { EXPECT_EQ(value, 16); EXPECT_TRUE(absl::ParseFlag("0XFFFF", &value, &err)); EXPECT_EQ(value, 65535); - // TODO(rogeeff): fix below validation - EXPECT_FALSE(absl::ParseFlag("+0x31", &value, &err)); - EXPECT_NE(value, 49); + EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err)); + EXPECT_EQ(value, 49); // Whitespace handling EXPECT_TRUE(absl::ParseFlag("10 ", &value, &err)); @@ -455,6 +452,125 @@ TEST(MarshallingTest, TestUInt64Parsing) { // -------------------------------------------------------------------- +TEST(MarshallingTest, TestInt128Parsing) { + std::string err; + absl::int128 value; + + // Decimal values. + EXPECT_TRUE(absl::ParseFlag("0", &value, &err)); + EXPECT_EQ(value, 0); + EXPECT_TRUE(absl::ParseFlag("1", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("-1", &value, &err)); + EXPECT_EQ(value, -1); + EXPECT_TRUE(absl::ParseFlag("123", &value, &err)); + EXPECT_EQ(value, 123); + EXPECT_TRUE(absl::ParseFlag("-98765", &value, &err)); + EXPECT_EQ(value, -98765); + EXPECT_TRUE(absl::ParseFlag("+3", &value, &err)); + EXPECT_EQ(value, 3); + + // Leading zero values. + EXPECT_TRUE(absl::ParseFlag("01", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("001", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("0000100", &value, &err)); + EXPECT_EQ(value, 100); + + // Hex values. + EXPECT_TRUE(absl::ParseFlag("0x10", &value, &err)); + EXPECT_EQ(value, 16); + EXPECT_TRUE(absl::ParseFlag("0xFFFAAABBBCCCDDD", &value, &err)); + EXPECT_EQ(value, 1152827684197027293); + EXPECT_TRUE(absl::ParseFlag("0xFFF0FFFFFFFFFFFFFFF", &value, &err)); + EXPECT_EQ(value, absl::MakeInt128(0x000000000000fff, 0xFFFFFFFFFFFFFFF)); + + EXPECT_TRUE(absl::ParseFlag("-0x10000000000000000", &value, &err)); + EXPECT_EQ(value, absl::MakeInt128(-1, 0)); + EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err)); + EXPECT_EQ(value, 49); + + // Whitespace handling + EXPECT_TRUE(absl::ParseFlag("16 ", &value, &err)); + EXPECT_EQ(value, 16); + EXPECT_TRUE(absl::ParseFlag(" 16", &value, &err)); + EXPECT_EQ(value, 16); + EXPECT_TRUE(absl::ParseFlag(" 0100 ", &value, &err)); + EXPECT_EQ(value, 100); + EXPECT_TRUE(absl::ParseFlag(" 0x7B ", &value, &err)); + EXPECT_EQ(value, 123); + + // Invalid values. + EXPECT_FALSE(absl::ParseFlag("", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("--1", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\n", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\t", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("2U", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("FFF", &value, &err)); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestUint128Parsing) { + std::string err; + absl::uint128 value; + + // Decimal values. + EXPECT_TRUE(absl::ParseFlag("0", &value, &err)); + EXPECT_EQ(value, 0); + EXPECT_TRUE(absl::ParseFlag("1", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("123", &value, &err)); + EXPECT_EQ(value, 123); + EXPECT_TRUE(absl::ParseFlag("+3", &value, &err)); + EXPECT_EQ(value, 3); + + // Leading zero values. + EXPECT_TRUE(absl::ParseFlag("01", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("001", &value, &err)); + EXPECT_EQ(value, 1); + EXPECT_TRUE(absl::ParseFlag("0000100", &value, &err)); + EXPECT_EQ(value, 100); + + // Hex values. + EXPECT_TRUE(absl::ParseFlag("0x10", &value, &err)); + EXPECT_EQ(value, 16); + EXPECT_TRUE(absl::ParseFlag("0xFFFAAABBBCCCDDD", &value, &err)); + EXPECT_EQ(value, 1152827684197027293); + EXPECT_TRUE(absl::ParseFlag("0xFFF0FFFFFFFFFFFFFFF", &value, &err)); + EXPECT_EQ(value, absl::MakeInt128(0x000000000000fff, 0xFFFFFFFFFFFFFFF)); + EXPECT_TRUE(absl::ParseFlag("+0x31", &value, &err)); + EXPECT_EQ(value, 49); + + // Whitespace handling + EXPECT_TRUE(absl::ParseFlag("16 ", &value, &err)); + EXPECT_EQ(value, 16); + EXPECT_TRUE(absl::ParseFlag(" 16", &value, &err)); + EXPECT_EQ(value, 16); + EXPECT_TRUE(absl::ParseFlag(" 0100 ", &value, &err)); + EXPECT_EQ(value, 100); + EXPECT_TRUE(absl::ParseFlag(" 0x7B ", &value, &err)); + EXPECT_EQ(value, 123); + + // Invalid values. + EXPECT_FALSE(absl::ParseFlag("", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag(" ", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("-1", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("--1", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\n", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("\t", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("2U", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("FFF", &value, &err)); + EXPECT_FALSE(absl::ParseFlag("-0x10000000000000000", &value, &err)); +} + +// -------------------------------------------------------------------- + TEST(MarshallingTest, TestFloatParsing) { std::string err; float value; @@ -844,6 +960,40 @@ TEST(MarshallingTest, TestUint64Unparsing) { // -------------------------------------------------------------------- +TEST(MarshallingTest, TestInt128Unparsing) { + absl::int128 value; + + value = 1; + EXPECT_EQ(absl::UnparseFlag(value), "1"); + value = 0; + EXPECT_EQ(absl::UnparseFlag(value), "0"); + value = -1; + EXPECT_EQ(absl::UnparseFlag(value), "-1"); + value = 123456789L; + EXPECT_EQ(absl::UnparseFlag(value), "123456789"); + value = -987654321L; + EXPECT_EQ(absl::UnparseFlag(value), "-987654321"); + value = 0x7FFFFFFFFFFFFFFF; + EXPECT_EQ(absl::UnparseFlag(value), "9223372036854775807"); +} + +// -------------------------------------------------------------------- + +TEST(MarshallingTest, TestUint128Unparsing) { + absl::uint128 value; + + value = 1; + EXPECT_EQ(absl::UnparseFlag(value), "1"); + value = 0; + EXPECT_EQ(absl::UnparseFlag(value), "0"); + value = 123456789L; + EXPECT_EQ(absl::UnparseFlag(value), "123456789"); + value = absl::MakeUint128(0, 0xFFFFFFFFFFFFFFFF); + EXPECT_EQ(absl::UnparseFlag(value), "18446744073709551615"); +} + +// -------------------------------------------------------------------- + TEST(MarshallingTest, TestFloatUnparsing) { float value; diff --git a/absl/flags/parse.cc b/absl/flags/parse.cc index fa953f55..526b61d1 100644 --- a/absl/flags/parse.cc +++ b/absl/flags/parse.cc @@ -19,9 +19,10 @@ #include <algorithm> #include <cstdint> +#include <cstdlib> #include <fstream> #include <iostream> -#include <iterator> +#include <ostream> #include <string> #include <tuple> #include <utility> @@ -98,6 +99,8 @@ struct SpecifiedFlagsCompare { ABSL_NAMESPACE_END } // namespace absl +// These flags influence how command line flags are parsed and are only intended +// to be set on the command line. Avoid reading or setting them from C++ code. ABSL_FLAG(std::vector<std::string>, flagfile, {}, "comma-separated list of files to load flags from") .OnUpdate([]() { @@ -147,6 +150,8 @@ ABSL_FLAG(std::vector<std::string>, tryfromenv, {}, absl::flags_internal::tryfromenv_needs_processing = true; }); +// Rather than reading or setting --undefok from C++ code, please consider using +// ABSL_RETIRED_FLAG instead. ABSL_FLAG(std::vector<std::string>, undefok, {}, "comma-separated list of flag names that it is okay to specify " "on the command line even if the program does not define a flag " @@ -190,7 +195,7 @@ bool ArgsList::ReadFromFlagfile(const std::string& flag_file_name) { // This argument represents fake argv[0], which should be present in all arg // lists. - args_.push_back(""); + args_.emplace_back(""); std::string line; bool success = true; @@ -212,7 +217,7 @@ bool ArgsList::ReadFromFlagfile(const std::string& flag_file_name) { break; } - args_.push_back(std::string(stripped)); + args_.emplace_back(stripped); continue; } @@ -278,7 +283,7 @@ std::tuple<absl::string_view, absl::string_view, bool> SplitNameAndValue( return std::make_tuple("", "", false); } - auto equal_sign_pos = arg.find("="); + auto equal_sign_pos = arg.find('='); absl::string_view flag_name = arg.substr(0, equal_sign_pos); @@ -367,7 +372,7 @@ bool ReadFlagsFromEnv(const std::vector<std::string>& flag_names, // This argument represents fake argv[0], which should be present in all arg // lists. - args.push_back(""); + args.emplace_back(""); for (const auto& flag_name : flag_names) { // Avoid infinite recursion. @@ -416,7 +421,7 @@ bool HandleGeneratorFlags(std::vector<ArgsList>& input_args, // programmatically before invoking ParseCommandLine. Note that we do not // actually process arguments specified in the flagfile, but instead // create a secondary arguments list to be processed along with the rest - // of the comamnd line arguments. Since we always the process most recently + // of the command line arguments. Since we always the process most recently // created list of arguments first, this will result in flagfile argument // being processed before any other argument in the command line. If // FLAGS_flagfile contains more than one file name we create multiple new @@ -599,12 +604,40 @@ bool CanIgnoreUndefinedFlag(absl::string_view flag_name) { return false; } +// -------------------------------------------------------------------- + +void ReportUnrecognizedFlags( + const std::vector<UnrecognizedFlag>& unrecognized_flags, + bool report_as_fatal_error) { + for (const auto& unrecognized : unrecognized_flags) { + // Verify if flag_name has the "no" already removed + std::vector<std::string> misspelling_hints; + if (unrecognized.source == UnrecognizedFlag::kFromArgv) { + misspelling_hints = + flags_internal::GetMisspellingHints(unrecognized.flag_name); + } + + if (misspelling_hints.empty()) { + flags_internal::ReportUsageError( + absl::StrCat("Unknown command line flag '", unrecognized.flag_name, + "'"), + report_as_fatal_error); + } else { + flags_internal::ReportUsageError( + absl::StrCat("Unknown command line flag '", unrecognized.flag_name, + "'. Did you mean: ", + absl::StrJoin(misspelling_hints, ", "), " ?"), + report_as_fatal_error); + } + } +} + } // namespace // -------------------------------------------------------------------- bool WasPresentOnCommandLine(absl::string_view flag_name) { - absl::MutexLock l(&specified_flags_guard); + absl::ReaderMutexLock l(&specified_flags_guard); ABSL_INTERNAL_CHECK(specified_flags != nullptr, "ParseCommandLine is not invoked yet"); @@ -638,7 +671,7 @@ std::vector<std::string> GetMisspellingHints(const absl::string_view flag) { const size_t maxCutoff = std::min(flag.size() / 2 + 1, kMaxDistance); auto undefok = absl::GetFlag(FLAGS_undefok); BestHints best_hints(static_cast<uint8_t>(maxCutoff)); - absl::flags_internal::ForEachFlag([&](const CommandLineFlag& f) { + flags_internal::ForEachFlag([&](const CommandLineFlag& f) { if (best_hints.hints.size() >= kMaxHints) return; uint8_t distance = strings_internal::CappedDamerauLevenshteinDistance( flag, f.Name(), best_hints.best_distance); @@ -664,59 +697,94 @@ std::vector<std::string> GetMisspellingHints(const absl::string_view flag) { // -------------------------------------------------------------------- std::vector<char*> ParseCommandLineImpl(int argc, char* argv[], - ArgvListAction arg_list_act, - UsageFlagsAction usage_flag_act, - OnUndefinedFlag on_undef_flag) { - ABSL_INTERNAL_CHECK(argc > 0, "Missing argv[0]"); + UsageFlagsAction usage_flag_action, + OnUndefinedFlag undef_flag_action, + std::ostream& error_help_output) { + std::vector<char*> positional_args; + std::vector<UnrecognizedFlag> unrecognized_flags; - // Once parsing has started we will not have more flag registrations. - // If we did, they would be missing during parsing, which is a problem on - // itself. - flags_internal::FinalizeRegistry(); + auto help_mode = flags_internal::ParseAbseilFlagsOnlyImpl( + argc, argv, positional_args, unrecognized_flags, usage_flag_action); - // This routine does not return anything since we abort on failure. - CheckDefaultValuesParsingRoundtrip(); + if (undef_flag_action != OnUndefinedFlag::kIgnoreUndefined) { + flags_internal::ReportUnrecognizedFlags( + unrecognized_flags, + (undef_flag_action == OnUndefinedFlag::kAbortIfUndefined)); - std::vector<std::string> flagfile_value; + if (undef_flag_action == OnUndefinedFlag::kAbortIfUndefined) { + if (!unrecognized_flags.empty()) { + flags_internal::HandleUsageFlags(error_help_output, + ProgramUsageMessage()); std::exit(1); + } + } + } + flags_internal::MaybeExit(help_mode); + + return positional_args; +} + +// -------------------------------------------------------------------- + +// This function handles all Abseil Flags and built-in usage flags and, if any +// help mode was handled, it returns that help mode. The caller of this function +// can decide to exit based on the returned help mode. +// The caller may decide to handle unrecognized positional arguments and +// unrecognized flags first before exiting. +// +// Returns: +// * HelpMode::kFull if parsing errors were detected in recognized arguments +// * The HelpMode that was handled in case when `usage_flag_action` is +// UsageFlagsAction::kHandleUsage and a usage flag was specified on the +// commandline +// * Otherwise it returns HelpMode::kNone +HelpMode ParseAbseilFlagsOnlyImpl( + int argc, char* argv[], std::vector<char*>& positional_args, + std::vector<UnrecognizedFlag>& unrecognized_flags, + UsageFlagsAction usage_flag_action) { + ABSL_INTERNAL_CHECK(argc > 0, "Missing argv[0]"); + + using flags_internal::ArgsList; + using flags_internal::specified_flags; + + std::vector<std::string> flagfile_value; std::vector<ArgsList> input_args; - input_args.push_back(ArgsList(argc, argv)); - std::vector<char*> output_args; - std::vector<char*> positional_args; - output_args.reserve(static_cast<size_t>(argc)); + // Once parsing has started we will not allow more flag registrations. + flags_internal::FinalizeRegistry(); - // This is the list of undefined flags. The element of the list is the pair - // consisting of boolean indicating if flag came from command line (vs from - // some flag file we've read) and flag name. - // TODO(rogeeff): Eliminate the first element in the pair after cleanup. - std::vector<std::pair<bool, std::string>> undefined_flag_names; + // This routine does not return anything since we abort on failure. + flags_internal::CheckDefaultValuesParsingRoundtrip(); + + input_args.push_back(ArgsList(argc, argv)); // Set program invocation name if it is not set before. - if (ProgramInvocationName() == "UNKNOWN") { + if (flags_internal::ProgramInvocationName() == "UNKNOWN") { flags_internal::SetProgramInvocationName(argv[0]); } - output_args.push_back(argv[0]); + positional_args.push_back(argv[0]); - absl::MutexLock l(&specified_flags_guard); + absl::MutexLock l(&flags_internal::specified_flags_guard); if (specified_flags == nullptr) { specified_flags = new std::vector<const CommandLineFlag*>; } else { specified_flags->clear(); } - // Iterate through the list of the input arguments. First level are arguments - // originated from argc/argv. Following levels are arguments originated from - // recursive parsing of flagfile(s). + // Iterate through the list of the input arguments. First level are + // arguments originated from argc/argv. Following levels are arguments + // originated from recursive parsing of flagfile(s). bool success = true; while (!input_args.empty()) { - // 10. First we process the built-in generator flags. - success &= HandleGeneratorFlags(input_args, flagfile_value); + // First we process the built-in generator flags. + success &= flags_internal::HandleGeneratorFlags(input_args, flagfile_value); - // 30. Select top-most (most recent) arguments list. If it is empty drop it + // Select top-most (most recent) arguments list. If it is empty drop it // and re-try. ArgsList& curr_list = input_args.back(); + // Every ArgsList starts with real or fake program name, so we can always + // start by skipping it. curr_list.PopFront(); if (curr_list.Size() == 0) { @@ -724,13 +792,13 @@ std::vector<char*> ParseCommandLineImpl(int argc, char* argv[], continue; } - // 40. Pick up the front remaining argument in the current list. If current - // stack of argument lists contains only one element - we are processing an - // argument from the original argv. + // Handle the next argument in the current list. If the stack of argument + // lists contains only one element - we are processing an argument from + // the original argv. absl::string_view arg(curr_list.Front()); bool arg_from_argv = input_args.size() == 1; - // 50. If argument does not start with - or is just "-" - this is + // If argument does not start with '-' or is just "-" - this is // positional argument. if (!absl::ConsumePrefix(&arg, "-") || arg.empty()) { ABSL_INTERNAL_CHECK(arg_from_argv, @@ -740,12 +808,8 @@ std::vector<char*> ParseCommandLineImpl(int argc, char* argv[], continue; } - if (arg_from_argv && (arg_list_act == ArgvListAction::kKeepParsedArgs)) { - output_args.push_back(argv[curr_list.FrontIndex()]); - } - - // 60. Split the current argument on '=' to figure out the argument - // name and value. If flag name is empty it means we've got "--". value + // Split the current argument on '=' to deduce the argument flag name and + // value. If flag name is empty it means we've got an "--" argument. Value // can be empty either if there were no '=' in argument string at all or // an argument looked like "--foo=". In a latter case is_empty_value is // true. @@ -753,10 +817,11 @@ std::vector<char*> ParseCommandLineImpl(int argc, char* argv[], absl::string_view value; bool is_empty_value = false; - std::tie(flag_name, value, is_empty_value) = SplitNameAndValue(arg); + std::tie(flag_name, value, is_empty_value) = + flags_internal::SplitNameAndValue(arg); - // 70. "--" alone means what it does for GNU: stop flags parsing. We do - // not support positional arguments in flagfiles, so we just drop them. + // Standalone "--" argument indicates that the rest of the arguments are + // positional. We do not support positional arguments in flagfiles. if (flag_name.empty()) { ABSL_INTERNAL_CHECK(arg_from_argv, "Flagfile cannot contain positional argument"); @@ -765,43 +830,36 @@ std::vector<char*> ParseCommandLineImpl(int argc, char* argv[], break; } - // 80. Locate the flag based on flag name. Handle both --foo and --nofoo + // Locate the flag based on flag name. Handle both --foo and --nofoo. CommandLineFlag* flag = nullptr; bool is_negative = false; - std::tie(flag, is_negative) = LocateFlag(flag_name); + std::tie(flag, is_negative) = flags_internal::LocateFlag(flag_name); if (flag == nullptr) { // Usage flags are not modeled as Abseil flags. Locate them separately. if (flags_internal::DeduceUsageFlags(flag_name, value)) { continue; } - - if (on_undef_flag != OnUndefinedFlag::kIgnoreUndefined) { - undefined_flag_names.emplace_back(arg_from_argv, - std::string(flag_name)); - } + unrecognized_flags.emplace_back(arg_from_argv + ? UnrecognizedFlag::kFromArgv + : UnrecognizedFlag::kFromFlagfile, + flag_name); continue; } - // 90. Deduce flag's value (from this or next argument) - auto curr_index = curr_list.FrontIndex(); + // Deduce flag's value (from this or next argument). bool value_success = true; - std::tie(value_success, value) = - DeduceFlagValue(*flag, value, is_negative, is_empty_value, &curr_list); + std::tie(value_success, value) = flags_internal::DeduceFlagValue( + *flag, value, is_negative, is_empty_value, &curr_list); success &= value_success; - // If above call consumed an argument, it was a standalone value - if (arg_from_argv && (arg_list_act == ArgvListAction::kKeepParsedArgs) && - (curr_index != curr_list.FrontIndex())) { - output_args.push_back(argv[curr_list.FrontIndex()]); - } - - // 100. Set the located flag to a new new value, unless it is retired. - // Setting retired flag fails, but we ignoring it here while also reporting - // access to retired flag. + // Set the located flag to a new value, unless it is retired. Setting + // retired flag fails, but we ignoring it here while also reporting access + // to retired flag. std::string error; if (!flags_internal::PrivateHandleAccessor::ParseFrom( - *flag, value, SET_FLAGS_VALUE, kCommandLine, error)) { + *flag, value, flags_internal::SET_FLAGS_VALUE, + flags_internal::kCommandLine, error)) { if (flag->IsRetired()) continue; flags_internal::ReportUsageError(error, true); @@ -811,78 +869,73 @@ std::vector<char*> ParseCommandLineImpl(int argc, char* argv[], } } - for (const auto& flag_name : undefined_flag_names) { - if (CanIgnoreUndefinedFlag(flag_name.second)) continue; - // Verify if flag_name has the "no" already removed - std::vector<std::string> flags; - if (flag_name.first) flags = GetMisspellingHints(flag_name.second); - if (flags.empty()) { - flags_internal::ReportUsageError( - absl::StrCat("Unknown command line flag '", flag_name.second, "'"), - true); - } else { - flags_internal::ReportUsageError( - absl::StrCat("Unknown command line flag '", flag_name.second, - "'. Did you mean: ", absl::StrJoin(flags, ", "), " ?"), - true); + flags_internal::ResetGeneratorFlags(flagfile_value); + + // All the remaining arguments are positional. + if (!input_args.empty()) { + for (size_t arg_index = input_args.back().FrontIndex(); + arg_index < static_cast<size_t>(argc); ++arg_index) { + positional_args.push_back(argv[arg_index]); } + } - success = false; + // Trim and sort the vector. + specified_flags->shrink_to_fit(); + std::sort(specified_flags->begin(), specified_flags->end(), + flags_internal::SpecifiedFlagsCompare{}); + + // Filter out unrecognized flags, which are ok to ignore. + std::vector<UnrecognizedFlag> filtered; + filtered.reserve(unrecognized_flags.size()); + for (const auto& unrecognized : unrecognized_flags) { + if (flags_internal::CanIgnoreUndefinedFlag(unrecognized.flag_name)) + continue; + filtered.push_back(unrecognized); } -#if ABSL_FLAGS_STRIP_NAMES + std::swap(unrecognized_flags, filtered); + if (!success) { +#if ABSL_FLAGS_STRIP_NAMES flags_internal::ReportUsageError( "NOTE: command line flags are disabled in this build", true); - } +#else + flags_internal::HandleUsageFlags(std::cerr, ProgramUsageMessage()); #endif - - if (!success) { - flags_internal::HandleUsageFlags(std::cout, - ProgramUsageMessage()); - std::exit(1); + return HelpMode::kFull; // We just need to make sure the exit with + // code 1. } - if (usage_flag_act == UsageFlagsAction::kHandleUsage) { - int exit_code = flags_internal::HandleUsageFlags( - std::cout, ProgramUsageMessage()); + return usage_flag_action == UsageFlagsAction::kHandleUsage + ? flags_internal::HandleUsageFlags(std::cout, + ProgramUsageMessage()) + : HelpMode::kNone; +} - if (exit_code != -1) { - std::exit(exit_code); - } - } +} // namespace flags_internal - ResetGeneratorFlags(flagfile_value); +void ParseAbseilFlagsOnly(int argc, char* argv[], + std::vector<char*>& positional_args, + std::vector<UnrecognizedFlag>& unrecognized_flags) { + auto help_mode = flags_internal::ParseAbseilFlagsOnlyImpl( + argc, argv, positional_args, unrecognized_flags, + flags_internal::UsageFlagsAction::kHandleUsage); - // Reinstate positional args which were intermixed with flags in the arguments - // list. - for (auto arg : positional_args) { - output_args.push_back(arg); - } + flags_internal::MaybeExit(help_mode); +} - // All the remaining arguments are positional. - if (!input_args.empty()) { - for (size_t arg_index = input_args.back().FrontIndex(); - arg_index < static_cast<size_t>(argc); ++arg_index) { - output_args.push_back(argv[arg_index]); - } - } +// -------------------------------------------------------------------- - // Trim and sort the vector. - specified_flags->shrink_to_fit(); - std::sort(specified_flags->begin(), specified_flags->end(), - SpecifiedFlagsCompare{}); - return output_args; +void ReportUnrecognizedFlags( + const std::vector<UnrecognizedFlag>& unrecognized_flags) { + flags_internal::ReportUnrecognizedFlags(unrecognized_flags, true); } -} // namespace flags_internal - // -------------------------------------------------------------------- std::vector<char*> ParseCommandLine(int argc, char* argv[]) { return flags_internal::ParseCommandLineImpl( - argc, argv, flags_internal::ArgvListAction::kRemoveParsedArgs, - flags_internal::UsageFlagsAction::kHandleUsage, + argc, argv, flags_internal::UsageFlagsAction::kHandleUsage, flags_internal::OnUndefinedFlag::kAbortIfUndefined); } diff --git a/absl/flags/parse.h b/absl/flags/parse.h index 929de2cb..f2a5cb10 100644 --- a/absl/flags/parse.h +++ b/absl/flags/parse.h @@ -23,6 +23,7 @@ #ifndef ABSL_FLAGS_PARSE_H_ #define ABSL_FLAGS_PARSE_H_ +#include <string> #include <vector> #include "absl/base/config.h" @@ -31,27 +32,96 @@ namespace absl { ABSL_NAMESPACE_BEGIN +// This type represent information about an unrecognized flag in the command +// line. +struct UnrecognizedFlag { + enum Source { kFromArgv, kFromFlagfile }; + + explicit UnrecognizedFlag(Source s, absl::string_view f) + : source(s), flag_name(f) {} + // This field indicates where we found this flag: on the original command line + // or read in some flag file. + Source source; + // Name of the flag we did not recognize in --flag_name=value or --flag_name. + std::string flag_name; +}; + +inline bool operator==(const UnrecognizedFlag& lhs, + const UnrecognizedFlag& rhs) { + return lhs.source == rhs.source && lhs.flag_name == rhs.flag_name; +} + +namespace flags_internal { + +HelpMode ParseAbseilFlagsOnlyImpl( + int argc, char* argv[], std::vector<char*>& positional_args, + std::vector<UnrecognizedFlag>& unrecognized_flags, + UsageFlagsAction usage_flag_action); + +} // namespace flags_internal + +// ParseAbseilFlagsOnly() +// +// Parses a list of command-line arguments, passed in the `argc` and `argv[]` +// parameters, into a set of Abseil Flag values, returning any unparsed +// arguments in `positional_args` and `unrecognized_flags` output parameters. +// +// This function classifies all the arguments (including content of the +// flagfiles, if any) into one of the following groups: +// +// * arguments specified as "--flag=value" or "--flag value" that match +// registered or built-in Abseil Flags. These are "Abseil Flag arguments." +// * arguments specified as "--flag" that are unrecognized as Abseil Flags +// * arguments that are not specified as "--flag" are positional arguments +// * arguments that follow the flag-terminating delimiter (`--`) are also +// treated as positional arguments regardless of their syntax. +// +// All of the deduced Abseil Flag arguments are then parsed into their +// corresponding flag values. If any syntax errors are found in these arguments, +// the binary exits with code 1. +// +// This function also handles Abseil Flags built-in usage flags (e.g. --help) +// if any were present on the command line. +// +// All the remaining positional arguments including original program name +// (argv[0]) are are returned in the `positional_args` output parameter. +// +// All unrecognized flags that are not otherwise ignored are returned in the +// `unrecognized_flags` output parameter. Note that the special `undefok` +// flag allows you to specify flags which can be safely ignored; `undefok` +// specifies these flags as a comma-separated list. Any unrecognized flags +// that appear within `undefok` will therefore be ignored and not included in +// the `unrecognized_flag` output parameter. +// +void ParseAbseilFlagsOnly(int argc, char* argv[], + std::vector<char*>& positional_args, + std::vector<UnrecognizedFlag>& unrecognized_flags); + +// ReportUnrecognizedFlags() +// +// Reports an error to `stderr` for all non-ignored unrecognized flags in +// the provided `unrecognized_flags` list. +void ReportUnrecognizedFlags( + const std::vector<UnrecognizedFlag>& unrecognized_flags); + // ParseCommandLine() // -// Parses the set of command-line arguments passed in the `argc` (argument -// count) and `argv[]` (argument vector) parameters from `main()`, assigning -// values to any defined Abseil flags. (Any arguments passed after the -// flag-terminating delimiter (`--`) are treated as positional arguments and -// ignored.) -// -// Any command-line flags (and arguments to those flags) are parsed into Abseil -// Flag values, if those flags are defined. Any undefined flags will either -// return an error, or be ignored if that flag is designated using `undefok` to -// indicate "undefined is OK." -// -// Any command-line positional arguments not part of any command-line flag (or -// arguments to a flag) are returned in a vector, with the program invocation -// name at position 0 of that vector. (Note that this includes positional -// arguments after the flag-terminating delimiter `--`.) -// -// After all flags and flag arguments are parsed, this function looks for any -// built-in usage flags (e.g. `--help`), and if any were specified, it reports -// help messages and then exits the program. +// First parses Abseil Flags only from the command line according to the +// description in `ParseAbseilFlagsOnly`. In addition this function handles +// unrecognized and usage flags. +// +// If any unrecognized flags are located they are reported using +// `ReportUnrecognizedFlags`. +// +// If any errors detected during command line parsing, this routine reports a +// usage message and aborts the program. +// +// If any built-in usage flags were specified on the command line (e.g. +// `--help`), this function reports help messages and then gracefully exits the +// program. +// +// This function returns all the remaining positional arguments collected by +// `ParseAbseilFlagsOnly`. std::vector<char*> ParseCommandLine(int argc, char* argv[]); ABSL_NAMESPACE_END diff --git a/absl/flags/parse_test.cc b/absl/flags/parse_test.cc index 418b0e55..97b78980 100644 --- a/absl/flags/parse_test.cc +++ b/absl/flags/parse_test.cc @@ -17,20 +17,19 @@ #include <stdlib.h> -#include <cstddef> #include <fstream> +#include <iostream> #include <string> #include <vector> #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "absl/base/internal/raw_logging.h" #include "absl/base/internal/scoped_set_env.h" -#include "absl/flags/declare.h" #include "absl/flags/flag.h" #include "absl/flags/internal/parse.h" #include "absl/flags/internal/usage.h" #include "absl/flags/reflection.h" +#include "absl/log/log.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/strings/substitute.h" @@ -151,8 +150,7 @@ const std::string& GetTestTempDir() { } if (res->empty()) { - ABSL_INTERNAL_LOG(FATAL, - "Failed to make temporary directory for data files"); + LOG(FATAL) << "Failed to make temporary directory for data files"; } #ifdef _WIN32 @@ -199,7 +197,7 @@ constexpr const char* const ff2_data[] = { // Builds flagfile flag in the flagfile_flag buffer and returns it. This // function also creates a temporary flagfile based on FlagfileData input. // We create a flagfile in a temporary directory with the name specified in -// FlagfileData and populate it with lines specifed in FlagfileData. If $0 is +// FlagfileData and populate it with lines specified in FlagfileData. If $0 is // referenced in any of the lines in FlagfileData they are replaced with // temporary directory location. This way we can test inclusion of one flagfile // from another flagfile. @@ -237,7 +235,9 @@ ABSL_RETIRED_FLAG(std::string, legacy_str, "l", ""); namespace { namespace flags = absl::flags_internal; +using testing::AllOf; using testing::ElementsAreArray; +using testing::HasSubstr; class ParseTest : public testing::Test { public: @@ -250,6 +250,38 @@ class ParseTest : public testing::Test { // -------------------------------------------------------------------- template <int N> +flags::HelpMode InvokeParseAbslOnlyImpl(const char* (&in_argv)[N]) { + std::vector<char*> positional_args; + std::vector<absl::UnrecognizedFlag> unrecognized_flags; + + return flags::ParseAbseilFlagsOnlyImpl(N, const_cast<char**>(in_argv), + positional_args, unrecognized_flags, + flags::UsageFlagsAction::kHandleUsage); +} + +// -------------------------------------------------------------------- + +template <int N> +void InvokeParseAbslOnly(const char* (&in_argv)[N]) { + std::vector<char*> positional_args; + std::vector<absl::UnrecognizedFlag> unrecognized_flags; + + absl::ParseAbseilFlagsOnly(2, const_cast<char**>(in_argv), positional_args, + unrecognized_flags); +} + +// -------------------------------------------------------------------- + +template <int N> +std::vector<char*> InvokeParseCommandLineImpl(const char* (&in_argv)[N]) { + return flags::ParseCommandLineImpl( + N, const_cast<char**>(in_argv), flags::UsageFlagsAction::kHandleUsage, + flags::OnUndefinedFlag::kAbortIfUndefined, std::cerr); +} + +// -------------------------------------------------------------------- + +template <int N> std::vector<char*> InvokeParse(const char* (&in_argv)[N]) { return absl::ParseCommandLine(N, const_cast<char**>(in_argv)); } @@ -854,129 +886,66 @@ TEST_F(ParseTest, TestReadingFlagsFromEnvMoxedWithRegularFlags) { // -------------------------------------------------------------------- -TEST_F(ParseTest, TestKeepParsedArgs) { - const char* in_args1[] = { - "testbin", "arg1", "--bool_flag", - "--int_flag=211", "arg2", "--double_flag=1.1", - "--string_flag", "asd", "--", - "arg3", "arg4", - }; - - auto out_args1 = InvokeParse(in_args1); - - EXPECT_THAT( - out_args1, - ElementsAreArray({absl::string_view("testbin"), absl::string_view("arg1"), - absl::string_view("arg2"), absl::string_view("arg3"), - absl::string_view("arg4")})); - - auto out_args2 = flags::ParseCommandLineImpl( - 11, const_cast<char**>(in_args1), flags::ArgvListAction::kKeepParsedArgs, - flags::UsageFlagsAction::kHandleUsage, - flags::OnUndefinedFlag::kAbortIfUndefined); - - EXPECT_THAT( - out_args2, - ElementsAreArray({absl::string_view("testbin"), - absl::string_view("--bool_flag"), - absl::string_view("--int_flag=211"), - absl::string_view("--double_flag=1.1"), - absl::string_view("--string_flag"), - absl::string_view("asd"), absl::string_view("--"), - absl::string_view("arg1"), absl::string_view("arg2"), - absl::string_view("arg3"), absl::string_view("arg4")})); -} - -// -------------------------------------------------------------------- - -TEST_F(ParseTest, TestIgnoreUndefinedFlags) { +TEST_F(ParseDeathTest, TestSimpleHelpFlagHandling) { const char* in_args1[] = { "testbin", - "arg1", - "--undef_flag=aa", - "--int_flag=21", + "--help", }; - auto out_args1 = flags::ParseCommandLineImpl( - 4, const_cast<char**>(in_args1), flags::ArgvListAction::kRemoveParsedArgs, - flags::UsageFlagsAction::kHandleUsage, - flags::OnUndefinedFlag::kIgnoreUndefined); - - EXPECT_THAT(out_args1, ElementsAreArray({absl::string_view("testbin"), - absl::string_view("arg1")})); - - EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 21); + EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args1), flags::HelpMode::kImportant); + EXPECT_EXIT(InvokeParse(in_args1), testing::ExitedWithCode(1), ""); const char* in_args2[] = { "testbin", - "arg1", - "--undef_flag=aa", - "--string_flag=AA", + "--help", + "--int_flag=3", }; - auto out_args2 = flags::ParseCommandLineImpl( - 4, const_cast<char**>(in_args2), flags::ArgvListAction::kKeepParsedArgs, - flags::UsageFlagsAction::kHandleUsage, - flags::OnUndefinedFlag::kIgnoreUndefined); + EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args2), flags::HelpMode::kImportant); + EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 3); - EXPECT_THAT( - out_args2, - ElementsAreArray( - {absl::string_view("testbin"), absl::string_view("--undef_flag=aa"), - absl::string_view("--string_flag=AA"), absl::string_view("arg1")})); + const char* in_args3[] = {"testbin", "--help", "some_positional_arg"}; - EXPECT_EQ(absl::GetFlag(FLAGS_string_flag), "AA"); + EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args3), flags::HelpMode::kImportant); } // -------------------------------------------------------------------- -TEST_F(ParseDeathTest, TestSimpleHelpFlagHandling) { +TEST_F(ParseTest, TestSubstringHelpFlagHandling) { const char* in_args1[] = { "testbin", - "--help", - }; - - EXPECT_EXIT(InvokeParse(in_args1), testing::ExitedWithCode(1), ""); - - const char* in_args2[] = { - "testbin", - "--help", - "--int_flag=3", + "--help=abcd", }; - auto out_args2 = flags::ParseCommandLineImpl( - 3, const_cast<char**>(in_args2), flags::ArgvListAction::kRemoveParsedArgs, - flags::UsageFlagsAction::kIgnoreUsage, - flags::OnUndefinedFlag::kAbortIfUndefined); - - EXPECT_EQ(flags::GetFlagsHelpMode(), flags::HelpMode::kImportant); - EXPECT_EQ(absl::GetFlag(FLAGS_int_flag), 3); + EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args1), flags::HelpMode::kMatch); + EXPECT_EQ(flags::GetFlagsHelpMatchSubstr(), "abcd"); } // -------------------------------------------------------------------- -TEST_F(ParseDeathTest, TestSubstringHelpFlagHandling) { +TEST_F(ParseDeathTest, TestVersionHandling) { const char* in_args1[] = { "testbin", - "--help=abcd", + "--version", }; - auto out_args1 = flags::ParseCommandLineImpl( - 2, const_cast<char**>(in_args1), flags::ArgvListAction::kRemoveParsedArgs, - flags::UsageFlagsAction::kIgnoreUsage, - flags::OnUndefinedFlag::kAbortIfUndefined); + EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args1), flags::HelpMode::kVersion); +} - EXPECT_EQ(flags::GetFlagsHelpMode(), flags::HelpMode::kMatch); - EXPECT_EQ(flags::GetFlagsHelpMatchSubstr(), "abcd"); +// -------------------------------------------------------------------- - const char* in_args2[] = {"testbin", "--help", "some_positional_arg"}; +TEST_F(ParseTest, TestCheckArgsHandling) { + const char* in_args1[] = {"testbin", "--only_check_args", "--int_flag=211"}; - auto out_args2 = flags::ParseCommandLineImpl( - 3, const_cast<char**>(in_args2), flags::ArgvListAction::kRemoveParsedArgs, - flags::UsageFlagsAction::kIgnoreUsage, - flags::OnUndefinedFlag::kAbortIfUndefined); + EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args1), flags::HelpMode::kOnlyCheckArgs); + EXPECT_EXIT(InvokeParseAbslOnly(in_args1), testing::ExitedWithCode(0), ""); + EXPECT_EXIT(InvokeParse(in_args1), testing::ExitedWithCode(0), ""); - EXPECT_EQ(flags::GetFlagsHelpMode(), flags::HelpMode::kImportant); + const char* in_args2[] = {"testbin", "--only_check_args", "--unknown_flag=a"}; + + EXPECT_EQ(InvokeParseAbslOnlyImpl(in_args2), flags::HelpMode::kOnlyCheckArgs); + EXPECT_EXIT(InvokeParseAbslOnly(in_args2), testing::ExitedWithCode(0), ""); + EXPECT_EXIT(InvokeParse(in_args2), testing::ExitedWithCode(1), ""); } // -------------------------------------------------------------------- @@ -1001,4 +970,118 @@ TEST_F(ParseTest, WasPresentOnCommandLine) { // -------------------------------------------------------------------- +TEST_F(ParseTest, ParseAbseilFlagsOnlySuccess) { + const char* in_args[] = { + "testbin", + "arg1", + "--bool_flag", + "--int_flag=211", + "arg2", + "--double_flag=1.1", + "--undef_flag1", + "--undef_flag2=123", + "--string_flag", + "asd", + "--", + "--some_flag", + "arg4", + }; + + std::vector<char*> positional_args; + std::vector<absl::UnrecognizedFlag> unrecognized_flags; + + absl::ParseAbseilFlagsOnly(13, const_cast<char**>(in_args), positional_args, + unrecognized_flags); + EXPECT_THAT(positional_args, + ElementsAreArray( + {absl::string_view("testbin"), absl::string_view("arg1"), + absl::string_view("arg2"), absl::string_view("--some_flag"), + absl::string_view("arg4")})); + EXPECT_THAT(unrecognized_flags, + ElementsAreArray( + {absl::UnrecognizedFlag(absl::UnrecognizedFlag::kFromArgv, + "undef_flag1"), + absl::UnrecognizedFlag(absl::UnrecognizedFlag::kFromArgv, + "undef_flag2")})); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseDeathTest, ParseAbseilFlagsOnlyFailure) { + const char* in_args[] = { + "testbin", + "--int_flag=21.1", + }; + + EXPECT_DEATH_IF_SUPPORTED( + InvokeParseAbslOnly(in_args), + "Illegal value '21.1' specified for flag 'int_flag'"); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseTest, UndefOkFlagsAreIgnored) { + const char* in_args[] = { + "testbin", "--undef_flag1", + "--undef_flag2=123", "--undefok=undef_flag2", + "--undef_flag3", "value", + }; + + std::vector<char*> positional_args; + std::vector<absl::UnrecognizedFlag> unrecognized_flags; + + absl::ParseAbseilFlagsOnly(6, const_cast<char**>(in_args), positional_args, + unrecognized_flags); + EXPECT_THAT(positional_args, ElementsAreArray({absl::string_view("testbin"), + absl::string_view("value")})); + EXPECT_THAT(unrecognized_flags, + ElementsAreArray( + {absl::UnrecognizedFlag(absl::UnrecognizedFlag::kFromArgv, + "undef_flag1"), + absl::UnrecognizedFlag(absl::UnrecognizedFlag::kFromArgv, + "undef_flag3")})); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseTest, AllUndefOkFlagsAreIgnored) { + const char* in_args[] = { + "testbin", + "--undef_flag1", + "--undef_flag2=123", + "--undefok=undef_flag2,undef_flag1,undef_flag3", + "--undef_flag3", + "value", + "--", + "--undef_flag4", + }; + + std::vector<char*> positional_args; + std::vector<absl::UnrecognizedFlag> unrecognized_flags; + + absl::ParseAbseilFlagsOnly(8, const_cast<char**>(in_args), positional_args, + unrecognized_flags); + EXPECT_THAT(positional_args, + ElementsAreArray({absl::string_view("testbin"), + absl::string_view("value"), + absl::string_view("--undef_flag4")})); + EXPECT_THAT(unrecognized_flags, testing::IsEmpty()); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseDeathTest, ExitOnUnrecognizedFlagPrintsHelp) { + const char* in_args[] = { + "testbin", + "--undef_flag1", + "--help=int_flag", + }; + + EXPECT_EXIT(InvokeParseCommandLineImpl(in_args), testing::ExitedWithCode(1), + AllOf(HasSubstr("Unknown command line flag 'undef_flag1'"), + HasSubstr("Try --helpfull to get a list of all flags"))); +} + +// -------------------------------------------------------------------- + } // namespace diff --git a/absl/flags/reflection.cc b/absl/flags/reflection.cc index dbce4032..841921a9 100644 --- a/absl/flags/reflection.cc +++ b/absl/flags/reflection.cc @@ -21,6 +21,7 @@ #include <string> #include "absl/base/config.h" +#include "absl/base/no_destructor.h" #include "absl/base/thread_annotations.h" #include "absl/container/flat_hash_map.h" #include "absl/flags/commandlineflag.h" @@ -169,7 +170,7 @@ void FlagRegistry::RegisterFlag(CommandLineFlag& flag, const char* filename) { } FlagRegistry& FlagRegistry::GlobalRegistry() { - static FlagRegistry* global_registry = new FlagRegistry; + static absl::NoDestructor<FlagRegistry> global_registry; return *global_registry; } diff --git a/absl/flags/usage.cc b/absl/flags/usage.cc index 452f6675..267a5039 100644 --- a/absl/flags/usage.cc +++ b/absl/flags/usage.cc @@ -21,6 +21,7 @@ #include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/const_init.h" +#include "absl/base/internal/raw_logging.h" #include "absl/base/thread_annotations.h" #include "absl/flags/internal/usage.h" #include "absl/strings/string_view.h" diff --git a/absl/functional/BUILD.bazel b/absl/functional/BUILD.bazel index c4fbce98..1a18af25 100644 --- a/absl/functional/BUILD.bazel +++ b/absl/functional/BUILD.bazel @@ -21,7 +21,14 @@ load( "ABSL_TEST_COPTS", ) -package(default_visibility = ["//visibility:public"]) +package( + default_visibility = ["//visibility:public"], + features = [ + "header_modules", + "layering_check", + "parse_headers", + ], +) licenses(["notice"]) @@ -55,6 +62,7 @@ cc_test( "//absl/base:core_headers", "//absl/meta:type_traits", "//absl/utility", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -81,6 +89,7 @@ cc_test( deps = [ ":bind_front", "//absl/memory", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -92,6 +101,7 @@ cc_library( copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ + ":any_invocable", "//absl/base:base_internal", "//absl/base:core_headers", "//absl/meta:type_traits", @@ -104,9 +114,38 @@ cc_test( srcs = ["function_ref_test.cc"], copts = ABSL_TEST_COPTS, deps = [ + ":any_invocable", ":function_ref", "//absl/container:test_instance_tracker", "//absl/memory", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "overload", + hdrs = ["overload.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base:config", + "//absl/meta:type_traits", + ], +) + +cc_test( + name = "overload_test", + size = "small", + srcs = ["overload_test.cc"], + copts = ABSL_TEST_COPTS, + deps = [ + ":overload", + "//absl/base:config", + "//absl/strings", + "//absl/strings:string_view", + "//absl/types:variant", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -124,5 +163,6 @@ cc_test( ":function_ref", "//absl/base:core_headers", "@com_github_google_benchmark//:benchmark_main", + "@com_google_googletest//:gtest", ], ) diff --git a/absl/functional/CMakeLists.txt b/absl/functional/CMakeLists.txt index c0f6eaaa..602829cb 100644 --- a/absl/functional/CMakeLists.txt +++ b/absl/functional/CMakeLists.txt @@ -39,7 +39,7 @@ absl_cc_test( "any_invocable_test.cc" "internal/any_invocable.h" COPTS - ${ABSL_DEFAULT_COPTS} + ${ABSL_TEST_COPTS} DEPS absl::any_invocable absl::base_internal @@ -90,6 +90,7 @@ absl_cc_library( DEPS absl::base_internal absl::core_headers + absl::any_invocable absl::meta PUBLIC ) @@ -107,3 +108,27 @@ absl_cc_test( absl::test_instance_tracker GTest::gmock_main ) + +absl_cc_library( + NAME + overload + HDRS + "overload.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::meta + PUBLIC +) + +absl_cc_test( + NAME + overload_test + SRCS + "overload_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::strings + GTest::gmock_main +) diff --git a/absl/functional/any_invocable.h b/absl/functional/any_invocable.h index 3e783c87..68d88253 100644 --- a/absl/functional/any_invocable.h +++ b/absl/functional/any_invocable.h @@ -266,9 +266,17 @@ class AnyInvocable : private internal_any_invocable::Impl<Sig> { // Exchanges the targets of `*this` and `other`. void swap(AnyInvocable& other) noexcept { std::swap(*this, other); } - // abl::AnyInvocable::operator bool() + // absl::AnyInvocable::operator bool() // // Returns `true` if `*this` is not empty. + // + // WARNING: An `AnyInvocable` that wraps an empty `std::function` is not + // itself empty. This behavior is consistent with the standard equivalent + // `std::move_only_function`. + // + // In other words: + // std::function<void()> f; // empty + // absl::AnyInvocable<void()> a = std::move(f); // not empty explicit operator bool() const noexcept { return this->HasValue(); } // Invokes the target object of `*this`. `*this` must not be empty. diff --git a/absl/functional/any_invocable_test.cc b/absl/functional/any_invocable_test.cc index 1ed85407..a740faa6 100644 --- a/absl/functional/any_invocable_test.cc +++ b/absl/functional/any_invocable_test.cc @@ -1418,7 +1418,7 @@ TYPED_TEST_P(AnyInvTestRvalue, NonConstCrashesOnSecondCall) { // Ensure we're still valid EXPECT_TRUE(static_cast<bool>(fun)); // NOLINT(bugprone-use-after-move) -#if !defined(NDEBUG) || ABSL_OPTION_HARDENED == 1 +#if !defined(NDEBUG) EXPECT_DEATH_IF_SUPPORTED(std::move(fun)(7, 8, 9), ""); #endif } @@ -1431,14 +1431,14 @@ TYPED_TEST_P(AnyInvTestRvalue, QualifierIndependentObjectLifetime) { auto refs = std::make_shared<std::nullptr_t>(); { AnyInvType fun([refs](auto&&...) noexcept { return 0; }); - EXPECT_FALSE(refs.unique()); + EXPECT_GT(refs.use_count(), 1); std::move(fun)(7, 8, 9); // Ensure destructor hasn't run even if rref-qualified - EXPECT_FALSE(refs.unique()); + EXPECT_GT(refs.use_count(), 1); } - EXPECT_TRUE(refs.unique()); + EXPECT_EQ(refs.use_count(), 1); } // NOTE: This test suite originally attempted to enumerate all possible diff --git a/absl/functional/bind_front.h b/absl/functional/bind_front.h index f9075bd1..a956eb02 100644 --- a/absl/functional/bind_front.h +++ b/absl/functional/bind_front.h @@ -46,7 +46,7 @@ ABSL_NAMESPACE_BEGIN // // Like `std::bind()`, `absl::bind_front()` is implicitly convertible to // `std::function`. In particular, it may be used as a simpler replacement for -// `std::bind()` in most cases, as it does not require placeholders to be +// `std::bind()` in most cases, as it does not require placeholders to be // specified. More importantly, it provides more reliable correctness guarantees // than `std::bind()`; while `std::bind()` will silently ignore passing more // parameters than expected, for example, `absl::bind_front()` will report such diff --git a/absl/functional/function_ref.h b/absl/functional/function_ref.h index f9779607..96cece55 100644 --- a/absl/functional/function_ref.h +++ b/absl/functional/function_ref.h @@ -66,11 +66,11 @@ class FunctionRef; // FunctionRef // -// An `absl::FunctionRef` is a lightweight wrapper to any invokable object with +// An `absl::FunctionRef` is a lightweight wrapper to any invocable object with // a compatible signature. Generally, an `absl::FunctionRef` should only be used // as an argument type and should be preferred as an argument over a const // reference to a `std::function`. `absl::FunctionRef` itself does not allocate, -// although the wrapped invokable may. +// although the wrapped invocable may. // // Example: // @@ -98,7 +98,7 @@ class FunctionRef<R(Args...)> { std::is_convertible<FR, R>::value>::type; public: - // Constructs a FunctionRef from any invokable type. + // Constructs a FunctionRef from any invocable type. template <typename F, typename = EnableIfCompatible<const F&>> // NOLINTNEXTLINE(runtime/explicit) FunctionRef(const F& f ABSL_ATTRIBUTE_LIFETIME_BOUND) @@ -137,6 +137,14 @@ class FunctionRef<R(Args...)> { absl::functional_internal::Invoker<R, Args...> invoker_; }; +// Allow const qualified function signatures. Since FunctionRef requires +// constness anyway we can just make this a no-op. +template <typename R, typename... Args> +class FunctionRef<R(Args...) const> : public FunctionRef<R(Args...)> { + public: + using FunctionRef<R(Args...)>::FunctionRef; +}; + ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/functional/function_ref_test.cc b/absl/functional/function_ref_test.cc index 412027cd..c0211135 100644 --- a/absl/functional/function_ref_test.cc +++ b/absl/functional/function_ref_test.cc @@ -20,6 +20,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/container/internal/test_instance_tracker.h" +#include "absl/functional/any_invocable.h" #include "absl/memory/memory.h" namespace absl { @@ -46,6 +47,11 @@ TEST(FunctionRefTest, Function2) { EXPECT_EQ(1337, ref()); } +TEST(FunctionRefTest, ConstFunction) { + FunctionRef<int() const> ref(Function); + EXPECT_EQ(1337, ref()); +} + int NoExceptFunction() noexcept { return 1337; } // TODO(jdennett): Add a test for noexcept member functions. @@ -157,6 +163,25 @@ TEST(FunctionRef, NullMemberPtrAssertFails) { EXPECT_DEBUG_DEATH({ FunctionRef<int(const S& s)> ref(mem_ptr); }, ""); } +TEST(FunctionRef, NullStdFunctionAssertPasses) { + std::function<void()> function = []() {}; + FunctionRef<void()> ref(function); +} + +TEST(FunctionRef, NullStdFunctionAssertFails) { + std::function<void()> function = nullptr; + EXPECT_DEBUG_DEATH({ FunctionRef<void()> ref(function); }, ""); +} + +TEST(FunctionRef, NullAnyInvocableAssertPasses) { + AnyInvocable<void() const> invocable = []() {}; + FunctionRef<void()> ref(invocable); +} +TEST(FunctionRef, NullAnyInvocableAssertFails) { + AnyInvocable<void() const> invocable = nullptr; + EXPECT_DEBUG_DEATH({ FunctionRef<void()> ref(invocable); }, ""); +} + #endif // GTEST_HAS_DEATH_TEST TEST(FunctionRef, CopiesAndMovesPerPassByValue) { @@ -237,7 +262,7 @@ TEST(FunctionRef, PassByValueTypes) { "Reference types should be preserved"); // Make sure the address of an object received by reference is the same as the - // addess of the object passed by the caller. + // address of the object passed by the caller. { LargeTrivial obj; auto test = [&obj](LargeTrivial& input) { ASSERT_EQ(&input, &obj); }; @@ -253,6 +278,16 @@ TEST(FunctionRef, PassByValueTypes) { } } +TEST(FunctionRef, ReferenceToIncompleteType) { + struct IncompleteType; + auto test = [](IncompleteType&) {}; + absl::FunctionRef<void(IncompleteType&)> ref(test); + + struct IncompleteType {}; + IncompleteType obj; + ref(obj); +} + } // namespace ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/functional/internal/any_invocable.h b/absl/functional/internal/any_invocable.h index 6bfbda18..b04436d1 100644 --- a/absl/functional/internal/any_invocable.h +++ b/absl/functional/internal/any_invocable.h @@ -56,6 +56,7 @@ #include <cassert> #include <cstddef> #include <cstring> +#include <exception> #include <functional> #include <initializer_list> #include <memory> @@ -134,8 +135,16 @@ void InvokeR(F&& f, P&&... args) { template <class ReturnType, class F, class... P, absl::enable_if_t<!std::is_void<ReturnType>::value, int> = 0> ReturnType InvokeR(F&& f, P&&... args) { + // GCC 12 has a false-positive -Wmaybe-uninitialized warning here. +#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif return absl::base_internal::invoke(std::forward<F>(f), std::forward<P>(args)...); +#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0) +#pragma GCC diagnostic pop +#endif } // @@ -196,7 +205,7 @@ union TypeErasedState { template <class T> T& ObjectInLocalStorage(TypeErasedState* const state) { // We launder here because the storage may be reused with the same type. -#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L +#if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606L return *std::launder(reinterpret_cast<T*>(&state->storage)); #elif ABSL_HAVE_BUILTIN(__builtin_launder) return *__builtin_launder(reinterpret_cast<T*>(&state->storage)); @@ -206,8 +215,8 @@ T& ObjectInLocalStorage(TypeErasedState* const state) { // behavior, which works as intended on Abseil's officially supported // platforms as of Q2 2022. #if !defined(__clang__) && defined(__GNUC__) -#pragma GCC diagnostic ignored "-Wstrict-aliasing" #pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstrict-aliasing" #endif return *reinterpret_cast<T*>(&state->storage); #if !defined(__clang__) && defined(__GNUC__) @@ -431,11 +440,11 @@ class CoreImpl { CoreImpl() noexcept : manager_(EmptyManager), invoker_(nullptr) {} - enum class TargetType : int { - kPointer = 0, - kCompatibleAnyInvocable = 1, - kIncompatibleAnyInvocable = 2, - kOther = 3, + enum class TargetType { + kPointer, + kCompatibleAnyInvocable, + kIncompatibleAnyInvocable, + kOther, }; // Note: QualDecayedTRef here includes the cv-ref qualifiers associated with @@ -457,8 +466,7 @@ class CoreImpl { // NOTE: We only use integers instead of enums as template parameters in // order to work around a bug on C++14 under MSVC 2017. // See b/236131881. - Initialize<static_cast<int>(kTargetType), QualDecayedTRef>( - std::forward<F>(f)); + Initialize<kTargetType, QualDecayedTRef>(std::forward<F>(f)); } // Note: QualTRef here includes the cv-ref qualifiers associated with the @@ -487,7 +495,7 @@ class CoreImpl { // object. Clear(); - // Perform the actual move/destory operation on the target function. + // Perform the actual move/destroy operation on the target function. other.manager_(FunctionToCall::relocate_from_to, &other.state_, &state_); manager_ = other.manager_; invoker_ = other.invoker_; @@ -509,8 +517,8 @@ class CoreImpl { invoker_ = nullptr; } - template <int target_type, class QualDecayedTRef, class F, - absl::enable_if_t<target_type == 0, int> = 0> + template <TargetType target_type, class QualDecayedTRef, class F, + absl::enable_if_t<target_type == TargetType::kPointer, int> = 0> void Initialize(F&& f) { // This condition handles types that decay into pointers, which includes // function references. Since function references cannot be null, GCC warns @@ -518,10 +526,10 @@ class CoreImpl { // Since this is template-heavy code, we prefer to disable these warnings // locally instead of adding yet another overload of this function. #if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wpragmas" #pragma GCC diagnostic ignored "-Waddress" #pragma GCC diagnostic ignored "-Wnonnull-compare" -#pragma GCC diagnostic push #endif if (static_cast<RemoveCVRef<QualDecayedTRef>>(f) == nullptr) { #if !defined(__clang__) && defined(__GNUC__) @@ -534,8 +542,9 @@ class CoreImpl { InitializeStorage<QualDecayedTRef>(std::forward<F>(f)); } - template <int target_type, class QualDecayedTRef, class F, - absl::enable_if_t<target_type == 1, int> = 0> + template <TargetType target_type, class QualDecayedTRef, class F, + absl::enable_if_t< + target_type == TargetType::kCompatibleAnyInvocable, int> = 0> void Initialize(F&& f) { // In this case we can "steal the guts" of the other AnyInvocable. f.manager_(FunctionToCall::relocate_from_to, &f.state_, &state_); @@ -546,8 +555,9 @@ class CoreImpl { f.invoker_ = nullptr; } - template <int target_type, class QualDecayedTRef, class F, - absl::enable_if_t<target_type == 2, int> = 0> + template <TargetType target_type, class QualDecayedTRef, class F, + absl::enable_if_t< + target_type == TargetType::kIncompatibleAnyInvocable, int> = 0> void Initialize(F&& f) { if (f.HasValue()) { InitializeStorage<QualDecayedTRef>(std::forward<F>(f)); @@ -557,8 +567,8 @@ class CoreImpl { } } - template <int target_type, class QualDecayedTRef, class F, - typename = absl::enable_if_t<target_type == 3>> + template <TargetType target_type, class QualDecayedTRef, class F, + typename = absl::enable_if_t<target_type == TargetType::kOther>> void Initialize(F&& f) { InitializeStorage<QualDecayedTRef>(std::forward<F>(f)); } @@ -810,19 +820,22 @@ using CanAssignReferenceWrapper = TrueAlias< : Core(absl::in_place_type<absl::decay_t<T> inv_quals>, \ std::forward<Args>(args)...) {} \ \ + /*Raises a fatal error when the AnyInvocable is invoked after a move*/ \ + static ReturnType InvokedAfterMove( \ + TypeErasedState*, \ + ForwardedParameterType<P>...) noexcept(noex) { \ + ABSL_HARDENING_ASSERT(false && "AnyInvocable use-after-move"); \ + std::terminate(); \ + } \ + \ 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]() { \ + ABSL_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(); \ - }; \ + const_cast<Impl*>(this)->invoker_ = InvokedAfterMove; \ return this->HasValue(); \ }()); \ } \ diff --git a/absl/functional/internal/function_ref.h b/absl/functional/internal/function_ref.h index b5bb8b43..1cd34a3c 100644 --- a/absl/functional/internal/function_ref.h +++ b/absl/functional/internal/function_ref.h @@ -20,6 +20,7 @@ #include <type_traits> #include "absl/base/internal/invoke.h" +#include "absl/functional/any_invocable.h" #include "absl/meta/type_traits.h" namespace absl { @@ -40,18 +41,21 @@ union VoidPtr { // Chooses the best type for passing T as an argument. // Attempt to be close to SystemV AMD64 ABI. Objects with trivial copy ctor are // passed by value. +template <typename T, + bool IsLValueReference = std::is_lvalue_reference<T>::value> +struct PassByValue : std::false_type {}; + template <typename T> -constexpr bool PassByValue() { - return !std::is_lvalue_reference<T>::value && - absl::is_trivially_copy_constructible<T>::value && - absl::is_trivially_copy_assignable< - typename std::remove_cv<T>::type>::value && - std::is_trivially_destructible<T>::value && - sizeof(T) <= 2 * sizeof(void*); -} +struct PassByValue<T, /*IsLValueReference=*/false> + : std::integral_constant<bool, + absl::is_trivially_copy_constructible<T>::value && + absl::is_trivially_copy_assignable< + typename std::remove_cv<T>::type>::value && + std::is_trivially_destructible<T>::value && + sizeof(T) <= 2 * sizeof(void*)> {}; template <typename T> -struct ForwardT : std::conditional<PassByValue<T>(), T, T&&> {}; +struct ForwardT : std::conditional<PassByValue<T>::value, T, T&&> {}; // An Invoker takes a pointer to the type-erased invokable object, followed by // the arguments that the invokable object expects. @@ -87,6 +91,12 @@ void AssertNonNull(const std::function<Sig>& f) { (void)f; } +template <typename Sig> +void AssertNonNull(const AnyInvocable<Sig>& f) { + assert(f != nullptr); + (void)f; +} + template <typename F> void AssertNonNull(const F&) {} diff --git a/absl/functional/overload.h b/absl/functional/overload.h new file mode 100644 index 00000000..4651f14b --- /dev/null +++ b/absl/functional/overload.h @@ -0,0 +1,75 @@ +// Copyright 2023 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// ----------------------------------------------------------------------------- +// File: overload.h +// ----------------------------------------------------------------------------- +// +// `absl::Overload()` returns a functor that provides overloads based on the +// functors passed to it. +// Before using this function, consider whether named function overloads would +// be a better design. +// One use case for this is locally defining visitors for `std::visit` inside a +// function using lambdas. + +// Example: Using `absl::Overload` to define a visitor for `std::variant`. +// +// std::variant<int, std::string, double> v(int{1}); +// +// assert(std::visit(absl::Overload( +// [](int) -> absl::string_view { return "int"; }, +// [](const std::string&) -> absl::string_view { +// return "string"; +// }, +// [](double) -> absl::string_view { return "double"; }), +// v) == "int"); +// +// Note: This requires C++17. + +#ifndef ABSL_FUNCTIONAL_OVERLOAD_H_ +#define ABSL_FUNCTIONAL_OVERLOAD_H_ + +#include "absl/base/config.h" +#include "absl/meta/type_traits.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +#if defined(ABSL_INTERNAL_CPLUSPLUS_LANG) && \ + ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L + +template <int&... ExplicitArgumentBarrier, typename... T> +auto Overload(T&&... ts) { + struct OverloadImpl : absl::remove_cvref_t<T>... { + using absl::remove_cvref_t<T>::operator()...; + }; + return OverloadImpl{std::forward<T>(ts)...}; +} +#else +namespace functional_internal { +template <typename T> +constexpr bool kDependentFalse = false; +} + +template <typename Dependent = int, typename... T> +auto Overload(T&&...) { + static_assert(functional_internal::kDependentFalse<Dependent>, + "Overload is only usable with C++17 or above."); +} + +#endif +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_FUNCTIONAL_OVERLOAD_H_ diff --git a/absl/functional/overload_test.cc b/absl/functional/overload_test.cc new file mode 100644 index 00000000..739c4c4c --- /dev/null +++ b/absl/functional/overload_test.cc @@ -0,0 +1,130 @@ +// Copyright 2023 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/functional/overload.h" + +#include <cstdint> +#include <string> +#include <type_traits> + +#include "absl/base/config.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "absl/types/variant.h" + +#if defined(ABSL_INTERNAL_CPLUSPLUS_LANG) && \ + ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L + +#include "gtest/gtest.h" + +namespace { + +TEST(OverloadTest, DispatchConsidersType) { + auto overloaded = absl::Overload( + [](int v) -> std::string { return absl::StrCat("int ", v); }, // + [](double v) -> std::string { return absl::StrCat("double ", v); }, // + [](const char* v) -> std::string { // + return absl::StrCat("const char* ", v); // + }, // + [](auto v) -> std::string { return absl::StrCat("auto ", v); } // + ); + EXPECT_EQ("int 1", overloaded(1)); + EXPECT_EQ("double 2.5", overloaded(2.5)); + EXPECT_EQ("const char* hello", overloaded("hello")); + EXPECT_EQ("auto 1.5", overloaded(1.5f)); +} + +TEST(OverloadTest, DispatchConsidersNumberOfArguments) { + auto overloaded = absl::Overload( // + [](int a) { return a + 1; }, // + [](int a, int b) { return a * b; }, // + []() -> absl::string_view { return "none"; } // + ); + EXPECT_EQ(3, overloaded(2)); + EXPECT_EQ(21, overloaded(3, 7)); + EXPECT_EQ("none", overloaded()); +} + +TEST(OverloadTest, SupportsConstantEvaluation) { + auto overloaded = absl::Overload( // + [](int a) { return a + 1; }, // + [](int a, int b) { return a * b; }, // + []() -> absl::string_view { return "none"; } // + ); + static_assert(overloaded() == "none"); + static_assert(overloaded(2) == 3); + static_assert(overloaded(3, 7) == 21); +} + +TEST(OverloadTest, PropogatesDefaults) { + auto overloaded = absl::Overload( // + [](int a, int b = 5) { return a * b; }, // + [](double c) { return c; } // + ); + + EXPECT_EQ(21, overloaded(3, 7)); + EXPECT_EQ(35, overloaded(7)); + EXPECT_EQ(2.5, overloaded(2.5)); +} + +TEST(OverloadTest, AmbiguousWithDefaultsNotInvocable) { + auto overloaded = absl::Overload( // + [](int a, int b = 5) { return a * b; }, // + [](int c) { return c; } // + ); + static_assert(!std::is_invocable_v<decltype(overloaded), int>); + static_assert(std::is_invocable_v<decltype(overloaded), int, int>); +} + +TEST(OverloadTest, AmbiguousDuplicatesNotInvocable) { + auto overloaded = absl::Overload( // + [](int a) { return a; }, // + [](int c) { return c; } // + ); + static_assert(!std::is_invocable_v<decltype(overloaded), int>); +} + +TEST(OverloadTest, AmbiguousConversionNotInvocable) { + auto overloaded = absl::Overload( // + [](uint16_t a) { return a; }, // + [](uint64_t c) { return c; } // + ); + static_assert(!std::is_invocable_v<decltype(overloaded), int>); +} + +TEST(OverloadTest, DispatchConsidersSfinae) { + auto overloaded = absl::Overload( // + [](auto a) -> decltype(a + 1) { return a + 1; } // + ); + static_assert(std::is_invocable_v<decltype(overloaded), int>); + static_assert(!std::is_invocable_v<decltype(overloaded), std::string>); +} + +TEST(OverloadTest, VariantVisitDispatchesCorrectly) { + absl::variant<int, double, std::string> v(1); + auto overloaded = absl::Overload( + [](int) -> absl::string_view { return "int"; }, // + [](double) -> absl::string_view { return "double"; }, // + [](const std::string&) -> absl::string_view { return "string"; } // + ); + EXPECT_EQ("int", absl::visit(overloaded, v)); + v = 1.1; + EXPECT_EQ("double", absl::visit(overloaded, v)); + v = "hello"; + EXPECT_EQ("string", absl::visit(overloaded, v)); +} + +} // namespace + +#endif diff --git a/absl/hash/BUILD.bazel b/absl/hash/BUILD.bazel index a0db919b..1e8ad451 100644 --- a/absl/hash/BUILD.bazel +++ b/absl/hash/BUILD.bazel @@ -21,7 +21,14 @@ load( "ABSL_TEST_COPTS", ) -package(default_visibility = ["//visibility:public"]) +package( + default_visibility = ["//visibility:public"], + features = [ + "header_modules", + "layering_check", + "parse_headers", + ], +) licenses(["notice"]) @@ -68,22 +75,53 @@ cc_library( cc_test( name = "hash_test", - srcs = ["hash_test.cc"], + srcs = [ + "hash_test.cc", + "internal/hash_test.h", + ], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":hash", ":hash_testing", ":spy_hash_state", + "//absl/base:config", "//absl/base:core_headers", "//absl/container:btree", "//absl/container:flat_hash_map", "//absl/container:flat_hash_set", "//absl/container:node_hash_map", "//absl/container:node_hash_set", + "//absl/memory", "//absl/meta:type_traits", "//absl/numeric:int128", "//absl/strings:cord_test_helpers", + "//absl/strings:string_view", + "//absl/types:optional", + "//absl/types:variant", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "hash_instantiated_test", + srcs = [ + "hash_instantiated_test.cc", + "internal/hash_test.h", + ], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":hash", + ":hash_testing", + "//absl/base:config", + "//absl/container:btree", + "//absl/container:flat_hash_map", + "//absl/container:flat_hash_set", + "//absl/container:node_hash_map", + "//absl/container:node_hash_set", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -144,6 +182,7 @@ cc_test( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":city", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -158,6 +197,7 @@ cc_library( deps = [ "//absl/base:config", "//absl/base:endian", + "//absl/base:prefetch", "//absl/numeric:int128", ], ) @@ -171,6 +211,7 @@ cc_test( deps = [ ":low_level_hash", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/hash/CMakeLists.txt b/absl/hash/CMakeLists.txt index f99f35bc..99d6fa1f 100644 --- a/absl/hash/CMakeLists.txt +++ b/absl/hash/CMakeLists.txt @@ -64,21 +64,46 @@ absl_cc_test( hash_test SRCS "hash_test.cc" + "internal/hash_test.h" COPTS ${ABSL_TEST_COPTS} DEPS + absl::btree absl::cord_test_helpers + absl::core_headers + absl::flat_hash_map + absl::flat_hash_set absl::hash absl::hash_testing - absl::core_headers + absl::int128 + absl::memory + absl::meta + absl::node_hash_map + absl::node_hash_set + absl::optional + absl::spy_hash_state + absl::string_view + absl::variant + GTest::gmock_main +) + +absl_cc_test( + NAME + hash_instantiated_test + SRCS + "hash_instantiated_test.cc" + "internal/hash_test.h" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::hash + absl::hash_testing + absl::config absl::btree absl::flat_hash_map absl::flat_hash_set absl::node_hash_map absl::node_hash_set - absl::spy_hash_state - absl::meta - absl::int128 GTest::gmock_main ) @@ -144,6 +169,7 @@ absl_cc_library( absl::config absl::endian absl::int128 + absl::prefetch ) absl_cc_test( diff --git a/absl/hash/hash.h b/absl/hash/hash.h index 74e2d7c0..470cca48 100644 --- a/absl/hash/hash.h +++ b/absl/hash/hash.h @@ -42,7 +42,7 @@ // // `absl::Hash` may also produce different values from different dynamically // loaded libraries. For this reason, `absl::Hash` values must never cross -// boundries in dynamically loaded libraries (including when used in types like +// boundaries in dynamically loaded libraries (including when used in types like // hash containers.) // // `absl::Hash` is intended to strongly mix input bits with a target of passing @@ -110,9 +110,12 @@ ABSL_NAMESPACE_BEGIN // * std::unique_ptr and std::shared_ptr // * All string-like types including: // * absl::Cord -// * std::string -// * std::string_view (as well as any instance of std::basic_string that -// uses char and std::char_traits) +// * std::string (as well as any instance of std::basic_string that +// uses one of {char, wchar_t, char16_t, char32_t} and its associated +// std::char_traits) +// * std::string_view (as well as any instance of std::basic_string_view +// that uses one of {char, wchar_t, char16_t, char32_t} and its associated +// std::char_traits) // * All the standard sequence containers (provided the elements are hashable) // * All the standard associative containers (provided the elements are // hashable) diff --git a/absl/hash/hash_benchmark.cc b/absl/hash/hash_benchmark.cc index 8712a01c..d18ea694 100644 --- a/absl/hash/hash_benchmark.cc +++ b/absl/hash/hash_benchmark.cc @@ -12,7 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include <algorithm> +#include <cassert> +#include <cstddef> +#include <cstdint> +#include <cstring> #include <string> +#include <tuple> #include <type_traits> #include <typeindex> #include <utility> @@ -79,12 +85,6 @@ struct TypeErasedAbslHash { } }; -template <typename FuncType> -inline FuncType* ODRUseFunction(FuncType* ptr) { - volatile FuncType* dummy = ptr; - return dummy; -} - absl::Cord FlatCord(size_t size) { absl::Cord result(std::string(size, 'a')); result.Flatten(); @@ -160,7 +160,7 @@ absl::flat_hash_set<T> FlatHashSet(size_t count) { return hash<decltype(__VA_ARGS__)>{}(arg); \ } \ bool absl_hash_test_odr_use##hash##name = \ - ODRUseFunction(&Codegen##hash##name); + (benchmark::DoNotOptimize(&Codegen##hash##name), false); MAKE_BENCHMARK(AbslHash, Int32, int32_t{}); MAKE_BENCHMARK(AbslHash, Int64, int64_t{}); diff --git a/absl/hash/hash_instantiated_test.cc b/absl/hash/hash_instantiated_test.cc new file mode 100644 index 00000000..e65de9ca --- /dev/null +++ b/absl/hash/hash_instantiated_test.cc @@ -0,0 +1,224 @@ +// 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. + +// This file contains a few select absl::Hash tests that, due to their reliance +// on INSTANTIATE_TYPED_TEST_SUITE_P, require a large amount of memory to +// compile. Put new tests in hash_test.cc, not this file. + +#include "absl/hash/hash.h" + +#include <stddef.h> + +#include <algorithm> +#include <deque> +#include <forward_list> +#include <initializer_list> +#include <list> +#include <map> +#include <set> +#include <string> +#include <type_traits> +#include <unordered_map> +#include <unordered_set> +#include <utility> +#include <vector> + +#include "gtest/gtest.h" +#include "absl/container/btree_map.h" +#include "absl/container/btree_set.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" +#include "absl/container/node_hash_map.h" +#include "absl/container/node_hash_set.h" +#include "absl/hash/hash_testing.h" +#include "absl/hash/internal/hash_test.h" + +namespace { + +using ::absl::hash_test_internal::is_hashable; +using ::absl::hash_test_internal::TypeErasedContainer; + +// Dummy type with unordered equality and hashing semantics. This preserves +// input order internally, and is used below to ensure we get test coverage +// for equal sequences with different iteraton orders. +template <typename T> +class UnorderedSequence { + public: + UnorderedSequence() = default; + template <typename TT> + UnorderedSequence(std::initializer_list<TT> l) + : values_(l.begin(), l.end()) {} + template <typename ForwardIterator, + typename std::enable_if<!std::is_integral<ForwardIterator>::value, + bool>::type = true> + UnorderedSequence(ForwardIterator begin, ForwardIterator end) + : values_(begin, end) {} + // one-argument constructor of value type T, to appease older toolchains that + // get confused by one-element initializer lists in some contexts + explicit UnorderedSequence(const T& v) : values_(&v, &v + 1) {} + + using value_type = T; + + size_t size() const { return values_.size(); } + typename std::vector<T>::const_iterator begin() const { + return values_.begin(); + } + typename std::vector<T>::const_iterator end() const { return values_.end(); } + + friend bool operator==(const UnorderedSequence& lhs, + const UnorderedSequence& rhs) { + return lhs.size() == rhs.size() && + std::is_permutation(lhs.begin(), lhs.end(), rhs.begin()); + } + friend bool operator!=(const UnorderedSequence& lhs, + const UnorderedSequence& rhs) { + return !(lhs == rhs); + } + template <typename H> + friend H AbslHashValue(H h, const UnorderedSequence& u) { + return H::combine(H::combine_unordered(std::move(h), u.begin(), u.end()), + u.size()); + } + + private: + std::vector<T> values_; +}; + +template <typename T> +class HashValueSequenceTest : public testing::Test {}; +TYPED_TEST_SUITE_P(HashValueSequenceTest); + +TYPED_TEST_P(HashValueSequenceTest, BasicUsage) { + EXPECT_TRUE((is_hashable<TypeParam>::value)); + + using IntType = typename TypeParam::value_type; + auto a = static_cast<IntType>(0); + auto b = static_cast<IntType>(23); + auto c = static_cast<IntType>(42); + + std::vector<TypeParam> exemplars = { + TypeParam(), TypeParam(), TypeParam{a, b, c}, + TypeParam{a, c, b}, TypeParam{c, a, b}, TypeParam{a}, + TypeParam{a, a}, TypeParam{a, a, a}, TypeParam{a, a, b}, + TypeParam{a, b, a}, TypeParam{b, a, a}, TypeParam{a, b}, + TypeParam{b, c}}; + EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(exemplars)); +} + +REGISTER_TYPED_TEST_SUITE_P(HashValueSequenceTest, BasicUsage); +using IntSequenceTypes = testing::Types< + std::deque<int>, std::forward_list<int>, std::list<int>, std::vector<int>, + std::vector<bool>, TypeErasedContainer<std::vector<int>>, std::set<int>, + std::multiset<int>, UnorderedSequence<int>, + TypeErasedContainer<UnorderedSequence<int>>, std::unordered_set<int>, + std::unordered_multiset<int>, absl::flat_hash_set<int>, + absl::node_hash_set<int>, absl::btree_set<int>>; +INSTANTIATE_TYPED_TEST_SUITE_P(My, HashValueSequenceTest, IntSequenceTypes); + +template <typename T> +class HashValueNestedSequenceTest : public testing::Test {}; +TYPED_TEST_SUITE_P(HashValueNestedSequenceTest); + +TYPED_TEST_P(HashValueNestedSequenceTest, BasicUsage) { + using T = TypeParam; + using V = typename T::value_type; + std::vector<T> exemplars = { + // empty case + T{}, + // sets of empty sets + T{V{}}, T{V{}, V{}}, T{V{}, V{}, V{}}, + // multisets of different values + T{V{1}}, T{V{1, 1}, V{1, 1}}, T{V{1, 1, 1}, V{1, 1, 1}, V{1, 1, 1}}, + // various orderings of same nested sets + T{V{}, V{1, 2}}, T{V{}, V{2, 1}}, T{V{1, 2}, V{}}, T{V{2, 1}, V{}}, + // various orderings of various nested sets, case 2 + T{V{1, 2}, V{3, 4}}, T{V{1, 2}, V{4, 3}}, T{V{1, 3}, V{2, 4}}, + T{V{1, 3}, V{4, 2}}, T{V{1, 4}, V{2, 3}}, T{V{1, 4}, V{3, 2}}, + T{V{2, 3}, V{1, 4}}, T{V{2, 3}, V{4, 1}}, T{V{2, 4}, V{1, 3}}, + T{V{2, 4}, V{3, 1}}, T{V{3, 4}, V{1, 2}}, T{V{3, 4}, V{2, 1}}}; + EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(exemplars)); +} + +REGISTER_TYPED_TEST_SUITE_P(HashValueNestedSequenceTest, BasicUsage); +template <typename T> +using TypeErasedSet = TypeErasedContainer<UnorderedSequence<T>>; + +using NestedIntSequenceTypes = testing::Types< + std::vector<std::vector<int>>, std::vector<UnorderedSequence<int>>, + std::vector<TypeErasedSet<int>>, UnorderedSequence<std::vector<int>>, + UnorderedSequence<UnorderedSequence<int>>, + UnorderedSequence<TypeErasedSet<int>>, TypeErasedSet<std::vector<int>>, + TypeErasedSet<UnorderedSequence<int>>, TypeErasedSet<TypeErasedSet<int>>>; +INSTANTIATE_TYPED_TEST_SUITE_P(My, HashValueNestedSequenceTest, + NestedIntSequenceTypes); + +template <typename T> +class HashValueAssociativeMapTest : public testing::Test {}; +TYPED_TEST_SUITE_P(HashValueAssociativeMapTest); + +TYPED_TEST_P(HashValueAssociativeMapTest, BasicUsage) { + using M = TypeParam; + using V = typename M::value_type; + std::vector<M> exemplars{M{}, + M{V{0, "foo"}}, + M{V{1, "foo"}}, + M{V{0, "bar"}}, + M{V{1, "bar"}}, + M{V{0, "foo"}, V{42, "bar"}}, + M{V{42, "bar"}, V{0, "foo"}}, + M{V{1, "foo"}, V{42, "bar"}}, + M{V{1, "foo"}, V{43, "bar"}}, + M{V{1, "foo"}, V{43, "baz"}}}; + EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(exemplars)); +} + +REGISTER_TYPED_TEST_SUITE_P(HashValueAssociativeMapTest, BasicUsage); +using AssociativeMapTypes = testing::Types< + std::map<int, std::string>, std::unordered_map<int, std::string>, + absl::flat_hash_map<int, std::string>, + absl::node_hash_map<int, std::string>, absl::btree_map<int, std::string>, + UnorderedSequence<std::pair<const int, std::string>>>; +INSTANTIATE_TYPED_TEST_SUITE_P(My, HashValueAssociativeMapTest, + AssociativeMapTypes); + +template <typename T> +class HashValueAssociativeMultimapTest : public testing::Test {}; +TYPED_TEST_SUITE_P(HashValueAssociativeMultimapTest); + +TYPED_TEST_P(HashValueAssociativeMultimapTest, BasicUsage) { + using MM = TypeParam; + using V = typename MM::value_type; + std::vector<MM> exemplars{MM{}, + MM{V{0, "foo"}}, + MM{V{1, "foo"}}, + MM{V{0, "bar"}}, + MM{V{1, "bar"}}, + MM{V{0, "foo"}, V{0, "bar"}}, + MM{V{0, "bar"}, V{0, "foo"}}, + MM{V{0, "foo"}, V{42, "bar"}}, + MM{V{1, "foo"}, V{42, "bar"}}, + MM{V{1, "foo"}, V{1, "foo"}, V{43, "bar"}}, + MM{V{1, "foo"}, V{43, "bar"}, V{1, "foo"}}, + MM{V{1, "foo"}, V{43, "baz"}}}; + EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(exemplars)); +} + +REGISTER_TYPED_TEST_SUITE_P(HashValueAssociativeMultimapTest, BasicUsage); +using AssociativeMultimapTypes = + testing::Types<std::multimap<int, std::string>, + std::unordered_multimap<int, std::string>>; +INSTANTIATE_TYPED_TEST_SUITE_P(My, HashValueAssociativeMultimapTest, + AssociativeMultimapTypes); + +} // namespace diff --git a/absl/hash/hash_test.cc b/absl/hash/hash_test.cc index 5b556180..59fe8dea 100644 --- a/absl/hash/hash_test.cc +++ b/absl/hash/hash_test.cc @@ -17,89 +17,50 @@ #include <algorithm> #include <array> #include <bitset> +#include <cstddef> +#include <cstdint> +#include <cstdlib> #include <cstring> -#include <deque> -#include <forward_list> #include <functional> #include <initializer_list> -#include <iterator> +#include <ios> #include <limits> -#include <list> -#include <map> #include <memory> -#include <numeric> -#include <random> +#include <ostream> #include <set> #include <string> #include <tuple> #include <type_traits> #include <unordered_map> -#include <unordered_set> #include <utility> #include <vector> -#include "gmock/gmock.h" #include "gtest/gtest.h" -#include "absl/container/btree_map.h" -#include "absl/container/btree_set.h" -#include "absl/container/flat_hash_map.h" +#include "absl/base/config.h" #include "absl/container/flat_hash_set.h" -#include "absl/container/node_hash_map.h" -#include "absl/container/node_hash_set.h" #include "absl/hash/hash_testing.h" +#include "absl/hash/internal/hash_test.h" #include "absl/hash/internal/spy_hash_state.h" +#include "absl/memory/memory.h" #include "absl/meta/type_traits.h" -#include "absl/numeric/int128.h" #include "absl/strings/cord_test_helpers.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "absl/types/variant.h" -namespace { - -// Utility wrapper of T for the purposes of testing the `AbslHash` type erasure -// mechanism. `TypeErasedValue<T>` can be constructed with a `T`, and can -// be compared and hashed. However, all hashing goes through the hashing -// type-erasure framework. -template <typename T> -class TypeErasedValue { - public: - TypeErasedValue() = default; - TypeErasedValue(const TypeErasedValue&) = default; - TypeErasedValue(TypeErasedValue&&) = default; - explicit TypeErasedValue(const T& n) : n_(n) {} - - template <typename H> - friend H AbslHashValue(H hash_state, const TypeErasedValue& v) { - v.HashValue(absl::HashState::Create(&hash_state)); - return hash_state; - } - - void HashValue(absl::HashState state) const { - absl::HashState::combine(std::move(state), n_); - } +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L +#include <filesystem> // NOLINT +#endif - bool operator==(const TypeErasedValue& rhs) const { return n_ == rhs.n_; } - bool operator!=(const TypeErasedValue& rhs) const { return !(*this == rhs); } +#ifdef ABSL_HAVE_STD_STRING_VIEW +#include <string_view> +#endif - private: - T n_; -}; +namespace { -// A TypeErasedValue refinement, for containers. It exposes the wrapped -// `value_type` and is constructible from an initializer list. -template <typename T> -class TypeErasedContainer : public TypeErasedValue<T> { - public: - using value_type = typename T::value_type; - TypeErasedContainer() = default; - TypeErasedContainer(const TypeErasedContainer&) = default; - TypeErasedContainer(TypeErasedContainer&&) = default; - explicit TypeErasedContainer(const T& n) : TypeErasedValue<T>(n) {} - TypeErasedContainer(std::initializer_list<value_type> init_list) - : TypeErasedContainer(T(init_list.begin(), init_list.end())) {} - // one-argument constructor of value type T, to appease older toolchains that - // get confused by one-element initializer lists in some contexts - explicit TypeErasedContainer(const value_type& v) - : TypeErasedContainer(T(&v, &v + 1)) {} -}; +using ::absl::hash_test_internal::is_hashable; +using ::absl::hash_test_internal::TypeErasedContainer; +using ::absl::hash_test_internal::TypeErasedValue; template <typename T> using TypeErasedVector = TypeErasedContainer<std::vector<T>>; @@ -117,11 +78,6 @@ SpyHashState SpyHash(const T& value) { return SpyHashState::combine(SpyHashState(), value); } -// Helper trait to verify if T is hashable. We use absl::Hash's poison status to -// detect it. -template <typename T> -using is_hashable = std::is_default_constructible<absl::Hash<T>>; - TYPED_TEST_P(HashValueIntTest, BasicUsage) { EXPECT_TRUE((is_hashable<TypeParam>::value)); @@ -487,6 +443,84 @@ TEST(HashValueTest, U32String) { std::u32string(U"Iñtërnâtiônà lizætiøn")))); } +TEST(HashValueTest, WStringView) { +#ifndef ABSL_HAVE_STD_STRING_VIEW + GTEST_SKIP(); +#else + EXPECT_TRUE((is_hashable<std::wstring_view>::value)); + + EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(std::make_tuple( + std::wstring_view(), std::wstring_view(L"ABC"), std::wstring_view(L"ABC"), + std::wstring_view(L"Some other different string_view"), + std::wstring_view(L"Iñtërnâtiônà lizætiøn")))); +#endif +} + +TEST(HashValueTest, U16StringView) { +#ifndef ABSL_HAVE_STD_STRING_VIEW + GTEST_SKIP(); +#else + EXPECT_TRUE((is_hashable<std::u16string_view>::value)); + + EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly( + std::make_tuple(std::u16string_view(), std::u16string_view(u"ABC"), + std::u16string_view(u"ABC"), + std::u16string_view(u"Some other different string_view"), + std::u16string_view(u"Iñtërnâtiônà lizætiøn")))); +#endif +} + +TEST(HashValueTest, U32StringView) { +#ifndef ABSL_HAVE_STD_STRING_VIEW + GTEST_SKIP(); +#else + EXPECT_TRUE((is_hashable<std::u32string_view>::value)); + + EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly( + std::make_tuple(std::u32string_view(), std::u32string_view(U"ABC"), + std::u32string_view(U"ABC"), + std::u32string_view(U"Some other different string_view"), + std::u32string_view(U"Iñtërnâtiônà lizætiøn")))); +#endif +} + +TEST(HashValueTest, StdFilesystemPath) { +#ifndef ABSL_INTERNAL_STD_FILESYSTEM_PATH_HASH_AVAILABLE + GTEST_SKIP() << "std::filesystem::path is unavailable on this platform"; +#else + EXPECT_TRUE((is_hashable<std::filesystem::path>::value)); + + // clang-format off + const auto kTestCases = std::make_tuple( + std::filesystem::path(), + std::filesystem::path("/"), +#ifndef __GLIBCXX__ + // libstdc++ has a known issue normalizing "//". + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=106452 + std::filesystem::path("//"), +#endif + std::filesystem::path("/a/b"), + std::filesystem::path("/a//b"), + std::filesystem::path("a/b"), + std::filesystem::path("a/b/"), + std::filesystem::path("a//b"), + std::filesystem::path("a//b/"), + std::filesystem::path("c:/"), + std::filesystem::path("c:\\"), + std::filesystem::path("c:\\/"), + std::filesystem::path("c:\\//"), + std::filesystem::path("c://"), + std::filesystem::path("c://\\"), + std::filesystem::path("/e/p"), + std::filesystem::path("/s/../e/p"), + std::filesystem::path("e/p"), + std::filesystem::path("s/../e/p")); + // clang-format on + + EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(kTestCases)); +#endif +} + TEST(HashValueTest, StdArray) { EXPECT_TRUE((is_hashable<std::array<int, 3>>::value)); @@ -520,121 +554,6 @@ TEST(HashValueTest, StdBitset) { std::bitset<kNumBits>(bit_strings[5].c_str())})); } // namespace -// Dummy type with unordered equality and hashing semantics. This preserves -// input order internally, and is used below to ensure we get test coverage -// for equal sequences with different iteraton orders. -template <typename T> -class UnorderedSequence { - public: - UnorderedSequence() = default; - template <typename TT> - UnorderedSequence(std::initializer_list<TT> l) - : values_(l.begin(), l.end()) {} - template <typename ForwardIterator, - typename std::enable_if<!std::is_integral<ForwardIterator>::value, - bool>::type = true> - UnorderedSequence(ForwardIterator begin, ForwardIterator end) - : values_(begin, end) {} - // one-argument constructor of value type T, to appease older toolchains that - // get confused by one-element initializer lists in some contexts - explicit UnorderedSequence(const T& v) : values_(&v, &v + 1) {} - - using value_type = T; - - size_t size() const { return values_.size(); } - typename std::vector<T>::const_iterator begin() const { - return values_.begin(); - } - typename std::vector<T>::const_iterator end() const { return values_.end(); } - - friend bool operator==(const UnorderedSequence& lhs, - const UnorderedSequence& rhs) { - return lhs.size() == rhs.size() && - std::is_permutation(lhs.begin(), lhs.end(), rhs.begin()); - } - friend bool operator!=(const UnorderedSequence& lhs, - const UnorderedSequence& rhs) { - return !(lhs == rhs); - } - template <typename H> - friend H AbslHashValue(H h, const UnorderedSequence& u) { - return H::combine(H::combine_unordered(std::move(h), u.begin(), u.end()), - u.size()); - } - - private: - std::vector<T> values_; -}; - -template <typename T> -class HashValueSequenceTest : public testing::Test { -}; -TYPED_TEST_SUITE_P(HashValueSequenceTest); - -TYPED_TEST_P(HashValueSequenceTest, BasicUsage) { - EXPECT_TRUE((is_hashable<TypeParam>::value)); - - using IntType = typename TypeParam::value_type; - auto a = static_cast<IntType>(0); - auto b = static_cast<IntType>(23); - auto c = static_cast<IntType>(42); - - std::vector<TypeParam> exemplars = { - TypeParam(), TypeParam(), TypeParam{a, b, c}, - TypeParam{a, c, b}, TypeParam{c, a, b}, TypeParam{a}, - TypeParam{a, a}, TypeParam{a, a, a}, TypeParam{a, a, b}, - TypeParam{a, b, a}, TypeParam{b, a, a}, TypeParam{a, b}, - TypeParam{b, c}}; - EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(exemplars)); -} - -REGISTER_TYPED_TEST_SUITE_P(HashValueSequenceTest, BasicUsage); -using IntSequenceTypes = testing::Types< - std::deque<int>, std::forward_list<int>, std::list<int>, std::vector<int>, - std::vector<bool>, TypeErasedContainer<std::vector<int>>, std::set<int>, - std::multiset<int>, UnorderedSequence<int>, - TypeErasedContainer<UnorderedSequence<int>>, std::unordered_set<int>, - std::unordered_multiset<int>, absl::flat_hash_set<int>, - absl::node_hash_set<int>, absl::btree_set<int>>; -INSTANTIATE_TYPED_TEST_SUITE_P(My, HashValueSequenceTest, IntSequenceTypes); - -template <typename T> -class HashValueNestedSequenceTest : public testing::Test {}; -TYPED_TEST_SUITE_P(HashValueNestedSequenceTest); - -TYPED_TEST_P(HashValueNestedSequenceTest, BasicUsage) { - using T = TypeParam; - using V = typename T::value_type; - std::vector<T> exemplars = { - // empty case - T{}, - // sets of empty sets - T{V{}}, T{V{}, V{}}, T{V{}, V{}, V{}}, - // multisets of different values - T{V{1}}, T{V{1, 1}, V{1, 1}}, T{V{1, 1, 1}, V{1, 1, 1}, V{1, 1, 1}}, - // various orderings of same nested sets - T{V{}, V{1, 2}}, T{V{}, V{2, 1}}, T{V{1, 2}, V{}}, T{V{2, 1}, V{}}, - // various orderings of various nested sets, case 2 - T{V{1, 2}, V{3, 4}}, T{V{1, 2}, V{4, 3}}, T{V{1, 3}, V{2, 4}}, - T{V{1, 3}, V{4, 2}}, T{V{1, 4}, V{2, 3}}, T{V{1, 4}, V{3, 2}}, - T{V{2, 3}, V{1, 4}}, T{V{2, 3}, V{4, 1}}, T{V{2, 4}, V{1, 3}}, - T{V{2, 4}, V{3, 1}}, T{V{3, 4}, V{1, 2}}, T{V{3, 4}, V{2, 1}}}; - EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(exemplars)); -} - -REGISTER_TYPED_TEST_SUITE_P(HashValueNestedSequenceTest, BasicUsage); -template <typename T> -using TypeErasedSet = TypeErasedContainer<UnorderedSequence<T>>; - -using NestedIntSequenceTypes = testing::Types< - std::vector<std::vector<int>>, std::vector<UnorderedSequence<int>>, - std::vector<TypeErasedSet<int>>, UnorderedSequence<std::vector<int>>, - UnorderedSequence<UnorderedSequence<int>>, - UnorderedSequence<TypeErasedSet<int>>, TypeErasedSet<std::vector<int>>, - TypeErasedSet<UnorderedSequence<int>>, TypeErasedSet<TypeErasedSet<int>>>; -INSTANTIATE_TYPED_TEST_SUITE_P(My, HashValueNestedSequenceTest, - NestedIntSequenceTypes); - // Private type that only supports AbslHashValue to make sure our chosen hash // implementation is recursive within absl::Hash. // It uses std::abs() on the value to provide different bitwise representations @@ -793,64 +712,6 @@ TEST(HashValueTest, Variant) { #endif } -template <typename T> -class HashValueAssociativeMapTest : public testing::Test {}; -TYPED_TEST_SUITE_P(HashValueAssociativeMapTest); - -TYPED_TEST_P(HashValueAssociativeMapTest, BasicUsage) { - using M = TypeParam; - using V = typename M::value_type; - std::vector<M> exemplars{M{}, - M{V{0, "foo"}}, - M{V{1, "foo"}}, - M{V{0, "bar"}}, - M{V{1, "bar"}}, - M{V{0, "foo"}, V{42, "bar"}}, - M{V{42, "bar"}, V{0, "foo"}}, - M{V{1, "foo"}, V{42, "bar"}}, - M{V{1, "foo"}, V{43, "bar"}}, - M{V{1, "foo"}, V{43, "baz"}}}; - EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(exemplars)); -} - -REGISTER_TYPED_TEST_SUITE_P(HashValueAssociativeMapTest, BasicUsage); -using AssociativeMapTypes = testing::Types< - std::map<int, std::string>, std::unordered_map<int, std::string>, - absl::flat_hash_map<int, std::string>, - absl::node_hash_map<int, std::string>, absl::btree_map<int, std::string>, - UnorderedSequence<std::pair<const int, std::string>>>; -INSTANTIATE_TYPED_TEST_SUITE_P(My, HashValueAssociativeMapTest, - AssociativeMapTypes); - -template <typename T> -class HashValueAssociativeMultimapTest : public testing::Test {}; -TYPED_TEST_SUITE_P(HashValueAssociativeMultimapTest); - -TYPED_TEST_P(HashValueAssociativeMultimapTest, BasicUsage) { - using MM = TypeParam; - using V = typename MM::value_type; - std::vector<MM> exemplars{MM{}, - MM{V{0, "foo"}}, - MM{V{1, "foo"}}, - MM{V{0, "bar"}}, - MM{V{1, "bar"}}, - MM{V{0, "foo"}, V{0, "bar"}}, - MM{V{0, "bar"}, V{0, "foo"}}, - MM{V{0, "foo"}, V{42, "bar"}}, - MM{V{1, "foo"}, V{42, "bar"}}, - MM{V{1, "foo"}, V{1, "foo"}, V{43, "bar"}}, - MM{V{1, "foo"}, V{43, "bar"}, V{1, "foo"}}, - MM{V{1, "foo"}, V{43, "baz"}}}; - EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(exemplars)); -} - -REGISTER_TYPED_TEST_SUITE_P(HashValueAssociativeMultimapTest, BasicUsage); -using AssociativeMultimapTypes = - testing::Types<std::multimap<int, std::string>, - std::unordered_multimap<int, std::string>>; -INSTANTIATE_TYPED_TEST_SUITE_P(My, HashValueAssociativeMultimapTest, - AssociativeMultimapTypes); - TEST(HashValueTest, ReferenceWrapper) { EXPECT_TRUE(is_hashable<std::reference_wrapper<Private>>::value); @@ -1241,14 +1102,24 @@ TEST(HashTest, DoesNotUseImplicitConversionsToBool) { TEST(HashOf, MatchesHashForSingleArgument) { std::string s = "forty two"; - int i = 42; double d = 42.0; std::tuple<int, int> t{4, 2}; + int i = 42; + int neg_i = -42; + int16_t i16 = 42; + int16_t neg_i16 = -42; + int8_t i8 = 42; + int8_t neg_i8 = -42; EXPECT_EQ(absl::HashOf(s), absl::Hash<std::string>{}(s)); - EXPECT_EQ(absl::HashOf(i), absl::Hash<int>{}(i)); EXPECT_EQ(absl::HashOf(d), absl::Hash<double>{}(d)); EXPECT_EQ(absl::HashOf(t), (absl::Hash<std::tuple<int, int>>{}(t))); + EXPECT_EQ(absl::HashOf(i), absl::Hash<int>{}(i)); + EXPECT_EQ(absl::HashOf(neg_i), absl::Hash<int>{}(neg_i)); + EXPECT_EQ(absl::HashOf(i16), absl::Hash<int16_t>{}(i16)); + EXPECT_EQ(absl::HashOf(neg_i16), absl::Hash<int16_t>{}(neg_i16)); + EXPECT_EQ(absl::HashOf(i8), absl::Hash<int8_t>{}(i8)); + EXPECT_EQ(absl::HashOf(neg_i8), absl::Hash<int8_t>{}(neg_i8)); } TEST(HashOf, MatchesHashOfTupleForMultipleArguments) { diff --git a/absl/hash/internal/hash.h b/absl/hash/internal/hash.h index ccf4cc1a..f4a94f91 100644 --- a/absl/hash/internal/hash.h +++ b/absl/hash/internal/hash.h @@ -19,6 +19,11 @@ #ifndef ABSL_HASH_INTERNAL_HASH_H_ #define ABSL_HASH_INTERNAL_HASH_H_ +#ifdef __APPLE__ +#include <Availability.h> +#include <TargetConditionals.h> +#endif + #include <algorithm> #include <array> #include <bitset> @@ -56,6 +61,15 @@ #include "absl/types/variant.h" #include "absl/utility/utility.h" +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L && \ + !defined(_LIBCPP_HAS_NO_FILESYSTEM_LIBRARY) +#include <filesystem> // NOLINT +#endif + +#ifdef ABSL_HAVE_STD_STRING_VIEW +#include <string_view> +#endif + namespace absl { ABSL_NAMESPACE_BEGIN @@ -405,9 +419,23 @@ AbslHashValue(H hash_state, LongDouble value) { return H::combine(std::move(hash_state), category); } +// Without this overload, an array decays to a pointer and we hash that, which +// is not likely to be what the caller intended. +template <typename H, typename T, size_t N> +H AbslHashValue(H hash_state, T (&)[N]) { + static_assert( + sizeof(T) == -1, + "Hashing C arrays is not allowed. For string literals, wrap the literal " + "in absl::string_view(). To hash the array contents, use " + "absl::MakeSpan() or make the array an std::array. To hash the array " + "address, use &array[0]."); + return hash_state; +} + // AbslHashValue() for hashing pointers template <typename H, typename T> -H AbslHashValue(H hash_state, T* ptr) { +std::enable_if_t<std::is_pointer<T>::value, H> AbslHashValue(H hash_state, + T ptr) { auto v = reinterpret_cast<uintptr_t>(ptr); // Due to alignment, pointers tend to have low bits as zero, and the next few // bits follow a pattern since they are also multiples of some base value. @@ -424,7 +452,7 @@ H AbslHashValue(H hash_state, std::nullptr_t) { // AbslHashValue() for hashing pointers-to-member template <typename H, typename T, typename C> -H AbslHashValue(H hash_state, T C::* ptr) { +H AbslHashValue(H hash_state, T C::*ptr) { auto salient_ptm_size = [](std::size_t n) -> std::size_t { #if defined(_MSC_VER) // Pointers-to-member-function on MSVC consist of one pointer plus 0, 1, 2, @@ -442,8 +470,8 @@ H AbslHashValue(H hash_state, T C::* ptr) { return n == 24 ? 20 : n == 16 ? 12 : n; } #else - // On other platforms, we assume that pointers-to-members do not have - // padding. + // On other platforms, we assume that pointers-to-members do not have + // padding. #ifdef __cpp_lib_has_unique_object_representations static_assert(std::has_unique_object_representations<T C::*>::value); #endif // __cpp_lib_has_unique_object_representations @@ -516,14 +544,15 @@ H AbslHashValue(H hash_state, const std::shared_ptr<T>& ptr) { // the same character sequence. These types are: // // - `absl::Cord` -// - `std::string` (and std::basic_string<char, std::char_traits<char>, A> for -// any allocator A) -// - `absl::string_view` and `std::string_view` +// - `std::string` (and std::basic_string<T, std::char_traits<T>, A> for +// any allocator A and any T in {char, wchar_t, char16_t, char32_t}) +// - `absl::string_view`, `std::string_view`, `std::wstring_view`, +// `std::u16string_view`, and `std::u32_string_view`. // -// For simplicity, we currently support only `char` strings. This support may -// be broadened, if necessary, but with some caution - this overload would -// misbehave in cases where the traits' `eq()` member isn't equivalent to `==` -// on the underlying character type. +// For simplicity, we currently support only strings built on `char`, `wchar_t`, +// `char16_t`, or `char32_t`. This support may be broadened, if necessary, but +// with some caution - this overload would misbehave in cases where the traits' +// `eq()` member isn't equivalent to `==` on the underlying character type. template <typename H> H AbslHashValue(H hash_state, absl::string_view str) { return H::combine( @@ -544,6 +573,44 @@ H AbslHashValue( str.size()); } +#ifdef ABSL_HAVE_STD_STRING_VIEW + +// Support std::wstring_view, std::u16string_view and std::u32string_view. +template <typename Char, typename H, + typename = absl::enable_if_t<std::is_same<Char, wchar_t>::value || + std::is_same<Char, char16_t>::value || + std::is_same<Char, char32_t>::value>> +H AbslHashValue(H hash_state, std::basic_string_view<Char> str) { + return H::combine( + H::combine_contiguous(std::move(hash_state), str.data(), str.size()), + str.size()); +} + +#endif // ABSL_HAVE_STD_STRING_VIEW + +#if defined(__cpp_lib_filesystem) && __cpp_lib_filesystem >= 201703L && \ + !defined(_LIBCPP_HAS_NO_FILESYSTEM_LIBRARY) && \ + (!defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__) || \ + __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ >= 130000) + +#define ABSL_INTERNAL_STD_FILESYSTEM_PATH_HASH_AVAILABLE 1 + +// Support std::filesystem::path. The SFINAE is required because some string +// types are implicitly convertible to std::filesystem::path. +template <typename Path, typename H, + typename = absl::enable_if_t< + std::is_same_v<Path, std::filesystem::path>>> +H AbslHashValue(H hash_state, const Path& path) { + // This is implemented by deferring to the standard library to compute the + // hash. The standard library requires that for two paths, `p1 == p2`, then + // `hash_value(p1) == hash_value(p2)`. `AbslHashValue` has the same + // requirement. Since `operator==` does platform specific matching, deferring + // to the standard library is the simplest approach. + return H::combine(std::move(hash_state), std::filesystem::hash_value(path)); +} + +#endif // ABSL_INTERNAL_STD_FILESYSTEM_PATH_HASH_AVAILABLE + // ----------------------------------------------------------------------------- // AbslHashValue for Sequence Containers // ----------------------------------------------------------------------------- @@ -798,7 +865,7 @@ AbslHashValue(H hash_state, const absl::variant<T...>& v) { template <typename H, size_t N> H AbslHashValue(H hash_state, const std::bitset<N>& set) { typename H::AbslInternalPiecewiseCombiner combiner; - for (int i = 0; i < N; i++) { + for (size_t i = 0; i < N; i++) { unsigned char c = static_cast<unsigned char>(set[i]); hash_state = combiner.add_buffer(std::move(hash_state), &c, sizeof(c)); } @@ -935,8 +1002,8 @@ class ABSL_DLL MixingHashState : public HashStateBase<MixingHashState> { #endif // ABSL_HAVE_INTRINSIC_INT128 static constexpr uint64_t kMul = - sizeof(size_t) == 4 ? uint64_t{0xcc9e2d51} - : uint64_t{0x9ddfea08eb382d69}; + sizeof(size_t) == 4 ? uint64_t{0xcc9e2d51} + : uint64_t{0x9ddfea08eb382d69}; template <typename T> using IntegralFastPath = @@ -969,7 +1036,8 @@ class ABSL_DLL MixingHashState : public HashStateBase<MixingHashState> { // The result should be the same as running the whole algorithm, but faster. template <typename T, absl::enable_if_t<IntegralFastPath<T>::value, int> = 0> static size_t hash(T value) { - return static_cast<size_t>(Mix(Seed(), static_cast<uint64_t>(value))); + return static_cast<size_t>( + Mix(Seed(), static_cast<std::make_unsigned_t<T>>(value))); } // Overload of MixingHashState::hash() @@ -1073,6 +1141,7 @@ class ABSL_DLL MixingHashState : public HashStateBase<MixingHashState> { // Reads 1 to 3 bytes from p. Zero pads to fill uint32_t. static uint32_t Read1To3(const unsigned char* p, size_t len) { + // The trick used by this implementation is to avoid branches if possible. unsigned char mem0 = p[0]; unsigned char mem1 = p[len / 2]; unsigned char mem2 = p[len - 1]; @@ -1082,7 +1151,7 @@ class ABSL_DLL MixingHashState : public HashStateBase<MixingHashState> { unsigned char significant0 = mem0; #else unsigned char significant2 = mem0; - unsigned char significant1 = mem1; + unsigned char significant1 = len == 2 ? mem0 : mem1; unsigned char significant0 = mem2; #endif return static_cast<uint32_t>(significant0 | // @@ -1135,7 +1204,8 @@ class ABSL_DLL MixingHashState : public HashStateBase<MixingHashState> { // probably per-build and not per-process. ABSL_ATTRIBUTE_ALWAYS_INLINE static uint64_t Seed() { #if (!defined(__clang__) || __clang_major__ > 11) && \ - !defined(__apple_build_version__) + (!defined(__apple_build_version__) || \ + __apple_build_version__ >= 19558921) // Xcode 12 return static_cast<uint64_t>(reinterpret_cast<uintptr_t>(&kSeed)); #else // Workaround the absence of diff --git a/absl/hash/internal/hash_test.h b/absl/hash/internal/hash_test.h new file mode 100644 index 00000000..9963dc0b --- /dev/null +++ b/absl/hash/internal/hash_test.h @@ -0,0 +1,87 @@ +// Copyright 2023 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. + +// Common code shared between absl/hash/hash_test.cc and +// absl/hash/hash_instantiated_test.cc. + +#ifndef ABSL_HASH_INTERNAL_HASH_TEST_H_ +#define ABSL_HASH_INTERNAL_HASH_TEST_H_ + +#include <type_traits> +#include <utility> + +#include "absl/base/config.h" +#include "absl/hash/hash.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace hash_test_internal { + +// Utility wrapper of T for the purposes of testing the `AbslHash` type erasure +// mechanism. `TypeErasedValue<T>` can be constructed with a `T`, and can +// be compared and hashed. However, all hashing goes through the hashing +// type-erasure framework. +template <typename T> +class TypeErasedValue { + public: + TypeErasedValue() = default; + TypeErasedValue(const TypeErasedValue&) = default; + TypeErasedValue(TypeErasedValue&&) = default; + explicit TypeErasedValue(const T& n) : n_(n) {} + + template <typename H> + friend H AbslHashValue(H hash_state, const TypeErasedValue& v) { + v.HashValue(absl::HashState::Create(&hash_state)); + return hash_state; + } + + void HashValue(absl::HashState state) const { + absl::HashState::combine(std::move(state), n_); + } + + bool operator==(const TypeErasedValue& rhs) const { return n_ == rhs.n_; } + bool operator!=(const TypeErasedValue& rhs) const { return !(*this == rhs); } + + private: + T n_; +}; + +// A TypeErasedValue refinement, for containers. It exposes the wrapped +// `value_type` and is constructible from an initializer list. +template <typename T> +class TypeErasedContainer : public TypeErasedValue<T> { + public: + using value_type = typename T::value_type; + TypeErasedContainer() = default; + TypeErasedContainer(const TypeErasedContainer&) = default; + TypeErasedContainer(TypeErasedContainer&&) = default; + explicit TypeErasedContainer(const T& n) : TypeErasedValue<T>(n) {} + TypeErasedContainer(std::initializer_list<value_type> init_list) + : TypeErasedContainer(T(init_list.begin(), init_list.end())) {} + // one-argument constructor of value type T, to appease older toolchains that + // get confused by one-element initializer lists in some contexts + explicit TypeErasedContainer(const value_type& v) + : TypeErasedContainer(T(&v, &v + 1)) {} +}; + +// Helper trait to verify if T is hashable. We use absl::Hash's poison status to +// detect it. +template <typename T> +using is_hashable = std::is_default_constructible<absl::Hash<T>>; + +} // namespace hash_test_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_HASH_INTERNAL_HASH_TEST_H_ diff --git a/absl/hash/internal/low_level_hash.cc b/absl/hash/internal/low_level_hash.cc index c917457a..b5db0b89 100644 --- a/absl/hash/internal/low_level_hash.cc +++ b/absl/hash/internal/low_level_hash.cc @@ -15,6 +15,7 @@ #include "absl/hash/internal/low_level_hash.h" #include "absl/base/internal/unaligned_access.h" +#include "absl/base/prefetch.h" #include "absl/numeric/int128.h" namespace absl { @@ -29,6 +30,8 @@ static uint64_t Mix(uint64_t v0, uint64_t v1) { uint64_t LowLevelHash(const void* data, size_t len, uint64_t seed, const uint64_t salt[5]) { + // Prefetch the cacheline that data resides in. + PrefetchToLocalCache(data); const uint8_t* ptr = static_cast<const uint8_t*>(data); uint64_t starting_length = static_cast<uint64_t>(len); uint64_t current_state = seed ^ salt[0]; @@ -40,6 +43,9 @@ uint64_t LowLevelHash(const void* data, size_t len, uint64_t seed, uint64_t duplicated_state = current_state; do { + // Always prefetch the next cacheline. + PrefetchToLocalCache(ptr + ABSL_CACHELINE_SIZE); + uint64_t a = absl::base_internal::UnalignedLoad64(ptr); uint64_t b = absl::base_internal::UnalignedLoad64(ptr + 8); uint64_t c = absl::base_internal::UnalignedLoad64(ptr + 16); diff --git a/absl/log/BUILD.bazel b/absl/log/BUILD.bazel index e9e411ff..40e87cc9 100644 --- a/absl/log/BUILD.bazel +++ b/absl/log/BUILD.bazel @@ -21,7 +21,14 @@ load( "ABSL_TEST_COPTS", ) -package(default_visibility = ["//visibility:public"]) +package( + default_visibility = ["//visibility:public"], + features = [ + "header_modules", + "layering_check", + "parse_headers", + ], +) licenses(["notice"]) @@ -91,6 +98,7 @@ cc_library( "//absl/flags:marshalling", "//absl/log/internal:config", "//absl/log/internal:flags", + "//absl/log/internal:vlog_config", "//absl/strings", ], # Binaries which do not access these flags from C++ still want this library linked in. @@ -109,7 +117,9 @@ cc_library( "//absl/base:config", "//absl/base:core_headers", "//absl/base:log_severity", + "//absl/base:raw_logging_internal", "//absl/hash", + "//absl/log/internal:vlog_config", "//absl/strings", ], ) @@ -135,6 +145,7 @@ cc_library( copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ + ":vlog_is_on", "//absl/log/internal:log_impl", ], ) @@ -227,6 +238,58 @@ cc_library( ], ) +cc_library( + name = "absl_vlog_is_on", + hdrs = ["absl_vlog_is_on.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/log:__subpackages__", + ], + deps = [ + "//absl/base:config", + "//absl/base:core_headers", + "//absl/log/internal:vlog_config", + "//absl/strings", + ], +) + +cc_library( + name = "vlog_is_on", + hdrs = ["vlog_is_on.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/log:__subpackages__", + ], + deps = [ + ":absl_vlog_is_on", + ], +) + +# TODO(b/200695798): run this in TAP projects with -DABSL_MAX_VLOG_VERBOSITY={-100,100} +cc_test( + name = "vlog_is_on_test", + size = "small", + srcs = [ + "vlog_is_on_test.cc", + ], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":flags", + ":globals", + ":log", + ":scoped_mock_log", + ":vlog_is_on", + "//absl/base:log_severity", + "//absl/flags:flag", + "//absl/types:optional", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) + # Test targets cc_test( @@ -243,6 +306,7 @@ cc_test( deps = [ ":absl_check", ":check_test_impl", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -256,6 +320,7 @@ cc_test( deps = [ ":absl_log", ":log_basic_test_impl", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -274,6 +339,7 @@ cc_test( deps = [ ":check", ":check_test_impl", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -288,7 +354,7 @@ cc_library( "no_test_ios", "no_test_wasm", ], - textual_hdrs = ["check_test_impl.h"], + textual_hdrs = ["check_test_impl.inc"], visibility = ["//visibility:private"], deps = [ "//absl/base:config", @@ -309,6 +375,7 @@ cc_test( ":die_if_null", "//absl/base:core_headers", "//absl/log/internal:test_helpers", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -332,6 +399,7 @@ cc_test( "//absl/log/internal:test_helpers", "//absl/log/internal:test_matchers", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -350,6 +418,7 @@ cc_test( "//absl/base:log_severity", "//absl/log/internal:globals", "//absl/log/internal:test_helpers", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -363,6 +432,7 @@ cc_test( deps = [ ":log", ":log_basic_test_impl", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -372,7 +442,7 @@ cc_library( testonly = True, copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, - textual_hdrs = ["log_basic_test_impl.h"], + textual_hdrs = ["log_basic_test_impl.inc"], visibility = ["//visibility:private"], deps = [ "//absl/base", @@ -404,6 +474,7 @@ cc_test( "//absl/strings", "//absl/time", "//absl/types:span", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -422,6 +493,7 @@ cc_test( "//absl/strings", "//absl/strings:str_format", "//absl/types:optional", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -437,6 +509,7 @@ cc_test( ":scoped_mock_log", "//absl/base:core_headers", "//absl/base:log_severity", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -458,11 +531,11 @@ cc_test( ":log_sink_registry", ":scoped_mock_log", "//absl/base:core_headers", - "//absl/base:raw_logging_internal", "//absl/log/internal:test_actions", "//absl/log/internal:test_helpers", "//absl/log/internal:test_matchers", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -484,6 +557,7 @@ cc_test( "//absl/log/internal:test_helpers", "//absl/log/internal:test_matchers", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -503,6 +577,7 @@ cc_test( "//absl/log/internal:test_matchers", "//absl/strings", "//absl/time", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -530,6 +605,7 @@ cc_test( "//absl/memory", "//absl/strings", "//absl/synchronization", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -546,11 +622,14 @@ cc_test( deps = [ ":check", ":log", + "//absl/base:log_severity", "//absl/base:strerror", "//absl/flags:program_name", "//absl/log/internal:test_helpers", + "//absl/status", "//absl/strings", "//absl/strings:str_format", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -568,13 +647,14 @@ cc_test( "//absl/base:core_headers", "//absl/log/internal:test_helpers", "//absl/log/internal:test_matchers", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) -cc_binary( +cc_test( name = "log_benchmark", - testonly = 1, + size = "small", srcs = ["log_benchmark.cc"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, diff --git a/absl/log/CMakeLists.txt b/absl/log/CMakeLists.txt index fb1b59f5..a7d8b690 100644 --- a/absl/log/CMakeLists.txt +++ b/absl/log/CMakeLists.txt @@ -1,4 +1,3 @@ -# # Copyright 2022 The Abseil Authors. # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -158,6 +157,7 @@ absl_cc_library( absl::log_internal_conditions absl::log_internal_message absl::log_internal_strip + absl::absl_vlog_is_on ) absl_cc_library( @@ -239,6 +239,7 @@ absl_cc_library( absl::log_entry absl::log_severity absl::log_sink + absl::no_destructor absl::raw_logging_internal absl::synchronization absl::span @@ -460,6 +461,11 @@ absl_cc_library( PUBLIC ) +# Warning: Many linkers will strip the contents of this library because its +# symbols are only used in a global constructor. A workaround is for clients +# to link this using $<LINK_LIBRARY:WHOLE_ARCHIVE,absl::log_flags> instead of +# the plain absl::log_flags. +# TODO(b/320467376): Implement the equivalent of Bazel's alwayslink=True. absl_cc_library( NAME log_flags @@ -481,6 +487,7 @@ absl_cc_library( absl::flags absl::flags_marshalling absl::strings + absl::vlog_config_internal PUBLIC ) @@ -501,7 +508,9 @@ absl_cc_library( absl::core_headers absl::hash absl::log_severity + absl::raw_logging_internal absl::strings + absl::vlog_config_internal ) absl_cc_library( @@ -535,6 +544,7 @@ absl_cc_library( ${ABSL_DEFAULT_LINKOPTS} DEPS absl::log_internal_log_impl + absl::vlog_is_on PUBLIC ) @@ -671,6 +681,95 @@ absl_cc_library( PUBLIC ) +absl_cc_library( + NAME + vlog_config_internal + SRCS + "internal/vlog_config.cc" + HDRS + "internal/vlog_config.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::base + absl::config + absl::core_headers + absl::log_internal_fnmatch + absl::memory + absl::no_destructor + absl::strings + absl::synchronization + absl::optional +) + +absl_cc_library( + NAME + absl_vlog_is_on + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + HDRS + "absl_vlog_is_on.h" + DEPS + absl::vlog_config_internal + absl::config + absl::core_headers + absl::strings +) + +absl_cc_library( + NAME + vlog_is_on + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + HDRS + "vlog_is_on.h" + DEPS + absl::absl_vlog_is_on +) + +absl_cc_test( + NAME + vlog_is_on_test + SRCS + "vlog_is_on_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::log + absl::log_flags + absl::log_globals + absl::scoped_mock_log + absl::vlog_is_on + absl::log_severity + absl::flags + absl::optional + GTest::gmock_main +) + +absl_cc_library( + NAME + log_internal_fnmatch + SRCS + "internal/fnmatch.cc" + HDRS + "internal/fnmatch.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::strings +) + # Test targets absl_cc_test( @@ -678,7 +777,7 @@ absl_cc_test( absl_check_test SRCS "absl_check_test.cc" - "check_test_impl.h" + "check_test_impl.inc" COPTS ${ABSL_TEST_COPTS} LINKOPTS @@ -689,8 +788,7 @@ absl_cc_test( absl::core_headers absl::log_internal_test_helpers absl::status - GTest::gmock - GTest::gtest_main + GTest::gmock_main ) absl_cc_test( @@ -698,7 +796,7 @@ absl_cc_test( absl_log_basic_test SRCS "log_basic_test.cc" - "log_basic_test_impl.h" + "log_basic_test_impl.inc" COPTS ${ABSL_TEST_COPTS} LINKOPTS @@ -713,8 +811,7 @@ absl_cc_test( absl::log_internal_test_helpers absl::log_internal_test_matchers absl::scoped_mock_log - GTest::gmock - GTest::gtest_main + GTest::gmock_main ) absl_cc_test( @@ -722,7 +819,7 @@ absl_cc_test( check_test SRCS "check_test.cc" - "check_test_impl.h" + "check_test_impl.inc" COPTS ${ABSL_TEST_COPTS} LINKOPTS @@ -733,8 +830,7 @@ absl_cc_test( absl::core_headers absl::log_internal_test_helpers absl::status - GTest::gmock - GTest::gtest_main + GTest::gmock_main ) absl_cc_test( @@ -758,7 +854,7 @@ absl_cc_test( log_basic_test SRCS "log_basic_test.cc" - "log_basic_test_impl.h" + "log_basic_test_impl.inc" COPTS ${ABSL_TEST_COPTS} LINKOPTS @@ -773,8 +869,7 @@ absl_cc_test( absl::log_internal_test_helpers absl::log_internal_test_matchers absl::scoped_mock_log - GTest::gmock - GTest::gtest_main + GTest::gmock_main ) absl_cc_test( @@ -798,8 +893,7 @@ absl_cc_test( absl::span absl::strings absl::time - GTest::gmock - GTest::gtest_main + GTest::gmock_main ) absl_cc_test( @@ -824,8 +918,7 @@ absl_cc_test( absl::flags_reflection absl::scoped_mock_log absl::strings - GTest::gmock - GTest::gtest_main + GTest::gmock_main ) absl_cc_test( @@ -845,7 +938,7 @@ absl_cc_test( absl::log_internal_test_helpers absl::log_severity absl::scoped_mock_log - GTest::gtest_main + GTest::gmock_main ) absl_cc_test( @@ -865,8 +958,7 @@ absl_cc_test( absl::scoped_mock_log absl::str_format absl::strings - GTest::gmock - GTest::gtest_main + GTest::gmock_main ) absl_cc_test( @@ -883,8 +975,7 @@ absl_cc_test( absl::log absl::log_severity absl::scoped_mock_log - GTest::gmock - GTest::gtest_main + GTest::gmock_main ) absl_cc_test( @@ -905,10 +996,9 @@ absl_cc_test( absl::log_sink absl::log_sink_registry absl::log_severity - absl::raw_logging_internal absl::scoped_mock_log absl::strings - GTest::gtest_main + GTest::gmock_main ) absl_cc_test( @@ -931,7 +1021,7 @@ absl_cc_test( absl::log_severity absl::scoped_mock_log absl::strings - GTest::gtest_main + GTest::gmock_main ) absl_cc_test( @@ -952,8 +1042,7 @@ absl_cc_test( absl::scoped_mock_log absl::strings absl::time - GTest::gmock - GTest::gtest_main + GTest::gmock_main ) absl_cc_test( @@ -996,8 +1085,7 @@ absl_cc_test( absl::log_globals absl::log_internal_test_helpers absl::log_severity - GTest::gmock - GTest::gtest_main + GTest::gmock_main ) absl_cc_test( @@ -1014,11 +1102,12 @@ absl_cc_test( absl::flags_program_name absl::log absl::log_internal_test_helpers + absl::log_severity + absl::status absl::strerror absl::strings absl::str_format - GTest::gmock - GTest::gtest_main + GTest::gmock_main ) absl_cc_test( @@ -1037,6 +1126,19 @@ absl_cc_test( absl::log_internal_test_matchers absl::log_structured absl::scoped_mock_log - GTest::gmock - GTest::gtest_main + GTest::gmock_main +) + +absl_cc_test( + NAME + internal_fnmatch_test + SRCS + "internal/fnmatch_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::log_internal_fnmatch + GTest::gmock_main ) diff --git a/absl/log/absl_check.h b/absl/log/absl_check.h index 14a2307f..1bb43bd3 100644 --- a/absl/log/absl_check.h +++ b/absl/log/absl_check.h @@ -37,69 +37,81 @@ #include "absl/log/internal/check_impl.h" -#define ABSL_CHECK(condition) ABSL_CHECK_IMPL((condition), #condition) -#define ABSL_QCHECK(condition) ABSL_QCHECK_IMPL((condition), #condition) -#define ABSL_PCHECK(condition) ABSL_PCHECK_IMPL((condition), #condition) -#define ABSL_DCHECK(condition) ABSL_DCHECK_IMPL((condition), #condition) +#define ABSL_CHECK(condition) \ + ABSL_LOG_INTERNAL_CHECK_IMPL((condition), #condition) +#define ABSL_QCHECK(condition) \ + ABSL_LOG_INTERNAL_QCHECK_IMPL((condition), #condition) +#define ABSL_PCHECK(condition) \ + ABSL_LOG_INTERNAL_PCHECK_IMPL((condition), #condition) +#define ABSL_DCHECK(condition) \ + ABSL_LOG_INTERNAL_DCHECK_IMPL((condition), #condition) #define ABSL_CHECK_EQ(val1, val2) \ - ABSL_CHECK_EQ_IMPL((val1), #val1, (val2), #val2) + ABSL_LOG_INTERNAL_CHECK_EQ_IMPL((val1), #val1, (val2), #val2) #define ABSL_CHECK_NE(val1, val2) \ - ABSL_CHECK_NE_IMPL((val1), #val1, (val2), #val2) + ABSL_LOG_INTERNAL_CHECK_NE_IMPL((val1), #val1, (val2), #val2) #define ABSL_CHECK_LE(val1, val2) \ - ABSL_CHECK_LE_IMPL((val1), #val1, (val2), #val2) + ABSL_LOG_INTERNAL_CHECK_LE_IMPL((val1), #val1, (val2), #val2) #define ABSL_CHECK_LT(val1, val2) \ - ABSL_CHECK_LT_IMPL((val1), #val1, (val2), #val2) + ABSL_LOG_INTERNAL_CHECK_LT_IMPL((val1), #val1, (val2), #val2) #define ABSL_CHECK_GE(val1, val2) \ - ABSL_CHECK_GE_IMPL((val1), #val1, (val2), #val2) + ABSL_LOG_INTERNAL_CHECK_GE_IMPL((val1), #val1, (val2), #val2) #define ABSL_CHECK_GT(val1, val2) \ - ABSL_CHECK_GT_IMPL((val1), #val1, (val2), #val2) + ABSL_LOG_INTERNAL_CHECK_GT_IMPL((val1), #val1, (val2), #val2) #define ABSL_QCHECK_EQ(val1, val2) \ - ABSL_QCHECK_EQ_IMPL((val1), #val1, (val2), #val2) + ABSL_LOG_INTERNAL_QCHECK_EQ_IMPL((val1), #val1, (val2), #val2) #define ABSL_QCHECK_NE(val1, val2) \ - ABSL_QCHECK_NE_IMPL((val1), #val1, (val2), #val2) + ABSL_LOG_INTERNAL_QCHECK_NE_IMPL((val1), #val1, (val2), #val2) #define ABSL_QCHECK_LE(val1, val2) \ - ABSL_QCHECK_LE_IMPL((val1), #val1, (val2), #val2) + ABSL_LOG_INTERNAL_QCHECK_LE_IMPL((val1), #val1, (val2), #val2) #define ABSL_QCHECK_LT(val1, val2) \ - ABSL_QCHECK_LT_IMPL((val1), #val1, (val2), #val2) + ABSL_LOG_INTERNAL_QCHECK_LT_IMPL((val1), #val1, (val2), #val2) #define ABSL_QCHECK_GE(val1, val2) \ - ABSL_QCHECK_GE_IMPL((val1), #val1, (val2), #val2) + ABSL_LOG_INTERNAL_QCHECK_GE_IMPL((val1), #val1, (val2), #val2) #define ABSL_QCHECK_GT(val1, val2) \ - ABSL_QCHECK_GT_IMPL((val1), #val1, (val2), #val2) + ABSL_LOG_INTERNAL_QCHECK_GT_IMPL((val1), #val1, (val2), #val2) #define ABSL_DCHECK_EQ(val1, val2) \ - ABSL_DCHECK_EQ_IMPL((val1), #val1, (val2), #val2) + ABSL_LOG_INTERNAL_DCHECK_EQ_IMPL((val1), #val1, (val2), #val2) #define ABSL_DCHECK_NE(val1, val2) \ - ABSL_DCHECK_NE_IMPL((val1), #val1, (val2), #val2) + ABSL_LOG_INTERNAL_DCHECK_NE_IMPL((val1), #val1, (val2), #val2) #define ABSL_DCHECK_LE(val1, val2) \ - ABSL_DCHECK_LE_IMPL((val1), #val1, (val2), #val2) + ABSL_LOG_INTERNAL_DCHECK_LE_IMPL((val1), #val1, (val2), #val2) #define ABSL_DCHECK_LT(val1, val2) \ - ABSL_DCHECK_LT_IMPL((val1), #val1, (val2), #val2) + ABSL_LOG_INTERNAL_DCHECK_LT_IMPL((val1), #val1, (val2), #val2) #define ABSL_DCHECK_GE(val1, val2) \ - ABSL_DCHECK_GE_IMPL((val1), #val1, (val2), #val2) + ABSL_LOG_INTERNAL_DCHECK_GE_IMPL((val1), #val1, (val2), #val2) #define ABSL_DCHECK_GT(val1, val2) \ - ABSL_DCHECK_GT_IMPL((val1), #val1, (val2), #val2) + ABSL_LOG_INTERNAL_DCHECK_GT_IMPL((val1), #val1, (val2), #val2) -#define ABSL_CHECK_OK(status) ABSL_CHECK_OK_IMPL((status), #status) -#define ABSL_QCHECK_OK(status) ABSL_QCHECK_OK_IMPL((status), #status) -#define ABSL_DCHECK_OK(status) ABSL_DCHECK_OK_IMPL((status), #status) +#define ABSL_CHECK_OK(status) ABSL_LOG_INTERNAL_CHECK_OK_IMPL((status), #status) +#define ABSL_QCHECK_OK(status) \ + ABSL_LOG_INTERNAL_QCHECK_OK_IMPL((status), #status) +#define ABSL_DCHECK_OK(status) \ + ABSL_LOG_INTERNAL_DCHECK_OK_IMPL((status), #status) -#define ABSL_CHECK_STREQ(s1, s2) ABSL_CHECK_STREQ_IMPL((s1), #s1, (s2), #s2) -#define ABSL_CHECK_STRNE(s1, s2) ABSL_CHECK_STRNE_IMPL((s1), #s1, (s2), #s2) +#define ABSL_CHECK_STREQ(s1, s2) \ + ABSL_LOG_INTERNAL_CHECK_STREQ_IMPL((s1), #s1, (s2), #s2) +#define ABSL_CHECK_STRNE(s1, s2) \ + ABSL_LOG_INTERNAL_CHECK_STRNE_IMPL((s1), #s1, (s2), #s2) #define ABSL_CHECK_STRCASEEQ(s1, s2) \ - ABSL_CHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2) + ABSL_LOG_INTERNAL_CHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2) #define ABSL_CHECK_STRCASENE(s1, s2) \ - ABSL_CHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2) -#define ABSL_QCHECK_STREQ(s1, s2) ABSL_QCHECK_STREQ_IMPL((s1), #s1, (s2), #s2) -#define ABSL_QCHECK_STRNE(s1, s2) ABSL_QCHECK_STRNE_IMPL((s1), #s1, (s2), #s2) + ABSL_LOG_INTERNAL_CHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2) +#define ABSL_QCHECK_STREQ(s1, s2) \ + ABSL_LOG_INTERNAL_QCHECK_STREQ_IMPL((s1), #s1, (s2), #s2) +#define ABSL_QCHECK_STRNE(s1, s2) \ + ABSL_LOG_INTERNAL_QCHECK_STRNE_IMPL((s1), #s1, (s2), #s2) #define ABSL_QCHECK_STRCASEEQ(s1, s2) \ - ABSL_QCHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2) + ABSL_LOG_INTERNAL_QCHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2) #define ABSL_QCHECK_STRCASENE(s1, s2) \ - ABSL_QCHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2) -#define ABSL_DCHECK_STREQ(s1, s2) ABSL_DCHECK_STREQ_IMPL((s1), #s1, (s2), #s2) -#define ABSL_DCHECK_STRNE(s1, s2) ABSL_DCHECK_STRNE_IMPL((s1), #s1, (s2), #s2) + ABSL_LOG_INTERNAL_QCHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2) +#define ABSL_DCHECK_STREQ(s1, s2) \ + ABSL_LOG_INTERNAL_DCHECK_STREQ_IMPL((s1), #s1, (s2), #s2) +#define ABSL_DCHECK_STRNE(s1, s2) \ + ABSL_LOG_INTERNAL_DCHECK_STRNE_IMPL((s1), #s1, (s2), #s2) #define ABSL_DCHECK_STRCASEEQ(s1, s2) \ - ABSL_DCHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2) + ABSL_LOG_INTERNAL_DCHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2) #define ABSL_DCHECK_STRCASENE(s1, s2) \ - ABSL_DCHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2) + ABSL_LOG_INTERNAL_DCHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2) #endif // ABSL_LOG_ABSL_CHECK_H_ diff --git a/absl/log/absl_check_test.cc b/absl/log/absl_check_test.cc index 8ddacdb1..d84940fa 100644 --- a/absl/log/absl_check_test.cc +++ b/absl/log/absl_check_test.cc @@ -55,4 +55,4 @@ #define ABSL_TEST_QCHECK_STRCASENE ABSL_QCHECK_STRCASENE #include "gtest/gtest.h" -#include "absl/log/check_test_impl.h" +#include "absl/log/check_test_impl.inc" diff --git a/absl/log/absl_log.h b/absl/log/absl_log.h index 1c6cf263..0fd9ae3f 100644 --- a/absl/log/absl_log.h +++ b/absl/log/absl_log.h @@ -35,60 +35,81 @@ #include "absl/log/internal/log_impl.h" -#define ABSL_LOG(severity) ABSL_LOG_IMPL(_##severity) -#define ABSL_PLOG(severity) ABSL_PLOG_IMPL(_##severity) -#define ABSL_DLOG(severity) ABSL_DLOG_IMPL(_##severity) +#define ABSL_LOG(severity) ABSL_LOG_INTERNAL_LOG_IMPL(_##severity) +#define ABSL_PLOG(severity) ABSL_LOG_INTERNAL_PLOG_IMPL(_##severity) +#define ABSL_DLOG(severity) ABSL_LOG_INTERNAL_DLOG_IMPL(_##severity) + +#define ABSL_VLOG(verbose_level) ABSL_LOG_INTERNAL_VLOG_IMPL(verbose_level) +#define ABSL_DVLOG(verbose_level) ABSL_LOG_INTERNAL_DVLOG_IMPL(verbose_level) #define ABSL_LOG_IF(severity, condition) \ - ABSL_LOG_IF_IMPL(_##severity, condition) + ABSL_LOG_INTERNAL_LOG_IF_IMPL(_##severity, condition) #define ABSL_PLOG_IF(severity, condition) \ - ABSL_PLOG_IF_IMPL(_##severity, condition) + ABSL_LOG_INTERNAL_PLOG_IF_IMPL(_##severity, condition) #define ABSL_DLOG_IF(severity, condition) \ - ABSL_DLOG_IF_IMPL(_##severity, condition) + ABSL_LOG_INTERNAL_DLOG_IF_IMPL(_##severity, condition) -#define ABSL_LOG_EVERY_N(severity, n) ABSL_LOG_EVERY_N_IMPL(_##severity, n) -#define ABSL_LOG_FIRST_N(severity, n) ABSL_LOG_FIRST_N_IMPL(_##severity, n) -#define ABSL_LOG_EVERY_POW_2(severity) ABSL_LOG_EVERY_POW_2_IMPL(_##severity) +#define ABSL_LOG_EVERY_N(severity, n) \ + ABSL_LOG_INTERNAL_LOG_EVERY_N_IMPL(_##severity, n) +#define ABSL_LOG_FIRST_N(severity, n) \ + ABSL_LOG_INTERNAL_LOG_FIRST_N_IMPL(_##severity, n) +#define ABSL_LOG_EVERY_POW_2(severity) \ + ABSL_LOG_INTERNAL_LOG_EVERY_POW_2_IMPL(_##severity) #define ABSL_LOG_EVERY_N_SEC(severity, n_seconds) \ - ABSL_LOG_EVERY_N_SEC_IMPL(_##severity, n_seconds) + ABSL_LOG_INTERNAL_LOG_EVERY_N_SEC_IMPL(_##severity, n_seconds) -#define ABSL_PLOG_EVERY_N(severity, n) ABSL_PLOG_EVERY_N_IMPL(_##severity, n) -#define ABSL_PLOG_FIRST_N(severity, n) ABSL_PLOG_FIRST_N_IMPL(_##severity, n) -#define ABSL_PLOG_EVERY_POW_2(severity) ABSL_PLOG_EVERY_POW_2_IMPL(_##severity) +#define ABSL_PLOG_EVERY_N(severity, n) \ + ABSL_LOG_INTERNAL_PLOG_EVERY_N_IMPL(_##severity, n) +#define ABSL_PLOG_FIRST_N(severity, n) \ + ABSL_LOG_INTERNAL_PLOG_FIRST_N_IMPL(_##severity, n) +#define ABSL_PLOG_EVERY_POW_2(severity) \ + ABSL_LOG_INTERNAL_PLOG_EVERY_POW_2_IMPL(_##severity) #define ABSL_PLOG_EVERY_N_SEC(severity, n_seconds) \ - ABSL_PLOG_EVERY_N_SEC_IMPL(_##severity, n_seconds) + ABSL_LOG_INTERNAL_PLOG_EVERY_N_SEC_IMPL(_##severity, n_seconds) -#define ABSL_DLOG_EVERY_N(severity, n) ABSL_DLOG_EVERY_N_IMPL(_##severity, n) -#define ABSL_DLOG_FIRST_N(severity, n) ABSL_DLOG_FIRST_N_IMPL(_##severity, n) -#define ABSL_DLOG_EVERY_POW_2(severity) ABSL_DLOG_EVERY_POW_2_IMPL(_##severity) +#define ABSL_DLOG_EVERY_N(severity, n) \ + ABSL_LOG_INTERNAL_DLOG_EVERY_N_IMPL(_##severity, n) +#define ABSL_DLOG_FIRST_N(severity, n) \ + ABSL_LOG_INTERNAL_DLOG_FIRST_N_IMPL(_##severity, n) +#define ABSL_DLOG_EVERY_POW_2(severity) \ + ABSL_LOG_INTERNAL_DLOG_EVERY_POW_2_IMPL(_##severity) #define ABSL_DLOG_EVERY_N_SEC(severity, n_seconds) \ - ABSL_DLOG_EVERY_N_SEC_IMPL(_##severity, n_seconds) + ABSL_LOG_INTERNAL_DLOG_EVERY_N_SEC_IMPL(_##severity, n_seconds) + +#define ABSL_VLOG_EVERY_N(verbose_level, n) \ + ABSL_LOG_INTERNAL_VLOG_EVERY_N_IMPL(verbose_level, n) +#define ABSL_VLOG_FIRST_N(verbose_level, n) \ + ABSL_LOG_INTERNAL_VLOG_FIRST_N_IMPL(verbose_level, n) +#define ABSL_VLOG_EVERY_POW_2(verbose_level, n) \ + ABSL_LOG_INTERNAL_VLOG_EVERY_POW_2_IMPL(verbose_level, n) +#define ABSL_VLOG_EVERY_N_SEC(verbose_level, n) \ + ABSL_LOG_INTERNAL_VLOG_EVERY_N_SEC_IMPL(verbose_level, n) #define ABSL_LOG_IF_EVERY_N(severity, condition, n) \ - ABSL_LOG_IF_EVERY_N_IMPL(_##severity, condition, n) + ABSL_LOG_INTERNAL_LOG_IF_EVERY_N_IMPL(_##severity, condition, n) #define ABSL_LOG_IF_FIRST_N(severity, condition, n) \ - ABSL_LOG_IF_FIRST_N_IMPL(_##severity, condition, n) + ABSL_LOG_INTERNAL_LOG_IF_FIRST_N_IMPL(_##severity, condition, n) #define ABSL_LOG_IF_EVERY_POW_2(severity, condition) \ - ABSL_LOG_IF_EVERY_POW_2_IMPL(_##severity, condition) + ABSL_LOG_INTERNAL_LOG_IF_EVERY_POW_2_IMPL(_##severity, condition) #define ABSL_LOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \ - ABSL_LOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds) + ABSL_LOG_INTERNAL_LOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds) #define ABSL_PLOG_IF_EVERY_N(severity, condition, n) \ - ABSL_PLOG_IF_EVERY_N_IMPL(_##severity, condition, n) + ABSL_LOG_INTERNAL_PLOG_IF_EVERY_N_IMPL(_##severity, condition, n) #define ABSL_PLOG_IF_FIRST_N(severity, condition, n) \ - ABSL_PLOG_IF_FIRST_N_IMPL(_##severity, condition, n) + ABSL_LOG_INTERNAL_PLOG_IF_FIRST_N_IMPL(_##severity, condition, n) #define ABSL_PLOG_IF_EVERY_POW_2(severity, condition) \ - ABSL_PLOG_IF_EVERY_POW_2_IMPL(_##severity, condition) + ABSL_LOG_INTERNAL_PLOG_IF_EVERY_POW_2_IMPL(_##severity, condition) #define ABSL_PLOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \ - ABSL_PLOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds) + ABSL_LOG_INTERNAL_PLOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds) #define ABSL_DLOG_IF_EVERY_N(severity, condition, n) \ - ABSL_DLOG_IF_EVERY_N_IMPL(_##severity, condition, n) + ABSL_LOG_INTERNAL_DLOG_IF_EVERY_N_IMPL(_##severity, condition, n) #define ABSL_DLOG_IF_FIRST_N(severity, condition, n) \ - ABSL_DLOG_IF_FIRST_N_IMPL(_##severity, condition, n) + ABSL_LOG_INTERNAL_DLOG_IF_FIRST_N_IMPL(_##severity, condition, n) #define ABSL_DLOG_IF_EVERY_POW_2(severity, condition) \ - ABSL_DLOG_IF_EVERY_POW_2_IMPL(_##severity, condition) + ABSL_LOG_INTERNAL_DLOG_IF_EVERY_POW_2_IMPL(_##severity, condition) #define ABSL_DLOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \ - ABSL_DLOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds) + ABSL_LOG_INTERNAL_DLOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds) #endif // ABSL_LOG_ABSL_LOG_H_ diff --git a/absl/log/absl_log_basic_test.cc b/absl/log/absl_log_basic_test.cc index bc8a787d..3a4b83c1 100644 --- a/absl/log/absl_log_basic_test.cc +++ b/absl/log/absl_log_basic_test.cc @@ -18,4 +18,4 @@ #define ABSL_TEST_LOG ABSL_LOG #include "gtest/gtest.h" -#include "absl/log/log_basic_test_impl.h" +#include "absl/log/log_basic_test_impl.inc" diff --git a/absl/log/absl_vlog_is_on.h b/absl/log/absl_vlog_is_on.h new file mode 100644 index 00000000..29096b48 --- /dev/null +++ b/absl/log/absl_vlog_is_on.h @@ -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. +// +// ----------------------------------------------------------------------------- +// File: log/absl_vlog_is_on.h +// ----------------------------------------------------------------------------- +// +// This header defines the `ABSL_VLOG_IS_ON()` macro that controls the +// variable-verbosity conditional logging. +// +// It's used by `VLOG` in log.h, or it can also be used directly like this: +// +// if (ABSL_VLOG_IS_ON(2)) { +// foo_server.RecomputeStatisticsExpensive(); +// LOG(INFO) << foo_server.LastStatisticsAsString(); +// } +// +// Each source file has an effective verbosity level that's a non-negative +// integer computed from the `--vmodule` and `--v` flags. +// `ABSL_VLOG_IS_ON(n)` is true, and `VLOG(n)` logs, if that effective verbosity +// level is greater than or equal to `n`. +// +// `--vmodule` takes a comma-delimited list of key=value pairs. Each key is a +// pattern matched against filenames, and the values give the effective severity +// level applied to matching files. '?' and '*' characters in patterns are +// interpreted as single-character and zero-or-more-character wildcards. +// Patterns including a slash character are matched against full pathnames, +// while those without are matched against basenames only. One suffix (i.e. the +// last . and everything after it) is stripped from each filename prior to +// matching, as is the special suffix "-inl". +// +// Files are matched against globs in `--vmodule` in order, and the first match +// determines the verbosity level. +// +// Files which do not match any pattern in `--vmodule` use the value of `--v` as +// their effective verbosity level. The default is 0. +// +// SetVLOGLevel helper function is provided to do limited dynamic control over +// V-logging by appending to `--vmodule`. Because these go at the beginning of +// the list, they take priority over any globs previously added. +// +// Resetting --vmodule will override all previous modifications to `--vmodule`, +// including via SetVLOGLevel. + +#ifndef ABSL_LOG_ABSL_VLOG_IS_ON_H_ +#define ABSL_LOG_ABSL_VLOG_IS_ON_H_ + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/log/internal/vlog_config.h" // IWYU pragma: export +#include "absl/strings/string_view.h" + +// IWYU pragma: private, include "absl/log/log.h" + +// This is expanded at the callsite to allow the compiler to optimize +// always-false cases out of the build. +// An ABSL_MAX_VLOG_VERBOSITY of 2 means that VLOG(3) and above should never +// log. +#ifdef ABSL_MAX_VLOG_VERBOSITY +#define ABSL_LOG_INTERNAL_MAX_LOG_VERBOSITY_CHECK(x) \ + ((x) <= ABSL_MAX_VLOG_VERBOSITY)&& +#else +#define ABSL_LOG_INTERNAL_MAX_LOG_VERBOSITY_CHECK(x) +#endif + +// Each ABSL_VLOG_IS_ON call site gets its own VLogSite that registers with the +// global linked list of sites to asynchronously update its verbosity level on +// changes to --v or --vmodule. The verbosity can also be set by manually +// calling SetVLOGLevel. +// +// ABSL_VLOG_IS_ON is not async signal safe, but it is guaranteed not to +// allocate new memory. +#define ABSL_VLOG_IS_ON(verbose_level) \ + (ABSL_LOG_INTERNAL_MAX_LOG_VERBOSITY_CHECK(verbose_level)[]() \ + ->::absl::log_internal::VLogSite * \ + { \ + ABSL_CONST_INIT static ::absl::log_internal::VLogSite site(__FILE__); \ + return &site; \ + }() \ + ->IsEnabled(verbose_level)) + +#endif // ABSL_LOG_ABSL_VLOG_IS_ON_H_ diff --git a/absl/log/check.h b/absl/log/check.h index 33145a57..50f633dd 100644 --- a/absl/log/check.h +++ b/absl/log/check.h @@ -54,7 +54,7 @@ // Might produce a message like: // // Check failed: !cheese.empty() Out of Cheese -#define CHECK(condition) ABSL_CHECK_IMPL((condition), #condition) +#define CHECK(condition) ABSL_LOG_INTERNAL_CHECK_IMPL((condition), #condition) // QCHECK() // @@ -62,7 +62,7 @@ // not run registered error handlers (as `QFATAL`). It is useful when the // problem is definitely unrelated to program flow, e.g. when validating user // input. -#define QCHECK(condition) ABSL_QCHECK_IMPL((condition), #condition) +#define QCHECK(condition) ABSL_LOG_INTERNAL_QCHECK_IMPL((condition), #condition) // PCHECK() // @@ -77,7 +77,7 @@ // Might produce a message like: // // Check failed: fd != -1 posix is difficult: No such file or directory [2] -#define PCHECK(condition) ABSL_PCHECK_IMPL((condition), #condition) +#define PCHECK(condition) ABSL_LOG_INTERNAL_PCHECK_IMPL((condition), #condition) // DCHECK() // @@ -85,7 +85,7 @@ // `DLOG`). Unlike with `CHECK` (but as with `assert`), it is not safe to rely // on evaluation of `condition`: when `NDEBUG` is enabled, DCHECK does not // evaluate the condition. -#define DCHECK(condition) ABSL_DCHECK_IMPL((condition), #condition) +#define DCHECK(condition) ABSL_LOG_INTERNAL_DCHECK_IMPL((condition), #condition) // `CHECK_EQ` and friends are syntactic sugar for `CHECK(x == y)` that // automatically output the expression being tested and the evaluated values on @@ -113,24 +113,42 @@ // // WARNING: Passing `NULL` as an argument to `CHECK_EQ` and similar macros does // not compile. Use `nullptr` instead. -#define CHECK_EQ(val1, val2) ABSL_CHECK_EQ_IMPL((val1), #val1, (val2), #val2) -#define CHECK_NE(val1, val2) ABSL_CHECK_NE_IMPL((val1), #val1, (val2), #val2) -#define CHECK_LE(val1, val2) ABSL_CHECK_LE_IMPL((val1), #val1, (val2), #val2) -#define CHECK_LT(val1, val2) ABSL_CHECK_LT_IMPL((val1), #val1, (val2), #val2) -#define CHECK_GE(val1, val2) ABSL_CHECK_GE_IMPL((val1), #val1, (val2), #val2) -#define CHECK_GT(val1, val2) ABSL_CHECK_GT_IMPL((val1), #val1, (val2), #val2) -#define QCHECK_EQ(val1, val2) ABSL_QCHECK_EQ_IMPL((val1), #val1, (val2), #val2) -#define QCHECK_NE(val1, val2) ABSL_QCHECK_NE_IMPL((val1), #val1, (val2), #val2) -#define QCHECK_LE(val1, val2) ABSL_QCHECK_LE_IMPL((val1), #val1, (val2), #val2) -#define QCHECK_LT(val1, val2) ABSL_QCHECK_LT_IMPL((val1), #val1, (val2), #val2) -#define QCHECK_GE(val1, val2) ABSL_QCHECK_GE_IMPL((val1), #val1, (val2), #val2) -#define QCHECK_GT(val1, val2) ABSL_QCHECK_GT_IMPL((val1), #val1, (val2), #val2) -#define DCHECK_EQ(val1, val2) ABSL_DCHECK_EQ_IMPL((val1), #val1, (val2), #val2) -#define DCHECK_NE(val1, val2) ABSL_DCHECK_NE_IMPL((val1), #val1, (val2), #val2) -#define DCHECK_LE(val1, val2) ABSL_DCHECK_LE_IMPL((val1), #val1, (val2), #val2) -#define DCHECK_LT(val1, val2) ABSL_DCHECK_LT_IMPL((val1), #val1, (val2), #val2) -#define DCHECK_GE(val1, val2) ABSL_DCHECK_GE_IMPL((val1), #val1, (val2), #val2) -#define DCHECK_GT(val1, val2) ABSL_DCHECK_GT_IMPL((val1), #val1, (val2), #val2) +#define CHECK_EQ(val1, val2) \ + ABSL_LOG_INTERNAL_CHECK_EQ_IMPL((val1), #val1, (val2), #val2) +#define CHECK_NE(val1, val2) \ + ABSL_LOG_INTERNAL_CHECK_NE_IMPL((val1), #val1, (val2), #val2) +#define CHECK_LE(val1, val2) \ + ABSL_LOG_INTERNAL_CHECK_LE_IMPL((val1), #val1, (val2), #val2) +#define CHECK_LT(val1, val2) \ + ABSL_LOG_INTERNAL_CHECK_LT_IMPL((val1), #val1, (val2), #val2) +#define CHECK_GE(val1, val2) \ + ABSL_LOG_INTERNAL_CHECK_GE_IMPL((val1), #val1, (val2), #val2) +#define CHECK_GT(val1, val2) \ + ABSL_LOG_INTERNAL_CHECK_GT_IMPL((val1), #val1, (val2), #val2) +#define QCHECK_EQ(val1, val2) \ + ABSL_LOG_INTERNAL_QCHECK_EQ_IMPL((val1), #val1, (val2), #val2) +#define QCHECK_NE(val1, val2) \ + ABSL_LOG_INTERNAL_QCHECK_NE_IMPL((val1), #val1, (val2), #val2) +#define QCHECK_LE(val1, val2) \ + ABSL_LOG_INTERNAL_QCHECK_LE_IMPL((val1), #val1, (val2), #val2) +#define QCHECK_LT(val1, val2) \ + ABSL_LOG_INTERNAL_QCHECK_LT_IMPL((val1), #val1, (val2), #val2) +#define QCHECK_GE(val1, val2) \ + ABSL_LOG_INTERNAL_QCHECK_GE_IMPL((val1), #val1, (val2), #val2) +#define QCHECK_GT(val1, val2) \ + ABSL_LOG_INTERNAL_QCHECK_GT_IMPL((val1), #val1, (val2), #val2) +#define DCHECK_EQ(val1, val2) \ + ABSL_LOG_INTERNAL_DCHECK_EQ_IMPL((val1), #val1, (val2), #val2) +#define DCHECK_NE(val1, val2) \ + ABSL_LOG_INTERNAL_DCHECK_NE_IMPL((val1), #val1, (val2), #val2) +#define DCHECK_LE(val1, val2) \ + ABSL_LOG_INTERNAL_DCHECK_LE_IMPL((val1), #val1, (val2), #val2) +#define DCHECK_LT(val1, val2) \ + ABSL_LOG_INTERNAL_DCHECK_LT_IMPL((val1), #val1, (val2), #val2) +#define DCHECK_GE(val1, val2) \ + ABSL_LOG_INTERNAL_DCHECK_GE_IMPL((val1), #val1, (val2), #val2) +#define DCHECK_GT(val1, val2) \ + ABSL_LOG_INTERNAL_DCHECK_GT_IMPL((val1), #val1, (val2), #val2) // `CHECK_OK` and friends validate that the provided `absl::Status` or // `absl::StatusOr<T>` is OK. If it isn't, they print a failure message that @@ -146,12 +164,12 @@ // Might produce a message like: // // Check failed: FunctionReturnsStatus(x, y, z) is OK (ABORTED: timeout) oops! -#define CHECK_OK(status) ABSL_CHECK_OK_IMPL((status), #status) -#define QCHECK_OK(status) ABSL_QCHECK_OK_IMPL((status), #status) -#define DCHECK_OK(status) ABSL_DCHECK_OK_IMPL((status), #status) +#define CHECK_OK(status) ABSL_LOG_INTERNAL_CHECK_OK_IMPL((status), #status) +#define QCHECK_OK(status) ABSL_LOG_INTERNAL_QCHECK_OK_IMPL((status), #status) +#define DCHECK_OK(status) ABSL_LOG_INTERNAL_DCHECK_OK_IMPL((status), #status) // `CHECK_STREQ` and friends provide `CHECK_EQ` functionality for C strings, -// i.e., nul-terminated char arrays. The `CASE` versions are case-insensitive. +// i.e., null-terminated char arrays. The `CASE` versions are case-insensitive. // // Example: // @@ -163,21 +181,29 @@ // Example: // // CHECK_STREQ(Foo().c_str(), Bar().c_str()); -#define CHECK_STREQ(s1, s2) ABSL_CHECK_STREQ_IMPL((s1), #s1, (s2), #s2) -#define CHECK_STRNE(s1, s2) ABSL_CHECK_STRNE_IMPL((s1), #s1, (s2), #s2) -#define CHECK_STRCASEEQ(s1, s2) ABSL_CHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2) -#define CHECK_STRCASENE(s1, s2) ABSL_CHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2) -#define QCHECK_STREQ(s1, s2) ABSL_QCHECK_STREQ_IMPL((s1), #s1, (s2), #s2) -#define QCHECK_STRNE(s1, s2) ABSL_QCHECK_STRNE_IMPL((s1), #s1, (s2), #s2) +#define CHECK_STREQ(s1, s2) \ + ABSL_LOG_INTERNAL_CHECK_STREQ_IMPL((s1), #s1, (s2), #s2) +#define CHECK_STRNE(s1, s2) \ + ABSL_LOG_INTERNAL_CHECK_STRNE_IMPL((s1), #s1, (s2), #s2) +#define CHECK_STRCASEEQ(s1, s2) \ + ABSL_LOG_INTERNAL_CHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2) +#define CHECK_STRCASENE(s1, s2) \ + ABSL_LOG_INTERNAL_CHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2) +#define QCHECK_STREQ(s1, s2) \ + ABSL_LOG_INTERNAL_QCHECK_STREQ_IMPL((s1), #s1, (s2), #s2) +#define QCHECK_STRNE(s1, s2) \ + ABSL_LOG_INTERNAL_QCHECK_STRNE_IMPL((s1), #s1, (s2), #s2) #define QCHECK_STRCASEEQ(s1, s2) \ - ABSL_QCHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2) + ABSL_LOG_INTERNAL_QCHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2) #define QCHECK_STRCASENE(s1, s2) \ - ABSL_QCHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2) -#define DCHECK_STREQ(s1, s2) ABSL_DCHECK_STREQ_IMPL((s1), #s1, (s2), #s2) -#define DCHECK_STRNE(s1, s2) ABSL_DCHECK_STRNE_IMPL((s1), #s1, (s2), #s2) + ABSL_LOG_INTERNAL_QCHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2) +#define DCHECK_STREQ(s1, s2) \ + ABSL_LOG_INTERNAL_DCHECK_STREQ_IMPL((s1), #s1, (s2), #s2) +#define DCHECK_STRNE(s1, s2) \ + ABSL_LOG_INTERNAL_DCHECK_STRNE_IMPL((s1), #s1, (s2), #s2) #define DCHECK_STRCASEEQ(s1, s2) \ - ABSL_DCHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2) + ABSL_LOG_INTERNAL_DCHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2) #define DCHECK_STRCASENE(s1, s2) \ - ABSL_DCHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2) + ABSL_LOG_INTERNAL_DCHECK_STRCASENE_IMPL((s1), #s1, (s2), #s2) #endif // ABSL_LOG_CHECK_H_ diff --git a/absl/log/check_test.cc b/absl/log/check_test.cc index f44a686e..ef415bd4 100644 --- a/absl/log/check_test.cc +++ b/absl/log/check_test.cc @@ -55,4 +55,4 @@ #define ABSL_TEST_QCHECK_STRCASENE QCHECK_STRCASENE #include "gtest/gtest.h" -#include "absl/log/check_test_impl.h" +#include "absl/log/check_test_impl.inc" diff --git a/absl/log/check_test_impl.h b/absl/log/check_test_impl.inc index d5c0aee4..d5c0aee4 100644 --- a/absl/log/check_test_impl.h +++ b/absl/log/check_test_impl.inc diff --git a/absl/log/flags.cc b/absl/log/flags.cc index b5308881..287b3e96 100644 --- a/absl/log/flags.cc +++ b/absl/log/flags.cc @@ -28,6 +28,7 @@ #include "absl/flags/marshalling.h" #include "absl/log/globals.h" #include "absl/log/internal/config.h" +#include "absl/log/internal/vlog_config.h" #include "absl/strings/numbers.h" #include "absl/strings/string_view.h" @@ -90,19 +91,27 @@ ABSL_FLAG(std::string, log_backtrace_at, "", .OnUpdate([] { const std::string log_backtrace_at = absl::GetFlag(FLAGS_log_backtrace_at); - if (log_backtrace_at.empty()) return; + if (log_backtrace_at.empty()) { + absl::ClearLogBacktraceLocation(); + return; + } const size_t last_colon = log_backtrace_at.rfind(':'); - if (last_colon == log_backtrace_at.npos) return; + if (last_colon == log_backtrace_at.npos) { + absl::ClearLogBacktraceLocation(); + return; + } const absl::string_view file = absl::string_view(log_backtrace_at).substr(0, last_colon); int line; - if (absl::SimpleAtoi( + if (!absl::SimpleAtoi( absl::string_view(log_backtrace_at).substr(last_colon + 1), &line)) { - absl::SetLogBacktraceLocation(file, line); + absl::ClearLogBacktraceLocation(); + return; } + absl::SetLogBacktraceLocation(file, line); }); ABSL_FLAG(bool, log_prefix, true, @@ -110,3 +119,25 @@ ABSL_FLAG(bool, log_prefix, true, .OnUpdate([] { absl::log_internal::RawEnableLogPrefix(absl::GetFlag(FLAGS_log_prefix)); }); + +ABSL_FLAG(int, v, 0, + "Show all VLOG(m) messages for m <= this. Overridable by --vmodule.") + .OnUpdate([] { + absl::log_internal::UpdateGlobalVLogLevel(absl::GetFlag(FLAGS_v)); + }); + +ABSL_FLAG( + std::string, vmodule, "", + "per-module log verbosity level." + " Argument is a comma-separated list of <module name>=<log level>." + " <module name> is a glob pattern, matched against the filename base" + " (that is, name ignoring .cc/.h./-inl.h)." + " A pattern without slashes matches just the file name portion, otherwise" + " the whole file path below the workspace root" + " (still without .cc/.h./-inl.h) is matched." + " ? and * in the glob pattern match any single or sequence of characters" + " respectively including slashes." + " <log level> overrides any value given by --v.") + .OnUpdate([] { + absl::log_internal::UpdateVModule(absl::GetFlag(FLAGS_vmodule)); + }); diff --git a/absl/log/flags_test.cc b/absl/log/flags_test.cc index a0f6d763..1080ea11 100644 --- a/absl/log/flags_test.cc +++ b/absl/log/flags_test.cc @@ -92,23 +92,23 @@ TEST_F(LogFlagsTest, PrependLogPrefix) { TEST_F(LogFlagsTest, EmptyBacktraceAtFlag) { absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo); - absl::SetFlag(&FLAGS_log_backtrace_at, ""); absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:"))))); test_sink.StartCapturingLogs(); + absl::SetFlag(&FLAGS_log_backtrace_at, ""); LOG(INFO) << "hello world"; } TEST_F(LogFlagsTest, BacktraceAtNonsense) { absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo); - absl::SetFlag(&FLAGS_log_backtrace_at, "gibberish"); absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:"))))); test_sink.StartCapturingLogs(); + absl::SetFlag(&FLAGS_log_backtrace_at, "gibberish"); LOG(INFO) << "hello world"; } @@ -116,13 +116,13 @@ TEST_F(LogFlagsTest, BacktraceAtWrongFile) { absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo); const int log_line = __LINE__ + 1; auto do_log = [] { LOG(INFO) << "hello world"; }; - absl::SetFlag(&FLAGS_log_backtrace_at, - absl::StrCat("some_other_file.cc:", log_line)); absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:"))))); test_sink.StartCapturingLogs(); + absl::SetFlag(&FLAGS_log_backtrace_at, + absl::StrCat("some_other_file.cc:", log_line)); do_log(); } @@ -130,13 +130,13 @@ TEST_F(LogFlagsTest, BacktraceAtWrongLine) { absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo); const int log_line = __LINE__ + 1; auto do_log = [] { LOG(INFO) << "hello world"; }; - absl::SetFlag(&FLAGS_log_backtrace_at, - absl::StrCat("flags_test.cc:", log_line + 1)); absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:"))))); test_sink.StartCapturingLogs(); + absl::SetFlag(&FLAGS_log_backtrace_at, + absl::StrCat("flags_test.cc:", log_line + 1)); do_log(); } @@ -144,12 +144,12 @@ TEST_F(LogFlagsTest, BacktraceAtWholeFilename) { absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo); const int log_line = __LINE__ + 1; auto do_log = [] { LOG(INFO) << "hello world"; }; - absl::SetFlag(&FLAGS_log_backtrace_at, absl::StrCat(__FILE__, ":", log_line)); absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:"))))); test_sink.StartCapturingLogs(); + absl::SetFlag(&FLAGS_log_backtrace_at, absl::StrCat(__FILE__, ":", log_line)); do_log(); } @@ -157,13 +157,13 @@ TEST_F(LogFlagsTest, BacktraceAtNonmatchingSuffix) { absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo); const int log_line = __LINE__ + 1; auto do_log = [] { LOG(INFO) << "hello world"; }; - absl::SetFlag(&FLAGS_log_backtrace_at, - absl::StrCat("flags_test.cc:", log_line, "gibberish")); absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:"))))); test_sink.StartCapturingLogs(); + absl::SetFlag(&FLAGS_log_backtrace_at, + absl::StrCat("flags_test.cc:", log_line, "gibberish")); do_log(); } @@ -171,13 +171,17 @@ TEST_F(LogFlagsTest, LogsBacktrace) { absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo); const int log_line = __LINE__ + 1; auto do_log = [] { LOG(INFO) << "hello world"; }; - absl::SetFlag(&FLAGS_log_backtrace_at, - absl::StrCat("flags_test.cc:", log_line)); absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + testing::InSequence seq; EXPECT_CALL(test_sink, Send(TextMessage(HasSubstr("(stacktrace:")))); + EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:"))))); test_sink.StartCapturingLogs(); + absl::SetFlag(&FLAGS_log_backtrace_at, + absl::StrCat("flags_test.cc:", log_line)); + do_log(); + absl::SetFlag(&FLAGS_log_backtrace_at, ""); do_log(); } diff --git a/absl/log/globals.cc b/absl/log/globals.cc index 6dfe81f0..cc85438f 100644 --- a/absl/log/globals.cc +++ b/absl/log/globals.cc @@ -14,14 +14,17 @@ #include "absl/log/globals.h" -#include <stddef.h> -#include <stdint.h> - #include <atomic> +#include <cstddef> +#include <cstdint> +#include <cstdlib> +#include <cstring> +#include <string> #include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/internal/atomic_hook.h" +#include "absl/base/internal/raw_logging.h" #include "absl/base/log_severity.h" #include "absl/hash/hash.h" #include "absl/strings/string_view.h" @@ -43,6 +46,9 @@ ABSL_CONST_INIT std::atomic<int> stderrthreshold{ ABSL_CONST_INIT std::atomic<size_t> log_backtrace_at_hash{0}; ABSL_CONST_INIT std::atomic<bool> prepend_log_prefix{true}; +constexpr char kDefaultAndroidTag[] = "native"; +ABSL_CONST_INIT std::atomic<const char*> android_log_tag{kDefaultAndroidTag}; + ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES absl::base_internal::AtomicHook<log_internal::LoggingGlobalsListener> logging_globals_listener; @@ -121,9 +127,29 @@ ScopedStderrThreshold::~ScopedStderrThreshold() { namespace log_internal { +const char* GetAndroidNativeTag() { + return android_log_tag.load(std::memory_order_acquire); +} + +} // namespace log_internal + +void SetAndroidNativeTag(const char* tag) { + ABSL_CONST_INIT static std::atomic<const std::string*> user_log_tag(nullptr); + ABSL_INTERNAL_CHECK(tag, "tag must be non-null."); + + const std::string* tag_str = new std::string(tag); + ABSL_INTERNAL_CHECK( + android_log_tag.exchange(tag_str->c_str(), std::memory_order_acq_rel) == + kDefaultAndroidTag, + "SetAndroidNativeTag() must only be called once per process!"); + user_log_tag.store(tag_str, std::memory_order_relaxed); +} + +namespace log_internal { + bool ShouldLogBacktraceAt(absl::string_view file, int line) { const size_t flag_hash = - log_backtrace_at_hash.load(std::memory_order_acquire); + log_backtrace_at_hash.load(std::memory_order_relaxed); return flag_hash != 0 && flag_hash == HashSiteForLogBacktraceAt(file, line); } @@ -132,7 +158,11 @@ bool ShouldLogBacktraceAt(absl::string_view file, int line) { void SetLogBacktraceLocation(absl::string_view file, int line) { log_backtrace_at_hash.store(HashSiteForLogBacktraceAt(file, line), - std::memory_order_release); + std::memory_order_relaxed); +} + +void ClearLogBacktraceLocation() { + log_backtrace_at_hash.store(0, std::memory_order_relaxed); } bool ShouldPrependLogPrefix() { diff --git a/absl/log/globals.h b/absl/log/globals.h index 32b87db0..b36e47e6 100644 --- a/absl/log/globals.h +++ b/absl/log/globals.h @@ -24,6 +24,7 @@ #include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/log_severity.h" +#include "absl/log/internal/vlog_config.h" #include "absl/strings/string_view.h" namespace absl { @@ -110,8 +111,8 @@ class ScopedStderrThreshold final { // Log Backtrace At //------------------------------------------------------------------------------ // -// Users can request backtrace to be logged at specific locations, specified -// by file and line number. +// Users can request an existing `LOG` statement, specified by file and line +// number, to also include a backtrace when logged. // ShouldLogBacktraceAt() // @@ -123,9 +124,16 @@ ABSL_MUST_USE_RESULT bool ShouldLogBacktraceAt(absl::string_view file, // SetLogBacktraceLocation() // -// Sets the location the backtrace should be logged at. +// Sets the location the backtrace should be logged at. If the specified +// location isn't a `LOG` statement, the effect will be the same as +// `ClearLogBacktraceLocation` (but less efficient). void SetLogBacktraceLocation(absl::string_view file, int line); +// ClearLogBacktraceLocation() +// +// Clears the set location so that backtraces will no longer be logged at it. +void ClearLogBacktraceLocation(); + //------------------------------------------------------------------------------ // Prepend Log Prefix //------------------------------------------------------------------------------ @@ -145,6 +153,51 @@ ABSL_MUST_USE_RESULT bool ShouldPrependLogPrefix(); // This function is async-signal-safe. void EnableLogPrefix(bool on_off); +//------------------------------------------------------------------------------ +// Set Global VLOG Level +//------------------------------------------------------------------------------ +// +// Sets the global `(ABSL_)VLOG(_IS_ON)` level to `log_level`. This level is +// applied to any sites whose filename doesn't match any `module_pattern`. +// Returns the prior value. +inline int SetGlobalVLogLevel(int log_level) { + return absl::log_internal::UpdateGlobalVLogLevel(log_level); +} + +//------------------------------------------------------------------------------ +// Set VLOG Level +//------------------------------------------------------------------------------ +// +// Sets `(ABSL_)VLOG(_IS_ON)` level for `module_pattern` to `log_level`. This +// allows programmatic control of what is normally set by the --vmodule flag. +// Returns the level that previously applied to `module_pattern`. +inline int SetVLogLevel(absl::string_view module_pattern, int log_level) { + return absl::log_internal::PrependVModule(module_pattern, log_level); +} + +//------------------------------------------------------------------------------ +// Configure Android Native Log Tag +//------------------------------------------------------------------------------ +// +// The logging library forwards to the Android system log API when built for +// Android. That API takes a string "tag" value in addition to a message and +// severity level. The tag is used to identify the source of messages and to +// filter them. This library uses the tag "native" by default. + +// SetAndroidNativeTag() +// +// Stores a copy of the string pointed to by `tag` and uses it as the Android +// logging tag thereafter. `tag` must not be null. +// This function must not be called more than once! +void SetAndroidNativeTag(const char* tag); + +namespace log_internal { +// GetAndroidNativeTag() +// +// Returns the configured Android logging tag. +const char* GetAndroidNativeTag(); +} // namespace log_internal + namespace log_internal { using LoggingGlobalsListener = void (*)(); diff --git a/absl/log/globals_test.cc b/absl/log/globals_test.cc index 6710c5aa..0dc54d57 100644 --- a/absl/log/globals_test.cc +++ b/absl/log/globals_test.cc @@ -15,8 +15,6 @@ #include "absl/log/globals.h" -#include <string> - #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/attributes.h" @@ -27,6 +25,8 @@ #include "absl/log/scoped_mock_log.h" namespace { +using ::testing::_; +using ::testing::StrEq; auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( new absl::log_internal::LogTestEnvironment); @@ -88,4 +88,60 @@ TEST(TestGlobals, LogPrefix) { EXPECT_TRUE(absl::ShouldPrependLogPrefix()); } +TEST(TestGlobals, SetGlobalVLogLevel) { + EXPECT_EQ(absl::SetGlobalVLogLevel(42), 0); + EXPECT_EQ(absl::SetGlobalVLogLevel(1337), 42); + // Restore the value since it affects the default unset module value for + // `SetVLogLevel()`. + EXPECT_EQ(absl::SetGlobalVLogLevel(0), 1337); +} + +TEST(TestGlobals, SetVLogLevel) { + EXPECT_EQ(absl::SetVLogLevel("setvloglevel", 42), 0); + EXPECT_EQ(absl::SetVLogLevel("setvloglevel", 1337), 42); + EXPECT_EQ(absl::SetVLogLevel("othersetvloglevel", 50), 0); +} + +TEST(TestGlobals, AndroidLogTag) { + // Verify invalid tags result in a check failure. + EXPECT_DEATH_IF_SUPPORTED(absl::SetAndroidNativeTag(nullptr), ".*"); + + // Verify valid tags applied. + EXPECT_THAT(absl::log_internal::GetAndroidNativeTag(), StrEq("native")); + absl::SetAndroidNativeTag("test_tag"); + EXPECT_THAT(absl::log_internal::GetAndroidNativeTag(), StrEq("test_tag")); + + // Verify that additional calls (more than 1) result in a check failure. + EXPECT_DEATH_IF_SUPPORTED(absl::SetAndroidNativeTag("test_tag_fail"), ".*"); +} + +TEST(TestExitOnDFatal, OffTest) { + // Turn off... + absl::log_internal::SetExitOnDFatal(false); + EXPECT_FALSE(absl::log_internal::ExitOnDFatal()); + + // We don't die. + { + absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected); + + // LOG(DFATAL) has severity FATAL if debugging, but is + // downgraded to ERROR if not debugging. + EXPECT_CALL(log, Log(absl::kLogDebugFatal, _, "This should not be fatal")); + + log.StartCapturingLogs(); + LOG(DFATAL) << "This should not be fatal"; + } +} + +#if GTEST_HAS_DEATH_TEST +TEST(TestDeathWhileExitOnDFatal, OnTest) { + absl::log_internal::SetExitOnDFatal(true); + EXPECT_TRUE(absl::log_internal::ExitOnDFatal()); + + // Death comes on little cats' feet. + EXPECT_DEBUG_DEATH({ LOG(DFATAL) << "This should be fatal in debug mode"; }, + "This should be fatal in debug mode"); +} +#endif + } // namespace diff --git a/absl/log/initialize.cc b/absl/log/initialize.cc index a3f6d6c1..ef5d3146 100644 --- a/absl/log/initialize.cc +++ b/absl/log/initialize.cc @@ -21,14 +21,18 @@ namespace absl { ABSL_NAMESPACE_BEGIN -void InitializeLog() { +namespace { +void InitializeLogImpl(absl::TimeZone time_zone) { // This comes first since it is used by RAW_LOG. - absl::log_internal::SetTimeZone(absl::LocalTimeZone()); + absl::log_internal::SetTimeZone(time_zone); // Note that initialization is complete, so logs can now be sent to their // proper destinations rather than stderr. log_internal::SetInitialized(); } +} // namespace + +void InitializeLog() { InitializeLogImpl(absl::LocalTimeZone()); } ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/log/internal/BUILD.bazel b/absl/log/internal/BUILD.bazel index a1f1a67c..1be13499 100644 --- a/absl/log/internal/BUILD.bazel +++ b/absl/log/internal/BUILD.bazel @@ -21,9 +21,16 @@ load( "ABSL_TEST_COPTS", ) -package(default_visibility = [ - "//absl/log:__pkg__", -]) +package( + default_visibility = [ + "//absl/log:__pkg__", + ], + features = [ + "header_modules", + "layering_check", + "parse_headers", + ], +) licenses(["notice"]) @@ -146,6 +153,7 @@ cc_library( ":conditions", ":log_message", ":strip", + "//absl/log:absl_vlog_is_on", ], ) @@ -213,6 +221,7 @@ cc_library( "//absl/base:config", "//absl/base:core_headers", "//absl/base:log_severity", + "//absl/base:no_destructor", "//absl/base:raw_logging_internal", "//absl/cleanup", "//absl/log:globals", @@ -320,13 +329,13 @@ cc_library( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":test_helpers", - "@com_google_googletest//:gtest", "//absl/base:config", "//absl/base:core_headers", "//absl/base:log_severity", "//absl/log:log_entry", "//absl/strings", "//absl/time", + "@com_google_googletest//:gtest", ] + select({ "//absl:msvc_compiler": [], "//conditions:default": [ @@ -357,6 +366,60 @@ cc_library( ], ) +cc_library( + name = "fnmatch", + srcs = ["fnmatch.cc"], + hdrs = ["fnmatch.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base:config", + "//absl/strings", + ], +) + +cc_library( + name = "vlog_config", + srcs = ["vlog_config.cc"], + hdrs = ["vlog_config.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//absl/log:__subpackages__"], + deps = [ + "//absl/base", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:no_destructor", + "//absl/log/internal:fnmatch", + "//absl/memory", + "//absl/strings", + "//absl/synchronization", + "//absl/types:optional", + ], +) + +cc_binary( + name = "vlog_config_benchmark", + testonly = 1, + srcs = ["vlog_config_benchmark.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = [ + "benchmark", + ], + visibility = ["//visibility:private"], + deps = [ + ":vlog_config", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/container:layout", + "//absl/memory", + "//absl/random:distributions", + "//absl/strings", + "@com_github_google_benchmark//:benchmark_main", + ], +) + # Test targets cc_test( name = "stderr_log_sink_test", @@ -378,6 +441,31 @@ cc_test( "//absl/base:log_severity", "//absl/log", "//absl/log:globals", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "fnmatch_test", + srcs = ["fnmatch_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":fnmatch", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) + +cc_test( + name = "fnmatch_benchmark", + srcs = ["fnmatch_benchmark.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = ["benchmark"], + deps = [ + ":fnmatch", + "@com_github_google_benchmark//:benchmark_main", + ], +) diff --git a/absl/log/internal/check_impl.h b/absl/log/internal/check_impl.h index c9c28e3e..00f25f80 100644 --- a/absl/log/internal/check_impl.h +++ b/absl/log/internal/check_impl.h @@ -22,128 +22,128 @@ #include "absl/log/internal/strip.h" // CHECK -#define ABSL_CHECK_IMPL(condition, condition_text) \ +#define ABSL_LOG_INTERNAL_CHECK_IMPL(condition, condition_text) \ ABSL_LOG_INTERNAL_CONDITION_FATAL(STATELESS, \ ABSL_PREDICT_FALSE(!(condition))) \ ABSL_LOG_INTERNAL_CHECK(condition_text).InternalStream() -#define ABSL_QCHECK_IMPL(condition, condition_text) \ +#define ABSL_LOG_INTERNAL_QCHECK_IMPL(condition, condition_text) \ ABSL_LOG_INTERNAL_CONDITION_QFATAL(STATELESS, \ ABSL_PREDICT_FALSE(!(condition))) \ ABSL_LOG_INTERNAL_QCHECK(condition_text).InternalStream() -#define ABSL_PCHECK_IMPL(condition, condition_text) \ - ABSL_CHECK_IMPL(condition, condition_text).WithPerror() +#define ABSL_LOG_INTERNAL_PCHECK_IMPL(condition, condition_text) \ + ABSL_LOG_INTERNAL_CHECK_IMPL(condition, condition_text).WithPerror() #ifndef NDEBUG -#define ABSL_DCHECK_IMPL(condition, condition_text) \ - ABSL_CHECK_IMPL(condition, condition_text) +#define ABSL_LOG_INTERNAL_DCHECK_IMPL(condition, condition_text) \ + ABSL_LOG_INTERNAL_CHECK_IMPL(condition, condition_text) #else -#define ABSL_DCHECK_IMPL(condition, condition_text) \ - ABSL_CHECK_IMPL(true || (condition), "true") +#define ABSL_LOG_INTERNAL_DCHECK_IMPL(condition, condition_text) \ + ABSL_LOG_INTERNAL_CHECK_IMPL(true || (condition), "true") #endif // CHECK_EQ -#define ABSL_CHECK_EQ_IMPL(val1, val1_text, val2, val2_text) \ +#define ABSL_LOG_INTERNAL_CHECK_EQ_IMPL(val1, val1_text, val2, val2_text) \ ABSL_LOG_INTERNAL_CHECK_OP(Check_EQ, ==, val1, val1_text, val2, val2_text) -#define ABSL_CHECK_NE_IMPL(val1, val1_text, val2, val2_text) \ +#define ABSL_LOG_INTERNAL_CHECK_NE_IMPL(val1, val1_text, val2, val2_text) \ ABSL_LOG_INTERNAL_CHECK_OP(Check_NE, !=, val1, val1_text, val2, val2_text) -#define ABSL_CHECK_LE_IMPL(val1, val1_text, val2, val2_text) \ +#define ABSL_LOG_INTERNAL_CHECK_LE_IMPL(val1, val1_text, val2, val2_text) \ ABSL_LOG_INTERNAL_CHECK_OP(Check_LE, <=, val1, val1_text, val2, val2_text) -#define ABSL_CHECK_LT_IMPL(val1, val1_text, val2, val2_text) \ +#define ABSL_LOG_INTERNAL_CHECK_LT_IMPL(val1, val1_text, val2, val2_text) \ ABSL_LOG_INTERNAL_CHECK_OP(Check_LT, <, val1, val1_text, val2, val2_text) -#define ABSL_CHECK_GE_IMPL(val1, val1_text, val2, val2_text) \ +#define ABSL_LOG_INTERNAL_CHECK_GE_IMPL(val1, val1_text, val2, val2_text) \ ABSL_LOG_INTERNAL_CHECK_OP(Check_GE, >=, val1, val1_text, val2, val2_text) -#define ABSL_CHECK_GT_IMPL(val1, val1_text, val2, val2_text) \ +#define ABSL_LOG_INTERNAL_CHECK_GT_IMPL(val1, val1_text, val2, val2_text) \ ABSL_LOG_INTERNAL_CHECK_OP(Check_GT, >, val1, val1_text, val2, val2_text) -#define ABSL_QCHECK_EQ_IMPL(val1, val1_text, val2, val2_text) \ +#define ABSL_LOG_INTERNAL_QCHECK_EQ_IMPL(val1, val1_text, val2, val2_text) \ ABSL_LOG_INTERNAL_QCHECK_OP(Check_EQ, ==, val1, val1_text, val2, val2_text) -#define ABSL_QCHECK_NE_IMPL(val1, val1_text, val2, val2_text) \ +#define ABSL_LOG_INTERNAL_QCHECK_NE_IMPL(val1, val1_text, val2, val2_text) \ ABSL_LOG_INTERNAL_QCHECK_OP(Check_NE, !=, val1, val1_text, val2, val2_text) -#define ABSL_QCHECK_LE_IMPL(val1, val1_text, val2, val2_text) \ +#define ABSL_LOG_INTERNAL_QCHECK_LE_IMPL(val1, val1_text, val2, val2_text) \ ABSL_LOG_INTERNAL_QCHECK_OP(Check_LE, <=, val1, val1_text, val2, val2_text) -#define ABSL_QCHECK_LT_IMPL(val1, val1_text, val2, val2_text) \ +#define ABSL_LOG_INTERNAL_QCHECK_LT_IMPL(val1, val1_text, val2, val2_text) \ ABSL_LOG_INTERNAL_QCHECK_OP(Check_LT, <, val1, val1_text, val2, val2_text) -#define ABSL_QCHECK_GE_IMPL(val1, val1_text, val2, val2_text) \ +#define ABSL_LOG_INTERNAL_QCHECK_GE_IMPL(val1, val1_text, val2, val2_text) \ ABSL_LOG_INTERNAL_QCHECK_OP(Check_GE, >=, val1, val1_text, val2, val2_text) -#define ABSL_QCHECK_GT_IMPL(val1, val1_text, val2, val2_text) \ +#define ABSL_LOG_INTERNAL_QCHECK_GT_IMPL(val1, val1_text, val2, val2_text) \ ABSL_LOG_INTERNAL_QCHECK_OP(Check_GT, >, val1, val1_text, val2, val2_text) #ifndef NDEBUG -#define ABSL_DCHECK_EQ_IMPL(val1, val1_text, val2, val2_text) \ - ABSL_CHECK_EQ_IMPL(val1, val1_text, val2, val2_text) -#define ABSL_DCHECK_NE_IMPL(val1, val1_text, val2, val2_text) \ - ABSL_CHECK_NE_IMPL(val1, val1_text, val2, val2_text) -#define ABSL_DCHECK_LE_IMPL(val1, val1_text, val2, val2_text) \ - ABSL_CHECK_LE_IMPL(val1, val1_text, val2, val2_text) -#define ABSL_DCHECK_LT_IMPL(val1, val1_text, val2, val2_text) \ - ABSL_CHECK_LT_IMPL(val1, val1_text, val2, val2_text) -#define ABSL_DCHECK_GE_IMPL(val1, val1_text, val2, val2_text) \ - ABSL_CHECK_GE_IMPL(val1, val1_text, val2, val2_text) -#define ABSL_DCHECK_GT_IMPL(val1, val1_text, val2, val2_text) \ - ABSL_CHECK_GT_IMPL(val1, val1_text, val2, val2_text) +#define ABSL_LOG_INTERNAL_DCHECK_EQ_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_CHECK_EQ_IMPL(val1, val1_text, val2, val2_text) +#define ABSL_LOG_INTERNAL_DCHECK_NE_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_CHECK_NE_IMPL(val1, val1_text, val2, val2_text) +#define ABSL_LOG_INTERNAL_DCHECK_LE_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_CHECK_LE_IMPL(val1, val1_text, val2, val2_text) +#define ABSL_LOG_INTERNAL_DCHECK_LT_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_CHECK_LT_IMPL(val1, val1_text, val2, val2_text) +#define ABSL_LOG_INTERNAL_DCHECK_GE_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_CHECK_GE_IMPL(val1, val1_text, val2, val2_text) +#define ABSL_LOG_INTERNAL_DCHECK_GT_IMPL(val1, val1_text, val2, val2_text) \ + ABSL_LOG_INTERNAL_CHECK_GT_IMPL(val1, val1_text, val2, val2_text) #else // ndef NDEBUG -#define ABSL_DCHECK_EQ_IMPL(val1, val1_text, val2, val2_text) \ +#define ABSL_LOG_INTERNAL_DCHECK_EQ_IMPL(val1, val1_text, val2, val2_text) \ ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) -#define ABSL_DCHECK_NE_IMPL(val1, val1_text, val2, val2_text) \ +#define ABSL_LOG_INTERNAL_DCHECK_NE_IMPL(val1, val1_text, val2, val2_text) \ ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) -#define ABSL_DCHECK_LE_IMPL(val1, val1_text, val2, val2_text) \ +#define ABSL_LOG_INTERNAL_DCHECK_LE_IMPL(val1, val1_text, val2, val2_text) \ ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) -#define ABSL_DCHECK_LT_IMPL(val1, val1_text, val2, val2_text) \ +#define ABSL_LOG_INTERNAL_DCHECK_LT_IMPL(val1, val1_text, val2, val2_text) \ ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) -#define ABSL_DCHECK_GE_IMPL(val1, val1_text, val2, val2_text) \ +#define ABSL_LOG_INTERNAL_DCHECK_GE_IMPL(val1, val1_text, val2, val2_text) \ ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) -#define ABSL_DCHECK_GT_IMPL(val1, val1_text, val2, val2_text) \ +#define ABSL_LOG_INTERNAL_DCHECK_GT_IMPL(val1, val1_text, val2, val2_text) \ ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) #endif // def NDEBUG // CHECK_OK -#define ABSL_CHECK_OK_IMPL(status, status_text) \ +#define ABSL_LOG_INTERNAL_CHECK_OK_IMPL(status, status_text) \ ABSL_LOG_INTERNAL_CHECK_OK(status, status_text) -#define ABSL_QCHECK_OK_IMPL(status, status_text) \ +#define ABSL_LOG_INTERNAL_QCHECK_OK_IMPL(status, status_text) \ ABSL_LOG_INTERNAL_QCHECK_OK(status, status_text) #ifndef NDEBUG -#define ABSL_DCHECK_OK_IMPL(status, status_text) \ +#define ABSL_LOG_INTERNAL_DCHECK_OK_IMPL(status, status_text) \ ABSL_LOG_INTERNAL_CHECK_OK(status, status_text) #else -#define ABSL_DCHECK_OK_IMPL(status, status_text) \ +#define ABSL_LOG_INTERNAL_DCHECK_OK_IMPL(status, status_text) \ ABSL_LOG_INTERNAL_DCHECK_NOP(status, nullptr) #endif // CHECK_STREQ -#define ABSL_CHECK_STREQ_IMPL(s1, s1_text, s2, s2_text) \ +#define ABSL_LOG_INTERNAL_CHECK_STREQ_IMPL(s1, s1_text, s2, s2_text) \ ABSL_LOG_INTERNAL_CHECK_STROP(strcmp, ==, true, s1, s1_text, s2, s2_text) -#define ABSL_CHECK_STRNE_IMPL(s1, s1_text, s2, s2_text) \ +#define ABSL_LOG_INTERNAL_CHECK_STRNE_IMPL(s1, s1_text, s2, s2_text) \ ABSL_LOG_INTERNAL_CHECK_STROP(strcmp, !=, false, s1, s1_text, s2, s2_text) -#define ABSL_CHECK_STRCASEEQ_IMPL(s1, s1_text, s2, s2_text) \ +#define ABSL_LOG_INTERNAL_CHECK_STRCASEEQ_IMPL(s1, s1_text, s2, s2_text) \ ABSL_LOG_INTERNAL_CHECK_STROP(strcasecmp, ==, true, s1, s1_text, s2, s2_text) -#define ABSL_CHECK_STRCASENE_IMPL(s1, s1_text, s2, s2_text) \ +#define ABSL_LOG_INTERNAL_CHECK_STRCASENE_IMPL(s1, s1_text, s2, s2_text) \ ABSL_LOG_INTERNAL_CHECK_STROP(strcasecmp, !=, false, s1, s1_text, s2, s2_text) -#define ABSL_QCHECK_STREQ_IMPL(s1, s1_text, s2, s2_text) \ +#define ABSL_LOG_INTERNAL_QCHECK_STREQ_IMPL(s1, s1_text, s2, s2_text) \ ABSL_LOG_INTERNAL_QCHECK_STROP(strcmp, ==, true, s1, s1_text, s2, s2_text) -#define ABSL_QCHECK_STRNE_IMPL(s1, s1_text, s2, s2_text) \ +#define ABSL_LOG_INTERNAL_QCHECK_STRNE_IMPL(s1, s1_text, s2, s2_text) \ ABSL_LOG_INTERNAL_QCHECK_STROP(strcmp, !=, false, s1, s1_text, s2, s2_text) -#define ABSL_QCHECK_STRCASEEQ_IMPL(s1, s1_text, s2, s2_text) \ +#define ABSL_LOG_INTERNAL_QCHECK_STRCASEEQ_IMPL(s1, s1_text, s2, s2_text) \ ABSL_LOG_INTERNAL_QCHECK_STROP(strcasecmp, ==, true, s1, s1_text, s2, s2_text) -#define ABSL_QCHECK_STRCASENE_IMPL(s1, s1_text, s2, s2_text) \ - ABSL_LOG_INTERNAL_QCHECK_STROP(strcasecmp, !=, false, s1, s1_text, s2, \ +#define ABSL_LOG_INTERNAL_QCHECK_STRCASENE_IMPL(s1, s1_text, s2, s2_text) \ + ABSL_LOG_INTERNAL_QCHECK_STROP(strcasecmp, !=, false, s1, s1_text, s2, \ s2_text) #ifndef NDEBUG -#define ABSL_DCHECK_STREQ_IMPL(s1, s1_text, s2, s2_text) \ - ABSL_CHECK_STREQ_IMPL(s1, s1_text, s2, s2_text) -#define ABSL_DCHECK_STRCASEEQ_IMPL(s1, s1_text, s2, s2_text) \ - ABSL_CHECK_STRCASEEQ_IMPL(s1, s1_text, s2, s2_text) -#define ABSL_DCHECK_STRNE_IMPL(s1, s1_text, s2, s2_text) \ - ABSL_CHECK_STRNE_IMPL(s1, s1_text, s2, s2_text) -#define ABSL_DCHECK_STRCASENE_IMPL(s1, s1_text, s2, s2_text) \ - ABSL_CHECK_STRCASENE_IMPL(s1, s1_text, s2, s2_text) +#define ABSL_LOG_INTERNAL_DCHECK_STREQ_IMPL(s1, s1_text, s2, s2_text) \ + ABSL_LOG_INTERNAL_CHECK_STREQ_IMPL(s1, s1_text, s2, s2_text) +#define ABSL_LOG_INTERNAL_DCHECK_STRCASEEQ_IMPL(s1, s1_text, s2, s2_text) \ + ABSL_LOG_INTERNAL_CHECK_STRCASEEQ_IMPL(s1, s1_text, s2, s2_text) +#define ABSL_LOG_INTERNAL_DCHECK_STRNE_IMPL(s1, s1_text, s2, s2_text) \ + ABSL_LOG_INTERNAL_CHECK_STRNE_IMPL(s1, s1_text, s2, s2_text) +#define ABSL_LOG_INTERNAL_DCHECK_STRCASENE_IMPL(s1, s1_text, s2, s2_text) \ + ABSL_LOG_INTERNAL_CHECK_STRCASENE_IMPL(s1, s1_text, s2, s2_text) #else // ndef NDEBUG -#define ABSL_DCHECK_STREQ_IMPL(s1, s1_text, s2, s2_text) \ +#define ABSL_LOG_INTERNAL_DCHECK_STREQ_IMPL(s1, s1_text, s2, s2_text) \ ABSL_LOG_INTERNAL_DCHECK_NOP(s1, s2) -#define ABSL_DCHECK_STRCASEEQ_IMPL(s1, s1_text, s2, s2_text) \ +#define ABSL_LOG_INTERNAL_DCHECK_STRCASEEQ_IMPL(s1, s1_text, s2, s2_text) \ ABSL_LOG_INTERNAL_DCHECK_NOP(s1, s2) -#define ABSL_DCHECK_STRNE_IMPL(s1, s1_text, s2, s2_text) \ +#define ABSL_LOG_INTERNAL_DCHECK_STRNE_IMPL(s1, s1_text, s2, s2_text) \ ABSL_LOG_INTERNAL_DCHECK_NOP(s1, s2) -#define ABSL_DCHECK_STRCASENE_IMPL(s1, s1_text, s2, s2_text) \ +#define ABSL_LOG_INTERNAL_DCHECK_STRCASENE_IMPL(s1, s1_text, s2, s2_text) \ ABSL_LOG_INTERNAL_DCHECK_NOP(s1, s2) #endif // def NDEBUG diff --git a/absl/log/internal/check_op.h b/absl/log/internal/check_op.h index 4907b89b..11f0f407 100644 --- a/absl/log/internal/check_op.h +++ b/absl/log/internal/check_op.h @@ -65,6 +65,7 @@ ::absl::log_internal::GetReferenceableValue(val2), \ ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(val1_text \ " " #op " " val2_text))) \ + ABSL_LOG_INTERNAL_CONDITION_FATAL(STATELESS, true) \ ABSL_LOG_INTERNAL_CHECK(*absl_log_internal_check_op_result).InternalStream() #define ABSL_LOG_INTERNAL_QCHECK_OP(name, op, val1, val1_text, val2, \ val2_text) \ @@ -74,6 +75,7 @@ ::absl::log_internal::GetReferenceableValue(val2), \ ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL( \ val1_text " " #op " " val2_text))) \ + ABSL_LOG_INTERNAL_CONDITION_QFATAL(STATELESS, true) \ ABSL_LOG_INTERNAL_QCHECK(*absl_log_internal_qcheck_op_result).InternalStream() #define ABSL_LOG_INTERNAL_CHECK_STROP(func, op, expected, s1, s1_text, s2, \ s2_text) \ @@ -82,6 +84,7 @@ (s1), (s2), \ ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(s1_text " " #op \ " " s2_text))) \ + ABSL_LOG_INTERNAL_CONDITION_FATAL(STATELESS, true) \ ABSL_LOG_INTERNAL_CHECK(*absl_log_internal_check_strop_result) \ .InternalStream() #define ABSL_LOG_INTERNAL_QCHECK_STROP(func, op, expected, s1, s1_text, s2, \ @@ -91,6 +94,7 @@ (s1), (s2), \ ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(s1_text " " #op \ " " s2_text))) \ + ABSL_LOG_INTERNAL_CONDITION_QFATAL(STATELESS, true) \ ABSL_LOG_INTERNAL_QCHECK(*absl_log_internal_qcheck_strop_result) \ .InternalStream() // This one is tricky: @@ -113,6 +117,10 @@ // * As usual, no braces so we can stream into the expansion with `operator<<`. // * Also as usual, it must expand to a single (partial) statement with no // ambiguous-else problems. +// * When stripped by `ABSL_MIN_LOG_LEVEL`, we must discard the `<expr> is OK` +// string literal and abort without doing any streaming. We don't need to +// strip the call to stringify the non-ok `Status` as long as we don't log it; +// dropping the `Status`'s message text is out of scope. #define ABSL_LOG_INTERNAL_CHECK_OK(val, val_text) \ for (::std::pair<const ::absl::Status*, ::std::string*> \ absl_log_internal_check_ok_goo; \ @@ -126,22 +134,24 @@ ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(val_text \ " is OK")), \ !ABSL_PREDICT_TRUE(absl_log_internal_check_ok_goo.first->ok());) \ + ABSL_LOG_INTERNAL_CONDITION_FATAL(STATELESS, true) \ ABSL_LOG_INTERNAL_CHECK(*absl_log_internal_check_ok_goo.second) \ .InternalStream() -#define ABSL_LOG_INTERNAL_QCHECK_OK(val, val_text) \ - for (::std::pair<const ::absl::Status*, ::std::string*> \ - absl_log_internal_check_ok_goo; \ - absl_log_internal_check_ok_goo.first = \ - ::absl::log_internal::AsStatus(val), \ - absl_log_internal_check_ok_goo.second = \ - ABSL_PREDICT_TRUE(absl_log_internal_check_ok_goo.first->ok()) \ - ? nullptr \ - : ::absl::status_internal::MakeCheckFailString( \ - absl_log_internal_check_ok_goo.first, \ - ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(val_text \ - " is OK")), \ - !ABSL_PREDICT_TRUE(absl_log_internal_check_ok_goo.first->ok());) \ - ABSL_LOG_INTERNAL_QCHECK(*absl_log_internal_check_ok_goo.second) \ +#define ABSL_LOG_INTERNAL_QCHECK_OK(val, val_text) \ + for (::std::pair<const ::absl::Status*, ::std::string*> \ + absl_log_internal_qcheck_ok_goo; \ + absl_log_internal_qcheck_ok_goo.first = \ + ::absl::log_internal::AsStatus(val), \ + absl_log_internal_qcheck_ok_goo.second = \ + ABSL_PREDICT_TRUE(absl_log_internal_qcheck_ok_goo.first->ok()) \ + ? nullptr \ + : ::absl::status_internal::MakeCheckFailString( \ + absl_log_internal_qcheck_ok_goo.first, \ + ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(val_text \ + " is OK")), \ + !ABSL_PREDICT_TRUE(absl_log_internal_qcheck_ok_goo.first->ok());) \ + ABSL_LOG_INTERNAL_CONDITION_QFATAL(STATELESS, true) \ + ABSL_LOG_INTERNAL_QCHECK(*absl_log_internal_qcheck_ok_goo.second) \ .InternalStream() namespace absl { @@ -152,8 +162,8 @@ template <typename T> class StatusOr; namespace status_internal { -std::string* MakeCheckFailString(const absl::Status* status, - const char* prefix); +ABSL_ATTRIBUTE_PURE_FUNCTION std::string* MakeCheckFailString( + const absl::Status* status, const char* prefix); } // namespace status_internal namespace log_internal { @@ -314,6 +324,20 @@ ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(const unsigned char*); ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(const void*); #undef ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN +// `ABSL_LOG_INTERNAL_CHECK_OP_IMPL_RESULT` skips formatting the Check_OP result +// string iff `ABSL_MIN_LOG_LEVEL` exceeds `kFatal`, instead returning an empty +// string. +#ifdef ABSL_MIN_LOG_LEVEL +#define ABSL_LOG_INTERNAL_CHECK_OP_IMPL_RESULT(U1, U2, v1, v2, exprtext) \ + ((::absl::LogSeverity::kFatal >= \ + static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL)) \ + ? MakeCheckOpString<U1, U2>(v1, v2, exprtext) \ + : new std::string()) +#else +#define ABSL_LOG_INTERNAL_CHECK_OP_IMPL_RESULT(U1, U2, v1, v2, exprtext) \ + MakeCheckOpString<U1, U2>(v1, v2, exprtext) +#endif + // Helper functions for `ABSL_LOG_INTERNAL_CHECK_OP` macro family. The // `(int, int)` override works around the issue that the compiler will not // instantiate the template version of the function on values of unnamed enum @@ -326,7 +350,8 @@ ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(const void*); using U2 = CheckOpStreamType<T2>; \ return ABSL_PREDICT_TRUE(v1 op v2) \ ? nullptr \ - : MakeCheckOpString<U1, U2>(v1, v2, exprtext); \ + : ABSL_LOG_INTERNAL_CHECK_OP_IMPL_RESULT(U1, U2, v1, v2, \ + exprtext); \ } \ inline constexpr ::std::string* name##Impl(int v1, int v2, \ const char* exprtext) { \ @@ -339,6 +364,7 @@ ABSL_LOG_INTERNAL_CHECK_OP_IMPL(Check_LE, <=) ABSL_LOG_INTERNAL_CHECK_OP_IMPL(Check_LT, <) ABSL_LOG_INTERNAL_CHECK_OP_IMPL(Check_GE, >=) ABSL_LOG_INTERNAL_CHECK_OP_IMPL(Check_GT, >) +#undef ABSL_LOG_INTERNAL_CHECK_OP_IMPL_RESULT #undef ABSL_LOG_INTERNAL_CHECK_OP_IMPL std::string* CheckstrcmptrueImpl(const char* s1, const char* s2, @@ -371,7 +397,9 @@ inline constexpr unsigned short GetReferenceableValue( // NOLINT return t; } inline constexpr int GetReferenceableValue(int t) { return t; } -inline unsigned int GetReferenceableValue(unsigned int t) { return t; } +inline constexpr unsigned int GetReferenceableValue(unsigned int t) { + return t; +} inline constexpr long GetReferenceableValue(long t) { return t; } // NOLINT inline constexpr unsigned long GetReferenceableValue( // NOLINT unsigned long t) { // NOLINT diff --git a/absl/log/internal/conditions.h b/absl/log/internal/conditions.h index b89f1dfd..645f3c23 100644 --- a/absl/log/internal/conditions.h +++ b/absl/log/internal/conditions.h @@ -23,7 +23,7 @@ #ifndef ABSL_LOG_INTERNAL_CONDITIONS_H_ #define ABSL_LOG_INTERNAL_CONDITIONS_H_ -#ifdef _WIN32 +#if defined(_WIN32) || defined(__hexagon__) #include <cstdlib> #else #include <unistd.h> @@ -56,13 +56,19 @@ // the ternary expression does a better job avoiding spurious diagnostics // (dangling else, missing switch case) and preserving noreturn semantics (e.g. // on `LOG(FATAL)`) without requiring braces. +// +// The `switch` ensures that this expansion is the beginning of a statement (as +// opposed to an expression) and prevents shenanigans like +// `AFunction(LOG(INFO))` and `decltype(LOG(INFO))`. The apparently-redundant +// `default` case makes the condition more amenable to Clang dataflow analysis. #define ABSL_LOG_INTERNAL_STATELESS_CONDITION(condition) \ switch (0) \ case 0: \ + default: \ !(condition) ? (void)0 : ::absl::log_internal::Voidify()&& // `ABSL_LOG_INTERNAL_STATEFUL_CONDITION` applies a condition like -// `ABSL_LOG_INTERNAL_CONDITION` but adds to that a series of variable +// `ABSL_LOG_INTERNAL_STATELESS_CONDITION` but adds to that a series of variable // declarations, including a local static object which stores the state needed // to implement the stateful macros like `LOG_EVERY_N`. // @@ -131,21 +137,30 @@ ? true \ : (::absl::log_internal::ExitQuietly(), false)) \ : false)) - -#define ABSL_LOG_INTERNAL_CONDITION_LEVEL(severity) \ - for (int log_internal_severity_loop = 1; log_internal_severity_loop; \ - log_internal_severity_loop = 0) \ - for (const absl::LogSeverity log_internal_severity = \ - ::absl::NormalizeLogSeverity(severity); \ - log_internal_severity_loop; log_internal_severity_loop = 0) \ +#define ABSL_LOG_INTERNAL_CONDITION_DFATAL(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION( \ + (ABSL_ASSUME(absl::kLogDebugFatal == absl::LogSeverity::kError || \ + absl::kLogDebugFatal == absl::LogSeverity::kFatal), \ + (condition) && \ + (::absl::kLogDebugFatal >= \ + static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) || \ + (::absl::kLogDebugFatal == ::absl::LogSeverity::kFatal && \ + (::absl::log_internal::AbortQuietly(), false))))) + +#define ABSL_LOG_INTERNAL_CONDITION_LEVEL(severity) \ + for (int absl_log_internal_severity_loop = 1; \ + absl_log_internal_severity_loop; absl_log_internal_severity_loop = 0) \ + for (const absl::LogSeverity absl_log_internal_severity = \ + ::absl::NormalizeLogSeverity(severity); \ + absl_log_internal_severity_loop; absl_log_internal_severity_loop = 0) \ ABSL_LOG_INTERNAL_CONDITION_LEVEL_IMPL -#define ABSL_LOG_INTERNAL_CONDITION_LEVEL_IMPL(type, condition) \ - ABSL_LOG_INTERNAL_##type##_CONDITION( \ - (condition) && \ - (log_internal_severity >= \ - static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) || \ - (log_internal_severity == ::absl::LogSeverity::kFatal && \ - (::absl::log_internal::AbortQuietly(), false)))) +#define ABSL_LOG_INTERNAL_CONDITION_LEVEL_IMPL(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION(( \ + (condition) && \ + (absl_log_internal_severity >= \ + static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) || \ + (absl_log_internal_severity == ::absl::LogSeverity::kFatal && \ + (::absl::log_internal::AbortQuietly(), false))))) #else // ndef ABSL_MIN_LOG_LEVEL #define ABSL_LOG_INTERNAL_CONDITION_INFO(type, condition) \ ABSL_LOG_INTERNAL_##type##_CONDITION(condition) @@ -157,12 +172,14 @@ ABSL_LOG_INTERNAL_##type##_CONDITION(condition) #define ABSL_LOG_INTERNAL_CONDITION_QFATAL(type, condition) \ ABSL_LOG_INTERNAL_##type##_CONDITION(condition) -#define ABSL_LOG_INTERNAL_CONDITION_LEVEL(severity) \ - for (int log_internal_severity_loop = 1; log_internal_severity_loop; \ - log_internal_severity_loop = 0) \ - for (const absl::LogSeverity log_internal_severity = \ - ::absl::NormalizeLogSeverity(severity); \ - log_internal_severity_loop; log_internal_severity_loop = 0) \ +#define ABSL_LOG_INTERNAL_CONDITION_DFATAL(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION(condition) +#define ABSL_LOG_INTERNAL_CONDITION_LEVEL(severity) \ + for (int absl_log_internal_severity_loop = 1; \ + absl_log_internal_severity_loop; absl_log_internal_severity_loop = 0) \ + for (const absl::LogSeverity absl_log_internal_severity = \ + ::absl::NormalizeLogSeverity(severity); \ + absl_log_internal_severity_loop; absl_log_internal_severity_loop = 0) \ ABSL_LOG_INTERNAL_CONDITION_LEVEL_IMPL #define ABSL_LOG_INTERNAL_CONDITION_LEVEL_IMPL(type, condition) \ ABSL_LOG_INTERNAL_##type##_CONDITION(condition) diff --git a/absl/log/internal/flags.h b/absl/log/internal/flags.h index 0c5e81ed..c4539785 100644 --- a/absl/log/internal/flags.h +++ b/absl/log/internal/flags.h @@ -33,7 +33,7 @@ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! // Log messages at this severity or above are sent to stderr in *addition* to -// logfiles. Defaults to `ERROR`. See log_severity.h for numeric values of +// `LogSink`s. Defaults to `ERROR`. See log_severity.h for numeric values of // severity levels. ABSL_DECLARE_FLAG(int, stderrthreshold); @@ -50,4 +50,10 @@ ABSL_DECLARE_FLAG(std::string, log_backtrace_at); // each message logged. Defaults to true. ABSL_DECLARE_FLAG(bool, log_prefix); +// Global log verbosity level. Default is 0. +ABSL_DECLARE_FLAG(int, v); + +// Per-module log verbosity level. By default is empty and is unused. +ABSL_DECLARE_FLAG(std::string, vmodule); + #endif // ABSL_LOG_INTERNAL_FLAGS_H_ diff --git a/absl/log/internal/fnmatch.cc b/absl/log/internal/fnmatch.cc new file mode 100644 index 00000000..26e1e57f --- /dev/null +++ b/absl/log/internal/fnmatch.cc @@ -0,0 +1,73 @@ +// Copyright 2023 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/log/internal/fnmatch.h" + +#include <cstddef> + +#include "absl/base/config.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { +bool FNMatch(absl::string_view pattern, absl::string_view str) { + bool in_wildcard_match = false; + while (true) { + if (pattern.empty()) { + // `pattern` is exhausted; succeed if all of `str` was consumed matching + // it. + return in_wildcard_match || str.empty(); + } + if (str.empty()) { + // `str` is exhausted; succeed if `pattern` is empty or all '*'s. + return pattern.find_first_not_of('*') == pattern.npos; + } + switch (pattern.front()) { + case '*': + pattern.remove_prefix(1); + in_wildcard_match = true; + break; + case '?': + pattern.remove_prefix(1); + str.remove_prefix(1); + break; + default: + if (in_wildcard_match) { + absl::string_view fixed_portion = pattern; + const size_t end = fixed_portion.find_first_of("*?"); + if (end != fixed_portion.npos) { + fixed_portion = fixed_portion.substr(0, end); + } + const size_t match = str.find(fixed_portion); + if (match == str.npos) { + return false; + } + pattern.remove_prefix(fixed_portion.size()); + str.remove_prefix(match + fixed_portion.size()); + in_wildcard_match = false; + } else { + if (pattern.front() != str.front()) { + return false; + } + pattern.remove_prefix(1); + str.remove_prefix(1); + } + break; + } + } +} +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/flags/flag.cc b/absl/log/internal/fnmatch.h index 531df128..4ea147cc 100644 --- a/absl/flags/flag.cc +++ b/absl/log/internal/fnmatch.h @@ -1,11 +1,10 @@ -// -// Copyright 2019 The Abseil Authors. +// Copyright 2023 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 +// 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, @@ -13,26 +12,24 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "absl/flags/flag.h" +#ifndef ABSL_LOG_INTERNAL_FNMATCH_H_ +#define ABSL_LOG_INTERNAL_FNMATCH_H_ #include "absl/base/config.h" +#include "absl/strings/string_view.h" namespace absl { ABSL_NAMESPACE_BEGIN - -// This global mutex protects on-demand construction of flag objects in MSVC -// builds. -#if defined(_MSC_VER) && !defined(__clang__) - -namespace flags_internal { - -ABSL_CONST_INIT static absl::Mutex construction_guard(absl::kConstInit); - -absl::Mutex* GetGlobalConstructionGuard() { return &construction_guard; } - -} // namespace flags_internal - -#endif - +namespace log_internal { +// Like POSIX `fnmatch`, but: +// * accepts `string_view` +// * does not allocate any dynamic memory +// * only supports * and ? wildcards and not bracket expressions [...] +// * wildcards may match / +// * no backslash-escaping +bool FNMatch(absl::string_view pattern, absl::string_view str); +} // namespace log_internal ABSL_NAMESPACE_END } // namespace absl + +#endif // ABSL_LOG_INTERNAL_FNMATCH_H_ diff --git a/absl/log/internal/fnmatch_benchmark.cc b/absl/log/internal/fnmatch_benchmark.cc new file mode 100644 index 00000000..f062ba20 --- /dev/null +++ b/absl/log/internal/fnmatch_benchmark.cc @@ -0,0 +1,29 @@ +// Copyright 2023 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/log/internal/fnmatch.h" +#include "benchmark/benchmark.h" + +namespace { +void BM_FNMatch(benchmark::State& state) { + while (state.KeepRunning()) { + bool ret = + absl::log_internal::FNMatch("*?*asdf*?*we???asdf**asdf*we", + "QWERFASVWERASDFWEDFASDasdfQWERGFWASDERREWF" + "weHOOasdf@#$%TW#ZSERasdfQW#REGTZSERERwe"); + benchmark::DoNotOptimize(ret); + } +} +BENCHMARK(BM_FNMatch); +} // namespace diff --git a/absl/log/internal/fnmatch_test.cc b/absl/log/internal/fnmatch_test.cc new file mode 100644 index 00000000..e16a64ec --- /dev/null +++ b/absl/log/internal/fnmatch_test.cc @@ -0,0 +1,59 @@ +// Copyright 2023 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/log/internal/fnmatch.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace { +using ::testing::IsFalse; +using ::testing::IsTrue; + +TEST(FNMatchTest, Works) { + using absl::log_internal::FNMatch; + EXPECT_THAT(FNMatch("foo", "foo"), IsTrue()); + EXPECT_THAT(FNMatch("foo", "bar"), IsFalse()); + EXPECT_THAT(FNMatch("foo", "fo"), IsFalse()); + EXPECT_THAT(FNMatch("foo", "foo2"), IsFalse()); + EXPECT_THAT(FNMatch("bar/foo.ext", "bar/foo.ext"), IsTrue()); + EXPECT_THAT(FNMatch("*ba*r/fo*o.ext*", "bar/foo.ext"), IsTrue()); + EXPECT_THAT(FNMatch("bar/foo.ext", "bar/baz.ext"), IsFalse()); + EXPECT_THAT(FNMatch("bar/foo.ext", "bar/foo"), IsFalse()); + EXPECT_THAT(FNMatch("bar/foo.ext", "bar/foo.ext.zip"), IsFalse()); + EXPECT_THAT(FNMatch("ba?/*.ext", "bar/foo.ext"), IsTrue()); + EXPECT_THAT(FNMatch("ba?/*.ext", "baZ/FOO.ext"), IsTrue()); + EXPECT_THAT(FNMatch("ba?/*.ext", "barr/foo.ext"), IsFalse()); + EXPECT_THAT(FNMatch("ba?/*.ext", "bar/foo.ext2"), IsFalse()); + EXPECT_THAT(FNMatch("ba?/*", "bar/foo.ext2"), IsTrue()); + EXPECT_THAT(FNMatch("ba?/*", "bar/"), IsTrue()); + EXPECT_THAT(FNMatch("ba?/?", "bar/"), IsFalse()); + EXPECT_THAT(FNMatch("ba?/*", "bar"), IsFalse()); + EXPECT_THAT(FNMatch("?x", "zx"), IsTrue()); + EXPECT_THAT(FNMatch("*b", "aab"), IsTrue()); + EXPECT_THAT(FNMatch("a*b", "aXb"), IsTrue()); + EXPECT_THAT(FNMatch("", ""), IsTrue()); + EXPECT_THAT(FNMatch("", "a"), IsFalse()); + EXPECT_THAT(FNMatch("ab*", "ab"), IsTrue()); + EXPECT_THAT(FNMatch("ab**", "ab"), IsTrue()); + EXPECT_THAT(FNMatch("ab*?", "ab"), IsFalse()); + EXPECT_THAT(FNMatch("*", "bbb"), IsTrue()); + EXPECT_THAT(FNMatch("*", ""), IsTrue()); + EXPECT_THAT(FNMatch("?", ""), IsFalse()); + EXPECT_THAT(FNMatch("***", "**p"), IsTrue()); + EXPECT_THAT(FNMatch("**", "*"), IsTrue()); + EXPECT_THAT(FNMatch("*?", "*"), IsTrue()); +} + +} // namespace diff --git a/absl/log/internal/globals.cc b/absl/log/internal/globals.cc index 863b047f..359858f1 100644 --- a/absl/log/internal/globals.cc +++ b/absl/log/internal/globals.cc @@ -17,11 +17,16 @@ #include <atomic> #include <cstdio> +#if defined(__EMSCRIPTEN__) +#include <emscripten/console.h> +#endif + #include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" #include "absl/base/log_severity.h" #include "absl/strings/string_view.h" +#include "absl/strings/strip.h" #include "absl/time/time.h" namespace absl { @@ -55,9 +60,24 @@ void SetInitialized() { } void WriteToStderr(absl::string_view message, absl::LogSeverity severity) { + if (message.empty()) return; +#if defined(__EMSCRIPTEN__) + // In WebAssembly, bypass filesystem emulation via fwrite. + // Skip a trailing newline character as emscripten_errn adds one itself. + const auto message_minus_newline = absl::StripSuffix(message, "\n"); + // emscripten_errn was introduced in 3.1.41 but broken in standalone mode + // until 3.1.43. +#if ABSL_INTERNAL_EMSCRIPTEN_VERSION >= 3001043 + emscripten_errn(message_minus_newline.data(), message_minus_newline.size()); +#else + std::string null_terminated_message(message_minus_newline); + _emscripten_err(null_terminated_message.c_str()); +#endif +#else // Avoid using std::cerr from this module since we may get called during // exit code, and cerr may be partially or fully destroyed by then. std::fwrite(message.data(), message.size(), 1, stderr); +#endif #if defined(_WIN64) || defined(_WIN32) || defined(_WIN16) // C99 requires stderr to not be fully-buffered by default (7.19.3.7), but diff --git a/absl/log/internal/log_format.cc b/absl/log/internal/log_format.cc index b8cd5ac4..23cef88a 100644 --- a/absl/log/internal/log_format.cc +++ b/absl/log/internal/log_format.cc @@ -49,7 +49,7 @@ 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. +// constexpr if once the minimum C++ version Abseil supports is C++17. template <typename T> inline std::enable_if_t<!std::is_signed<T>::value> PutLeadingWhitespace(T tid, char*& p) { @@ -113,27 +113,29 @@ 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(static_cast<size_t>(ci.cs.month()), p); + absl::numbers_internal::PutTwoDigits(static_cast<uint32_t>(ci.cs.month()), p); p += 2; - absl::numbers_internal::PutTwoDigits(static_cast<size_t>(ci.cs.day()), p); + absl::numbers_internal::PutTwoDigits(static_cast<uint32_t>(ci.cs.day()), p); p += 2; *p++ = ' '; - absl::numbers_internal::PutTwoDigits(static_cast<size_t>(ci.cs.hour()), p); + absl::numbers_internal::PutTwoDigits(static_cast<uint32_t>(ci.cs.hour()), p); p += 2; *p++ = ':'; - absl::numbers_internal::PutTwoDigits(static_cast<size_t>(ci.cs.minute()), p); + absl::numbers_internal::PutTwoDigits(static_cast<uint32_t>(ci.cs.minute()), + p); p += 2; *p++ = ':'; - absl::numbers_internal::PutTwoDigits(static_cast<size_t>(ci.cs.second()), p); + absl::numbers_internal::PutTwoDigits(static_cast<uint32_t>(ci.cs.second()), + p); p += 2; *p++ = '.'; const int64_t usecs = absl::ToInt64Microseconds(ci.subsecond); - absl::numbers_internal::PutTwoDigits(static_cast<size_t>(usecs / 10000), p); + absl::numbers_internal::PutTwoDigits(static_cast<uint32_t>(usecs / 10000), p); p += 2; - absl::numbers_internal::PutTwoDigits(static_cast<size_t>(usecs / 100 % 100), + absl::numbers_internal::PutTwoDigits(static_cast<uint32_t>(usecs / 100 % 100), p); p += 2; - absl::numbers_internal::PutTwoDigits(static_cast<size_t>(usecs % 100), p); + absl::numbers_internal::PutTwoDigits(static_cast<uint32_t>(usecs % 100), p); p += 2; *p++ = ' '; PutLeadingWhitespace(tid, p); diff --git a/absl/log/internal/log_impl.h b/absl/log/internal/log_impl.h index 82b5ed84..99de6dbb 100644 --- a/absl/log/internal/log_impl.h +++ b/absl/log/internal/log_impl.h @@ -15,195 +15,265 @@ #ifndef ABSL_LOG_INTERNAL_LOG_IMPL_H_ #define ABSL_LOG_INTERNAL_LOG_IMPL_H_ +#include "absl/log/absl_vlog_is_on.h" #include "absl/log/internal/conditions.h" #include "absl/log/internal/log_message.h" #include "absl/log/internal/strip.h" // ABSL_LOG() -#define ABSL_LOG_IMPL(severity) \ +#define ABSL_LOG_INTERNAL_LOG_IMPL(severity) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, true) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() // ABSL_PLOG() -#define ABSL_PLOG_IMPL(severity) \ +#define ABSL_LOG_INTERNAL_PLOG_IMPL(severity) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, true) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ .WithPerror() // ABSL_DLOG() #ifndef NDEBUG -#define ABSL_DLOG_IMPL(severity) \ +#define ABSL_LOG_INTERNAL_DLOG_IMPL(severity) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, true) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() #else -#define ABSL_DLOG_IMPL(severity) \ +#define ABSL_LOG_INTERNAL_DLOG_IMPL(severity) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, false) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() #endif -#define ABSL_LOG_IF_IMPL(severity, condition) \ +// The `switch` ensures that this expansion is the begnning of a statement (as +// opposed to an expression). The use of both `case 0` and `default` is to +// suppress a compiler warning. +#define ABSL_LOG_INTERNAL_VLOG_IMPL(verbose_level) \ + switch (const int absl_logging_internal_verbose_level = (verbose_level)) \ + case 0: \ + default: \ + ABSL_LOG_INTERNAL_LOG_IF_IMPL( \ + _INFO, ABSL_VLOG_IS_ON(absl_logging_internal_verbose_level)) \ + .WithVerbosity(absl_logging_internal_verbose_level) + +#ifndef NDEBUG +#define ABSL_LOG_INTERNAL_DVLOG_IMPL(verbose_level) \ + switch (const int absl_logging_internal_verbose_level = (verbose_level)) \ + case 0: \ + default: \ + ABSL_LOG_INTERNAL_LOG_IF_IMPL( \ + _INFO, ABSL_VLOG_IS_ON(absl_logging_internal_verbose_level)) \ + .WithVerbosity(absl_logging_internal_verbose_level) +#else +#define ABSL_LOG_INTERNAL_DVLOG_IMPL(verbose_level) \ + switch (const int absl_logging_internal_verbose_level = (verbose_level)) \ + case 0: \ + default: \ + ABSL_LOG_INTERNAL_LOG_IF_IMPL( \ + _INFO, false && ABSL_VLOG_IS_ON(absl_logging_internal_verbose_level)) \ + .WithVerbosity(absl_logging_internal_verbose_level) +#endif + +#define ABSL_LOG_INTERNAL_LOG_IF_IMPL(severity, condition) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, condition) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() -#define ABSL_PLOG_IF_IMPL(severity, condition) \ +#define ABSL_LOG_INTERNAL_PLOG_IF_IMPL(severity, condition) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, condition) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ .WithPerror() #ifndef NDEBUG -#define ABSL_DLOG_IF_IMPL(severity, condition) \ +#define ABSL_LOG_INTERNAL_DLOG_IF_IMPL(severity, condition) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, condition) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() #else -#define ABSL_DLOG_IF_IMPL(severity, condition) \ +#define ABSL_LOG_INTERNAL_DLOG_IF_IMPL(severity, condition) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, false && (condition)) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() #endif // ABSL_LOG_EVERY_N -#define ABSL_LOG_EVERY_N_IMPL(severity, n) \ +#define ABSL_LOG_INTERNAL_LOG_EVERY_N_IMPL(severity, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryN, n) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() // ABSL_LOG_FIRST_N -#define ABSL_LOG_FIRST_N_IMPL(severity, n) \ +#define ABSL_LOG_INTERNAL_LOG_FIRST_N_IMPL(severity, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(FirstN, n) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() // ABSL_LOG_EVERY_POW_2 -#define ABSL_LOG_EVERY_POW_2_IMPL(severity) \ +#define ABSL_LOG_INTERNAL_LOG_EVERY_POW_2_IMPL(severity) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryPow2) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() // ABSL_LOG_EVERY_N_SEC -#define ABSL_LOG_EVERY_N_SEC_IMPL(severity, n_seconds) \ +#define ABSL_LOG_INTERNAL_LOG_EVERY_N_SEC_IMPL(severity, n_seconds) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryNSec, n_seconds) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() -#define ABSL_PLOG_EVERY_N_IMPL(severity, n) \ +#define ABSL_LOG_INTERNAL_PLOG_EVERY_N_IMPL(severity, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryN, n) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ .WithPerror() -#define ABSL_PLOG_FIRST_N_IMPL(severity, n) \ +#define ABSL_LOG_INTERNAL_PLOG_FIRST_N_IMPL(severity, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(FirstN, n) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ .WithPerror() -#define ABSL_PLOG_EVERY_POW_2_IMPL(severity) \ +#define ABSL_LOG_INTERNAL_PLOG_EVERY_POW_2_IMPL(severity) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryPow2) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ .WithPerror() -#define ABSL_PLOG_EVERY_N_SEC_IMPL(severity, n_seconds) \ +#define ABSL_LOG_INTERNAL_PLOG_EVERY_N_SEC_IMPL(severity, n_seconds) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryNSec, n_seconds) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ .WithPerror() #ifndef NDEBUG -#define ABSL_DLOG_EVERY_N_IMPL(severity, n) \ - ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ +#define ABSL_LOG_INTERNAL_DLOG_EVERY_N_IMPL(severity, n) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ (EveryN, n) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() -#define ABSL_DLOG_FIRST_N_IMPL(severity, n) \ - ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ +#define ABSL_LOG_INTERNAL_DLOG_FIRST_N_IMPL(severity, n) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ (FirstN, n) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() -#define ABSL_DLOG_EVERY_POW_2_IMPL(severity) \ - ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ +#define ABSL_LOG_INTERNAL_DLOG_EVERY_POW_2_IMPL(severity) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ (EveryPow2) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() -#define ABSL_DLOG_EVERY_N_SEC_IMPL(severity, n_seconds) \ - ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ +#define ABSL_LOG_INTERNAL_DLOG_EVERY_N_SEC_IMPL(severity, n_seconds) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ (EveryNSec, n_seconds) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() #else // def NDEBUG -#define ABSL_DLOG_EVERY_N_IMPL(severity, n) \ - ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ +#define ABSL_LOG_INTERNAL_DLOG_EVERY_N_IMPL(severity, n) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ (EveryN, n) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() -#define ABSL_DLOG_FIRST_N_IMPL(severity, n) \ - ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ +#define ABSL_LOG_INTERNAL_DLOG_FIRST_N_IMPL(severity, n) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ (FirstN, n) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() -#define ABSL_DLOG_EVERY_POW_2_IMPL(severity) \ - ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ +#define ABSL_LOG_INTERNAL_DLOG_EVERY_POW_2_IMPL(severity) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ (EveryPow2) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() -#define ABSL_DLOG_EVERY_N_SEC_IMPL(severity, n_seconds) \ - ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ +#define ABSL_LOG_INTERNAL_DLOG_EVERY_N_SEC_IMPL(severity, n_seconds) \ + ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ (EveryNSec, n_seconds) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() #endif // def NDEBUG -#define ABSL_LOG_IF_EVERY_N_IMPL(severity, condition, n) \ +#define ABSL_LOG_INTERNAL_VLOG_EVERY_N_IMPL(verbose_level, n) \ + switch (const int absl_logging_internal_verbose_level = (verbose_level)) \ + case 0: \ + default: \ + ABSL_LOG_INTERNAL_CONDITION_INFO( \ + STATEFUL, ABSL_VLOG_IS_ON(absl_logging_internal_verbose_level)) \ + (EveryN, n) ABSL_LOGGING_INTERNAL_LOG_INFO.InternalStream().WithVerbosity( \ + absl_logging_internal_verbose_level) + +#define ABSL_LOG_INTERNAL_VLOG_FIRST_N_IMPL(verbose_level, n) \ + switch (const int absl_logging_internal_verbose_level = (verbose_level)) \ + case 0: \ + default: \ + ABSL_LOG_INTERNAL_CONDITION_INFO( \ + STATEFUL, ABSL_VLOG_IS_ON(absl_logging_internal_verbose_level)) \ + (FirstN, n) ABSL_LOGGING_INTERNAL_LOG_INFO.InternalStream().WithVerbosity( \ + absl_logging_internal_verbose_level) + +#define ABSL_LOG_INTERNAL_VLOG_EVERY_POW_2_IMPL(verbose_level) \ + switch (const int absl_logging_internal_verbose_level = (verbose_level)) \ + case 0: \ + default: \ + ABSL_LOG_INTERNAL_CONDITION_INFO( \ + STATEFUL, ABSL_VLOG_IS_ON(absl_logging_internal_verbose_level)) \ + (EveryPow2) ABSL_LOGGING_INTERNAL_LOG_INFO.InternalStream().WithVerbosity( \ + absl_logging_internal_verbose_level) + +#define ABSL_LOG_INTERNAL_VLOG_EVERY_N_SEC_IMPL(verbose_level, n_seconds) \ + switch (const int absl_logging_internal_verbose_level = (verbose_level)) \ + case 0: \ + default: \ + ABSL_LOG_INTERNAL_CONDITION_INFO( \ + STATEFUL, ABSL_VLOG_IS_ON(absl_logging_internal_verbose_level)) \ + (EveryNSec, n_seconds) ABSL_LOGGING_INTERNAL_LOG_INFO.InternalStream() \ + .WithVerbosity(absl_logging_internal_verbose_level) + +#define ABSL_LOG_INTERNAL_LOG_IF_EVERY_N_IMPL(severity, condition, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryN, n) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() -#define ABSL_LOG_IF_FIRST_N_IMPL(severity, condition, n) \ +#define ABSL_LOG_INTERNAL_LOG_IF_FIRST_N_IMPL(severity, condition, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(FirstN, n) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() -#define ABSL_LOG_IF_EVERY_POW_2_IMPL(severity, condition) \ +#define ABSL_LOG_INTERNAL_LOG_IF_EVERY_POW_2_IMPL(severity, condition) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryPow2) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() -#define ABSL_LOG_IF_EVERY_N_SEC_IMPL(severity, condition, n_seconds) \ +#define ABSL_LOG_INTERNAL_LOG_IF_EVERY_N_SEC_IMPL(severity, condition, \ + n_seconds) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryNSec, \ n_seconds) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() -#define ABSL_PLOG_IF_EVERY_N_IMPL(severity, condition, n) \ +#define ABSL_LOG_INTERNAL_PLOG_IF_EVERY_N_IMPL(severity, condition, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryN, n) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ .WithPerror() -#define ABSL_PLOG_IF_FIRST_N_IMPL(severity, condition, n) \ +#define ABSL_LOG_INTERNAL_PLOG_IF_FIRST_N_IMPL(severity, condition, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(FirstN, n) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ .WithPerror() -#define ABSL_PLOG_IF_EVERY_POW_2_IMPL(severity, condition) \ +#define ABSL_LOG_INTERNAL_PLOG_IF_EVERY_POW_2_IMPL(severity, condition) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryPow2) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ .WithPerror() -#define ABSL_PLOG_IF_EVERY_N_SEC_IMPL(severity, condition, n_seconds) \ +#define ABSL_LOG_INTERNAL_PLOG_IF_EVERY_N_SEC_IMPL(severity, condition, \ + n_seconds) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryNSec, \ n_seconds) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ .WithPerror() #ifndef NDEBUG -#define ABSL_DLOG_IF_EVERY_N_IMPL(severity, condition, n) \ +#define ABSL_LOG_INTERNAL_DLOG_IF_EVERY_N_IMPL(severity, condition, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryN, n) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() -#define ABSL_DLOG_IF_FIRST_N_IMPL(severity, condition, n) \ +#define ABSL_LOG_INTERNAL_DLOG_IF_FIRST_N_IMPL(severity, condition, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(FirstN, n) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() -#define ABSL_DLOG_IF_EVERY_POW_2_IMPL(severity, condition) \ +#define ABSL_LOG_INTERNAL_DLOG_IF_EVERY_POW_2_IMPL(severity, condition) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryPow2) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() -#define ABSL_DLOG_IF_EVERY_N_SEC_IMPL(severity, condition, n_seconds) \ +#define ABSL_LOG_INTERNAL_DLOG_IF_EVERY_N_SEC_IMPL(severity, condition, \ + n_seconds) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryNSec, \ n_seconds) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() #else // def NDEBUG -#define ABSL_DLOG_IF_EVERY_N_IMPL(severity, condition, n) \ +#define ABSL_LOG_INTERNAL_DLOG_IF_EVERY_N_IMPL(severity, condition, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, false && (condition))( \ EveryN, n) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() -#define ABSL_DLOG_IF_FIRST_N_IMPL(severity, condition, n) \ +#define ABSL_LOG_INTERNAL_DLOG_IF_FIRST_N_IMPL(severity, condition, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, false && (condition))( \ FirstN, n) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() -#define ABSL_DLOG_IF_EVERY_POW_2_IMPL(severity, condition) \ +#define ABSL_LOG_INTERNAL_DLOG_IF_EVERY_POW_2_IMPL(severity, condition) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, false && (condition))( \ EveryPow2) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() -#define ABSL_DLOG_IF_EVERY_N_SEC_IMPL(severity, condition, n_seconds) \ +#define ABSL_LOG_INTERNAL_DLOG_IF_EVERY_N_SEC_IMPL(severity, condition, \ + n_seconds) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, false && (condition))( \ EveryNSec, n_seconds) \ ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() diff --git a/absl/log/internal/log_message.cc b/absl/log/internal/log_message.cc index bdb10f2a..10ac2453 100644 --- a/absl/log/internal/log_message.cc +++ b/absl/log/internal/log_message.cc @@ -234,6 +234,13 @@ LogMessage::LogMessage(const char* file, int line, absl::LogSeverity severity) LogBacktraceIfNeeded(); } +LogMessage::LogMessage(const char* file, int line, InfoTag) + : LogMessage(file, line, absl::LogSeverity::kInfo) {} +LogMessage::LogMessage(const char* file, int line, WarningTag) + : LogMessage(file, line, absl::LogSeverity::kWarning) {} +LogMessage::LogMessage(const char* file, int line, ErrorTag) + : LogMessage(file, line, absl::LogSeverity::kError) {} + LogMessage::~LogMessage() { #ifdef ABSL_MIN_LOG_LEVEL if (data_->entry.log_severity() < @@ -346,12 +353,12 @@ void LogMessage::FailQuietly() { } LogMessage& LogMessage::operator<<(const std::string& v) { - CopyToEncodedBuffer(v, StringType::kNotLiteral); + CopyToEncodedBuffer<StringType::kNotLiteral>(v); return *this; } LogMessage& LogMessage::operator<<(absl::string_view v) { - CopyToEncodedBuffer(v, StringType::kNotLiteral); + CopyToEncodedBuffer<StringType::kNotLiteral>(v); return *this; } LogMessage& LogMessage::operator<<(std::ostream& (*m)(std::ostream& os)) { @@ -383,8 +390,7 @@ template LogMessage& LogMessage::operator<<(const double& v); template LogMessage& LogMessage::operator<<(const bool& v); void LogMessage::Flush() { - if (data_->entry.log_severity() < absl::MinLogLevel()) - return; + if (data_->entry.log_severity() < absl::MinLogLevel()) return; if (data_->is_perror) { InternalStream() << ": " << absl::base_internal::StrError(errno_saver_()) @@ -427,7 +433,7 @@ LogMessage::OstreamView::OstreamView(LogMessageData& message_data) &encoded_remaining_copy_); string_start_ = EncodeMessageStart(ValueTag::kString, encoded_remaining_copy_.size(), - &encoded_remaining_copy_); + &encoded_remaining_copy_); setp(encoded_remaining_copy_.data(), encoded_remaining_copy_.data() + encoded_remaining_copy_.size()); data_.manipulated.rdbuf(this); @@ -519,8 +525,8 @@ void LogMessage::LogBacktraceIfNeeded() { // containing the specified string data using a `Value` field appropriate to // `str_type`. Truncates `str` if necessary, but emits nothing and marks the // buffer full if even the field headers do not fit. -void LogMessage::CopyToEncodedBuffer(absl::string_view str, - StringType str_type) { +template <LogMessage::StringType str_type> +void LogMessage::CopyToEncodedBuffer(absl::string_view str) { auto encoded_remaining_copy = data_->encoded_remaining; auto start = EncodeMessageStart( EventTag::kValue, BufferSizeFor(WireType::kLengthDelimited) + str.size(), @@ -541,7 +547,12 @@ void LogMessage::CopyToEncodedBuffer(absl::string_view str, data_->encoded_remaining.remove_suffix(data_->encoded_remaining.size()); } } -void LogMessage::CopyToEncodedBuffer(char ch, size_t num, StringType str_type) { +template void LogMessage::CopyToEncodedBuffer<LogMessage::StringType::kLiteral>( + absl::string_view str); +template void LogMessage::CopyToEncodedBuffer< + LogMessage::StringType::kNotLiteral>(absl::string_view str); +template <LogMessage::StringType str_type> +void LogMessage::CopyToEncodedBuffer(char ch, size_t num) { auto encoded_remaining_copy = data_->encoded_remaining; auto value_start = EncodeMessageStart( EventTag::kValue, BufferSizeFor(WireType::kLengthDelimited) + num, @@ -562,6 +573,10 @@ void LogMessage::CopyToEncodedBuffer(char ch, size_t num, StringType str_type) { data_->encoded_remaining.remove_suffix(data_->encoded_remaining.size()); } } +template void LogMessage::CopyToEncodedBuffer<LogMessage::StringType::kLiteral>( + char ch, size_t num); +template void LogMessage::CopyToEncodedBuffer< + LogMessage::StringType::kNotLiteral>(char ch, size_t num); LogMessageFatal::LogMessageFatal(const char* file, int line) : LogMessage(file, line, absl::LogSeverity::kFatal) {} diff --git a/absl/log/internal/log_message.h b/absl/log/internal/log_message.h index 3744276b..4ecb8a14 100644 --- a/absl/log/internal/log_message.h +++ b/absl/log/internal/log_message.h @@ -40,7 +40,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/has_absl_stringify.h" #include "absl/strings/string_view.h" #include "absl/time/time.h" @@ -51,9 +51,21 @@ constexpr int kLogMessageBufferSize = 15000; class LogMessage { public: + struct InfoTag {}; + struct WarningTag {}; + struct ErrorTag {}; + // Used for `LOG`. LogMessage(const char* file, int line, absl::LogSeverity severity) ABSL_ATTRIBUTE_COLD; + // These constructors are slightly smaller/faster to call; the severity is + // curried into the function pointer. + LogMessage(const char* file, int line, + InfoTag) ABSL_ATTRIBUTE_COLD ABSL_ATTRIBUTE_NOINLINE; + LogMessage(const char* file, int line, + WarningTag) ABSL_ATTRIBUTE_COLD ABSL_ATTRIBUTE_NOINLINE; + LogMessage(const char* file, int line, + ErrorTag) ABSL_ATTRIBUTE_COLD ABSL_ATTRIBUTE_NOINLINE; LogMessage(const LogMessage&) = delete; LogMessage& operator=(const LogMessage&) = delete; ~LogMessage() ABSL_ATTRIBUTE_COLD; @@ -158,15 +170,15 @@ class LogMessage { // Types that support `AbslStringify()` are serialized that way. template <typename T, - typename std::enable_if< - strings_internal::HasAbslStringify<T>::value, int>::type = 0> + typename std::enable_if<absl::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> + typename std::enable_if<!absl::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 @@ -219,10 +231,10 @@ class LogMessage { kLiteral, kNotLiteral, }; - void CopyToEncodedBuffer(absl::string_view str, - StringType str_type) ABSL_ATTRIBUTE_NOINLINE; - void CopyToEncodedBuffer(char ch, size_t num, - StringType str_type) ABSL_ATTRIBUTE_NOINLINE; + template <StringType str_type> + void CopyToEncodedBuffer(absl::string_view str) ABSL_ATTRIBUTE_NOINLINE; + template <StringType str_type> + void CopyToEncodedBuffer(char ch, size_t num) ABSL_ATTRIBUTE_NOINLINE; // Returns `true` if the message is fatal or enabled debug-fatal. bool IsFatal() const; @@ -252,12 +264,12 @@ class StringifySink final { explicit StringifySink(LogMessage& message) : message_(message) {} void Append(size_t count, char ch) { - message_.CopyToEncodedBuffer(ch, count, - LogMessage::StringType::kNotLiteral); + message_.CopyToEncodedBuffer<LogMessage::StringType::kNotLiteral>(ch, + count); } void Append(absl::string_view v) { - message_.CopyToEncodedBuffer(v, LogMessage::StringType::kNotLiteral); + message_.CopyToEncodedBuffer<LogMessage::StringType::kNotLiteral>(v); } // For types that implement `AbslStringify` using `absl::Format()`. @@ -271,8 +283,7 @@ class StringifySink final { // Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE` template <typename T, - typename std::enable_if<strings_internal::HasAbslStringify<T>::value, - int>::type> + typename std::enable_if<absl::HasAbslStringify<T>::value, int>::type> LogMessage& LogMessage::operator<<(const T& v) { StringifySink sink(*this); // Replace with public API. @@ -282,8 +293,7 @@ LogMessage& LogMessage::operator<<(const T& v) { // Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE` template <typename T, - typename std::enable_if<!strings_internal::HasAbslStringify<T>::value, - int>::type> + typename std::enable_if<!absl::HasAbslStringify<T>::value, int>::type> LogMessage& LogMessage::operator<<(const T& v) { OstreamView view(*data_); view.stream() << log_internal::NullGuard<T>().Guard(v); @@ -292,14 +302,14 @@ LogMessage& LogMessage::operator<<(const T& v) { template <int SIZE> LogMessage& LogMessage::operator<<(const char (&buf)[SIZE]) { - CopyToEncodedBuffer(buf, StringType::kLiteral); + CopyToEncodedBuffer<StringType::kLiteral>(buf); return *this; } // Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE` template <int SIZE> LogMessage& LogMessage::operator<<(char (&buf)[SIZE]) { - CopyToEncodedBuffer(buf, StringType::kNotLiteral); + CopyToEncodedBuffer<StringType::kNotLiteral>(buf); return *this; } // We instantiate these specializations in the library's TU to save space in @@ -327,6 +337,16 @@ extern template LogMessage& LogMessage::operator<<(const float& v); extern template LogMessage& LogMessage::operator<<(const double& v); extern template LogMessage& LogMessage::operator<<(const bool& v); +extern template void LogMessage::CopyToEncodedBuffer< + LogMessage::StringType::kLiteral>(absl::string_view str); +extern template void LogMessage::CopyToEncodedBuffer< + LogMessage::StringType::kNotLiteral>(absl::string_view str); +extern template void +LogMessage::CopyToEncodedBuffer<LogMessage::StringType::kLiteral>(char ch, + size_t num); +extern template void LogMessage::CopyToEncodedBuffer< + LogMessage::StringType::kNotLiteral>(char ch, size_t num); + // `LogMessageFatal` ensures the process will exit in failure after logging this // message. class LogMessageFatal final : public LogMessage { diff --git a/absl/log/internal/log_sink_set.cc b/absl/log/internal/log_sink_set.cc index f9d030aa..3d5c6995 100644 --- a/absl/log/internal/log_sink_set.cc +++ b/absl/log/internal/log_sink_set.cc @@ -35,6 +35,7 @@ #include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" #include "absl/base/log_severity.h" +#include "absl/base/no_destructor.h" #include "absl/base/thread_annotations.h" #include "absl/cleanup/cleanup.h" #include "absl/log/globals.h" @@ -122,11 +123,11 @@ class AndroidLogSink final : public LogSink { void Send(const absl::LogEntry& entry) override { const int level = AndroidLogLevel(entry); - // TODO(b/37587197): make the tag ("native") configurable. - __android_log_write(level, "native", + const char* const tag = GetAndroidNativeTag(); + __android_log_write(level, tag, entry.text_message_with_prefix_and_newline_c_str()); if (entry.log_severity() == absl::LogSeverity::kFatal) - __android_log_write(ANDROID_LOG_FATAL, "native", "terminating.\n"); + __android_log_write(ANDROID_LOG_FATAL, tag, "terminating.\n"); } private: @@ -168,17 +169,16 @@ class GlobalLogSinkSet final { #if defined(__myriad2__) || defined(__Fuchsia__) // myriad2 and Fuchsia do not log to stderr by default. #else - static StderrLogSink* stderr_log_sink = new StderrLogSink; - AddLogSink(stderr_log_sink); + static absl::NoDestructor<StderrLogSink> stderr_log_sink; + AddLogSink(stderr_log_sink.get()); #endif #ifdef __ANDROID__ - static AndroidLogSink* android_log_sink = new AndroidLogSink; - AddLogSink(android_log_sink); + static absl::NoDestructor<AndroidLogSink> android_log_sink; + AddLogSink(android_log_sink.get()); #endif #if defined(_WIN32) - static WindowsDebuggerLogSink* debugger_log_sink = - new WindowsDebuggerLogSink; - AddLogSink(debugger_log_sink); + static absl::NoDestructor<WindowsDebuggerLogSink> debugger_log_sink; + AddLogSink(debugger_log_sink.get()); #endif // !defined(_WIN32) } @@ -268,7 +268,7 @@ class GlobalLogSinkSet final { // Returns reference to the global LogSinks set. GlobalLogSinkSet& GlobalSinks() { - static GlobalLogSinkSet* global_sinks = new GlobalLogSinkSet; + static absl::NoDestructor<GlobalLogSinkSet> global_sinks; return *global_sinks; } diff --git a/absl/log/internal/nullguard.cc b/absl/log/internal/nullguard.cc index 4f2f9d40..3296c014 100644 --- a/absl/log/internal/nullguard.cc +++ b/absl/log/internal/nullguard.cc @@ -23,11 +23,11 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace log_internal { -ABSL_DLL ABSL_CONST_INIT const std::array<char, 7> kCharNull{ +ABSL_CONST_INIT ABSL_DLL const std::array<char, 7> kCharNull{ {'(', 'n', 'u', 'l', 'l', ')', '\0'}}; -ABSL_DLL ABSL_CONST_INIT const std::array<signed char, 7> kSignedCharNull{ +ABSL_CONST_INIT ABSL_DLL const std::array<signed char, 7> kSignedCharNull{ {'(', 'n', 'u', 'l', 'l', ')', '\0'}}; -ABSL_DLL ABSL_CONST_INIT const std::array<unsigned char, 7> kUnsignedCharNull{ +ABSL_CONST_INIT ABSL_DLL const std::array<unsigned char, 7> kUnsignedCharNull{ {'(', 'n', 'u', 'l', 'l', ')', '\0'}}; } // namespace log_internal diff --git a/absl/log/internal/nullguard.h b/absl/log/internal/nullguard.h index 926f61bb..623943c5 100644 --- a/absl/log/internal/nullguard.h +++ b/absl/log/internal/nullguard.h @@ -34,10 +34,10 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace log_internal { -ABSL_DLL ABSL_CONST_INIT extern const std::array<char, 7> kCharNull; -ABSL_DLL ABSL_CONST_INIT extern const std::array<signed char, 7> +ABSL_CONST_INIT ABSL_DLL extern const std::array<char, 7> kCharNull; +ABSL_CONST_INIT ABSL_DLL extern const std::array<signed char, 7> kSignedCharNull; -ABSL_DLL ABSL_CONST_INIT extern const std::array<unsigned char, 7> +ABSL_CONST_INIT ABSL_DLL extern const std::array<unsigned char, 7> kUnsignedCharNull; template <typename T> diff --git a/absl/log/internal/nullstream.h b/absl/log/internal/nullstream.h index 8ed63d52..9266852e 100644 --- a/absl/log/internal/nullstream.h +++ b/absl/log/internal/nullstream.h @@ -102,7 +102,9 @@ class NullStreamMaybeFatal final : public NullStream { explicit NullStreamMaybeFatal(absl::LogSeverity severity) : fatal_(severity == absl::LogSeverity::kFatal) {} ~NullStreamMaybeFatal() { - if (fatal_) _exit(1); + if (fatal_) { + _exit(1); + } } private: @@ -114,7 +116,7 @@ class NullStreamMaybeFatal final : public NullStream { // and expression-defined severity use `NullStreamMaybeFatal` above. class NullStreamFatal final : public NullStream { public: - NullStreamFatal() {} + NullStreamFatal() = default; // ABSL_ATTRIBUTE_NORETURN doesn't seem to work on destructors with msvc, so // disable msvc's warning about the d'tor never returning. #if defined(_MSC_VER) && !defined(__clang__) diff --git a/absl/log/internal/strip.h b/absl/log/internal/strip.h index 848c3867..f8d27869 100644 --- a/absl/log/internal/strip.h +++ b/absl/log/internal/strip.h @@ -37,28 +37,29 @@ #define ABSL_LOGGING_INTERNAL_LOG_DFATAL \ ::absl::log_internal::NullStreamMaybeFatal(::absl::kLogDebugFatal) #define ABSL_LOGGING_INTERNAL_LOG_LEVEL(severity) \ - ::absl::log_internal::NullStreamMaybeFatal(log_internal_severity) + ::absl::log_internal::NullStreamMaybeFatal(absl_log_internal_severity) #define ABSL_LOG_INTERNAL_CHECK(failure_message) ABSL_LOGGING_INTERNAL_LOG_FATAL #define ABSL_LOG_INTERNAL_QCHECK(failure_message) \ ABSL_LOGGING_INTERNAL_LOG_QFATAL #else // !defined(STRIP_LOG) || !STRIP_LOG -#define ABSL_LOGGING_INTERNAL_LOG_INFO \ - ::absl::log_internal::LogMessage(__FILE__, __LINE__, \ - ::absl::LogSeverity::kInfo) -#define ABSL_LOGGING_INTERNAL_LOG_WARNING \ - ::absl::log_internal::LogMessage(__FILE__, __LINE__, \ - ::absl::LogSeverity::kWarning) -#define ABSL_LOGGING_INTERNAL_LOG_ERROR \ - ::absl::log_internal::LogMessage(__FILE__, __LINE__, \ - ::absl::LogSeverity::kError) +#define ABSL_LOGGING_INTERNAL_LOG_INFO \ + ::absl::log_internal::LogMessage( \ + __FILE__, __LINE__, ::absl::log_internal::LogMessage::InfoTag{}) +#define ABSL_LOGGING_INTERNAL_LOG_WARNING \ + ::absl::log_internal::LogMessage( \ + __FILE__, __LINE__, ::absl::log_internal::LogMessage::WarningTag{}) +#define ABSL_LOGGING_INTERNAL_LOG_ERROR \ + ::absl::log_internal::LogMessage( \ + __FILE__, __LINE__, ::absl::log_internal::LogMessage::ErrorTag{}) #define ABSL_LOGGING_INTERNAL_LOG_FATAL \ ::absl::log_internal::LogMessageFatal(__FILE__, __LINE__) #define ABSL_LOGGING_INTERNAL_LOG_QFATAL \ ::absl::log_internal::LogMessageQuietlyFatal(__FILE__, __LINE__) #define ABSL_LOGGING_INTERNAL_LOG_DFATAL \ ::absl::log_internal::LogMessage(__FILE__, __LINE__, ::absl::kLogDebugFatal) -#define ABSL_LOGGING_INTERNAL_LOG_LEVEL(severity) \ - ::absl::log_internal::LogMessage(__FILE__, __LINE__, log_internal_severity) +#define ABSL_LOGGING_INTERNAL_LOG_LEVEL(severity) \ + ::absl::log_internal::LogMessage(__FILE__, __LINE__, \ + absl_log_internal_severity) // These special cases dispatch to special-case constructors that allow us to // avoid an extra function call and shrink non-LTO binaries by a percent or so. #define ABSL_LOG_INTERNAL_CHECK(failure_message) \ diff --git a/absl/log/internal/structured.h b/absl/log/internal/structured.h index 08caea66..5223dbc3 100644 --- a/absl/log/internal/structured.h +++ b/absl/log/internal/structured.h @@ -42,7 +42,7 @@ class ABSL_MUST_USE_RESULT AsLiteralImpl final { return os << as_literal.str_; } void AddToMessage(log_internal::LogMessage& m) { - m.CopyToEncodedBuffer(str_, log_internal::LogMessage::StringType::kLiteral); + m.CopyToEncodedBuffer<log_internal::LogMessage::StringType::kLiteral>(str_); } friend log_internal::LogMessage& operator<<(log_internal::LogMessage& m, AsLiteralImpl as_literal) { diff --git a/absl/log/internal/test_helpers.cc b/absl/log/internal/test_helpers.cc index 0de5b96b..bfcc9679 100644 --- a/absl/log/internal/test_helpers.cc +++ b/absl/log/internal/test_helpers.cc @@ -68,7 +68,7 @@ bool DiedOfQFatal(int exit_status) { #endif // ----------------------------------------------------------------------------- -// Helper for Log inititalization in test +// Helper for Log initialization in test // ----------------------------------------------------------------------------- void LogTestEnvironment::SetUp() { diff --git a/absl/log/internal/test_helpers.h b/absl/log/internal/test_helpers.h index fd06e295..714bc7bd 100644 --- a/absl/log/internal/test_helpers.h +++ b/absl/log/internal/test_helpers.h @@ -54,7 +54,7 @@ bool DiedOfQFatal(int exit_status); #endif // ----------------------------------------------------------------------------- -// Helper for Log inititalization in test +// Helper for Log initialization in test // ----------------------------------------------------------------------------- class LogTestEnvironment : public ::testing::Environment { diff --git a/absl/log/internal/vlog_config.cc b/absl/log/internal/vlog_config.cc new file mode 100644 index 00000000..b5788500 --- /dev/null +++ b/absl/log/internal/vlog_config.cc @@ -0,0 +1,340 @@ +// 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/log/internal/vlog_config.h" + +#include <stddef.h> + +#include <algorithm> +#include <atomic> +#include <functional> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/const_init.h" +#include "absl/base/internal/spinlock.h" +#include "absl/base/no_destructor.h" +#include "absl/base/optimization.h" +#include "absl/base/thread_annotations.h" +#include "absl/log/internal/fnmatch.h" +#include "absl/memory/memory.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_split.h" +#include "absl/strings/string_view.h" +#include "absl/strings/strip.h" +#include "absl/synchronization/mutex.h" +#include "absl/types/optional.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +namespace { +bool ModuleIsPath(absl::string_view module_pattern) { +#ifdef _WIN32 + return module_pattern.find_first_of("/\\") != module_pattern.npos; +#else + return module_pattern.find('/') != module_pattern.npos; +#endif +} +} // namespace + +bool VLogSite::SlowIsEnabled(int stale_v, int level) { + if (ABSL_PREDICT_TRUE(stale_v != kUninitialized)) { + // Because of the prerequisites to this function, we know that stale_v is + // either uninitialized or >= level. If it's not uninitialized, that means + // it must be >= level, thus we should log. + return true; + } + stale_v = log_internal::RegisterAndInitialize(this); + return ABSL_PREDICT_FALSE(stale_v >= level); +} + +bool VLogSite::SlowIsEnabled0(int stale_v) { return SlowIsEnabled(stale_v, 0); } +bool VLogSite::SlowIsEnabled1(int stale_v) { return SlowIsEnabled(stale_v, 1); } +bool VLogSite::SlowIsEnabled2(int stale_v) { return SlowIsEnabled(stale_v, 2); } +bool VLogSite::SlowIsEnabled3(int stale_v) { return SlowIsEnabled(stale_v, 3); } +bool VLogSite::SlowIsEnabled4(int stale_v) { return SlowIsEnabled(stale_v, 4); } +bool VLogSite::SlowIsEnabled5(int stale_v) { return SlowIsEnabled(stale_v, 5); } + +namespace { +struct VModuleInfo final { + std::string module_pattern; + bool module_is_path; // i.e. it contains a path separator. + int vlog_level; + + // Allocates memory. + VModuleInfo(absl::string_view module_pattern_arg, bool module_is_path_arg, + int vlog_level_arg) + : module_pattern(std::string(module_pattern_arg)), + module_is_path(module_is_path_arg), + vlog_level(vlog_level_arg) {} +}; + +// `mutex` guards all of the data structures that aren't lock-free. +// To avoid problems with the heap checker which calls into `VLOG`, `mutex` must +// be a `SpinLock` that prevents fiber scheduling instead of a `Mutex`. +ABSL_CONST_INIT absl::base_internal::SpinLock mutex( + absl::kConstInit, absl::base_internal::SCHEDULE_KERNEL_ONLY); + +// `GetUpdateSitesMutex()` serializes updates to all of the sites (i.e. those in +// `site_list_head`) themselves. +absl::Mutex* GetUpdateSitesMutex() { + // Chromium requires no global destructors, so we can't use the + // absl::kConstInit idiom since absl::Mutex as a non-trivial destructor. + static absl::NoDestructor<absl::Mutex> update_sites_mutex ABSL_ACQUIRED_AFTER( + mutex); + return update_sites_mutex.get(); +} + +ABSL_CONST_INIT int global_v ABSL_GUARDED_BY(mutex) = 0; +// `site_list_head` is the head of a singly-linked list. Traversal, insertion, +// and reads are atomic, so no locks are required, but updates to existing +// elements are guarded by `GetUpdateSitesMutex()`. +ABSL_CONST_INIT std::atomic<VLogSite*> site_list_head{nullptr}; +ABSL_CONST_INIT std::vector<VModuleInfo>* vmodule_info ABSL_GUARDED_BY(mutex) + ABSL_PT_GUARDED_BY(mutex){nullptr}; + +// Only used for lisp. +ABSL_CONST_INIT std::vector<std::function<void()>>* update_callbacks + ABSL_GUARDED_BY(GetUpdateSitesMutex()) + ABSL_PT_GUARDED_BY(GetUpdateSitesMutex()){nullptr}; + +// Allocates memory. +std::vector<VModuleInfo>& get_vmodule_info() + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex) { + if (!vmodule_info) vmodule_info = new std::vector<VModuleInfo>; + return *vmodule_info; +} + +// Does not allocate or take locks. +int VLogLevel(absl::string_view file, const std::vector<VModuleInfo>* infos, + int current_global_v) { + // `infos` is null during a call to `VLOG` prior to setting `vmodule` (e.g. by + // parsing flags). We can't allocate in `VLOG`, so we treat null as empty + // here and press on. + if (!infos || infos->empty()) return current_global_v; + // Get basename for file + absl::string_view basename = file; + { + const size_t sep = basename.rfind('/'); + if (sep != basename.npos) { + basename.remove_prefix(sep + 1); +#ifdef _WIN32 + } else { + const size_t sep = basename.rfind('\\'); + if (sep != basename.npos) basename.remove_prefix(sep + 1); +#endif + } + } + + absl::string_view stem = file, stem_basename = basename; + { + const size_t sep = stem_basename.find('.'); + if (sep != stem_basename.npos) { + stem.remove_suffix(stem_basename.size() - sep); + stem_basename.remove_suffix(stem_basename.size() - sep); + } + if (absl::ConsumeSuffix(&stem_basename, "-inl")) { + stem.remove_suffix(absl::string_view("-inl").size()); + } + } + for (const auto& info : *infos) { + if (info.module_is_path) { + // If there are any slashes in the pattern, try to match the full + // name. + if (FNMatch(info.module_pattern, stem)) { + return info.vlog_level == kUseFlag ? current_global_v : info.vlog_level; + } + } else if (FNMatch(info.module_pattern, stem_basename)) { + return info.vlog_level == kUseFlag ? current_global_v : info.vlog_level; + } + } + + return current_global_v; +} + +// Allocates memory. +int AppendVModuleLocked(absl::string_view module_pattern, int log_level) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex) { + for (const auto& info : get_vmodule_info()) { + if (FNMatch(info.module_pattern, module_pattern)) { + // This is a memory optimization to avoid storing patterns that will never + // match due to exit early semantics. Primarily optimized for our own unit + // tests. + return info.vlog_level; + } + } + bool module_is_path = ModuleIsPath(module_pattern); + get_vmodule_info().emplace_back(std::string(module_pattern), module_is_path, + log_level); + return global_v; +} + +// Allocates memory. +int PrependVModuleLocked(absl::string_view module_pattern, int log_level) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex) { + absl::optional<int> old_log_level; + for (const auto& info : get_vmodule_info()) { + if (FNMatch(info.module_pattern, module_pattern)) { + old_log_level = info.vlog_level; + break; + } + } + bool module_is_path = ModuleIsPath(module_pattern); + auto iter = get_vmodule_info().emplace(get_vmodule_info().cbegin(), + std::string(module_pattern), + module_is_path, log_level); + + // This is a memory optimization to avoid storing patterns that will never + // match due to exit early semantics. Primarily optimized for our own unit + // tests. + get_vmodule_info().erase( + std::remove_if(++iter, get_vmodule_info().end(), + [module_pattern](const VModuleInfo& info) { + return FNMatch(info.module_pattern, module_pattern); + }), + get_vmodule_info().cend()); + return old_log_level.value_or(global_v); +} +} // namespace + +int VLogLevel(absl::string_view file) ABSL_LOCKS_EXCLUDED(mutex) { + absl::base_internal::SpinLockHolder l(&mutex); + return VLogLevel(file, vmodule_info, global_v); +} + +int RegisterAndInitialize(VLogSite* v) ABSL_LOCKS_EXCLUDED(mutex) { + // std::memory_order_seq_cst is overkill in this function, but given that this + // path is intended to be slow, it's not worth the brain power to relax that. + VLogSite* h = site_list_head.load(std::memory_order_seq_cst); + + VLogSite* old = nullptr; + if (v->next_.compare_exchange_strong(old, h, std::memory_order_seq_cst, + std::memory_order_seq_cst)) { + // Multiple threads may attempt to register this site concurrently. + // By successfully setting `v->next` this thread commits to being *the* + // thread that installs `v` in the list. + while (!site_list_head.compare_exchange_weak( + h, v, std::memory_order_seq_cst, std::memory_order_seq_cst)) { + v->next_.store(h, std::memory_order_seq_cst); + } + } + + int old_v = VLogSite::kUninitialized; + int new_v = VLogLevel(v->file_); + // No loop, if someone else set this, we should respect their evaluation of + // `VLogLevel`. This may mean we return a stale `v`, but `v` itself will + // always arrive at the freshest value. Otherwise, we could be writing a + // stale value and clobbering the fresher one. + if (v->v_.compare_exchange_strong(old_v, new_v, std::memory_order_seq_cst, + std::memory_order_seq_cst)) { + return new_v; + } + return old_v; +} + +void UpdateVLogSites() ABSL_UNLOCK_FUNCTION(mutex) + ABSL_LOCKS_EXCLUDED(GetUpdateSitesMutex()) { + std::vector<VModuleInfo> infos = get_vmodule_info(); + int current_global_v = global_v; + // We need to grab `GetUpdateSitesMutex()` before we release `mutex` to ensure + // that updates are not interleaved (resulting in an inconsistent final state) + // and to ensure that the final state in the sites matches the final state of + // `vmodule_info`. We unlock `mutex` to ensure that uninitialized sites don't + // have to wait on all updates in order to acquire `mutex` and initialize + // themselves. + absl::MutexLock ul(GetUpdateSitesMutex()); + mutex.Unlock(); + VLogSite* n = site_list_head.load(std::memory_order_seq_cst); + // Because sites are added to the list in the order they are executed, there + // tend to be clusters of entries with the same file. + const char* last_file = nullptr; + int last_file_level = 0; + while (n != nullptr) { + if (n->file_ != last_file) { + last_file = n->file_; + last_file_level = VLogLevel(n->file_, &infos, current_global_v); + } + n->v_.store(last_file_level, std::memory_order_seq_cst); + n = n->next_.load(std::memory_order_seq_cst); + } + if (update_callbacks) { + for (auto& cb : *update_callbacks) { + cb(); + } + } +} + +void UpdateVModule(absl::string_view vmodule) + ABSL_LOCKS_EXCLUDED(mutex, GetUpdateSitesMutex()) { + std::vector<std::pair<absl::string_view, int>> glob_levels; + for (absl::string_view glob_level : absl::StrSplit(vmodule, ',')) { + const size_t eq = glob_level.rfind('='); + if (eq == glob_level.npos) continue; + const absl::string_view glob = glob_level.substr(0, eq); + int level; + if (!absl::SimpleAtoi(glob_level.substr(eq + 1), &level)) continue; + glob_levels.emplace_back(glob, level); + } + mutex.Lock(); // Unlocked by UpdateVLogSites(). + get_vmodule_info().clear(); + for (const auto& it : glob_levels) { + const absl::string_view glob = it.first; + const int level = it.second; + AppendVModuleLocked(glob, level); + } + UpdateVLogSites(); +} + +int UpdateGlobalVLogLevel(int v) + ABSL_LOCKS_EXCLUDED(mutex, GetUpdateSitesMutex()) { + mutex.Lock(); // Unlocked by UpdateVLogSites(). + const int old_global_v = global_v; + if (v == global_v) { + mutex.Unlock(); + return old_global_v; + } + global_v = v; + UpdateVLogSites(); + return old_global_v; +} + +int PrependVModule(absl::string_view module_pattern, int log_level) + ABSL_LOCKS_EXCLUDED(mutex, GetUpdateSitesMutex()) { + mutex.Lock(); // Unlocked by UpdateVLogSites(). + int old_v = PrependVModuleLocked(module_pattern, log_level); + UpdateVLogSites(); + return old_v; +} + +void OnVLogVerbosityUpdate(std::function<void()> cb) + ABSL_LOCKS_EXCLUDED(GetUpdateSitesMutex()) { + absl::MutexLock ul(GetUpdateSitesMutex()); + if (!update_callbacks) + update_callbacks = new std::vector<std::function<void()>>; + update_callbacks->push_back(std::move(cb)); +} + +VLogSite* SetVModuleListHeadForTestOnly(VLogSite* v) { + return site_list_head.exchange(v, std::memory_order_seq_cst); +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/internal/vlog_config.h b/absl/log/internal/vlog_config.h new file mode 100644 index 00000000..b6e322c4 --- /dev/null +++ b/absl/log/internal/vlog_config.h @@ -0,0 +1,163 @@ +// 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. +// +// ----------------------------------------------------------------------------- +// vlog_config.h +// ----------------------------------------------------------------------------- +// +// This header file defines `VLogSite`, a public primitive that represents +// a callsite for the `VLOG` family of macros and related libraries. +// It also declares and defines multiple internal utilities used to implement +// `VLOG`, such as `VLogSiteManager`. + +#ifndef ABSL_LOG_INTERNAL_VLOG_CONFIG_H_ +#define ABSL_LOG_INTERNAL_VLOG_CONFIG_H_ + +// IWYU pragma: private, include "absl/log/log.h" + +#include <atomic> +#include <cstdint> +#include <functional> +#include <limits> +#include <type_traits> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/optimization.h" +#include "absl/base/thread_annotations.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +class SyntheticBinary; +class VLogSite; + +int RegisterAndInitialize(VLogSite* v); +void UpdateVLogSites(); +constexpr int kUseFlag = (std::numeric_limits<int16_t>::min)(); + +// Represents a unique callsite for a `VLOG()` or `VLOG_IS_ON()` call. +// +// Libraries that provide `VLOG`-like functionality should use this to +// efficiently handle --vmodule. +// +// VLogSite objects must not be destroyed until the program exits. Doing so will +// probably yield nasty segfaults in VLogSiteManager::UpdateLogSites(). The +// recommendation is to make all such objects function-local statics. +class VLogSite final { + public: + // `f` must not be destroyed until the program exits. + explicit constexpr VLogSite(const char* f) + : file_(f), v_(kUninitialized), next_(nullptr) {} + VLogSite(const VLogSite&) = delete; + VLogSite& operator=(const VLogSite&) = delete; + + // Inlining the function yields a ~3x performance improvement at the cost of a + // 1.5x code size increase at the call site. + // Takes locks but does not allocate memory. + ABSL_ATTRIBUTE_ALWAYS_INLINE + bool IsEnabled(int level) { + int stale_v = v_.load(std::memory_order_relaxed); + if (ABSL_PREDICT_TRUE(level > stale_v)) { + return false; + } + + // We put everything other than the fast path, i.e. vlogging is initialized + // but not on, behind an out-of-line function to reduce code size. + // "level" is almost always a call-site constant, so we can save a bit + // of code space by special-casing for a few common levels. +#if ABSL_HAVE_BUILTIN(__builtin_constant_p) || defined(__GNUC__) + if (__builtin_constant_p(level)) { + if (level == 0) return SlowIsEnabled0(stale_v); + if (level == 1) return SlowIsEnabled1(stale_v); + if (level == 2) return SlowIsEnabled2(stale_v); + if (level == 3) return SlowIsEnabled3(stale_v); + if (level == 4) return SlowIsEnabled4(stale_v); + if (level == 5) return SlowIsEnabled5(stale_v); + } +#endif + return SlowIsEnabled(stale_v, level); + } + + private: + friend int log_internal::RegisterAndInitialize(VLogSite* v); + friend void log_internal::UpdateVLogSites(); + friend class log_internal::SyntheticBinary; + static constexpr int kUninitialized = (std::numeric_limits<int>::max)(); + + // SlowIsEnabled performs slower checks to determine whether a log site is + // enabled. Because it is expected to be called somewhat rarely + // (comparatively), it is not inlined to save on code size. + // + // Prerequisites to calling SlowIsEnabled: + // 1) stale_v is uninitialized OR + // 2) stale_v is initialized and >= level (meaning we must log). + // Takes locks but does not allocate memory. + ABSL_ATTRIBUTE_NOINLINE + bool SlowIsEnabled(int stale_v, int level); + ABSL_ATTRIBUTE_NOINLINE bool SlowIsEnabled0(int stale_v); + ABSL_ATTRIBUTE_NOINLINE bool SlowIsEnabled1(int stale_v); + ABSL_ATTRIBUTE_NOINLINE bool SlowIsEnabled2(int stale_v); + ABSL_ATTRIBUTE_NOINLINE bool SlowIsEnabled3(int stale_v); + ABSL_ATTRIBUTE_NOINLINE bool SlowIsEnabled4(int stale_v); + ABSL_ATTRIBUTE_NOINLINE bool SlowIsEnabled5(int stale_v); + + // This object is too size-sensitive to use absl::string_view. + const char* const file_; + std::atomic<int> v_; + std::atomic<VLogSite*> next_; +}; +static_assert(std::is_trivially_destructible<VLogSite>::value, + "VLogSite must be trivially destructible"); + +// Returns the current verbose log level of `file`. +// Does not allocate memory. +int VLogLevel(absl::string_view file); + +// Registers a site `v` to get updated as `vmodule` and `v` change. Also +// initializes the site based on their current values, and returns that result. +// Does not allocate memory. +int RegisterAndInitialize(VLogSite* v); + +// Allocates memory. +void UpdateVLogSites(); + +// Completely overwrites the saved value of `vmodule`. +// Allocates memory. +void UpdateVModule(absl::string_view vmodule); + +// Updates the global verbosity level to `v` and returns the prior value. +// Allocates memory. +int UpdateGlobalVLogLevel(int v); + +// Atomically prepends `module_pattern=log_level` to the start of vmodule. +// Returns the prior value for `module_pattern` if there was an exact match and +// `global_v` otherwise. +// Allocates memory. +int PrependVModule(absl::string_view module_pattern, int log_level); + +// Registers `on_update` to be called whenever `v` or `vmodule` change. +// Allocates memory. +void OnVLogVerbosityUpdate(std::function<void()> cb); + +// Does not allocate memory. +VLogSite* SetVModuleListHeadForTestOnly(VLogSite* v); + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_VLOG_CONFIG_H_ diff --git a/absl/log/internal/vlog_config_benchmark.cc b/absl/log/internal/vlog_config_benchmark.cc new file mode 100644 index 00000000..9004e2ee --- /dev/null +++ b/absl/log/internal/vlog_config_benchmark.cc @@ -0,0 +1,187 @@ +// 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 <algorithm> +#include <atomic> +#include <cstddef> +#include <cstring> +#include <memory> +#include <new> +#include <random> +#include <string> +#include <type_traits> +#include <utility> +#include <vector> + +#include "absl/base/config.h" +#include "absl/container/internal/layout.h" +#include "absl/log/internal/vlog_config.h" +#include "absl/memory/memory.h" +#include "absl/random/distributions.h" +#include "absl/strings/str_cat.h" +#include "benchmark/benchmark.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { +// Performance of `UpdateLogSites` depends upon the number and organization of +// `VLogSite`s in the program. We can synthesize some on the heap to mimic +// their layout and linkage in static data. +class SyntheticBinary { + public: + explicit SyntheticBinary(const size_t num_tus, + const size_t max_extra_static_data_bytes_per_tu, + const size_t max_sites_per_tu, + const int num_shuffles) { + per_tu_data_.reserve(num_tus); + auto sites = absl::make_unique<VLogSite *[]>(num_tus * max_sites_per_tu); + for (size_t i = 0; i < num_tus; i++) { + const std::string filename = + absl::StrCat("directory-", i / 100, "/subdirectory-", i % 100 / 10, + "/file-", i % 10, ".cc"); + container_internal::Layout<char, VLogSite, char> layout( + filename.size() + 1, + absl::LogUniform<size_t>(bitgen_, 1, max_sites_per_tu), + absl::LogUniform<size_t>(bitgen_, 0, + max_extra_static_data_bytes_per_tu)); + auto buf = absl::make_unique<char[]>(layout.AllocSize()); + layout.PoisonPadding(buf.get()); + memcpy(layout.Pointer<0>(buf.get()), filename.c_str(), + filename.size() + 1); + for (VLogSite &site : layout.Slice<1>(buf.get())) { + sites[num_sites_++] = + new (&site) VLogSite(layout.Pointer<0>(buf.get())); + // The last one is a dangling pointer but will be fixed below. + site.next_.store(&site + 1, std::memory_order_seq_cst); + } + num_extra_static_data_bytes_ += layout.Size<2>(); + per_tu_data_.push_back(PerTU{layout, std::move(buf)}); + } + // Now link the files together back-to-front into a circular list. + for (size_t i = 0; i < num_tus; i++) { + auto &tu = per_tu_data_[i]; + auto &next_tu = per_tu_data_[(i + 1) % num_tus]; + tu.layout.Slice<1>(tu.buf.get()) + .back() + .next_.store(next_tu.layout.Pointer<1>(next_tu.buf.get()), + std::memory_order_seq_cst); + } + // Now do some shufflin'. + auto new_sites = absl::make_unique<VLogSite *[]>(num_sites_); + for (int shuffle_num = 0; shuffle_num < num_shuffles; shuffle_num++) { + // Each shuffle cuts the ring into three pieces and rearranges them. + const size_t cut_a = absl::Uniform(bitgen_, size_t{0}, num_sites_); + const size_t cut_b = absl::Uniform(bitgen_, size_t{0}, num_sites_); + const size_t cut_c = absl::Uniform(bitgen_, size_t{0}, num_sites_); + if (cut_a == cut_b || cut_b == cut_c || cut_a == cut_c) continue; + // The same cuts, sorted: + const size_t cut_1 = std::min({cut_a, cut_b, cut_c}); + const size_t cut_3 = std::max({cut_a, cut_b, cut_c}); + const size_t cut_2 = cut_a ^ cut_b ^ cut_c ^ cut_1 ^ cut_3; + VLogSite *const tmp = sites[cut_1]->next_.load(std::memory_order_seq_cst); + sites[cut_1]->next_.store( + sites[cut_2]->next_.load(std::memory_order_seq_cst), + std::memory_order_seq_cst); + sites[cut_2]->next_.store( + sites[cut_3]->next_.load(std::memory_order_seq_cst), + std::memory_order_seq_cst); + sites[cut_3]->next_.store(tmp, std::memory_order_seq_cst); + memcpy(&new_sites[0], &sites[0], sizeof(VLogSite *) * (cut_1 + 1)); + memcpy(&new_sites[cut_1 + 1], &sites[cut_2 + 1], + sizeof(VLogSite *) * (cut_3 - cut_2)); + memcpy(&new_sites[cut_1 + 1 + cut_3 - cut_2], &sites[cut_1 + 1], + sizeof(VLogSite *) * (cut_2 - cut_1)); + memcpy(&new_sites[cut_3 + 1], &sites[cut_3 + 1], + sizeof(VLogSite *) * (num_sites_ - cut_3 - 1)); + sites.swap(new_sites); + } + const char *last_file = nullptr; + for (size_t i = 0; i < num_sites_; i++) { + if (sites[i]->file_ == last_file) continue; + last_file = sites[i]->file_; + num_new_files_++; + } + absl::log_internal::SetVModuleListHeadForTestOnly(sites[0]); + sites[num_tus - 1]->next_.store(nullptr, std::memory_order_seq_cst); + } + ~SyntheticBinary() { + static_assert(std::is_trivially_destructible<VLogSite>::value, ""); + absl::log_internal::SetVModuleListHeadForTestOnly(nullptr); + } + + size_t num_sites() const { return num_sites_; } + size_t num_new_files() const { return num_new_files_; } + size_t num_extra_static_data_bytes() const { + return num_extra_static_data_bytes_; + } + + private: + struct PerTU { + container_internal::Layout<char, VLogSite, char> layout; + std::unique_ptr<char[]> buf; + }; + + std::mt19937 bitgen_; + std::vector<PerTU> per_tu_data_; + size_t num_sites_ = 0; + size_t num_new_files_ = 0; + size_t num_extra_static_data_bytes_ = 0; +}; + +namespace { +void BM_UpdateVModuleEmpty(benchmark::State& state) { + SyntheticBinary bin(static_cast<size_t>(state.range(0)), 10 * 1024 * 1024, + 256, state.range(1)); + for (auto s : state) { + absl::log_internal::UpdateVModule(""); + } + state.SetItemsProcessed(static_cast<int>(bin.num_new_files())); +} +BENCHMARK(BM_UpdateVModuleEmpty) + ->ArgPair(100, 200) + ->ArgPair(1000, 2000) + ->ArgPair(10000, 20000); + +void BM_UpdateVModuleShort(benchmark::State& state) { + SyntheticBinary bin(static_cast<size_t>(state.range(0)), 10 * 1024 * 1024, + 256, state.range(1)); + for (auto s : state) { + absl::log_internal::UpdateVModule("directory2/*=4"); + } + state.SetItemsProcessed(static_cast<int>(bin.num_new_files())); +} +BENCHMARK(BM_UpdateVModuleShort) + ->ArgPair(100, 200) + ->ArgPair(1000, 2000) + ->ArgPair(10000, 20000); + +void BM_UpdateVModuleLong(benchmark::State& state) { + SyntheticBinary bin(static_cast<size_t>(state.range(0)), 10 * 1024 * 1024, + 256, state.range(1)); + for (auto s : state) { + absl::log_internal::UpdateVModule( + "d?r?c?o?y2/*=4,d?*r?*c?**o?*y1/*=2,d?*rc?**o?*y3/*=2,," + "d?*r?*c?**o?*1/*=1,d?*r?**o?*y1/*=2,d?*r???***y1/*=7," + "d?*r?**o?*y1/aaa=6"); + } + state.SetItemsProcessed(static_cast<int>(bin.num_new_files())); +} +BENCHMARK(BM_UpdateVModuleLong) + ->ArgPair(100, 200) + ->ArgPair(1000, 2000) + ->ArgPair(10000, 20000); +} // namespace +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/log.h b/absl/log/log.h index e060a0b6..b721b087 100644 --- a/absl/log/log.h +++ b/absl/log/log.h @@ -32,6 +32,8 @@ // * The `QFATAL` pseudo-severity level is equivalent to `FATAL` but triggers // quieter termination messages, e.g. without a full stack trace, and skips // running registered error handlers. +// * The `DFATAL` pseudo-severity level is defined as `FATAL` in debug mode and +// as `ERROR` otherwise. // Some preprocessor shenanigans are used to ensure that e.g. `LOG(INFO)` has // the same meaning even if a local symbol or preprocessor macro named `INFO` is // defined. To specify a severity level using an expression instead of a @@ -51,6 +53,14 @@ // * .NoPrefix() // Omits the prefix from this line. The prefix includes metadata about the // logged data such as source code location and timestamp. +// * .WithVerbosity(int verbose_level) +// Sets the verbosity field of the logged message as if it was logged by +// `VLOG(verbose_level)`. Unlike `VLOG`, this method does not affect +// evaluation of the statement when the specified `verbose_level` has been +// disabled. The only effect is on `LogSink` implementations which make use +// of the `absl::LogSink::verbosity()` value. The value +// `absl::LogEntry::kNoVerbosityLevel` can be specified to mark the message +// not verbose. // * .WithTimestamp(absl::Time timestamp) // Uses the specified timestamp instead of one collected at the time of // execution. @@ -188,6 +198,7 @@ #define ABSL_LOG_LOG_H_ #include "absl/log/internal/log_impl.h" +#include "absl/log/vlog_is_on.h" // IWYU pragma: export // LOG() // @@ -196,29 +207,53 @@ // Example: // // LOG(INFO) << "Found " << num_cookies << " cookies"; -#define LOG(severity) ABSL_LOG_IMPL(_##severity) +#define LOG(severity) ABSL_LOG_INTERNAL_LOG_IMPL(_##severity) // PLOG() // // `PLOG` behaves like `LOG` except that a description of the current state of // `errno` is appended to the streamed message. -#define PLOG(severity) ABSL_PLOG_IMPL(_##severity) +#define PLOG(severity) ABSL_LOG_INTERNAL_PLOG_IMPL(_##severity) // DLOG() // // `DLOG` behaves like `LOG` in debug mode (i.e. `#ifndef NDEBUG`). Otherwise // it compiles away and does nothing. Note that `DLOG(FATAL)` does not // terminate the program if `NDEBUG` is defined. -#define DLOG(severity) ABSL_DLOG_IMPL(_##severity) +#define DLOG(severity) ABSL_LOG_INTERNAL_DLOG_IMPL(_##severity) + +// `VLOG` uses numeric levels to provide verbose logging that can configured at +// runtime, including at a per-module level. `VLOG` statements are logged at +// `INFO` severity if they are logged at all; the numeric levels are on a +// different scale than the proper severity levels. Positive levels are +// disabled by default. Negative levels should not be used. +// Example: +// +// VLOG(1) << "I print when you run the program with --v=1 or higher"; +// VLOG(2) << "I print when you run the program with --v=2 or higher"; +// +// See vlog_is_on.h for further documentation, including the usage of the +// --vmodule flag to log at different levels in different source files. +#define VLOG(severity) ABSL_LOG_INTERNAL_VLOG_IMPL(severity) + +// `DVLOG` behaves like `VLOG` in debug mode (i.e. `#ifndef NDEBUG`). +// Otherwise, it compiles away and does nothing. +#define DVLOG(severity) ABSL_LOG_INTERNAL_DVLOG_IMPL(severity) // `LOG_IF` and friends add a second argument which specifies a condition. If // the condition is false, nothing is logged. // Example: // // LOG_IF(INFO, num_cookies > 10) << "Got lots of cookies"; -#define LOG_IF(severity, condition) ABSL_LOG_IF_IMPL(_##severity, condition) -#define PLOG_IF(severity, condition) ABSL_PLOG_IF_IMPL(_##severity, condition) -#define DLOG_IF(severity, condition) ABSL_DLOG_IF_IMPL(_##severity, condition) +// +// 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_LOG_IF_IMPL(_##severity, condition) +#define PLOG_IF(severity, condition) \ + ABSL_LOG_INTERNAL_PLOG_IF_IMPL(_##severity, condition) +#define DLOG_IF(severity, condition) \ + ABSL_LOG_INTERNAL_DLOG_IF_IMPL(_##severity, condition) // LOG_EVERY_N // @@ -231,21 +266,24 @@ // // LOG_EVERY_N(WARNING, 1000) << "Got a packet with a bad CRC (" << COUNTER // << " total)"; -#define LOG_EVERY_N(severity, n) ABSL_LOG_EVERY_N_IMPL(_##severity, n) +#define LOG_EVERY_N(severity, n) \ + ABSL_LOG_INTERNAL_LOG_EVERY_N_IMPL(_##severity, n) // LOG_FIRST_N // // `LOG_FIRST_N` behaves like `LOG_EVERY_N` except that the specified message is // logged when the counter's value is less than `n`. `LOG_FIRST_N` is // thread-safe. -#define LOG_FIRST_N(severity, n) ABSL_LOG_FIRST_N_IMPL(_##severity, n) +#define LOG_FIRST_N(severity, n) \ + ABSL_LOG_INTERNAL_LOG_FIRST_N_IMPL(_##severity, n) // LOG_EVERY_POW_2 // // `LOG_EVERY_POW_2` behaves like `LOG_EVERY_N` except that the specified // message is logged when the counter's value is a power of 2. // `LOG_EVERY_POW_2` is thread-safe. -#define LOG_EVERY_POW_2(severity) ABSL_LOG_EVERY_POW_2_IMPL(_##severity) +#define LOG_EVERY_POW_2(severity) \ + ABSL_LOG_INTERNAL_LOG_EVERY_POW_2_IMPL(_##severity) // LOG_EVERY_N_SEC // @@ -257,19 +295,34 @@ // // LOG_EVERY_N_SEC(INFO, 2.5) << "Got " << COUNTER << " cookies so far"; #define LOG_EVERY_N_SEC(severity, n_seconds) \ - ABSL_LOG_EVERY_N_SEC_IMPL(_##severity, n_seconds) + ABSL_LOG_INTERNAL_LOG_EVERY_N_SEC_IMPL(_##severity, n_seconds) -#define PLOG_EVERY_N(severity, n) ABSL_PLOG_EVERY_N_IMPL(_##severity, n) -#define PLOG_FIRST_N(severity, n) ABSL_PLOG_FIRST_N_IMPL(_##severity, n) -#define PLOG_EVERY_POW_2(severity) ABSL_PLOG_EVERY_POW_2_IMPL(_##severity) +#define PLOG_EVERY_N(severity, n) \ + ABSL_LOG_INTERNAL_PLOG_EVERY_N_IMPL(_##severity, n) +#define PLOG_FIRST_N(severity, n) \ + ABSL_LOG_INTERNAL_PLOG_FIRST_N_IMPL(_##severity, n) +#define PLOG_EVERY_POW_2(severity) \ + ABSL_LOG_INTERNAL_PLOG_EVERY_POW_2_IMPL(_##severity) #define PLOG_EVERY_N_SEC(severity, n_seconds) \ - ABSL_PLOG_EVERY_N_SEC_IMPL(_##severity, n_seconds) + ABSL_LOG_INTERNAL_PLOG_EVERY_N_SEC_IMPL(_##severity, n_seconds) -#define DLOG_EVERY_N(severity, n) ABSL_DLOG_EVERY_N_IMPL(_##severity, n) -#define DLOG_FIRST_N(severity, n) ABSL_DLOG_FIRST_N_IMPL(_##severity, n) -#define DLOG_EVERY_POW_2(severity) ABSL_DLOG_EVERY_POW_2_IMPL(_##severity) +#define DLOG_EVERY_N(severity, n) \ + ABSL_LOG_INTERNAL_DLOG_EVERY_N_IMPL(_##severity, n) +#define DLOG_FIRST_N(severity, n) \ + ABSL_LOG_INTERNAL_DLOG_FIRST_N_IMPL(_##severity, n) +#define DLOG_EVERY_POW_2(severity) \ + ABSL_LOG_INTERNAL_DLOG_EVERY_POW_2_IMPL(_##severity) #define DLOG_EVERY_N_SEC(severity, n_seconds) \ - ABSL_DLOG_EVERY_N_SEC_IMPL(_##severity, n_seconds) + ABSL_LOG_INTERNAL_DLOG_EVERY_N_SEC_IMPL(_##severity, n_seconds) + +#define VLOG_EVERY_N(severity, n) \ + ABSL_LOG_INTERNAL_VLOG_EVERY_N_IMPL(severity, n) +#define VLOG_FIRST_N(severity, n) \ + ABSL_LOG_INTERNAL_VLOG_FIRST_N_IMPL(severity, n) +#define VLOG_EVERY_POW_2(severity) \ + ABSL_LOG_INTERNAL_VLOG_EVERY_POW_2_IMPL(severity) +#define VLOG_EVERY_N_SEC(severity, n_seconds) \ + ABSL_LOG_INTERNAL_VLOG_EVERY_N_SEC_IMPL(severity, n_seconds) // `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 @@ -279,30 +332,30 @@ // LOG_IF_EVERY_N(INFO, (size > 1024), 10) << "Got the " << COUNTER // << "th big cookie"; #define LOG_IF_EVERY_N(severity, condition, n) \ - ABSL_LOG_IF_EVERY_N_IMPL(_##severity, condition, n) + ABSL_LOG_INTERNAL_LOG_IF_EVERY_N_IMPL(_##severity, condition, n) #define LOG_IF_FIRST_N(severity, condition, n) \ - ABSL_LOG_IF_FIRST_N_IMPL(_##severity, condition, n) + ABSL_LOG_INTERNAL_LOG_IF_FIRST_N_IMPL(_##severity, condition, n) #define LOG_IF_EVERY_POW_2(severity, condition) \ - ABSL_LOG_IF_EVERY_POW_2_IMPL(_##severity, condition) + ABSL_LOG_INTERNAL_LOG_IF_EVERY_POW_2_IMPL(_##severity, condition) #define LOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \ - ABSL_LOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds) + ABSL_LOG_INTERNAL_LOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds) #define PLOG_IF_EVERY_N(severity, condition, n) \ - ABSL_PLOG_IF_EVERY_N_IMPL(_##severity, condition, n) + ABSL_LOG_INTERNAL_PLOG_IF_EVERY_N_IMPL(_##severity, condition, n) #define PLOG_IF_FIRST_N(severity, condition, n) \ - ABSL_PLOG_IF_FIRST_N_IMPL(_##severity, condition, n) + ABSL_LOG_INTERNAL_PLOG_IF_FIRST_N_IMPL(_##severity, condition, n) #define PLOG_IF_EVERY_POW_2(severity, condition) \ - ABSL_PLOG_IF_EVERY_POW_2_IMPL(_##severity, condition) + ABSL_LOG_INTERNAL_PLOG_IF_EVERY_POW_2_IMPL(_##severity, condition) #define PLOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \ - ABSL_PLOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds) + ABSL_LOG_INTERNAL_PLOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds) #define DLOG_IF_EVERY_N(severity, condition, n) \ - ABSL_DLOG_IF_EVERY_N_IMPL(_##severity, condition, n) + ABSL_LOG_INTERNAL_DLOG_IF_EVERY_N_IMPL(_##severity, condition, n) #define DLOG_IF_FIRST_N(severity, condition, n) \ - ABSL_DLOG_IF_FIRST_N_IMPL(_##severity, condition, n) + ABSL_LOG_INTERNAL_DLOG_IF_FIRST_N_IMPL(_##severity, condition, n) #define DLOG_IF_EVERY_POW_2(severity, condition) \ - ABSL_DLOG_IF_EVERY_POW_2_IMPL(_##severity, condition) + ABSL_LOG_INTERNAL_DLOG_IF_EVERY_POW_2_IMPL(_##severity, condition) #define DLOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \ - ABSL_DLOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds) + ABSL_LOG_INTERNAL_DLOG_IF_EVERY_N_SEC_IMPL(_##severity, condition, n_seconds) #endif // ABSL_LOG_LOG_H_ diff --git a/absl/log/log_basic_test.cc b/absl/log/log_basic_test.cc index b8d87c94..7fc7111d 100644 --- a/absl/log/log_basic_test.cc +++ b/absl/log/log_basic_test.cc @@ -18,4 +18,4 @@ #define ABSL_TEST_LOG LOG #include "gtest/gtest.h" -#include "absl/log/log_basic_test_impl.h" +#include "absl/log/log_basic_test_impl.inc" diff --git a/absl/log/log_basic_test_impl.h b/absl/log/log_basic_test_impl.inc index 35c0b690..e2f33566 100644 --- a/absl/log/log_basic_test_impl.h +++ b/absl/log/log_basic_test_impl.inc @@ -94,7 +94,7 @@ TEST_P(BasicLogTest, Info) { EXPECT_CALL( test_sink, Send(AllOf(SourceFilename(Eq(__FILE__)), - SourceBasename(Eq("log_basic_test_impl.h")), + SourceBasename(Eq("log_basic_test_impl.inc")), SourceLine(Eq(log_line)), Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kInfo)), TimestampInMatchWindow(), @@ -123,7 +123,7 @@ TEST_P(BasicLogTest, Warning) { EXPECT_CALL( test_sink, Send(AllOf(SourceFilename(Eq(__FILE__)), - SourceBasename(Eq("log_basic_test_impl.h")), + SourceBasename(Eq("log_basic_test_impl.inc")), SourceLine(Eq(log_line)), Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kWarning)), TimestampInMatchWindow(), @@ -152,7 +152,7 @@ TEST_P(BasicLogTest, Error) { EXPECT_CALL( test_sink, Send(AllOf(SourceFilename(Eq(__FILE__)), - SourceBasename(Eq("log_basic_test_impl.h")), + SourceBasename(Eq("log_basic_test_impl.inc")), SourceLine(Eq(log_line)), Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kError)), TimestampInMatchWindow(), @@ -203,7 +203,7 @@ TEST_P(BasicLogDeathTest, Fatal) { EXPECT_CALL( test_sink, Send(AllOf(SourceFilename(Eq(__FILE__)), - SourceBasename(Eq("log_basic_test_impl.h")), + SourceBasename(Eq("log_basic_test_impl.inc")), SourceLine(Eq(log_line)), Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kFatal)), TimestampInMatchWindow(), @@ -219,7 +219,7 @@ TEST_P(BasicLogDeathTest, Fatal) { EXPECT_CALL( test_sink, Send(AllOf(SourceFilename(Eq(__FILE__)), - SourceBasename(Eq("log_basic_test_impl.h")), + SourceBasename(Eq("log_basic_test_impl.inc")), SourceLine(Eq(log_line)), Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kFatal)), TimestampInMatchWindow(), @@ -257,7 +257,7 @@ TEST_P(BasicLogDeathTest, QFatal) { EXPECT_CALL( test_sink, Send(AllOf(SourceFilename(Eq(__FILE__)), - SourceBasename(Eq("log_basic_test_impl.h")), + SourceBasename(Eq("log_basic_test_impl.inc")), SourceLine(Eq(log_line)), Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kFatal)), TimestampInMatchWindow(), @@ -277,6 +277,96 @@ TEST_P(BasicLogDeathTest, QFatal) { } #endif +#ifdef NDEBUG +TEST_P(BasicLogTest, DFatal) { + absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); + + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int log_line = __LINE__ + 1; + auto do_log = [] { ABSL_TEST_LOG(DFATAL) << "hello world"; }; + + if (LoggingEnabledAt(absl::LogSeverity::kError)) { + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq(__FILE__)), + SourceBasename(Eq("log_basic_test_impl.inc")), + SourceLine(Eq(log_line)), Prefix(IsTrue()), + LogSeverity(Eq(absl::LogSeverity::kError)), + TimestampInMatchWindow(), + ThreadID(Eq(absl::base_internal::GetTID())), + TextMessage(Eq("hello world")), + Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + literal: "hello world" + })pb")), + Stacktrace(IsEmpty())))); + } + + test_sink.StartCapturingLogs(); + do_log(); +} + +#elif GTEST_HAS_DEATH_TEST +TEST_P(BasicLogDeathTest, DFatal) { + // TODO(b/242568884): re-enable once bug is fixed. + // absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); + + const int log_line = __LINE__ + 1; + auto do_log = [] { ABSL_TEST_LOG(DFATAL) << "hello world"; }; + + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink( + absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + + ::testing::InSequence s; + + if (LoggingEnabledAt(absl::LogSeverity::kFatal)) { + // The first call without the stack trace. + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq(__FILE__)), + SourceBasename(Eq("log_basic_test_impl.inc")), + SourceLine(Eq(log_line)), Prefix(IsTrue()), + LogSeverity(Eq(absl::LogSeverity::kFatal)), + TimestampInMatchWindow(), + ThreadID(Eq(absl::base_internal::GetTID())), + TextMessage(Eq("hello world")), + Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { literal: "hello world" })pb")), + Stacktrace(IsEmpty())))) + .WillOnce(DeathTestExpectedLogging()); + + // The second call with the stack trace. + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq(__FILE__)), + SourceBasename(Eq("log_basic_test_impl.inc")), + SourceLine(Eq(log_line)), Prefix(IsTrue()), + LogSeverity(Eq(absl::LogSeverity::kFatal)), + TimestampInMatchWindow(), + ThreadID(Eq(absl::base_internal::GetTID())), + TextMessage(Eq("hello world")), + Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { literal: "hello world" })pb")), + Stacktrace(Not(IsEmpty()))))) + .WillOnce(DeathTestExpectedLogging()); + } + + test_sink.StartCapturingLogs(); + do_log(); + }, + DiedOfFatal, DeathTestValidateExpectations()); +} +#endif + TEST_P(BasicLogTest, Level) { absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); @@ -293,7 +383,7 @@ TEST_P(BasicLogTest, Level) { EXPECT_CALL( test_sink, Send(AllOf(SourceFilename(Eq(__FILE__)), - SourceBasename(Eq("log_basic_test_impl.h")), + SourceBasename(Eq("log_basic_test_impl.inc")), SourceLine(Eq(log_line)), Prefix(IsTrue()), LogSeverity(Eq(severity)), TimestampInMatchWindow(), ThreadID(Eq(absl::base_internal::GetTID())), @@ -336,7 +426,7 @@ TEST_P(BasicLogDeathTest, Level) { EXPECT_CALL( test_sink, Send(AllOf(SourceFilename(Eq(__FILE__)), - SourceBasename(Eq("log_basic_test_impl.h")), + SourceBasename(Eq("log_basic_test_impl.inc")), SourceLine(Eq(log_line)), Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kFatal)), TimestampInMatchWindow(), @@ -351,7 +441,7 @@ TEST_P(BasicLogDeathTest, Level) { EXPECT_CALL( test_sink, Send(AllOf(SourceFilename(Eq(__FILE__)), - SourceBasename(Eq("log_basic_test_impl.h")), + SourceBasename(Eq("log_basic_test_impl.inc")), SourceLine(Eq(log_line)), Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kFatal)), TimestampInMatchWindow(), diff --git a/absl/log/log_entry.cc b/absl/log/log_entry.cc index 19c3b3f1..fe58a576 100644 --- a/absl/log/log_entry.cc +++ b/absl/log/log_entry.cc @@ -25,5 +25,17 @@ constexpr int LogEntry::kNoVerbosityLevel; constexpr int LogEntry::kNoVerboseLevel; #endif +// https://github.com/abseil/abseil-cpp/issues/1465 +// CMake builds on Apple platforms error when libraries are empty. +// Our CMake configuration can avoid this error on header-only libraries, +// but since this library is conditionally empty, including a single +// variable is an easy workaround. +#ifdef __APPLE__ +namespace log_internal { +extern const char kAvoidEmptyLogEntryLibraryWarning; +const char kAvoidEmptyLogEntryLibraryWarning = 0; +} // namespace log_internal +#endif // __APPLE__ + ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/log/log_entry.h b/absl/log/log_entry.h index 9e4ae8eb..7a55dfe2 100644 --- a/absl/log/log_entry.h +++ b/absl/log/log_entry.h @@ -96,7 +96,8 @@ class LogEntry final { // LogEntry::verbosity() // // Returns this entry's verbosity, or `kNoVerbosityLevel` for a non-verbose - // entry. Verbosity control is not available outside of Google yet. + // entry. Taken from the argument to `VLOG` or from + // `LOG(...).WithVerbosity(...)`. int verbosity() const { return verbose_level_; } // LogEntry::timestamp() diff --git a/absl/log/log_sink_test.cc b/absl/log/log_sink_test.cc index 8903da72..fa743060 100644 --- a/absl/log/log_sink_test.cc +++ b/absl/log/log_sink_test.cc @@ -18,7 +18,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/attributes.h" -#include "absl/base/internal/raw_logging.h" #include "absl/log/internal/test_actions.h" #include "absl/log/internal/test_helpers.h" #include "absl/log/internal/test_matchers.h" @@ -205,7 +204,7 @@ class ReentrancyTest : public ::testing::Test { << "The log is coming from *inside the sink*."; break; default: - ABSL_RAW_LOG(FATAL, "Invalid mode %d.\n", static_cast<int>(mode_)); + LOG(FATAL) << "Invalid mode " << static_cast<int>(mode_); } } diff --git a/absl/log/log_streamer.h b/absl/log/log_streamer.h index 2d41a07f..4ed2435d 100644 --- a/absl/log/log_streamer.h +++ b/absl/log/log_streamer.h @@ -165,6 +165,16 @@ inline LogStreamer LogFatalStreamer(absl::string_view file, int line) { return absl::LogStreamer(absl::LogSeverity::kFatal, file, line); } +// LogDebugFatalStreamer() +// +// Returns a LogStreamer that writes at level LogSeverity::kLogDebugFatal. +// +// In debug mode, the program will be terminated when this `LogStreamer` is +// destroyed, regardless of whether any data were streamed in. +inline LogStreamer LogDebugFatalStreamer(absl::string_view file, int line) { + return absl::LogStreamer(absl::kLogDebugFatal, file, line); +} + ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/log/log_streamer_test.cc b/absl/log/log_streamer_test.cc index 328d70d0..40c7d488 100644 --- a/absl/log/log_streamer_test.cc +++ b/absl/log/log_streamer_test.cc @@ -151,6 +151,57 @@ TEST(LogStreamerDeathTest, LogFatalStreamer) { } #endif +#ifdef NDEBUG +TEST(LogStreamerTest, LogDebugFatalStreamer) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)), + Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kError)), + TimestampInMatchWindow(), + ThreadID(Eq(absl::base_internal::GetTID())), + TextMessage(Eq("WriteToStream: foo")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "WriteToStream: foo" + })pb")), + Stacktrace(IsEmpty())))); + + test_sink.StartCapturingLogs(); + WriteToStream("foo", + &absl::LogDebugFatalStreamer("path/file.cc", 1234).stream()); +} +#elif GTEST_HAS_DEATH_TEST +TEST(LogStreamerDeathTest, LogDebugFatalStreamer) { + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink; + + EXPECT_CALL(test_sink, Send) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + + EXPECT_CALL( + test_sink, + Send(AllOf( + SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)), + Prefix(IsTrue()), LogSeverity(Eq(absl::LogSeverity::kFatal)), + TimestampInMatchWindow(), + ThreadID(Eq(absl::base_internal::GetTID())), + TextMessage(Eq("WriteToStream: foo")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "WriteToStream: foo" + })pb"))))) + .WillOnce(DeathTestExpectedLogging()); + + test_sink.StartCapturingLogs(); + WriteToStream( + "foo", &absl::LogDebugFatalStreamer("path/file.cc", 1234).stream()); + }, + DiedOfFatal, DeathTestValidateExpectations()); +} +#endif + TEST(LogStreamerTest, LogStreamer) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); diff --git a/absl/log/scoped_mock_log.cc b/absl/log/scoped_mock_log.cc index 4ebc0a9f..39a0a52a 100644 --- a/absl/log/scoped_mock_log.cc +++ b/absl/log/scoped_mock_log.cc @@ -30,7 +30,7 @@ namespace absl { ABSL_NAMESPACE_BEGIN ScopedMockLog::ScopedMockLog(MockLogDefault default_exp) - : sink_(this), is_capturing_logs_(false) { + : sink_(this), is_capturing_logs_(false), is_triggered_(false) { if (default_exp == MockLogDefault::kIgnoreUnexpected) { // Ignore all calls to Log we did not set expectations for. EXPECT_CALL(*this, Log).Times(::testing::AnyNumber()); diff --git a/absl/log/scoped_mock_log.h b/absl/log/scoped_mock_log.h index 44470c16..399e604d 100644 --- a/absl/log/scoped_mock_log.h +++ b/absl/log/scoped_mock_log.h @@ -185,6 +185,9 @@ class ScopedMockLog final { ForwardingSink sink_; bool is_capturing_logs_; + // Until C++20, the default constructor leaves the underlying value wrapped in + // std::atomic uninitialized, so all constructors should be sure to initialize + // is_triggered_. std::atomic<bool> is_triggered_; }; diff --git a/absl/log/scoped_mock_log_test.cc b/absl/log/scoped_mock_log_test.cc index 44b8d737..42736939 100644 --- a/absl/log/scoped_mock_log_test.cc +++ b/absl/log/scoped_mock_log_test.cc @@ -71,6 +71,11 @@ TEST(ScopedMockLogDeathTest, StopCapturingLogsCannotBeCalledWhenNotCapturing) { }, "StopCapturingLogs"); } + +TEST(ScopedMockLogDeathTest, FailsCheckIfStartCapturingLogsIsNeverCalled) { + EXPECT_DEATH({ absl::ScopedMockLog log; }, + "Did you forget to call StartCapturingLogs"); +} #endif // Tests that ScopedMockLog intercepts LOG()s when it's alive. diff --git a/absl/log/stripping_test.cc b/absl/log/stripping_test.cc index d6a6606e..271fae1d 100644 --- a/absl/log/stripping_test.cc +++ b/absl/log/stripping_test.cc @@ -49,14 +49,20 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/internal/strerror.h" +#include "absl/base/log_severity.h" #include "absl/flags/internal/program_name.h" #include "absl/log/check.h" #include "absl/log/internal/test_helpers.h" #include "absl/log/log.h" +#include "absl/status/status.h" #include "absl/strings/escaping.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" +// Set a flag that controls whether we actually execute fatal statements, but +// prevent the compiler from optimizing it out. +static volatile bool kReallyDie = false; + namespace { using ::testing::_; using ::testing::Eq; @@ -304,7 +310,10 @@ TEST_F(StrippingTest, Fatal) { // as would happen if we used a literal. We might (or might not) leave it // lying around later; that's what the tests are for! const std::string needle = absl::Base64Escape("StrippingTest.Fatal"); - EXPECT_DEATH_IF_SUPPORTED(LOG(FATAL) << "U3RyaXBwaW5nVGVzdC5GYXRhbA==", ""); + // We don't care if the LOG statement is actually executed, we're just + // checking that it's stripped. + if (kReallyDie) LOG(FATAL) << "U3RyaXBwaW5nVGVzdC5GYXRhbA=="; + std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable(); ASSERT_THAT(exe, NotNull()); if (absl::LogSeverity::kFatal >= kAbslMinLogLevel) { @@ -314,6 +323,49 @@ TEST_F(StrippingTest, Fatal) { } } +TEST_F(StrippingTest, DFatal) { + // We need to load a copy of the needle string into memory (so we can search + // for it) without leaving it lying around in plaintext in the executable file + // as would happen if we used a literal. We might (or might not) leave it + // lying around later; that's what the tests are for! + const std::string needle = absl::Base64Escape("StrippingTest.DFatal"); + // We don't care if the LOG statement is actually executed, we're just + // checking that it's stripped. + if (kReallyDie) LOG(DFATAL) << "U3RyaXBwaW5nVGVzdC5ERmF0YWw="; + + std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable(); + ASSERT_THAT(exe, NotNull()); + // `DFATAL` can be `ERROR` or `FATAL`, and a compile-time optimizer doesn't + // know which, because `absl::kLogDebugFatal` is declared `extern` and defined + // in another TU. Link-time optimization might do better. We have six cases: + // | `AMLL` is-> | `<=ERROR` | `FATAL` | `>FATAL` | + // | ------------------- | --------- | ------- | -------- | + // | `DFATAL` is `ERROR` | present | ? | stripped | + // | `DFATAL` is `FATAL` | present | present | stripped | + + // These constexpr variables are used to suppress unreachable code warnings + // in the if-else statements below. + + // "present" in the table above: `DFATAL` exceeds `ABSL_MIN_LOG_LEVEL`, so + // `DFATAL` statements should not be stripped (and they should be logged + // when executed, but that's a different testsuite). + constexpr bool kExpectPresent = absl::kLogDebugFatal >= kAbslMinLogLevel; + + // "stripped" in the table above: even though the compiler may not know + // which value `DFATAL` has, it should be able to strip it since both + // possible values ought to be stripped. + constexpr bool kExpectStripped = kAbslMinLogLevel > absl::LogSeverity::kFatal; + + if (kExpectPresent) { + EXPECT_THAT(exe.get(), FileHasSubstr(needle)); + } else if (kExpectStripped) { + EXPECT_THAT(exe.get(), Not(FileHasSubstr(needle))); + } else { + // "?" in the table above; may or may not be stripped depending on whether + // any link-time optimization is done. Either outcome is ok. + } +} + TEST_F(StrippingTest, Level) { const std::string needle = absl::Base64Escape("StrippingTest.Level"); volatile auto severity = absl::LogSeverity::kWarning; @@ -337,4 +389,114 @@ TEST_F(StrippingTest, Level) { } } +TEST_F(StrippingTest, Check) { + // Here we also need a variable name with enough entropy that it's unlikely to + // appear in the binary by chance. `volatile` keeps the tautological + // comparison (and the rest of the `CHECK`) from being optimized away. + const std::string var_needle = absl::Base64Escape("StrippingTestCheckVar"); + const std::string msg_needle = absl::Base64Escape("StrippingTest.Check"); + volatile int U3RyaXBwaW5nVGVzdENoZWNrVmFy = 0xCAFE; + // We don't care if the CHECK is actually executed, just that stripping works. + // Hiding it behind `kReallyDie` works around some overly aggressive + // optimizations in older versions of MSVC. + if (kReallyDie) { + CHECK(U3RyaXBwaW5nVGVzdENoZWNrVmFy != U3RyaXBwaW5nVGVzdENoZWNrVmFy) + << "U3RyaXBwaW5nVGVzdC5DaGVjaw=="; + } + + std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable(); + ASSERT_THAT(exe, NotNull()); + if (absl::LogSeverity::kFatal >= kAbslMinLogLevel) { + EXPECT_THAT(exe.get(), FileHasSubstr(var_needle)); + EXPECT_THAT(exe.get(), FileHasSubstr(msg_needle)); + } else { + EXPECT_THAT(exe.get(), Not(FileHasSubstr(var_needle))); + EXPECT_THAT(exe.get(), Not(FileHasSubstr(msg_needle))); + } +} + +TEST_F(StrippingTest, CheckOp) { + // See `StrippingTest.Check` for some hairy implementation notes. + const std::string var_needle1 = + absl::Base64Escape("StrippingTestCheckOpVar1"); + const std::string var_needle2 = + absl::Base64Escape("StrippingTestCheckOpVar2"); + const std::string msg_needle = absl::Base64Escape("StrippingTest.CheckOp"); + volatile int U3RyaXBwaW5nVGVzdENoZWNrT3BWYXIx = 0xFEED; + volatile int U3RyaXBwaW5nVGVzdENoZWNrT3BWYXIy = 0xCAFE; + if (kReallyDie) { + CHECK_EQ(U3RyaXBwaW5nVGVzdENoZWNrT3BWYXIx, U3RyaXBwaW5nVGVzdENoZWNrT3BWYXIy) + << "U3RyaXBwaW5nVGVzdC5DaGVja09w"; + } + + std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable(); + ASSERT_THAT(exe, NotNull()); + + if (absl::LogSeverity::kFatal >= kAbslMinLogLevel) { + EXPECT_THAT(exe.get(), FileHasSubstr(var_needle1)); + EXPECT_THAT(exe.get(), FileHasSubstr(var_needle2)); + EXPECT_THAT(exe.get(), FileHasSubstr(msg_needle)); + } else { + EXPECT_THAT(exe.get(), Not(FileHasSubstr(var_needle1))); + EXPECT_THAT(exe.get(), Not(FileHasSubstr(var_needle2))); + EXPECT_THAT(exe.get(), Not(FileHasSubstr(msg_needle))); + } +} + +TEST_F(StrippingTest, CheckStrOp) { + // See `StrippingTest.Check` for some hairy implementation notes. + const std::string var_needle1 = + absl::Base64Escape("StrippingTestCheckStrOpVar1"); + const std::string var_needle2 = + absl::Base64Escape("StrippingTestCheckStrOpVar2"); + const std::string msg_needle = absl::Base64Escape("StrippingTest.CheckStrOp"); + const char *volatile U3RyaXBwaW5nVGVzdENoZWNrU3RyT3BWYXIx = "FEED"; + const char *volatile U3RyaXBwaW5nVGVzdENoZWNrU3RyT3BWYXIy = "CAFE"; + if (kReallyDie) { + CHECK_STREQ(U3RyaXBwaW5nVGVzdENoZWNrU3RyT3BWYXIx, + U3RyaXBwaW5nVGVzdENoZWNrU3RyT3BWYXIy) + << "U3RyaXBwaW5nVGVzdC5DaGVja1N0ck9w"; + } + + std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable(); + ASSERT_THAT(exe, NotNull()); + + if (absl::LogSeverity::kFatal >= kAbslMinLogLevel) { + EXPECT_THAT(exe.get(), FileHasSubstr(var_needle1)); + EXPECT_THAT(exe.get(), FileHasSubstr(var_needle2)); + EXPECT_THAT(exe.get(), FileHasSubstr(msg_needle)); + } else { + EXPECT_THAT(exe.get(), Not(FileHasSubstr(var_needle1))); + EXPECT_THAT(exe.get(), Not(FileHasSubstr(var_needle2))); + EXPECT_THAT(exe.get(), Not(FileHasSubstr(msg_needle))); + } +} + +TEST_F(StrippingTest, CheckOk) { + // See `StrippingTest.Check` for some hairy implementation notes. + const std::string var_needle = absl::Base64Escape("StrippingTestCheckOkVar1"); + const std::string msg_needle = absl::Base64Escape("StrippingTest.CheckOk"); + volatile bool x = false; + auto U3RyaXBwaW5nVGVzdENoZWNrT2tWYXIx = absl::OkStatus(); + if (x) { + U3RyaXBwaW5nVGVzdENoZWNrT2tWYXIx = + absl::InvalidArgumentError("Stripping this is not my job!"); + } + if (kReallyDie) { + CHECK_OK(U3RyaXBwaW5nVGVzdENoZWNrT2tWYXIx) + << "U3RyaXBwaW5nVGVzdC5DaGVja09r"; + } + + std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable(); + ASSERT_THAT(exe, NotNull()); + + if (absl::LogSeverity::kFatal >= kAbslMinLogLevel) { + EXPECT_THAT(exe.get(), FileHasSubstr(var_needle)); + EXPECT_THAT(exe.get(), FileHasSubstr(msg_needle)); + } else { + EXPECT_THAT(exe.get(), Not(FileHasSubstr(var_needle))); + EXPECT_THAT(exe.get(), Not(FileHasSubstr(msg_needle))); + } +} + } // namespace diff --git a/absl/log/vlog_is_on.h b/absl/log/vlog_is_on.h new file mode 100644 index 00000000..78986513 --- /dev/null +++ b/absl/log/vlog_is_on.h @@ -0,0 +1,72 @@ +// 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. +// +// ----------------------------------------------------------------------------- +// File: log/vlog_is_on.h +// ----------------------------------------------------------------------------- +// +// This header defines the `VLOG_IS_ON()` macro that controls the +// variable-verbosity conditional logging. +// +// It's used by `VLOG` in log.h, or it can also be used directly like this: +// +// if (VLOG_IS_ON(2)) { +// foo_server.RecomputeStatisticsExpensive(); +// LOG(INFO) << foo_server.LastStatisticsAsString(); +// } +// +// Each source file has an effective verbosity level that's a non-negative +// integer computed from the `--vmodule` and `--v` flags. +// `VLOG_IS_ON(n)` is true, and `VLOG(n)` logs, if that effective verbosity +// level is greater than or equal to `n`. +// +// `--vmodule` takes a comma-delimited list of key=value pairs. Each key is a +// pattern matched against filenames, and the values give the effective severity +// level applied to matching files. '?' and '*' characters in patterns are +// interpreted as single-character and zero-or-more-character wildcards. +// Patterns including a slash character are matched against full pathnames, +// while those without are matched against basenames only. One suffix (i.e. the +// last . and everything after it) is stripped from each filename prior to +// matching, as is the special suffix "-inl". +// +// Files are matched against globs in `--vmodule` in order, and the first match +// determines the verbosity level. +// +// Files which do not match any pattern in `--vmodule` use the value of `--v` as +// their effective verbosity level. The default is 0. +// +// SetVLOGLevel helper function is provided to do limited dynamic control over +// V-logging by appending to `--vmodule`. Because these go at the beginning of +// the list, they take priority over any globs previously added. +// +// Resetting --vmodule will override all previous modifications to `--vmodule`, +// including via SetVLOGLevel. + +#ifndef ABSL_LOG_VLOG_IS_ON_H_ +#define ABSL_LOG_VLOG_IS_ON_H_ + +#include "absl/log/absl_vlog_is_on.h" // IWYU pragma: export + +// IWYU pragma: private, include "absl/log/log.h" + +// Each VLOG_IS_ON call site gets its own VLogSite that registers with the +// global linked list of sites to asynchronously update its verbosity level on +// changes to --v or --vmodule. The verbosity can also be set by manually +// calling SetVLOGLevel. +// +// VLOG_IS_ON is not async signal safe, but it is guaranteed not to allocate +// new memory. +#define VLOG_IS_ON(verbose_level) ABSL_VLOG_IS_ON(verbose_level) + +#endif // ABSL_LOG_VLOG_IS_ON_H_ diff --git a/absl/log/vlog_is_on_test.cc b/absl/log/vlog_is_on_test.cc new file mode 100644 index 00000000..883d798e --- /dev/null +++ b/absl/log/vlog_is_on_test.cc @@ -0,0 +1,176 @@ +// Copyright 2023 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/log/vlog_is_on.h" + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/log_severity.h" +#include "absl/flags/flag.h" +#include "absl/log/flags.h" +#include "absl/log/globals.h" +#include "absl/log/log.h" +#include "absl/log/scoped_mock_log.h" +#include "absl/types/optional.h" + +namespace { + +using ::testing::_; + +absl::optional<int> MaxLogVerbosity() { +#ifdef ABSL_MAX_VLOG_VERBOSITY + return ABSL_MAX_VLOG_VERBOSITY; +#else + return absl::nullopt; +#endif +} + +absl::optional<int> MinLogLevel() { +#ifdef ABSL_MIN_LOG_LEVEL + return static_cast<int>(ABSL_MIN_LOG_LEVEL); +#else + return absl::nullopt; +#endif +} + +TEST(VLogIsOn, GlobalWorksWithoutMaxVerbosityAndMinLogLevel) { + if (MaxLogVerbosity().has_value() || MinLogLevel().has_value()) { + GTEST_SKIP(); + } + + absl::SetGlobalVLogLevel(3); + absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "important")); + + log.StartCapturingLogs(); + VLOG(3) << "important"; + VLOG(4) << "spam"; +} + +TEST(VLogIsOn, FileWorksWithoutMaxVerbosityAndMinLogLevel) { + if (MaxLogVerbosity().has_value() || MinLogLevel().has_value()) { + GTEST_SKIP(); + } + + absl::SetVLogLevel("vlog_is_on_test", 3); + absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "important")); + + log.StartCapturingLogs(); + VLOG(3) << "important"; + VLOG(4) << "spam"; +} + +TEST(VLogIsOn, PatternWorksWithoutMaxVerbosityAndMinLogLevel) { + if (MaxLogVerbosity().has_value() || MinLogLevel().has_value()) { + GTEST_SKIP(); + } + + absl::SetVLogLevel("vlog_is_on*", 3); + absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "important")); + + log.StartCapturingLogs(); + VLOG(3) << "important"; + VLOG(4) << "spam"; +} + +TEST(VLogIsOn, GlobalDoesNotFilterBelowMaxVerbosity) { + if (!MaxLogVerbosity().has_value() || *MaxLogVerbosity() < 2) { + GTEST_SKIP(); + } + + // Set an arbitrary high value to avoid filtering VLOGs in tests by default. + absl::SetGlobalVLogLevel(1000); + absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "asdf")); + + log.StartCapturingLogs(); + VLOG(2) << "asdf"; +} + +TEST(VLogIsOn, FileDoesNotFilterBelowMaxVerbosity) { + if (!MaxLogVerbosity().has_value() || *MaxLogVerbosity() < 2) { + GTEST_SKIP(); + } + + // Set an arbitrary high value to avoid filtering VLOGs in tests by default. + absl::SetVLogLevel("vlog_is_on_test", 1000); + absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "asdf")); + + log.StartCapturingLogs(); + VLOG(2) << "asdf"; +} + +TEST(VLogIsOn, PatternDoesNotFilterBelowMaxVerbosity) { + if (!MaxLogVerbosity().has_value() || *MaxLogVerbosity() < 2) { + GTEST_SKIP(); + } + + // Set an arbitrary high value to avoid filtering VLOGs in tests by default. + absl::SetVLogLevel("vlog_is_on*", 1000); + absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "asdf")); + + log.StartCapturingLogs(); + VLOG(2) << "asdf"; +} + +TEST(VLogIsOn, GlobalFiltersAboveMaxVerbosity) { + if (!MaxLogVerbosity().has_value() || *MaxLogVerbosity() >= 4) { + GTEST_SKIP(); + } + + // Set an arbitrary high value to avoid filtering VLOGs in tests by default. + absl::SetGlobalVLogLevel(1000); + absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected); + + log.StartCapturingLogs(); + VLOG(4) << "dfgh"; +} + +TEST(VLogIsOn, FileFiltersAboveMaxVerbosity) { + if (!MaxLogVerbosity().has_value() || *MaxLogVerbosity() >= 4) { + GTEST_SKIP(); + } + + // Set an arbitrary high value to avoid filtering VLOGs in tests by default. + absl::SetVLogLevel("vlog_is_on_test", 1000); + absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected); + + log.StartCapturingLogs(); + VLOG(4) << "dfgh"; +} + +TEST(VLogIsOn, PatternFiltersAboveMaxVerbosity) { + if (!MaxLogVerbosity().has_value() || *MaxLogVerbosity() >= 4) { + GTEST_SKIP(); + } + + // Set an arbitrary high value to avoid filtering VLOGs in tests by default. + absl::SetVLogLevel("vlog_is_on*", 1000); + absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected); + + log.StartCapturingLogs(); + VLOG(4) << "dfgh"; +} + +} // namespace diff --git a/absl/memory/BUILD.bazel b/absl/memory/BUILD.bazel index a93f54a6..4573f17d 100644 --- a/absl/memory/BUILD.bazel +++ b/absl/memory/BUILD.bazel @@ -21,7 +21,14 @@ load( "ABSL_TEST_COPTS", ) -package(default_visibility = ["//visibility:public"]) +package( + default_visibility = ["//visibility:public"], + features = [ + "header_modules", + "layering_check", + "parse_headers", + ], +) licenses(["notice"]) @@ -47,6 +54,7 @@ cc_test( deps = [ ":memory", "//absl/base:core_headers", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/meta/BUILD.bazel b/absl/meta/BUILD.bazel index 125446f9..cf5df9b8 100644 --- a/absl/meta/BUILD.bazel +++ b/absl/meta/BUILD.bazel @@ -21,7 +21,14 @@ load( "ABSL_TEST_COPTS", ) -package(default_visibility = ["//visibility:public"]) +package( + default_visibility = ["//visibility:public"], + features = [ + "header_modules", + "layering_check", + "parse_headers", + ], +) licenses(["notice"]) @@ -32,6 +39,7 @@ cc_library( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ "//absl/base:config", + "//absl/base:core_headers", ], ) @@ -45,6 +53,7 @@ cc_test( "//absl/base:config", "//absl/base:core_headers", "//absl/time", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/meta/CMakeLists.txt b/absl/meta/CMakeLists.txt index bb767d12..d509114c 100644 --- a/absl/meta/CMakeLists.txt +++ b/absl/meta/CMakeLists.txt @@ -23,6 +23,7 @@ absl_cc_library( ${ABSL_DEFAULT_COPTS} DEPS absl::config + absl::core_headers PUBLIC ) diff --git a/absl/meta/type_traits.h b/absl/meta/type_traits.h index b1656c39..cf71164b 100644 --- a/absl/meta/type_traits.h +++ b/absl/meta/type_traits.h @@ -39,14 +39,9 @@ #include <functional> #include <type_traits> +#include "absl/base/attributes.h" #include "absl/base/config.h" -// MSVC constructibility traits do not detect destructor properties and so our -// implementations should not use them as a source-of-truth. -#if defined(_MSC_VER) && !defined(__clang__) && !defined(__GNUC__) -#define ABSL_META_INTERNAL_STD_CONSTRUCTION_TRAITS_DONT_CHECK_DESTRUCTION 1 -#endif - // Defines the default alignment. `__STDCPP_DEFAULT_NEW_ALIGNMENT__` is a C++17 // feature. #if defined(__STDCPP_DEFAULT_NEW_ALIGNMENT__) @@ -58,57 +53,8 @@ namespace absl { ABSL_NAMESPACE_BEGIN -// Defined and documented later on in this file. -template <typename T> -struct is_trivially_destructible; - -// Defined and documented later on in this file. -template <typename T> -struct is_trivially_move_assignable; - namespace type_traits_internal { -// Silence MSVC warnings about the destructor being defined as deleted. -#if defined(_MSC_VER) && !defined(__GNUC__) -#pragma warning(push) -#pragma warning(disable : 4624) -#endif // defined(_MSC_VER) && !defined(__GNUC__) - -template <class T> -union SingleMemberUnion { - T t; -}; - -// Restore the state of the destructor warning that was silenced above. -#if defined(_MSC_VER) && !defined(__GNUC__) -#pragma warning(pop) -#endif // defined(_MSC_VER) && !defined(__GNUC__) - -template <class T> -struct IsTriviallyMoveConstructibleObject - : std::integral_constant< - bool, std::is_move_constructible< - type_traits_internal::SingleMemberUnion<T>>::value && - absl::is_trivially_destructible<T>::value> {}; - -template <class T> -struct IsTriviallyCopyConstructibleObject - : std::integral_constant< - bool, std::is_copy_constructible< - type_traits_internal::SingleMemberUnion<T>>::value && - absl::is_trivially_destructible<T>::value> {}; - -template <class T> -struct IsTriviallyMoveAssignableReference : std::false_type {}; - -template <class T> -struct IsTriviallyMoveAssignableReference<T&> - : absl::is_trivially_move_assignable<T>::type {}; - -template <class T> -struct IsTriviallyMoveAssignableReference<T&&> - : absl::is_trivially_move_assignable<T>::type {}; - template <typename... Ts> struct VoidTImpl { using type = void; @@ -157,39 +103,8 @@ template <class To, template <class...> class Op, class... Args> struct is_detected_convertible : is_detected_convertible_impl<void, To, Op, Args...>::type {}; -template <typename T> -using IsCopyAssignableImpl = - decltype(std::declval<T&>() = std::declval<const T&>()); - -template <typename T> -using IsMoveAssignableImpl = decltype(std::declval<T&>() = std::declval<T&&>()); - } // namespace type_traits_internal -// MSVC 19.20 has a regression that causes our workarounds to fail, but their -// std forms now appear to be compliant. -#if defined(_MSC_VER) && !defined(__clang__) && (_MSC_VER >= 1920) - -template <typename T> -using is_copy_assignable = std::is_copy_assignable<T>; - -template <typename T> -using is_move_assignable = std::is_move_assignable<T>; - -#else - -template <typename T> -struct is_copy_assignable : type_traits_internal::is_detected< - type_traits_internal::IsCopyAssignableImpl, T> { -}; - -template <typename T> -struct is_move_assignable : type_traits_internal::is_detected< - type_traits_internal::IsMoveAssignableImpl, T> { -}; - -#endif - // void_t() // // Ignores the type of any its arguments and returns `void`. In general, this @@ -270,246 +185,29 @@ struct is_function bool, !(std::is_reference<T>::value || std::is_const<typename std::add_const<T>::type>::value)> {}; +// is_copy_assignable() +// is_move_assignable() // is_trivially_destructible() -// -// Determines whether the passed type `T` is trivially destructible. -// -// This metafunction is designed to be a drop-in replacement for the C++11 -// `std::is_trivially_destructible()` metafunction for platforms that have -// incomplete C++11 support (such as libstdc++ 4.x). On any platforms that do -// fully support C++11, we check whether this yields the same result as the std -// implementation. -// -// NOTE: the extensions (__has_trivial_xxx) are implemented in gcc (version >= -// 4.3) and clang. Since we are supporting libstdc++ > 4.7, they should always -// be present. These extensions are documented at -// https://gcc.gnu.org/onlinedocs/gcc/Type-Traits.html#Type-Traits. -template <typename T> -struct is_trivially_destructible -#ifdef ABSL_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE - : std::is_trivially_destructible<T> { -#else - : std::integral_constant<bool, __has_trivial_destructor(T) && - std::is_destructible<T>::value> { -#endif -#ifdef ABSL_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE - private: - static constexpr bool compliant = std::is_trivially_destructible<T>::value == - is_trivially_destructible::value; - static_assert(compliant || std::is_trivially_destructible<T>::value, - "Not compliant with std::is_trivially_destructible; " - "Standard: false, Implementation: true"); - static_assert(compliant || !std::is_trivially_destructible<T>::value, - "Not compliant with std::is_trivially_destructible; " - "Standard: true, Implementation: false"); -#endif // ABSL_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE -}; - // is_trivially_default_constructible() -// -// Determines whether the passed type `T` is trivially default constructible. -// -// This metafunction is designed to be a drop-in replacement for the C++11 -// `std::is_trivially_default_constructible()` metafunction for platforms that -// have incomplete C++11 support (such as libstdc++ 4.x). On any platforms that -// do fully support C++11, we check whether this yields the same result as the -// std implementation. -// -// NOTE: according to the C++ standard, Section: 20.15.4.3 [meta.unary.prop] -// "The predicate condition for a template specialization is_constructible<T, -// Args...> shall be satisfied if and only if the following variable -// definition would be well-formed for some invented variable t: -// -// T t(declval<Args>()...); -// -// is_trivially_constructible<T, Args...> additionally requires that the -// variable definition does not call any operation that is not trivial. -// For the purposes of this check, the call to std::declval is considered -// trivial." -// -// Notes from https://en.cppreference.com/w/cpp/types/is_constructible: -// In many implementations, is_nothrow_constructible also checks if the -// destructor throws because it is effectively noexcept(T(arg)). Same -// applies to is_trivially_constructible, which, in these implementations, also -// requires that the destructor is trivial. -// GCC bug 51452: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=51452 -// LWG issue 2116: http://cplusplus.github.io/LWG/lwg-active.html#2116. -// -// "T obj();" need to be well-formed and not call any nontrivial operation. -// Nontrivially destructible types will cause the expression to be nontrivial. -template <typename T> -struct is_trivially_default_constructible -#if defined(ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE) - : std::is_trivially_default_constructible<T> { -#else - : std::integral_constant<bool, __has_trivial_constructor(T) && - std::is_default_constructible<T>::value && - is_trivially_destructible<T>::value> { -#endif -#if defined(ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE) && \ - !defined( \ - ABSL_META_INTERNAL_STD_CONSTRUCTION_TRAITS_DONT_CHECK_DESTRUCTION) - private: - static constexpr bool compliant = - std::is_trivially_default_constructible<T>::value == - is_trivially_default_constructible::value; - static_assert(compliant || std::is_trivially_default_constructible<T>::value, - "Not compliant with std::is_trivially_default_constructible; " - "Standard: false, Implementation: true"); - static_assert(compliant || !std::is_trivially_default_constructible<T>::value, - "Not compliant with std::is_trivially_default_constructible; " - "Standard: true, Implementation: false"); -#endif // ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE -}; - // is_trivially_move_constructible() -// -// Determines whether the passed type `T` is trivially move constructible. -// -// This metafunction is designed to be a drop-in replacement for the C++11 -// `std::is_trivially_move_constructible()` metafunction for platforms that have -// incomplete C++11 support (such as libstdc++ 4.x). On any platforms that do -// fully support C++11, we check whether this yields the same result as the std -// implementation. -// -// NOTE: `T obj(declval<T>());` needs to be well-formed and not call any -// nontrivial operation. Nontrivially destructible types will cause the -// expression to be nontrivial. -template <typename T> -struct is_trivially_move_constructible -#if defined(ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE) - : std::is_trivially_move_constructible<T> { -#else - : std::conditional< - std::is_object<T>::value && !std::is_array<T>::value, - type_traits_internal::IsTriviallyMoveConstructibleObject<T>, - std::is_reference<T>>::type::type { -#endif -#if defined(ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE) && \ - !defined( \ - ABSL_META_INTERNAL_STD_CONSTRUCTION_TRAITS_DONT_CHECK_DESTRUCTION) - private: - static constexpr bool compliant = - std::is_trivially_move_constructible<T>::value == - is_trivially_move_constructible::value; - static_assert(compliant || std::is_trivially_move_constructible<T>::value, - "Not compliant with std::is_trivially_move_constructible; " - "Standard: false, Implementation: true"); - static_assert(compliant || !std::is_trivially_move_constructible<T>::value, - "Not compliant with std::is_trivially_move_constructible; " - "Standard: true, Implementation: false"); -#endif // ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE -}; - // is_trivially_copy_constructible() -// -// Determines whether the passed type `T` is trivially copy constructible. -// -// This metafunction is designed to be a drop-in replacement for the C++11 -// `std::is_trivially_copy_constructible()` metafunction for platforms that have -// incomplete C++11 support (such as libstdc++ 4.x). On any platforms that do -// fully support C++11, we check whether this yields the same result as the std -// implementation. -// -// NOTE: `T obj(declval<const T&>());` needs to be well-formed and not call any -// nontrivial operation. Nontrivially destructible types will cause the -// expression to be nontrivial. -template <typename T> -struct is_trivially_copy_constructible - : std::conditional< - std::is_object<T>::value && !std::is_array<T>::value, - type_traits_internal::IsTriviallyCopyConstructibleObject<T>, - std::is_lvalue_reference<T>>::type::type { -#if defined(ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE) && \ - !defined( \ - ABSL_META_INTERNAL_STD_CONSTRUCTION_TRAITS_DONT_CHECK_DESTRUCTION) - private: - static constexpr bool compliant = - std::is_trivially_copy_constructible<T>::value == - is_trivially_copy_constructible::value; - static_assert(compliant || std::is_trivially_copy_constructible<T>::value, - "Not compliant with std::is_trivially_copy_constructible; " - "Standard: false, Implementation: true"); - static_assert(compliant || !std::is_trivially_copy_constructible<T>::value, - "Not compliant with std::is_trivially_copy_constructible; " - "Standard: true, Implementation: false"); -#endif // ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE -}; - // is_trivially_move_assignable() -// -// Determines whether the passed type `T` is trivially move assignable. -// -// This metafunction is designed to be a drop-in replacement for the C++11 -// `std::is_trivially_move_assignable()` metafunction for platforms that have -// incomplete C++11 support (such as libstdc++ 4.x). On any platforms that do -// fully support C++11, we check whether this yields the same result as the std -// implementation. -// -// NOTE: `is_assignable<T, U>::value` is `true` if the expression -// `declval<T>() = declval<U>()` is well-formed when treated as an unevaluated -// operand. `is_trivially_assignable<T, U>` requires the assignment to call no -// operation that is not trivial. `is_trivially_copy_assignable<T>` is simply -// `is_trivially_assignable<T&, T>`. -template <typename T> -struct is_trivially_move_assignable - : std::conditional< - std::is_object<T>::value && !std::is_array<T>::value && - std::is_move_assignable<T>::value, - std::is_move_assignable<type_traits_internal::SingleMemberUnion<T>>, - type_traits_internal::IsTriviallyMoveAssignableReference<T>>::type:: - type { -#ifdef ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE - private: - static constexpr bool compliant = - std::is_trivially_move_assignable<T>::value == - is_trivially_move_assignable::value; - static_assert(compliant || std::is_trivially_move_assignable<T>::value, - "Not compliant with std::is_trivially_move_assignable; " - "Standard: false, Implementation: true"); - static_assert(compliant || !std::is_trivially_move_assignable<T>::value, - "Not compliant with std::is_trivially_move_assignable; " - "Standard: true, Implementation: false"); -#endif // ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE -}; - // is_trivially_copy_assignable() // -// Determines whether the passed type `T` is trivially copy assignable. -// -// This metafunction is designed to be a drop-in replacement for the C++11 -// `std::is_trivially_copy_assignable()` metafunction for platforms that have -// incomplete C++11 support (such as libstdc++ 4.x). On any platforms that do -// fully support C++11, we check whether this yields the same result as the std -// implementation. -// -// NOTE: `is_assignable<T, U>::value` is `true` if the expression -// `declval<T>() = declval<U>()` is well-formed when treated as an unevaluated -// operand. `is_trivially_assignable<T, U>` requires the assignment to call no -// operation that is not trivial. `is_trivially_copy_assignable<T>` is simply -// `is_trivially_assignable<T&, const T&>`. -template <typename T> -struct is_trivially_copy_assignable -#ifdef ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE - : std::is_trivially_copy_assignable<T> { -#else - : std::integral_constant< - bool, __has_trivial_assign(typename std::remove_reference<T>::type) && - absl::is_copy_assignable<T>::value> { -#endif -#ifdef ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE - private: - static constexpr bool compliant = - std::is_trivially_copy_assignable<T>::value == - is_trivially_copy_assignable::value; - static_assert(compliant || std::is_trivially_copy_assignable<T>::value, - "Not compliant with std::is_trivially_copy_assignable; " - "Standard: false, Implementation: true"); - static_assert(compliant || !std::is_trivially_copy_assignable<T>::value, - "Not compliant with std::is_trivially_copy_assignable; " - "Standard: true, Implementation: false"); -#endif // ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE -}; +// Historical note: Abseil once provided implementations of these type traits +// for platforms that lacked full support. New code should prefer to use the +// std variants. +// +// See the documentation for the STL <type_traits> header for more information: +// https://en.cppreference.com/w/cpp/header/type_traits +using std::is_copy_assignable; +using std::is_move_assignable; +using std::is_trivially_copy_assignable; +using std::is_trivially_copy_constructible; +using std::is_trivially_default_constructible; +using std::is_trivially_destructible; +using std::is_trivially_move_assignable; +using std::is_trivially_move_constructible; #if defined(__cpp_lib_remove_cvref) && __cpp_lib_remove_cvref >= 201711L template <typename T> @@ -532,55 +230,6 @@ template <typename T> using remove_cvref_t = typename remove_cvref<T>::type; #endif -namespace type_traits_internal { -// is_trivially_copyable() -// -// Determines whether the passed type `T` is trivially copyable. -// -// This metafunction is designed to be a drop-in replacement for the C++11 -// `std::is_trivially_copyable()` metafunction for platforms that have -// incomplete C++11 support (such as libstdc++ 4.x). We use the C++17 definition -// of TriviallyCopyable. -// -// NOTE: `is_trivially_copyable<T>::value` is `true` if all of T's copy/move -// constructors/assignment operators are trivial or deleted, T has at least -// one non-deleted copy/move constructor/assignment operator, and T is trivially -// destructible. Arrays of trivially copyable types are trivially copyable. -// -// We expose this metafunction only for internal use within absl. - -#if defined(ABSL_HAVE_STD_IS_TRIVIALLY_COPYABLE) -template <typename T> -struct is_trivially_copyable : std::is_trivially_copyable<T> {}; -#else -template <typename T> -class is_trivially_copyable_impl { - using ExtentsRemoved = typename std::remove_all_extents<T>::type; - static constexpr bool kIsCopyOrMoveConstructible = - std::is_copy_constructible<ExtentsRemoved>::value || - std::is_move_constructible<ExtentsRemoved>::value; - static constexpr bool kIsCopyOrMoveAssignable = - absl::is_copy_assignable<ExtentsRemoved>::value || - absl::is_move_assignable<ExtentsRemoved>::value; - - public: - static constexpr bool kValue = - (__has_trivial_copy(ExtentsRemoved) || !kIsCopyOrMoveConstructible) && - (__has_trivial_assign(ExtentsRemoved) || !kIsCopyOrMoveAssignable) && - (kIsCopyOrMoveConstructible || kIsCopyOrMoveAssignable) && - is_trivially_destructible<ExtentsRemoved>::value && - // We need to check for this explicitly because otherwise we'll say - // references are trivial copyable when compiled by MSVC. - !std::is_reference<ExtentsRemoved>::value; -}; - -template <typename T> -struct is_trivially_copyable - : std::integral_constant< - bool, type_traits_internal::is_trivially_copyable_impl<T>::kValue> {}; -#endif -} // namespace type_traits_internal - // ----------------------------------------------------------------------------- // C++14 "_t" trait aliases // ----------------------------------------------------------------------------- @@ -630,6 +279,7 @@ using remove_extent_t = typename std::remove_extent<T>::type; template <typename T> using remove_all_extents_t = typename std::remove_all_extents<T>::type; +ABSL_INTERNAL_DISABLE_DEPRECATED_DECLARATION_WARNING namespace type_traits_internal { // This trick to retrieve a default alignment is necessary for our // implementation of aligned_storage_t to be consistent with any @@ -648,6 +298,7 @@ struct default_alignment_of_aligned_storage< template <size_t Len, size_t Align = type_traits_internal:: default_alignment_of_aligned_storage<Len>::value> using aligned_storage_t = typename std::aligned_storage<Len, Align>::type; +ABSL_INTERNAL_RESTORE_DEPRECATED_DECLARATION_WARNING template <typename T> using decay_t = typename std::decay<T>::type; @@ -818,9 +469,14 @@ using swap_internal::StdSwapIsUnconstrained; } // namespace type_traits_internal // absl::is_trivially_relocatable<T> -// Detects whether a type is "trivially relocatable" -- meaning it can be -// relocated without invoking the constructor/destructor, using a form of move -// elision. +// +// Detects whether a type is known to be "trivially relocatable" -- meaning it +// can be relocated without invoking the constructor/destructor, using a form of +// move elision. +// +// This trait is conservative, for backwards compatibility. If it's true then +// the type is definitely trivially relocatable, but if it's false then the type +// may or may not be. // // Example: // @@ -834,14 +490,33 @@ using swap_internal::StdSwapIsUnconstrained; // Upstream documentation: // // https://clang.llvm.org/docs/LanguageExtensions.html#:~:text=__is_trivially_relocatable + +// If the compiler offers a builtin that tells us the answer, we can use that. +// This covers all of the cases in the fallback below, plus types that opt in +// using e.g. [[clang::trivial_abi]]. // -#if ABSL_HAVE_BUILTIN(__is_trivially_relocatable) +// Clang on Windows has the builtin, but it falsely claims types with a +// user-provided destructor are trivial (http://b/275003464). So we opt out +// there. +// +// TODO(b/275003464): remove the opt-out once the bug is fixed. +// +// According to https://github.com/abseil/abseil-cpp/issues/1479, this does not +// work with NVCC either. +#if ABSL_HAVE_BUILTIN(__is_trivially_relocatable) && \ + !(defined(__clang__) && (defined(_WIN32) || defined(_WIN64))) && \ + !defined(__NVCC__) template <class T> struct is_trivially_relocatable : std::integral_constant<bool, __is_trivially_relocatable(T)> {}; #else +// Otherwise we use a fallback that detects only those types we can feasibly +// detect. Any time that has trivial move-construction and destruction +// operations is by definition trivially relocatable. template <class T> -struct is_trivially_relocatable : std::integral_constant<bool, false> {}; +struct is_trivially_relocatable + : absl::conjunction<absl::is_trivially_move_constructible<T>, + absl::is_trivially_destructible<T>> {}; #endif // absl::is_constant_evaluated() diff --git a/absl/meta/type_traits_test.cc b/absl/meta/type_traits_test.cc index b2a7a67b..7412f33d 100644 --- a/absl/meta/type_traits_test.cc +++ b/absl/meta/type_traits_test.cc @@ -352,29 +352,6 @@ class Base { virtual ~Base() {} }; -// Old versions of libc++, around Clang 3.5 to 3.6, consider deleted destructors -// as also being trivial. With the resolution of CWG 1928 and CWG 1734, this -// is no longer considered true and has thus been amended. -// Compiler Explorer: https://godbolt.org/g/zT59ZL -// CWG issue 1734: http://open-std.org/JTC1/SC22/WG21/docs/cwg_defects.html#1734 -// CWG issue 1928: http://open-std.org/JTC1/SC22/WG21/docs/cwg_closed.html#1928 -#if !defined(_LIBCPP_VERSION) || _LIBCPP_VERSION >= 3700 -#define ABSL_TRIVIALLY_DESTRUCTIBLE_CONSIDER_DELETED_DESTRUCTOR_NOT_TRIVIAL 1 -#endif - -// As of the moment, GCC versions >5.1 have a problem compiling for -// std::is_trivially_default_constructible<NontrivialDestructor[10]>, where -// NontrivialDestructor is a struct with a custom nontrivial destructor. Note -// that this problem only occurs for arrays of a known size, so something like -// std::is_trivially_default_constructible<NontrivialDestructor[]> does not -// have any problems. -// Compiler Explorer: https://godbolt.org/g/dXRbdK -// GCC bug 83689: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=83689 -#if defined(__clang__) || defined(_MSC_VER) || \ - (defined(__GNUC__) && __GNUC__ < 5) -#define ABSL_GCC_BUG_TRIVIALLY_CONSTRUCTIBLE_ON_ARRAY_OF_NONTRIVIAL 1 -#endif - TEST(TypeTraitsTest, TestIsFunction) { struct Callable { void operator()() {} @@ -391,562 +368,6 @@ TEST(TypeTraitsTest, TestIsFunction) { EXPECT_FALSE(absl::is_function<Callable>::value); } -TEST(TypeTraitsTest, TestTrivialDestructor) { - // Verify that arithmetic types and pointers have trivial destructors. - EXPECT_TRUE(absl::is_trivially_destructible<bool>::value); - EXPECT_TRUE(absl::is_trivially_destructible<char>::value); - EXPECT_TRUE(absl::is_trivially_destructible<unsigned char>::value); - EXPECT_TRUE(absl::is_trivially_destructible<signed char>::value); - EXPECT_TRUE(absl::is_trivially_destructible<wchar_t>::value); - EXPECT_TRUE(absl::is_trivially_destructible<int>::value); - EXPECT_TRUE(absl::is_trivially_destructible<unsigned int>::value); - EXPECT_TRUE(absl::is_trivially_destructible<int16_t>::value); - EXPECT_TRUE(absl::is_trivially_destructible<uint16_t>::value); - EXPECT_TRUE(absl::is_trivially_destructible<int64_t>::value); - EXPECT_TRUE(absl::is_trivially_destructible<uint64_t>::value); - EXPECT_TRUE(absl::is_trivially_destructible<float>::value); - EXPECT_TRUE(absl::is_trivially_destructible<double>::value); - EXPECT_TRUE(absl::is_trivially_destructible<long double>::value); - EXPECT_TRUE(absl::is_trivially_destructible<std::string*>::value); - EXPECT_TRUE(absl::is_trivially_destructible<Trivial*>::value); - EXPECT_TRUE(absl::is_trivially_destructible<const std::string*>::value); - EXPECT_TRUE(absl::is_trivially_destructible<const Trivial*>::value); - EXPECT_TRUE(absl::is_trivially_destructible<std::string**>::value); - EXPECT_TRUE(absl::is_trivially_destructible<Trivial**>::value); - - // classes with destructors - EXPECT_TRUE(absl::is_trivially_destructible<Trivial>::value); - EXPECT_TRUE(absl::is_trivially_destructible<TrivialDestructor>::value); - - // Verify that types with a nontrivial or deleted destructor - // are marked as such. - EXPECT_FALSE(absl::is_trivially_destructible<NontrivialDestructor>::value); -#ifdef ABSL_TRIVIALLY_DESTRUCTIBLE_CONSIDER_DELETED_DESTRUCTOR_NOT_TRIVIAL - EXPECT_FALSE(absl::is_trivially_destructible<DeletedDestructor>::value); -#endif - - // simple_pair of such types is trivial - EXPECT_TRUE((absl::is_trivially_destructible<simple_pair<int, int>>::value)); - EXPECT_TRUE((absl::is_trivially_destructible< - simple_pair<Trivial, TrivialDestructor>>::value)); - - // Verify that types without trivial destructors are correctly marked as such. - EXPECT_FALSE(absl::is_trivially_destructible<std::string>::value); - EXPECT_FALSE(absl::is_trivially_destructible<std::vector<int>>::value); - - // Verify that simple_pairs of types without trivial destructors - // are not marked as trivial. - EXPECT_FALSE((absl::is_trivially_destructible< - simple_pair<int, std::string>>::value)); - EXPECT_FALSE((absl::is_trivially_destructible< - simple_pair<std::string, int>>::value)); - - // array of such types is trivial - using int10 = int[10]; - EXPECT_TRUE(absl::is_trivially_destructible<int10>::value); - using Trivial10 = Trivial[10]; - EXPECT_TRUE(absl::is_trivially_destructible<Trivial10>::value); - using TrivialDestructor10 = TrivialDestructor[10]; - EXPECT_TRUE(absl::is_trivially_destructible<TrivialDestructor10>::value); - - // Conversely, the opposite also holds. - using NontrivialDestructor10 = NontrivialDestructor[10]; - EXPECT_FALSE(absl::is_trivially_destructible<NontrivialDestructor10>::value); -} - -TEST(TypeTraitsTest, TestTrivialDefaultCtor) { - // arithmetic types and pointers have trivial default constructors. - EXPECT_TRUE(absl::is_trivially_default_constructible<bool>::value); - EXPECT_TRUE(absl::is_trivially_default_constructible<char>::value); - EXPECT_TRUE(absl::is_trivially_default_constructible<unsigned char>::value); - EXPECT_TRUE(absl::is_trivially_default_constructible<signed char>::value); - EXPECT_TRUE(absl::is_trivially_default_constructible<wchar_t>::value); - EXPECT_TRUE(absl::is_trivially_default_constructible<int>::value); - EXPECT_TRUE(absl::is_trivially_default_constructible<unsigned int>::value); - EXPECT_TRUE(absl::is_trivially_default_constructible<int16_t>::value); - EXPECT_TRUE(absl::is_trivially_default_constructible<uint16_t>::value); - EXPECT_TRUE(absl::is_trivially_default_constructible<int64_t>::value); - EXPECT_TRUE(absl::is_trivially_default_constructible<uint64_t>::value); - EXPECT_TRUE(absl::is_trivially_default_constructible<float>::value); - EXPECT_TRUE(absl::is_trivially_default_constructible<double>::value); - EXPECT_TRUE(absl::is_trivially_default_constructible<long double>::value); - EXPECT_TRUE(absl::is_trivially_default_constructible<std::string*>::value); - EXPECT_TRUE(absl::is_trivially_default_constructible<Trivial*>::value); - EXPECT_TRUE( - absl::is_trivially_default_constructible<const std::string*>::value); - EXPECT_TRUE(absl::is_trivially_default_constructible<const Trivial*>::value); - EXPECT_TRUE(absl::is_trivially_default_constructible<std::string**>::value); - EXPECT_TRUE(absl::is_trivially_default_constructible<Trivial**>::value); - - // types with compiler generated default ctors - EXPECT_TRUE(absl::is_trivially_default_constructible<Trivial>::value); - EXPECT_TRUE( - absl::is_trivially_default_constructible<TrivialDefaultCtor>::value); - - // Verify that types without them are not. - EXPECT_FALSE( - absl::is_trivially_default_constructible<NontrivialDefaultCtor>::value); - EXPECT_FALSE( - absl::is_trivially_default_constructible<DeletedDefaultCtor>::value); - - // types with nontrivial destructor are nontrivial - EXPECT_FALSE( - absl::is_trivially_default_constructible<NontrivialDestructor>::value); - - // types with vtables - EXPECT_FALSE(absl::is_trivially_default_constructible<Base>::value); - - // Verify that simple_pair has trivial constructors where applicable. - EXPECT_TRUE((absl::is_trivially_default_constructible< - simple_pair<int, char*>>::value)); - EXPECT_TRUE((absl::is_trivially_default_constructible< - simple_pair<int, Trivial>>::value)); - EXPECT_TRUE((absl::is_trivially_default_constructible< - simple_pair<int, TrivialDefaultCtor>>::value)); - - // Verify that types without trivial constructors are - // correctly marked as such. - EXPECT_FALSE(absl::is_trivially_default_constructible<std::string>::value); - EXPECT_FALSE( - absl::is_trivially_default_constructible<std::vector<int>>::value); - - // Verify that simple_pairs of types without trivial constructors - // are not marked as trivial. - EXPECT_FALSE((absl::is_trivially_default_constructible< - simple_pair<int, std::string>>::value)); - EXPECT_FALSE((absl::is_trivially_default_constructible< - simple_pair<std::string, int>>::value)); - - // Verify that arrays of such types are trivially default constructible - using int10 = int[10]; - EXPECT_TRUE(absl::is_trivially_default_constructible<int10>::value); - using Trivial10 = Trivial[10]; - EXPECT_TRUE(absl::is_trivially_default_constructible<Trivial10>::value); - using TrivialDefaultCtor10 = TrivialDefaultCtor[10]; - EXPECT_TRUE( - absl::is_trivially_default_constructible<TrivialDefaultCtor10>::value); - - // Conversely, the opposite also holds. -#ifdef ABSL_GCC_BUG_TRIVIALLY_CONSTRUCTIBLE_ON_ARRAY_OF_NONTRIVIAL - using NontrivialDefaultCtor10 = NontrivialDefaultCtor[10]; - EXPECT_FALSE( - absl::is_trivially_default_constructible<NontrivialDefaultCtor10>::value); -#endif -} - -// GCC prior to 7.4 had a bug in its trivially-constructible traits -// (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=80654). -// This test makes sure that we do not depend on the trait in these cases when -// implementing absl triviality traits. - -template <class T> -struct BadConstructors { - BadConstructors() { static_assert(T::value, ""); } - - BadConstructors(BadConstructors&&) { static_assert(T::value, ""); } - - BadConstructors(const BadConstructors&) { static_assert(T::value, ""); } -}; - -TEST(TypeTraitsTest, TestTrivialityBadConstructors) { - using BadType = BadConstructors<int>; - - EXPECT_FALSE(absl::is_trivially_default_constructible<BadType>::value); - EXPECT_FALSE(absl::is_trivially_move_constructible<BadType>::value); - EXPECT_FALSE(absl::is_trivially_copy_constructible<BadType>::value); -} - -TEST(TypeTraitsTest, TestTrivialMoveCtor) { - // Verify that arithmetic types and pointers have trivial move - // constructors. - EXPECT_TRUE(absl::is_trivially_move_constructible<bool>::value); - EXPECT_TRUE(absl::is_trivially_move_constructible<char>::value); - EXPECT_TRUE(absl::is_trivially_move_constructible<unsigned char>::value); - EXPECT_TRUE(absl::is_trivially_move_constructible<signed char>::value); - EXPECT_TRUE(absl::is_trivially_move_constructible<wchar_t>::value); - EXPECT_TRUE(absl::is_trivially_move_constructible<int>::value); - EXPECT_TRUE(absl::is_trivially_move_constructible<unsigned int>::value); - EXPECT_TRUE(absl::is_trivially_move_constructible<int16_t>::value); - EXPECT_TRUE(absl::is_trivially_move_constructible<uint16_t>::value); - EXPECT_TRUE(absl::is_trivially_move_constructible<int64_t>::value); - EXPECT_TRUE(absl::is_trivially_move_constructible<uint64_t>::value); - EXPECT_TRUE(absl::is_trivially_move_constructible<float>::value); - EXPECT_TRUE(absl::is_trivially_move_constructible<double>::value); - EXPECT_TRUE(absl::is_trivially_move_constructible<long double>::value); - EXPECT_TRUE(absl::is_trivially_move_constructible<std::string*>::value); - EXPECT_TRUE(absl::is_trivially_move_constructible<Trivial*>::value); - EXPECT_TRUE(absl::is_trivially_move_constructible<const std::string*>::value); - EXPECT_TRUE(absl::is_trivially_move_constructible<const Trivial*>::value); - EXPECT_TRUE(absl::is_trivially_move_constructible<std::string**>::value); - EXPECT_TRUE(absl::is_trivially_move_constructible<Trivial**>::value); - - // Reference types - EXPECT_TRUE(absl::is_trivially_move_constructible<int&>::value); - EXPECT_TRUE(absl::is_trivially_move_constructible<int&&>::value); - - // types with compiler generated move ctors - EXPECT_TRUE(absl::is_trivially_move_constructible<Trivial>::value); - EXPECT_TRUE(absl::is_trivially_move_constructible<TrivialMoveCtor>::value); - - // Verify that types without them (i.e. nontrivial or deleted) are not. - EXPECT_FALSE( - absl::is_trivially_move_constructible<NontrivialCopyCtor>::value); - EXPECT_FALSE(absl::is_trivially_move_constructible<DeletedCopyCtor>::value); - EXPECT_FALSE( - absl::is_trivially_move_constructible<NonCopyableOrMovable>::value); - - // type with nontrivial destructor are nontrivial move construbtible - EXPECT_FALSE( - absl::is_trivially_move_constructible<NontrivialDestructor>::value); - - // types with vtables - EXPECT_FALSE(absl::is_trivially_move_constructible<Base>::value); - - // Verify that simple_pair of such types is trivially move constructible - EXPECT_TRUE( - (absl::is_trivially_move_constructible<simple_pair<int, char*>>::value)); - EXPECT_TRUE(( - absl::is_trivially_move_constructible<simple_pair<int, Trivial>>::value)); - EXPECT_TRUE((absl::is_trivially_move_constructible< - simple_pair<int, TrivialMoveCtor>>::value)); - - // Verify that types without trivial move constructors are - // correctly marked as such. - EXPECT_FALSE(absl::is_trivially_move_constructible<std::string>::value); - EXPECT_FALSE(absl::is_trivially_move_constructible<std::vector<int>>::value); - - // Verify that simple_pairs of types without trivial move constructors - // are not marked as trivial. - EXPECT_FALSE((absl::is_trivially_move_constructible< - simple_pair<int, std::string>>::value)); - EXPECT_FALSE((absl::is_trivially_move_constructible< - simple_pair<std::string, int>>::value)); - - // Verify that arrays are not - using int10 = int[10]; - EXPECT_FALSE(absl::is_trivially_move_constructible<int10>::value); -} - -TEST(TypeTraitsTest, TestTrivialCopyCtor) { - // Verify that arithmetic types and pointers have trivial copy - // constructors. - EXPECT_TRUE(absl::is_trivially_copy_constructible<bool>::value); - EXPECT_TRUE(absl::is_trivially_copy_constructible<char>::value); - EXPECT_TRUE(absl::is_trivially_copy_constructible<unsigned char>::value); - EXPECT_TRUE(absl::is_trivially_copy_constructible<signed char>::value); - EXPECT_TRUE(absl::is_trivially_copy_constructible<wchar_t>::value); - EXPECT_TRUE(absl::is_trivially_copy_constructible<int>::value); - EXPECT_TRUE(absl::is_trivially_copy_constructible<unsigned int>::value); - EXPECT_TRUE(absl::is_trivially_copy_constructible<int16_t>::value); - EXPECT_TRUE(absl::is_trivially_copy_constructible<uint16_t>::value); - EXPECT_TRUE(absl::is_trivially_copy_constructible<int64_t>::value); - EXPECT_TRUE(absl::is_trivially_copy_constructible<uint64_t>::value); - EXPECT_TRUE(absl::is_trivially_copy_constructible<float>::value); - EXPECT_TRUE(absl::is_trivially_copy_constructible<double>::value); - EXPECT_TRUE(absl::is_trivially_copy_constructible<long double>::value); - EXPECT_TRUE(absl::is_trivially_copy_constructible<std::string*>::value); - EXPECT_TRUE(absl::is_trivially_copy_constructible<Trivial*>::value); - EXPECT_TRUE(absl::is_trivially_copy_constructible<const std::string*>::value); - EXPECT_TRUE(absl::is_trivially_copy_constructible<const Trivial*>::value); - EXPECT_TRUE(absl::is_trivially_copy_constructible<std::string**>::value); - EXPECT_TRUE(absl::is_trivially_copy_constructible<Trivial**>::value); - - // Reference types - EXPECT_TRUE(absl::is_trivially_copy_constructible<int&>::value); - EXPECT_FALSE(absl::is_trivially_copy_constructible<int&&>::value); - - // types with compiler generated copy ctors - EXPECT_TRUE(absl::is_trivially_copy_constructible<Trivial>::value); - EXPECT_TRUE(absl::is_trivially_copy_constructible<TrivialCopyCtor>::value); - - // Verify that types without them (i.e. nontrivial or deleted) are not. - EXPECT_FALSE( - absl::is_trivially_copy_constructible<NontrivialCopyCtor>::value); - EXPECT_FALSE(absl::is_trivially_copy_constructible<DeletedCopyCtor>::value); - EXPECT_FALSE( - absl::is_trivially_copy_constructible<MovableNonCopyable>::value); - EXPECT_FALSE( - absl::is_trivially_copy_constructible<NonCopyableOrMovable>::value); - - // type with nontrivial destructor are nontrivial copy construbtible - EXPECT_FALSE( - absl::is_trivially_copy_constructible<NontrivialDestructor>::value); - - // types with vtables - EXPECT_FALSE(absl::is_trivially_copy_constructible<Base>::value); - - // Verify that simple_pair of such types is trivially copy constructible - EXPECT_TRUE( - (absl::is_trivially_copy_constructible<simple_pair<int, char*>>::value)); - EXPECT_TRUE(( - absl::is_trivially_copy_constructible<simple_pair<int, Trivial>>::value)); - EXPECT_TRUE((absl::is_trivially_copy_constructible< - simple_pair<int, TrivialCopyCtor>>::value)); - - // Verify that types without trivial copy constructors are - // correctly marked as such. - EXPECT_FALSE(absl::is_trivially_copy_constructible<std::string>::value); - EXPECT_FALSE(absl::is_trivially_copy_constructible<std::vector<int>>::value); - - // Verify that simple_pairs of types without trivial copy constructors - // are not marked as trivial. - EXPECT_FALSE((absl::is_trivially_copy_constructible< - simple_pair<int, std::string>>::value)); - EXPECT_FALSE((absl::is_trivially_copy_constructible< - simple_pair<std::string, int>>::value)); - - // Verify that arrays are not - using int10 = int[10]; - EXPECT_FALSE(absl::is_trivially_copy_constructible<int10>::value); -} - -TEST(TypeTraitsTest, TestTrivialMoveAssign) { - // Verify that arithmetic types and pointers have trivial move - // assignment operators. - EXPECT_TRUE(absl::is_trivially_move_assignable<bool>::value); - EXPECT_TRUE(absl::is_trivially_move_assignable<char>::value); - EXPECT_TRUE(absl::is_trivially_move_assignable<unsigned char>::value); - EXPECT_TRUE(absl::is_trivially_move_assignable<signed char>::value); - EXPECT_TRUE(absl::is_trivially_move_assignable<wchar_t>::value); - EXPECT_TRUE(absl::is_trivially_move_assignable<int>::value); - EXPECT_TRUE(absl::is_trivially_move_assignable<unsigned int>::value); - EXPECT_TRUE(absl::is_trivially_move_assignable<int16_t>::value); - EXPECT_TRUE(absl::is_trivially_move_assignable<uint16_t>::value); - EXPECT_TRUE(absl::is_trivially_move_assignable<int64_t>::value); - EXPECT_TRUE(absl::is_trivially_move_assignable<uint64_t>::value); - EXPECT_TRUE(absl::is_trivially_move_assignable<float>::value); - EXPECT_TRUE(absl::is_trivially_move_assignable<double>::value); - EXPECT_TRUE(absl::is_trivially_move_assignable<long double>::value); - EXPECT_TRUE(absl::is_trivially_move_assignable<std::string*>::value); - EXPECT_TRUE(absl::is_trivially_move_assignable<Trivial*>::value); - EXPECT_TRUE(absl::is_trivially_move_assignable<const std::string*>::value); - EXPECT_TRUE(absl::is_trivially_move_assignable<const Trivial*>::value); - EXPECT_TRUE(absl::is_trivially_move_assignable<std::string**>::value); - EXPECT_TRUE(absl::is_trivially_move_assignable<Trivial**>::value); - - // const qualified types are not assignable - EXPECT_FALSE(absl::is_trivially_move_assignable<const int>::value); - - // types with compiler generated move assignment - EXPECT_TRUE(absl::is_trivially_move_assignable<Trivial>::value); - EXPECT_TRUE(absl::is_trivially_move_assignable<TrivialMoveAssign>::value); - - // Verify that types without them (i.e. nontrivial or deleted) are not. - EXPECT_FALSE(absl::is_trivially_move_assignable<NontrivialCopyAssign>::value); - EXPECT_FALSE(absl::is_trivially_move_assignable<DeletedCopyAssign>::value); - EXPECT_FALSE(absl::is_trivially_move_assignable<NonCopyableOrMovable>::value); - - // types with vtables - EXPECT_FALSE(absl::is_trivially_move_assignable<Base>::value); - - // Verify that simple_pair is trivially assignable - EXPECT_TRUE( - (absl::is_trivially_move_assignable<simple_pair<int, char*>>::value)); - EXPECT_TRUE( - (absl::is_trivially_move_assignable<simple_pair<int, Trivial>>::value)); - EXPECT_TRUE((absl::is_trivially_move_assignable< - simple_pair<int, TrivialMoveAssign>>::value)); - - // Verify that types not trivially move assignable are - // correctly marked as such. - EXPECT_FALSE(absl::is_trivially_move_assignable<std::string>::value); - EXPECT_FALSE(absl::is_trivially_move_assignable<std::vector<int>>::value); - - // Verify that simple_pairs of types not trivially move assignable - // are not marked as trivial. - EXPECT_FALSE((absl::is_trivially_move_assignable< - simple_pair<int, std::string>>::value)); - EXPECT_FALSE((absl::is_trivially_move_assignable< - simple_pair<std::string, int>>::value)); - - // Verify that arrays are not trivially move assignable - using int10 = int[10]; - EXPECT_FALSE(absl::is_trivially_move_assignable<int10>::value); - - // Verify that references are handled correctly - EXPECT_TRUE(absl::is_trivially_move_assignable<Trivial&&>::value); - EXPECT_TRUE(absl::is_trivially_move_assignable<Trivial&>::value); -} - -TEST(TypeTraitsTest, TestTrivialCopyAssign) { - // Verify that arithmetic types and pointers have trivial copy - // assignment operators. - EXPECT_TRUE(absl::is_trivially_copy_assignable<bool>::value); - EXPECT_TRUE(absl::is_trivially_copy_assignable<char>::value); - EXPECT_TRUE(absl::is_trivially_copy_assignable<unsigned char>::value); - EXPECT_TRUE(absl::is_trivially_copy_assignable<signed char>::value); - EXPECT_TRUE(absl::is_trivially_copy_assignable<wchar_t>::value); - EXPECT_TRUE(absl::is_trivially_copy_assignable<int>::value); - EXPECT_TRUE(absl::is_trivially_copy_assignable<unsigned int>::value); - EXPECT_TRUE(absl::is_trivially_copy_assignable<int16_t>::value); - EXPECT_TRUE(absl::is_trivially_copy_assignable<uint16_t>::value); - EXPECT_TRUE(absl::is_trivially_copy_assignable<int64_t>::value); - EXPECT_TRUE(absl::is_trivially_copy_assignable<uint64_t>::value); - EXPECT_TRUE(absl::is_trivially_copy_assignable<float>::value); - EXPECT_TRUE(absl::is_trivially_copy_assignable<double>::value); - EXPECT_TRUE(absl::is_trivially_copy_assignable<long double>::value); - EXPECT_TRUE(absl::is_trivially_copy_assignable<std::string*>::value); - EXPECT_TRUE(absl::is_trivially_copy_assignable<Trivial*>::value); - EXPECT_TRUE(absl::is_trivially_copy_assignable<const std::string*>::value); - EXPECT_TRUE(absl::is_trivially_copy_assignable<const Trivial*>::value); - EXPECT_TRUE(absl::is_trivially_copy_assignable<std::string**>::value); - EXPECT_TRUE(absl::is_trivially_copy_assignable<Trivial**>::value); - - // const qualified types are not assignable - EXPECT_FALSE(absl::is_trivially_copy_assignable<const int>::value); - - // types with compiler generated copy assignment - EXPECT_TRUE(absl::is_trivially_copy_assignable<Trivial>::value); - EXPECT_TRUE(absl::is_trivially_copy_assignable<TrivialCopyAssign>::value); - - // Verify that types without them (i.e. nontrivial or deleted) are not. - EXPECT_FALSE(absl::is_trivially_copy_assignable<NontrivialCopyAssign>::value); - EXPECT_FALSE(absl::is_trivially_copy_assignable<DeletedCopyAssign>::value); - EXPECT_FALSE(absl::is_trivially_copy_assignable<MovableNonCopyable>::value); - EXPECT_FALSE(absl::is_trivially_copy_assignable<NonCopyableOrMovable>::value); - - // types with vtables - EXPECT_FALSE(absl::is_trivially_copy_assignable<Base>::value); - - // Verify that simple_pair is trivially assignable - EXPECT_TRUE( - (absl::is_trivially_copy_assignable<simple_pair<int, char*>>::value)); - EXPECT_TRUE( - (absl::is_trivially_copy_assignable<simple_pair<int, Trivial>>::value)); - EXPECT_TRUE((absl::is_trivially_copy_assignable< - simple_pair<int, TrivialCopyAssign>>::value)); - - // Verify that types not trivially copy assignable are - // correctly marked as such. - EXPECT_FALSE(absl::is_trivially_copy_assignable<std::string>::value); - EXPECT_FALSE(absl::is_trivially_copy_assignable<std::vector<int>>::value); - - // Verify that simple_pairs of types not trivially copy assignable - // are not marked as trivial. - EXPECT_FALSE((absl::is_trivially_copy_assignable< - simple_pair<int, std::string>>::value)); - EXPECT_FALSE((absl::is_trivially_copy_assignable< - simple_pair<std::string, int>>::value)); - - // Verify that arrays are not trivially copy assignable - using int10 = int[10]; - EXPECT_FALSE(absl::is_trivially_copy_assignable<int10>::value); - - // Verify that references are handled correctly - EXPECT_TRUE(absl::is_trivially_copy_assignable<Trivial&&>::value); - EXPECT_TRUE(absl::is_trivially_copy_assignable<Trivial&>::value); -} - -TEST(TypeTraitsTest, TestTriviallyCopyable) { - // Verify that arithmetic types and pointers are trivially copyable. - EXPECT_TRUE(absl::type_traits_internal::is_trivially_copyable<bool>::value); - EXPECT_TRUE(absl::type_traits_internal::is_trivially_copyable<char>::value); - EXPECT_TRUE( - absl::type_traits_internal::is_trivially_copyable<unsigned char>::value); - EXPECT_TRUE( - absl::type_traits_internal::is_trivially_copyable<signed char>::value); - EXPECT_TRUE( - absl::type_traits_internal::is_trivially_copyable<wchar_t>::value); - EXPECT_TRUE(absl::type_traits_internal::is_trivially_copyable<int>::value); - EXPECT_TRUE( - absl::type_traits_internal::is_trivially_copyable<unsigned int>::value); - EXPECT_TRUE( - absl::type_traits_internal::is_trivially_copyable<int16_t>::value); - EXPECT_TRUE( - absl::type_traits_internal::is_trivially_copyable<uint16_t>::value); - EXPECT_TRUE( - absl::type_traits_internal::is_trivially_copyable<int64_t>::value); - EXPECT_TRUE( - absl::type_traits_internal::is_trivially_copyable<uint64_t>::value); - EXPECT_TRUE(absl::type_traits_internal::is_trivially_copyable<float>::value); - EXPECT_TRUE(absl::type_traits_internal::is_trivially_copyable<double>::value); - EXPECT_TRUE( - absl::type_traits_internal::is_trivially_copyable<long double>::value); - EXPECT_TRUE( - absl::type_traits_internal::is_trivially_copyable<std::string*>::value); - EXPECT_TRUE( - absl::type_traits_internal::is_trivially_copyable<Trivial*>::value); - EXPECT_TRUE(absl::type_traits_internal::is_trivially_copyable< - const std::string*>::value); - EXPECT_TRUE( - absl::type_traits_internal::is_trivially_copyable<const Trivial*>::value); - EXPECT_TRUE( - absl::type_traits_internal::is_trivially_copyable<std::string**>::value); - EXPECT_TRUE( - absl::type_traits_internal::is_trivially_copyable<Trivial**>::value); - - // const qualified types are not assignable but are constructible - EXPECT_TRUE( - absl::type_traits_internal::is_trivially_copyable<const int>::value); - - // Trivial copy constructor/assignment and destructor. - EXPECT_TRUE( - absl::type_traits_internal::is_trivially_copyable<Trivial>::value); - // Trivial copy assignment, but non-trivial copy constructor/destructor. - EXPECT_FALSE(absl::type_traits_internal::is_trivially_copyable< - TrivialCopyAssign>::value); - // Trivial copy constructor, but non-trivial assignment. - EXPECT_FALSE(absl::type_traits_internal::is_trivially_copyable< - TrivialCopyCtor>::value); - - // Types with a non-trivial copy constructor/assignment - EXPECT_FALSE(absl::type_traits_internal::is_trivially_copyable< - NontrivialCopyCtor>::value); - EXPECT_FALSE(absl::type_traits_internal::is_trivially_copyable< - NontrivialCopyAssign>::value); - - // Types without copy constructor/assignment, but with move - // MSVC disagrees with other compilers about this: - // EXPECT_TRUE(absl::type_traits_internal::is_trivially_copyable< - // MovableNonCopyable>::value); - - // Types without copy/move constructor/assignment - EXPECT_FALSE(absl::type_traits_internal::is_trivially_copyable< - NonCopyableOrMovable>::value); - - // No copy assign, but has trivial copy constructor. - EXPECT_TRUE(absl::type_traits_internal::is_trivially_copyable< - DeletedCopyAssign>::value); - - // types with vtables - EXPECT_FALSE(absl::type_traits_internal::is_trivially_copyable<Base>::value); - - // Verify that simple_pair is trivially copyable if members are - EXPECT_TRUE((absl::type_traits_internal::is_trivially_copyable< - simple_pair<int, char*>>::value)); - EXPECT_TRUE((absl::type_traits_internal::is_trivially_copyable< - simple_pair<int, Trivial>>::value)); - - // Verify that types not trivially copyable are - // correctly marked as such. - EXPECT_FALSE( - absl::type_traits_internal::is_trivially_copyable<std::string>::value); - EXPECT_FALSE(absl::type_traits_internal::is_trivially_copyable< - std::vector<int>>::value); - - // Verify that simple_pairs of types not trivially copyable - // are not marked as trivial. - EXPECT_FALSE((absl::type_traits_internal::is_trivially_copyable< - simple_pair<int, std::string>>::value)); - EXPECT_FALSE((absl::type_traits_internal::is_trivially_copyable< - simple_pair<std::string, int>>::value)); - EXPECT_FALSE((absl::type_traits_internal::is_trivially_copyable< - simple_pair<int, TrivialCopyAssign>>::value)); - - // Verify that arrays of trivially copyable types are trivially copyable - using int10 = int[10]; - EXPECT_TRUE(absl::type_traits_internal::is_trivially_copyable<int10>::value); - using int10x10 = int[10][10]; - EXPECT_TRUE( - absl::type_traits_internal::is_trivially_copyable<int10x10>::value); - - // Verify that references are handled correctly - EXPECT_FALSE( - absl::type_traits_internal::is_trivially_copyable<Trivial&&>::value); - EXPECT_FALSE( - absl::type_traits_internal::is_trivially_copyable<Trivial&>::value); -} - TEST(TypeTraitsTest, TestRemoveCVRef) { EXPECT_TRUE( (std::is_same<typename absl::remove_cvref<int>::type, int>::value)); @@ -1241,82 +662,6 @@ TEST(TypeTraitsTest, TestResultOf) { EXPECT_EQ(TypeEnum::D, GetTypeExt(Wrap<TypeD>())); } -template <typename T> -bool TestCopyAssign() { - return absl::is_copy_assignable<T>::value == - std::is_copy_assignable<T>::value; -} - -TEST(TypeTraitsTest, IsCopyAssignable) { - EXPECT_TRUE(TestCopyAssign<int>()); - EXPECT_TRUE(TestCopyAssign<int&>()); - EXPECT_TRUE(TestCopyAssign<int&&>()); - - struct S {}; - EXPECT_TRUE(TestCopyAssign<S>()); - EXPECT_TRUE(TestCopyAssign<S&>()); - EXPECT_TRUE(TestCopyAssign<S&&>()); - - class C { - public: - explicit C(C* c) : c_(c) {} - ~C() { delete c_; } - - private: - C* c_; - }; - EXPECT_TRUE(TestCopyAssign<C>()); - EXPECT_TRUE(TestCopyAssign<C&>()); - EXPECT_TRUE(TestCopyAssign<C&&>()); - - // Reason for ifndef: add_lvalue_reference<T> in libc++ breaks for these cases -#ifndef _LIBCPP_VERSION - EXPECT_TRUE(TestCopyAssign<int()>()); - EXPECT_TRUE(TestCopyAssign<int(int) const>()); - EXPECT_TRUE(TestCopyAssign<int(...) volatile&>()); - EXPECT_TRUE(TestCopyAssign<int(int, ...) const volatile&&>()); -#endif // _LIBCPP_VERSION -} - -template <typename T> -bool TestMoveAssign() { - return absl::is_move_assignable<T>::value == - std::is_move_assignable<T>::value; -} - -TEST(TypeTraitsTest, IsMoveAssignable) { - EXPECT_TRUE(TestMoveAssign<int>()); - EXPECT_TRUE(TestMoveAssign<int&>()); - EXPECT_TRUE(TestMoveAssign<int&&>()); - - struct S {}; - EXPECT_TRUE(TestMoveAssign<S>()); - EXPECT_TRUE(TestMoveAssign<S&>()); - EXPECT_TRUE(TestMoveAssign<S&&>()); - - class C { - public: - explicit C(C* c) : c_(c) {} - ~C() { delete c_; } - void operator=(const C&) = delete; - void operator=(C&&) = delete; - - private: - C* c_; - }; - EXPECT_TRUE(TestMoveAssign<C>()); - EXPECT_TRUE(TestMoveAssign<C&>()); - EXPECT_TRUE(TestMoveAssign<C&&>()); - - // Reason for ifndef: add_lvalue_reference<T> in libc++ breaks for these cases -#ifndef _LIBCPP_VERSION - EXPECT_TRUE(TestMoveAssign<int()>()); - EXPECT_TRUE(TestMoveAssign<int(int) const>()); - EXPECT_TRUE(TestMoveAssign<int(...) volatile&>()); - EXPECT_TRUE(TestMoveAssign<int(int, ...) const volatile&&>()); -#endif // _LIBCPP_VERSION -} - namespace adl_namespace { struct DeletedSwap { @@ -1398,24 +743,72 @@ TEST(TypeTraitsTest, IsNothrowSwappable) { EXPECT_TRUE(IsNothrowSwappable<adl_namespace::SpecialNoexceptSwap>::value); } -TEST(TrivallyRelocatable, Sanity) { -#if !defined(ABSL_HAVE_ATTRIBUTE_TRIVIAL_ABI) || \ - !ABSL_HAVE_BUILTIN(__is_trivially_relocatable) - GTEST_SKIP() << "No trivial ABI support."; -#endif +TEST(TriviallyRelocatable, PrimitiveTypes) { + static_assert(absl::is_trivially_relocatable<int>::value, ""); + static_assert(absl::is_trivially_relocatable<char>::value, ""); + static_assert(absl::is_trivially_relocatable<void*>::value, ""); +} - struct Trivial {}; - struct NonTrivial { - NonTrivial(const NonTrivial&) {} // NOLINT +// User-defined types can be trivially relocatable as long as they don't have a +// user-provided move constructor or destructor. +TEST(TriviallyRelocatable, UserDefinedTriviallyReconstructible) { + struct S { + int x; + int y; }; - struct ABSL_ATTRIBUTE_TRIVIAL_ABI TrivialAbi { - TrivialAbi(const TrivialAbi&) {} // NOLINT + + static_assert(absl::is_trivially_relocatable<S>::value, ""); +} + +// A user-provided move constructor disqualifies a type from being trivially +// relocatable. +TEST(TriviallyRelocatable, UserProvidedMoveConstructor) { + struct S { + S(S&&) {} // NOLINT(modernize-use-equals-default) + }; + + static_assert(!absl::is_trivially_relocatable<S>::value, ""); +} + +// A user-provided copy constructor disqualifies a type from being trivially +// relocatable. +TEST(TriviallyRelocatable, UserProvidedCopyConstructor) { + struct S { + S(const S&) {} // NOLINT(modernize-use-equals-default) }; - EXPECT_TRUE(absl::is_trivially_relocatable<Trivial>::value); - EXPECT_FALSE(absl::is_trivially_relocatable<NonTrivial>::value); - EXPECT_TRUE(absl::is_trivially_relocatable<TrivialAbi>::value); + + static_assert(!absl::is_trivially_relocatable<S>::value, ""); } +// A user-provided destructor disqualifies a type from being trivially +// relocatable. +TEST(TriviallyRelocatable, UserProvidedDestructor) { + struct S { + ~S() {} // NOLINT(modernize-use-equals-default) + }; + + static_assert(!absl::is_trivially_relocatable<S>::value, ""); +} + +// TODO(b/275003464): remove the opt-out for Clang on Windows once +// __is_trivially_relocatable is used there again. +#if defined(ABSL_HAVE_ATTRIBUTE_TRIVIAL_ABI) && \ + ABSL_HAVE_BUILTIN(__is_trivially_relocatable) && \ + !(defined(__clang__) && (defined(_WIN32) || defined(_WIN64))) +// A type marked with the "trivial ABI" attribute is trivially relocatable even +// if it has user-provided move/copy constructors and a user-provided +// destructor. +TEST(TrivallyRelocatable, TrivialAbi) { + struct ABSL_ATTRIBUTE_TRIVIAL_ABI S { + S(S&&) {} // NOLINT(modernize-use-equals-default) + S(const S&) {} // NOLINT(modernize-use-equals-default) + ~S() {} // NOLINT(modernize-use-equals-default) + }; + + static_assert(absl::is_trivially_relocatable<S>::value, ""); +} +#endif + #ifdef ABSL_HAVE_CONSTANT_EVALUATED constexpr int64_t NegateIfConstantEvaluated(int64_t i) { diff --git a/absl/numeric/BUILD.bazel b/absl/numeric/BUILD.bazel index ec0b8701..db02b9c0 100644 --- a/absl/numeric/BUILD.bazel +++ b/absl/numeric/BUILD.bazel @@ -19,7 +19,14 @@ load( "ABSL_TEST_COPTS", ) -package(default_visibility = ["//visibility:public"]) +package( + default_visibility = ["//visibility:public"], + features = [ + "header_modules", + "layering_check", + "parse_headers", + ], +) licenses(["notice"]) @@ -48,6 +55,7 @@ cc_binary( "//absl/base:core_headers", "//absl/random", "@com_github_google_benchmark//:benchmark_main", + "@com_google_googletest//:gtest", ], ) @@ -62,6 +70,7 @@ cc_test( deps = [ ":bits", "//absl/random", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -97,6 +106,8 @@ cc_test( "//absl/base", "//absl/hash:hash_testing", "//absl/meta:type_traits", + "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -111,6 +122,7 @@ cc_test( ":int128", "//absl/base:config", "@com_github_google_benchmark//:benchmark_main", + "@com_google_googletest//:gtest", ], ) diff --git a/absl/numeric/CMakeLists.txt b/absl/numeric/CMakeLists.txt index 384c0fc0..7181b91a 100644 --- a/absl/numeric/CMakeLists.txt +++ b/absl/numeric/CMakeLists.txt @@ -72,6 +72,7 @@ absl_cc_test( absl::base absl::hash_testing absl::type_traits + absl::strings GTest::gmock_main ) diff --git a/absl/numeric/bits.h b/absl/numeric/bits.h index df81b9a9..c76454c8 100644 --- a/absl/numeric/bits.h +++ b/absl/numeric/bits.h @@ -38,20 +38,30 @@ #include <limits> #include <type_traits> -#if (defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L) || \ - (defined(__cpp_lib_bitops) && __cpp_lib_bitops >= 201907L) +#include "absl/base/config.h" + +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L #include <bit> #endif #include "absl/base/attributes.h" -#include "absl/base/config.h" #include "absl/numeric/internal/bits.h" namespace absl { ABSL_NAMESPACE_BEGIN -#if !(defined(__cpp_lib_bitops) && __cpp_lib_bitops >= 201907L) -// rotating +// https://github.com/llvm/llvm-project/issues/64544 +// libc++ had the wrong signature for std::rotl and std::rotr +// prior to libc++ 18.0. +// +#if (defined(__cpp_lib_bitops) && __cpp_lib_bitops >= 201907L) && \ + (!defined(_LIBCPP_VERSION) || _LIBCPP_VERSION >= 180000) +using std::rotl; +using std::rotr; + +#else + +// Rotating functions template <class T> ABSL_MUST_USE_RESULT constexpr typename std::enable_if<std::is_unsigned<T>::value, T>::type @@ -66,6 +76,22 @@ ABSL_MUST_USE_RESULT constexpr return numeric_internal::RotateRight(x, s); } +#endif + +// https://github.com/llvm/llvm-project/issues/64544 +// libc++ had the wrong signature for std::rotl and std::rotr +// prior to libc++ 18.0. +// +#if (defined(__cpp_lib_bitops) && __cpp_lib_bitops >= 201907L) + +using std::countl_one; +using std::countl_zero; +using std::countr_one; +using std::countr_zero; +using std::popcount; + +#else + // Counting functions // // While these functions are typically constexpr, on some platforms, they may @@ -107,19 +133,18 @@ ABSL_INTERNAL_CONSTEXPR_POPCOUNT inline popcount(T x) noexcept { return numeric_internal::Popcount(x); } -#else // defined(__cpp_lib_bitops) && __cpp_lib_bitops >= 201907L - -using std::countl_one; -using std::countl_zero; -using std::countr_one; -using std::countr_zero; -using std::popcount; -using std::rotl; -using std::rotr; #endif -#if !(defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L) +#if (defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L) + +using std::bit_ceil; +using std::bit_floor; +using std::bit_width; +using std::has_single_bit; + +#else + // Returns: true if x is an integral power of two; false otherwise. template <class T> constexpr inline typename std::enable_if<std::is_unsigned<T>::value, bool>::type @@ -162,12 +187,6 @@ ABSL_INTERNAL_CONSTEXPR_CLZ inline return has_single_bit(x) ? T{1} << (bit_width(x) - 1) : numeric_internal::BitCeilNonPowerOf2(x); } -#else // defined(__cpp_lib_int_pow2) && __cpp_lib_int_pow2 >= 202002L - -using std::bit_ceil; -using std::bit_floor; -using std::bit_width; -using std::has_single_bit; #endif diff --git a/absl/numeric/bits_test.cc b/absl/numeric/bits_test.cc index 7c942aae..14955eb3 100644 --- a/absl/numeric/bits_test.cc +++ b/absl/numeric/bits_test.cc @@ -15,6 +15,7 @@ #include "absl/numeric/bits.h" #include <limits> +#include <type_traits> #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -24,6 +25,73 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace { +template <typename IntT> +class IntegerTypesTest : public ::testing::Test {}; + +using OneByteIntegerTypes = ::testing::Types< + unsigned char, + uint8_t + >; + +TYPED_TEST_SUITE(IntegerTypesTest, OneByteIntegerTypes); + +TYPED_TEST(IntegerTypesTest, HandlesTypes) { + using UIntType = TypeParam; + + EXPECT_EQ(rotl(UIntType{0x12}, 0), uint8_t{0x12}); + EXPECT_EQ(rotr(UIntType{0x12}, -4), uint8_t{0x21}); + static_assert(rotl(UIntType{0x12}, 0) == uint8_t{0x12}, ""); + + static_assert(rotr(UIntType{0x12}, 0) == uint8_t{0x12}, ""); + EXPECT_EQ(rotr(UIntType{0x12}, 0), uint8_t{0x12}); + +#if ABSL_INTERNAL_HAS_CONSTEXPR_CLZ + static_assert(countl_zero(UIntType{}) == 8, ""); + static_assert(countl_zero(static_cast<UIntType>(-1)) == 0, ""); + + static_assert(countl_one(UIntType{}) == 0, ""); + static_assert(countl_one(static_cast<UIntType>(-1)) == 8, ""); + + static_assert(countr_zero(UIntType{}) == 8, ""); + static_assert(countr_zero(static_cast<UIntType>(-1)) == 0, ""); + + static_assert(countr_one(UIntType{}) == 0, ""); + static_assert(countr_one(static_cast<UIntType>(-1)) == 8, ""); + + static_assert(popcount(UIntType{}) == 0, ""); + static_assert(popcount(UIntType{1}) == 1, ""); + static_assert(popcount(static_cast<UIntType>(-1)) == 8, ""); + + static_assert(bit_width(UIntType{}) == 0, ""); + static_assert(bit_width(UIntType{1}) == 1, ""); + static_assert(bit_width(UIntType{3}) == 2, ""); + static_assert(bit_width(static_cast<UIntType>(-1)) == 8, ""); +#endif + + EXPECT_EQ(countl_zero(UIntType{}), 8); + EXPECT_EQ(countl_zero(static_cast<UIntType>(-1)), 0); + + EXPECT_EQ(countl_one(UIntType{}), 0); + EXPECT_EQ(countl_one(static_cast<UIntType>(-1)), 8); + + EXPECT_EQ(countr_zero(UIntType{}), 8); + EXPECT_EQ(countr_zero(static_cast<UIntType>(-1)), 0); + + EXPECT_EQ(countr_one(UIntType{}), 0); + EXPECT_EQ(countr_one(static_cast<UIntType>(-1)), 8); + + EXPECT_EQ(popcount(UIntType{}), 0); + EXPECT_EQ(popcount(UIntType{1}), 1); + + EXPECT_FALSE(has_single_bit(UIntType{})); + EXPECT_FALSE(has_single_bit(static_cast<UIntType>(-1))); + + EXPECT_EQ(bit_width(UIntType{}), 0); + EXPECT_EQ(bit_width(UIntType{1}), 1); + EXPECT_EQ(bit_width(UIntType{3}), 2); + EXPECT_EQ(bit_width(static_cast<UIntType>(-1)), 8); +} + TEST(Rotate, Left) { static_assert(rotl(uint8_t{0x12}, 0) == uint8_t{0x12}, ""); static_assert(rotl(uint16_t{0x1234}, 0) == uint16_t{0x1234}, ""); diff --git a/absl/numeric/int128.cc b/absl/numeric/int128.cc index e5526c6f..daa32b51 100644 --- a/absl/numeric/int128.cc +++ b/absl/numeric/int128.cc @@ -111,7 +111,7 @@ uint128 MakeUint128FromFloat(T v) { return MakeUint128(0, static_cast<uint64_t>(v)); } -#if defined(__clang__) && !defined(__SSE3__) +#if defined(__clang__) && (__clang_major__ < 9) && !defined(__SSE3__) // Workaround for clang bug: https://bugs.llvm.org/show_bug.cgi?id=38289 // Casting from long double to uint64_t is miscompiled and drops bits. // It is more work, so only use when we need the workaround. @@ -131,7 +131,7 @@ uint128 MakeUint128FromFloat(long double v) { return (static_cast<uint128>(w0) << 100) | (static_cast<uint128>(w1) << 50) | static_cast<uint128>(w2); } -#endif // __clang__ && !__SSE3__ +#endif // __clang__ && (__clang_major__ < 9) && !__SSE3__ } // namespace uint128::uint128(float v) : uint128(MakeUint128FromFloat(v)) {} @@ -202,6 +202,10 @@ std::string Uint128ToFormattedString(uint128 v, std::ios_base::fmtflags flags) { } // namespace +std::string uint128::ToString() const { + return Uint128ToFormattedString(*this, std::ios_base::dec); +} + std::ostream& operator<<(std::ostream& os, uint128 v) { std::ios_base::fmtflags flags = os.flags(); std::string rep = Uint128ToFormattedString(v, flags); @@ -216,9 +220,9 @@ std::ostream& operator<<(std::ostream& os, uint128 v) { } else if (adjustfield == std::ios::internal && (flags & std::ios::showbase) && (flags & std::ios::basefield) == std::ios::hex && v != 0) { - rep.insert(2, count, os.fill()); + rep.insert(size_t{2}, count, os.fill()); } else { - rep.insert(0, count, os.fill()); + rep.insert(size_t{0}, count, os.fill()); } } @@ -285,6 +289,14 @@ int128 operator%(int128 lhs, int128 rhs) { } #endif // ABSL_HAVE_INTRINSIC_INT128 +std::string int128::ToString() const { + std::string rep; + if (Int128High64(*this) < 0) rep = "-"; + rep.append(Uint128ToFormattedString(UnsignedAbsoluteValue(*this), + std::ios_base::dec)); + return rep; +} + std::ostream& operator<<(std::ostream& os, int128 v) { std::ios_base::fmtflags flags = os.flags(); std::string rep; @@ -314,16 +326,16 @@ std::ostream& operator<<(std::ostream& os, int128 v) { break; case std::ios::internal: if (print_as_decimal && (rep[0] == '+' || rep[0] == '-')) { - rep.insert(1, count, os.fill()); + rep.insert(size_t{1}, count, os.fill()); } else if ((flags & std::ios::basefield) == std::ios::hex && (flags & std::ios::showbase) && v != 0) { - rep.insert(2, count, os.fill()); + rep.insert(size_t{2}, count, os.fill()); } else { - rep.insert(0, count, os.fill()); + rep.insert(size_t{0}, count, os.fill()); } break; default: // std::ios::right - rep.insert(0, count, os.fill()); + rep.insert(size_t{0}, count, os.fill()); break; } } diff --git a/absl/numeric/int128.h b/absl/numeric/int128.h index 7a899eec..7530a793 100644 --- a/absl/numeric/int128.h +++ b/absl/numeric/int128.h @@ -32,6 +32,7 @@ #include <cstring> #include <iosfwd> #include <limits> +#include <string> #include <utility> #include "absl/base/config.h" @@ -119,8 +120,8 @@ class #ifdef ABSL_HAVE_INTRINSIC_INT128 constexpr uint128(__int128 v); // NOLINT(runtime/explicit) constexpr uint128(unsigned __int128 v); // NOLINT(runtime/explicit) -#endif // ABSL_HAVE_INTRINSIC_INT128 - constexpr uint128(int128 v); // NOLINT(runtime/explicit) +#endif // ABSL_HAVE_INTRINSIC_INT128 + constexpr uint128(int128 v); // NOLINT(runtime/explicit) explicit uint128(float v); explicit uint128(double v); explicit uint128(long double v); @@ -217,9 +218,17 @@ class return H::combine(std::move(h), Uint128High64(v), Uint128Low64(v)); } + // Support for absl::StrCat() etc. + template <typename Sink> + friend void AbslStringify(Sink& sink, uint128 v) { + sink.Append(v.ToString()); + } + private: constexpr uint128(uint64_t high, uint64_t low); + std::string ToString() const; + // TODO(strel) Update implementation to use __int128 once all users of // uint128 are fixed to not depend on alignof(uint128) == 8. Also add // alignas(16) to class definition to keep alignment consistent across @@ -286,9 +295,9 @@ class numeric_limits<absl::uint128> { #endif // ABSL_HAVE_INTRINSIC_INT128 static constexpr bool tinyness_before = false; - static constexpr absl::uint128 (min)() { return 0; } + static constexpr absl::uint128(min)() { return 0; } static constexpr absl::uint128 lowest() { return 0; } - static constexpr absl::uint128 (max)() { return absl::Uint128Max(); } + static constexpr absl::uint128(max)() { return absl::Uint128Max(); } static constexpr absl::uint128 epsilon() { return 0; } static constexpr absl::uint128 round_error() { return 0; } static constexpr absl::uint128 infinity() { return 0; } @@ -454,9 +463,17 @@ class int128 { return H::combine(std::move(h), Int128High64(v), Int128Low64(v)); } + // Support for absl::StrCat() etc. + template <typename Sink> + friend void AbslStringify(Sink& sink, int128 v) { + sink.Append(v.ToString()); + } + private: constexpr int128(int64_t high, uint64_t low); + std::string ToString() const; + #if defined(ABSL_HAVE_INTRINSIC_INT128) __int128 v_; #else // ABSL_HAVE_INTRINSIC_INT128 @@ -521,9 +538,9 @@ class numeric_limits<absl::int128> { #endif // ABSL_HAVE_INTRINSIC_INT128 static constexpr bool tinyness_before = false; - static constexpr absl::int128 (min)() { return absl::Int128Min(); } + static constexpr absl::int128(min)() { return absl::Int128Min(); } static constexpr absl::int128 lowest() { return absl::Int128Min(); } - static constexpr absl::int128 (max)() { return absl::Int128Max(); } + static constexpr absl::int128(max)() { return absl::Int128Max(); } static constexpr absl::int128 epsilon() { return 0; } static constexpr absl::int128 round_error() { return 0; } static constexpr absl::int128 infinity() { return 0; } @@ -561,9 +578,7 @@ inline uint128& uint128::operator=(unsigned long v) { } // NOLINTNEXTLINE(runtime/int) -inline uint128& uint128::operator=(long long v) { - return *this = uint128(v); -} +inline uint128& uint128::operator=(long long v) { return *this = uint128(v); } // NOLINTNEXTLINE(runtime/int) inline uint128& uint128::operator=(unsigned long long v) { @@ -571,18 +586,14 @@ inline uint128& uint128::operator=(unsigned long long v) { } #ifdef ABSL_HAVE_INTRINSIC_INT128 -inline uint128& uint128::operator=(__int128 v) { - return *this = uint128(v); -} +inline uint128& uint128::operator=(__int128 v) { return *this = uint128(v); } inline uint128& uint128::operator=(unsigned __int128 v) { return *this = uint128(v); } #endif // ABSL_HAVE_INTRINSIC_INT128 -inline uint128& uint128::operator=(int128 v) { - return *this = uint128(v); -} +inline uint128& uint128::operator=(int128 v) { return *this = uint128(v); } // Arithmetic operators. @@ -637,8 +648,7 @@ constexpr uint64_t Uint128High64(uint128 v) { return v.hi_; } #if defined(ABSL_IS_LITTLE_ENDIAN) -constexpr uint128::uint128(uint64_t high, uint64_t low) - : lo_{low}, hi_{high} {} +constexpr uint128::uint128(uint64_t high, uint64_t low) : lo_{low}, hi_{high} {} constexpr uint128::uint128(int v) : lo_{static_cast<uint64_t>(v)}, @@ -670,8 +680,7 @@ constexpr uint128::uint128(int128 v) #elif defined(ABSL_IS_BIG_ENDIAN) -constexpr uint128::uint128(uint64_t high, uint64_t low) - : hi_{high}, lo_{low} {} +constexpr uint128::uint128(uint64_t high, uint64_t low) : hi_{high}, lo_{low} {} constexpr uint128::uint128(int v) : hi_{v < 0 ? (std::numeric_limits<uint64_t>::max)() : 0}, @@ -817,13 +826,9 @@ constexpr bool operator>=(uint128 lhs, uint128 rhs) { return !(lhs < rhs); } // Unary operators. -constexpr inline uint128 operator+(uint128 val) { - return val; -} +constexpr inline uint128 operator+(uint128 val) { return val; } -constexpr inline int128 operator+(int128 val) { - return val; -} +constexpr inline int128 operator+(int128 val) { return val; } constexpr uint128 operator-(uint128 val) { #if defined(ABSL_HAVE_INTRINSIC_INT128) @@ -906,7 +911,7 @@ constexpr uint128 operator<<(uint128 lhs, int amount) { #else // uint64_t shifts of >= 64 are undefined, so we will need some // special-casing. - return amount >= 64 ? MakeUint128(Uint128Low64(lhs) << (amount - 64), 0) + return amount >= 64 ? MakeUint128(Uint128Low64(lhs) << (amount - 64), 0) : amount == 0 ? lhs : MakeUint128((Uint128High64(lhs) << amount) | (Uint128Low64(lhs) >> (64 - amount)), @@ -920,7 +925,7 @@ constexpr uint128 operator>>(uint128 lhs, int amount) { #else // uint64_t shifts of >= 64 are undefined, so we will need some // special-casing. - return amount >= 64 ? MakeUint128(0, Uint128High64(lhs) >> (amount - 64)) + return amount >= 64 ? MakeUint128(0, Uint128High64(lhs) >> (amount - 64)) : amount == 0 ? lhs : MakeUint128(Uint128High64(lhs) >> amount, (Uint128Low64(lhs) >> amount) | @@ -1042,27 +1047,19 @@ constexpr int128 MakeInt128(int64_t high, uint64_t low) { } // Assignment from integer types. -inline int128& int128::operator=(int v) { - return *this = int128(v); -} +inline int128& int128::operator=(int v) { return *this = int128(v); } -inline int128& int128::operator=(unsigned int v) { - return *this = int128(v); -} +inline int128& int128::operator=(unsigned int v) { return *this = int128(v); } inline int128& int128::operator=(long v) { // NOLINT(runtime/int) return *this = int128(v); } // NOLINTNEXTLINE(runtime/int) -inline int128& int128::operator=(unsigned long v) { - return *this = int128(v); -} +inline int128& int128::operator=(unsigned long v) { return *this = int128(v); } // NOLINTNEXTLINE(runtime/int) -inline int128& int128::operator=(long long v) { - return *this = int128(v); -} +inline int128& int128::operator=(long long v) { return *this = int128(v); } // NOLINTNEXTLINE(runtime/int) inline int128& int128::operator=(unsigned long long v) { diff --git a/absl/numeric/int128_have_intrinsic.inc b/absl/numeric/int128_have_intrinsic.inc index 3945fa29..6f1ac644 100644 --- a/absl/numeric/int128_have_intrinsic.inc +++ b/absl/numeric/int128_have_intrinsic.inc @@ -162,9 +162,6 @@ inline int128::operator long double() const { } #else // Clang on PowerPC -// Forward declaration for conversion operators to floating point types. -constexpr int128 operator-(int128 v); -constexpr bool operator!=(int128 lhs, int128 rhs); inline int128::operator float() const { // We must convert the absolute value and then negate as needed, because diff --git a/absl/numeric/int128_no_intrinsic.inc b/absl/numeric/int128_no_intrinsic.inc index 8834804c..6f5d8377 100644 --- a/absl/numeric/int128_no_intrinsic.inc +++ b/absl/numeric/int128_no_intrinsic.inc @@ -23,8 +23,7 @@ constexpr int64_t Int128High64(int128 v) { return v.hi_; } #if defined(ABSL_IS_LITTLE_ENDIAN) -constexpr int128::int128(int64_t high, uint64_t low) : - lo_(low), hi_(high) {} +constexpr int128::int128(int64_t high, uint64_t low) : lo_(low), hi_(high) {} constexpr int128::int128(int v) : lo_{static_cast<uint64_t>(v)}, hi_{v < 0 ? ~int64_t{0} : 0} {} @@ -44,8 +43,7 @@ constexpr int128::int128(uint128 v) #elif defined(ABSL_IS_BIG_ENDIAN) -constexpr int128::int128(int64_t high, uint64_t low) : - hi_{high}, lo_{low} {} +constexpr int128::int128(int64_t high, uint64_t low) : hi_{high}, lo_{low} {} constexpr int128::int128(int v) : hi_{v < 0 ? ~int64_t{0} : 0}, lo_{static_cast<uint64_t>(v)} {} @@ -279,33 +277,52 @@ constexpr int128 operator^(int128 lhs, int128 rhs) { } constexpr int128 operator<<(int128 lhs, int amount) { - // int64_t shifts of >= 64 are undefined, so we need some special-casing. - return amount >= 64 - ? MakeInt128( - static_cast<int64_t>(Int128Low64(lhs) << (amount - 64)), 0) - : amount == 0 - ? lhs - : MakeInt128( - (Int128High64(lhs) << amount) | - static_cast<int64_t>(Int128Low64(lhs) >> (64 - amount)), - Int128Low64(lhs) << amount); + // int64_t shifts of >= 63 are undefined, so we need some special-casing. + assert(amount >= 0 && amount < 127); + if (amount <= 0) { + return lhs; + } else if (amount < 63) { + return MakeInt128( + (Int128High64(lhs) << amount) | + static_cast<int64_t>(Int128Low64(lhs) >> (64 - amount)), + Int128Low64(lhs) << amount); + } else if (amount == 63) { + return MakeInt128(((Int128High64(lhs) << 32) << 31) | + static_cast<int64_t>(Int128Low64(lhs) >> 1), + (Int128Low64(lhs) << 32) << 31); + } else if (amount == 127) { + return MakeInt128(static_cast<int64_t>(Int128Low64(lhs) << 63), 0); + } else if (amount > 127) { + return MakeInt128(0, 0); + } else { + // amount >= 64 && amount < 127 + return MakeInt128(static_cast<int64_t>(Int128Low64(lhs) << (amount - 64)), + 0); + } } constexpr int128 operator>>(int128 lhs, int amount) { - // int64_t shifts of >= 64 are undefined, so we need some special-casing. - // The (Int128High64(lhs) >> 32) >> 32 "trick" causes the the most significant - // int64 to be inititialized with all zeros or all ones correctly. It takes - // into account whether the number is negative or positive, and whether the - // current architecture does arithmetic or logical right shifts for negative - // numbers. - return amount >= 64 - ? MakeInt128( - (Int128High64(lhs) >> 32) >> 32, - static_cast<uint64_t>(Int128High64(lhs) >> (amount - 64))) - : amount == 0 - ? lhs - : MakeInt128(Int128High64(lhs) >> amount, - (Int128Low64(lhs) >> amount) | - (static_cast<uint64_t>(Int128High64(lhs)) - << (64 - amount))); + // int64_t shifts of >= 63 are undefined, so we need some special-casing. + assert(amount >= 0 && amount < 127); + if (amount <= 0) { + return lhs; + } else if (amount < 63) { + return MakeInt128( + Int128High64(lhs) >> amount, + Int128Low64(lhs) >> amount | static_cast<uint64_t>(Int128High64(lhs)) + << (64 - amount)); + } else if (amount == 63) { + return MakeInt128((Int128High64(lhs) >> 32) >> 31, + static_cast<uint64_t>(Int128High64(lhs) << 1) | + (Int128Low64(lhs) >> 32) >> 31); + + } else if (amount >= 127) { + return MakeInt128((Int128High64(lhs) >> 32) >> 31, + static_cast<uint64_t>((Int128High64(lhs) >> 32) >> 31)); + } else { + // amount >= 64 && amount < 127 + return MakeInt128( + (Int128High64(lhs) >> 32) >> 31, + static_cast<uint64_t>(Int128High64(lhs) >> (amount - 64))); + } } diff --git a/absl/numeric/int128_stream_test.cc b/absl/numeric/int128_stream_test.cc index 81d2adee..bd937847 100644 --- a/absl/numeric/int128_stream_test.cc +++ b/absl/numeric/int128_stream_test.cc @@ -18,6 +18,7 @@ #include <string> #include "gtest/gtest.h" +#include "absl/strings/str_cat.h" namespace { @@ -87,6 +88,9 @@ constexpr std::ios::fmtflags kBase = std::ios::showbase; constexpr std::ios::fmtflags kPos = std::ios::showpos; void CheckUint128Case(const Uint128TestCase& test_case) { + if (test_case.flags == kDec && test_case.width == 0) { + EXPECT_EQ(absl::StrCat(test_case.value), test_case.expected); + } std::ostringstream os; os.flags(test_case.flags); os.width(test_case.width); @@ -155,6 +159,9 @@ struct Int128TestCase { }; void CheckInt128Case(const Int128TestCase& test_case) { + if (test_case.flags == kDec && test_case.width == 0) { + EXPECT_EQ(absl::StrCat(test_case.value), test_case.expected); + } std::ostringstream os; os.flags(test_case.flags); os.width(test_case.width); diff --git a/absl/numeric/int128_test.cc b/absl/numeric/int128_test.cc index dd9425d7..01e3eb5c 100644 --- a/absl/numeric/int128_test.cc +++ b/absl/numeric/int128_test.cc @@ -32,6 +32,8 @@ #pragma warning(disable:4146) #endif +#define MAKE_INT128(HI, LO) absl::MakeInt128(static_cast<int64_t>(HI), LO) + namespace { template <typename T> @@ -283,8 +285,9 @@ TEST(Uint128, ConversionTests) { EXPECT_EQ(from_precise_double, from_precise_ints); EXPECT_DOUBLE_EQ(static_cast<double>(from_precise_ints), precise_double); - double approx_double = 0xffffeeeeddddcccc * std::pow(2.0, 64.0) + - 0xbbbbaaaa99998888; + double approx_double = + static_cast<double>(0xffffeeeeddddcccc) * std::pow(2.0, 64.0) + + static_cast<double>(0xbbbbaaaa99998888); absl::uint128 from_approx_double(approx_double); EXPECT_DOUBLE_EQ(static_cast<double>(from_approx_double), approx_double); @@ -1245,6 +1248,27 @@ TEST(Int128, BitwiseShiftTest) { absl::MakeInt128(uint64_t{1} << j, 0) >>= (j - i)); } } + + // Manually calculated cases with shift count for positive (val1) and negative + // (val2) values + absl::int128 val1 = MAKE_INT128(0x123456789abcdef0, 0x123456789abcdef0); + absl::int128 val2 = MAKE_INT128(0xfedcba0987654321, 0xfedcba0987654321); + + EXPECT_EQ(val1 << 63, MAKE_INT128(0x91a2b3c4d5e6f78, 0x0)); + EXPECT_EQ(val1 << 64, MAKE_INT128(0x123456789abcdef0, 0x0)); + EXPECT_EQ(val2 << 63, MAKE_INT128(0xff6e5d04c3b2a190, 0x8000000000000000)); + EXPECT_EQ(val2 << 64, MAKE_INT128(0xfedcba0987654321, 0x0)); + + EXPECT_EQ(val1 << 126, MAKE_INT128(0x0, 0x0)); + EXPECT_EQ(val2 << 126, MAKE_INT128(0x4000000000000000, 0x0)); + + EXPECT_EQ(val1 >> 63, MAKE_INT128(0x0, 0x2468acf13579bde0)); + EXPECT_EQ(val1 >> 64, MAKE_INT128(0x0, 0x123456789abcdef0)); + EXPECT_EQ(val2 >> 63, MAKE_INT128(0xffffffffffffffff, 0xfdb974130eca8643)); + EXPECT_EQ(val2 >> 64, MAKE_INT128(0xffffffffffffffff, 0xfedcba0987654321)); + + EXPECT_EQ(val1 >> 126, MAKE_INT128(0x0, 0x0)); + EXPECT_EQ(val2 >> 126, MAKE_INT128(0xffffffffffffffff, 0xffffffffffffffff)); } TEST(Int128, NumericLimitsTest) { diff --git a/absl/profiling/BUILD.bazel b/absl/profiling/BUILD.bazel index 3392c96c..86f205f9 100644 --- a/absl/profiling/BUILD.bazel +++ b/absl/profiling/BUILD.bazel @@ -19,7 +19,14 @@ load( "ABSL_TEST_COPTS", ) -package(default_visibility = ["//visibility:private"]) +package( + default_visibility = ["//visibility:private"], + features = [ + "header_modules", + "layering_check", + "parse_headers", + ], +) licenses(["notice"]) @@ -52,6 +59,7 @@ cc_test( "//absl/synchronization", "//absl/synchronization:thread_pool", "//absl/time", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -80,6 +88,7 @@ cc_test( deps = [ ":exponential_biased", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -91,6 +100,7 @@ cc_library( copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, visibility = [ + # TODO(b/304670045): remove after periodic_sampler moves to //spanner/common. "//absl:__subpackages__", ], deps = [ @@ -109,6 +119,7 @@ cc_test( deps = [ ":periodic_sampler", "//absl/base:core_headers", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/random/BUILD.bazel b/absl/random/BUILD.bazel index 133c0659..80c4f055 100644 --- a/absl/random/BUILD.bazel +++ b/absl/random/BUILD.bazel @@ -23,7 +23,14 @@ load( "ABSL_TEST_COPTS", ) -package(default_visibility = ["//visibility:public"]) +package( + default_visibility = ["//visibility:public"], + features = [ + "header_modules", + "layering_check", + "parse_headers", + ], +) licenses(["notice"]) @@ -171,6 +178,7 @@ cc_test( ":random", "//absl/random/internal:pcg_engine", "//absl/random/internal:sequence_urbg", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -189,13 +197,14 @@ cc_test( deps = [ ":distributions", ":random", - "//absl/base:raw_logging_internal", + "//absl/log", "//absl/numeric:representation", "//absl/random/internal:distribution_test_util", "//absl/random/internal:pcg_engine", "//absl/random/internal:sequence_urbg", "//absl/strings", "//absl/strings:str_format", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -213,6 +222,7 @@ cc_test( ":distributions", ":random", "//absl/random/internal:distribution_test_util", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -226,6 +236,7 @@ cc_test( deps = [ ":distributions", ":random", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -244,12 +255,13 @@ cc_test( deps = [ ":distributions", ":random", - "//absl/base:raw_logging_internal", + "//absl/log", "//absl/random/internal:distribution_test_util", "//absl/random/internal:pcg_engine", "//absl/random/internal:sequence_urbg", "//absl/strings", "//absl/strings:str_format", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -265,11 +277,12 @@ cc_test( deps = [ ":distributions", ":random", - "//absl/base:raw_logging_internal", + "//absl/log", "//absl/random/internal:distribution_test_util", "//absl/random/internal:pcg_engine", "//absl/random/internal:sequence_urbg", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -292,13 +305,14 @@ cc_test( ":distributions", ":random", "//absl/base:core_headers", - "//absl/base:raw_logging_internal", "//absl/container:flat_hash_map", + "//absl/log", "//absl/random/internal:distribution_test_util", "//absl/random/internal:pcg_engine", "//absl/random/internal:sequence_urbg", "//absl/strings", "//absl/strings:str_format", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -314,13 +328,14 @@ cc_test( ":distributions", ":random", "//absl/base:core_headers", - "//absl/base:raw_logging_internal", + "//absl/log", "//absl/numeric:representation", "//absl/random/internal:distribution_test_util", "//absl/random/internal:pcg_engine", "//absl/random/internal:sequence_urbg", "//absl/strings", "//absl/strings:str_format", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -338,12 +353,13 @@ cc_test( ":distributions", ":random", "//absl/base:core_headers", - "//absl/base:raw_logging_internal", + "//absl/log", "//absl/numeric:representation", "//absl/random/internal:distribution_test_util", "//absl/random/internal:sequence_urbg", "//absl/strings", "//absl/strings:str_format", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -360,11 +376,12 @@ cc_test( deps = [ ":distributions", ":random", - "//absl/base:raw_logging_internal", + "//absl/log", "//absl/random/internal:distribution_test_util", "//absl/random/internal:pcg_engine", "//absl/random/internal:sequence_urbg", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -385,12 +402,13 @@ cc_test( deps = [ ":distributions", ":random", - "//absl/base:raw_logging_internal", + "//absl/log", "//absl/numeric:representation", "//absl/random/internal:distribution_test_util", "//absl/random/internal:pcg_engine", "//absl/random/internal:sequence_urbg", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -406,11 +424,12 @@ cc_test( deps = [ ":distributions", ":random", - "//absl/base:raw_logging_internal", + "//absl/log", "//absl/random/internal:distribution_test_util", "//absl/random/internal:pcg_engine", "//absl/random/internal:sequence_urbg", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -426,6 +445,7 @@ cc_test( ":random", "//absl/base:fast_type_id", "//absl/random/internal:sequence_urbg", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -444,6 +464,7 @@ cc_test( ":mock_distributions", ":mocking_bit_gen", ":random", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -461,6 +482,7 @@ cc_test( ":mock_distributions", ":mocking_bit_gen", ":random", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -476,6 +498,7 @@ cc_test( ], deps = [ ":random", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -490,6 +513,7 @@ cc_test( ":random", ":seed_sequences", "//absl/random/internal:nonsecure_base", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/random/CMakeLists.txt b/absl/random/CMakeLists.txt index c74fd300..bd363d88 100644 --- a/absl/random/CMakeLists.txt +++ b/absl/random/CMakeLists.txt @@ -260,13 +260,13 @@ absl_cc_test( LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS + absl::log absl::numeric_representation absl::random_distributions absl::random_random absl::random_internal_distribution_test_util absl::random_internal_sequence_urbg absl::random_internal_pcg_engine - absl::raw_logging_internal absl::strings absl::str_format GTest::gmock @@ -299,6 +299,7 @@ absl_cc_test( ${ABSL_TEST_COPTS} LINKOPTS ${ABSL_DEFAULT_LINKOPTS} + DEPS absl::random_distributions absl::random_random absl::raw_logging_internal @@ -315,12 +316,13 @@ absl_cc_test( ${ABSL_TEST_COPTS} LINKOPTS ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::log absl::random_distributions absl::random_internal_distribution_test_util absl::random_internal_pcg_engine absl::random_internal_sequence_urbg absl::random_random - absl::raw_logging_internal absl::strings absl::str_format GTest::gmock @@ -337,12 +339,12 @@ absl_cc_test( LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS + absl::log absl::random_distributions absl::random_internal_distribution_test_util absl::random_internal_pcg_engine absl::random_internal_sequence_urbg absl::random_random - absl::raw_logging_internal absl::strings GTest::gmock GTest::gtest_main @@ -362,10 +364,10 @@ absl_cc_test( absl::random_random absl::core_headers absl::flat_hash_map + absl::log absl::random_internal_distribution_test_util absl::random_internal_pcg_engine absl::random_internal_sequence_urbg - absl::raw_logging_internal absl::strings absl::str_format GTest::gmock @@ -383,13 +385,13 @@ absl_cc_test( ${ABSL_DEFAULT_LINKOPTS} DEPS absl::core_headers + absl::log absl::numeric_representation absl::random_distributions absl::random_internal_distribution_test_util absl::random_internal_pcg_engine absl::random_internal_sequence_urbg absl::random_random - absl::raw_logging_internal absl::strings absl::str_format GTest::gmock @@ -407,12 +409,12 @@ absl_cc_test( ${ABSL_DEFAULT_LINKOPTS} DEPS absl::core_headers + absl::log absl::numeric_representation absl::random_distributions absl::random_internal_distribution_test_util absl::random_internal_sequence_urbg absl::random_random - absl::raw_logging_internal absl::strings absl::str_format GTest::gmock @@ -429,12 +431,12 @@ absl_cc_test( LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS + absl::log absl::random_distributions absl::random_internal_distribution_test_util absl::random_internal_pcg_engine absl::random_internal_sequence_urbg absl::random_random - absl::raw_logging_internal absl::strings GTest::gmock GTest::gtest_main @@ -450,6 +452,7 @@ absl_cc_test( LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS + absl::log absl::numeric_representation absl::random_distributions absl::random_internal_distribution_test_util @@ -471,12 +474,12 @@ absl_cc_test( LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS + absl::log absl::random_distributions absl::random_internal_distribution_test_util absl::random_internal_pcg_engine absl::random_internal_sequence_urbg absl::random_random - absl::raw_logging_internal absl::strings GTest::gmock GTest::gtest_main @@ -1090,9 +1093,9 @@ absl_cc_test( LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS + absl::log absl::random_internal_explicit_seed_seq absl::random_internal_randen_engine - absl::raw_logging_internal absl::strings absl::time GTest::gmock @@ -1142,10 +1145,10 @@ absl_cc_test( LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS + absl::log absl::random_internal_platform absl::random_internal_randen_hwaes absl::random_internal_randen_hwaes_impl - absl::raw_logging_internal absl::str_format GTest::gmock GTest::gtest diff --git a/absl/random/benchmarks.cc b/absl/random/benchmarks.cc index 87bbb981..0900e818 100644 --- a/absl/random/benchmarks.cc +++ b/absl/random/benchmarks.cc @@ -62,7 +62,7 @@ class PrecompiledSeedSeq { public: using result_type = uint32_t; - PrecompiledSeedSeq() {} + PrecompiledSeedSeq() = default; template <typename Iterator> PrecompiledSeedSeq(Iterator begin, Iterator end) {} diff --git a/absl/random/beta_distribution_test.cc b/absl/random/beta_distribution_test.cc index c16fbb4f..c93b2a33 100644 --- a/absl/random/beta_distribution_test.cc +++ b/absl/random/beta_distribution_test.cc @@ -28,7 +28,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "absl/base/internal/raw_logging.h" +#include "absl/log/log.h" #include "absl/numeric/internal/representation.h" #include "absl/random/internal/chi_square.h" #include "absl/random/internal/distribution_test_util.h" @@ -107,8 +107,8 @@ TYPED_TEST(BetaDistributionInterfaceTest, SerializeTest) { }; for (TypeParam alpha : kValues) { for (TypeParam beta : kValues) { - ABSL_INTERNAL_LOG( - INFO, absl::StrFormat("Smoke test for Beta(%a, %a)", alpha, beta)); + LOG(INFO) << absl::StreamFormat("Smoke test for Beta(%a, %a)", alpha, + beta); param_type param(alpha, beta); absl::beta_distribution<TypeParam> before(alpha, beta); @@ -327,15 +327,13 @@ bool BetaDistributionTest::SingleZTestOnMeanAndVariance(double p, absl::random_internal::Near("z", z_mean, 0.0, max_err) && absl::random_internal::Near("z_variance", z_variance, 0.0, max_err); if (!pass) { - ABSL_INTERNAL_LOG( - INFO, - absl::StrFormat( - "Beta(%f, %f), " - "mean: sample %f, expect %f, which is %f stddevs away, " - "variance: sample %f, expect %f, which is %f stddevs away.", - alpha_, beta_, m.mean, Mean(), - std::abs(m.mean - Mean()) / mean_stddev, m.variance, Variance(), - std::abs(m.variance - Variance()) / variance_stddev)); + LOG(INFO) << "Beta(" << alpha_ << ", " << beta_ << "), mean: sample " + << m.mean << ", expect " << Mean() << ", which is " + << std::abs(m.mean - Mean()) / mean_stddev + << " stddevs away, variance: sample " << m.variance << ", expect " + << Variance() << ", which is " + << std::abs(m.variance - Variance()) / variance_stddev + << " stddevs away."; } return pass; } @@ -396,18 +394,15 @@ bool BetaDistributionTest::SingleChiSquaredTest(double p, size_t samples, const bool pass = (absl::random_internal::ChiSquarePValue(chi_square, dof) >= p); if (!pass) { - for (int i = 0; i < cutoffs.size(); i++) { - ABSL_INTERNAL_LOG( - INFO, absl::StrFormat("cutoff[%d] = %f, actual count %d, expected %d", - i, cutoffs[i], counts[i], - static_cast<int>(expected[i]))); + for (size_t i = 0; i < cutoffs.size(); i++) { + LOG(INFO) << "cutoff[" << i << "] = " << cutoffs[i] << ", actual count " + << counts[i] << ", expected " << static_cast<int>(expected[i]); } - ABSL_INTERNAL_LOG( - INFO, absl::StrFormat( - "Beta(%f, %f) %s %f, p = %f", alpha_, beta_, - absl::random_internal::kChiSquared, chi_square, - absl::random_internal::ChiSquarePValue(chi_square, dof))); + LOG(INFO) << "Beta(" << alpha_ << ", " << beta_ << ") " + << absl::random_internal::kChiSquared << " " << chi_square + << ", p = " + << absl::random_internal::ChiSquarePValue(chi_square, dof); } return pass; } diff --git a/absl/random/discrete_distribution_test.cc b/absl/random/discrete_distribution_test.cc index 415b14cc..32405ea9 100644 --- a/absl/random/discrete_distribution_test.cc +++ b/absl/random/discrete_distribution_test.cc @@ -26,7 +26,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "absl/base/internal/raw_logging.h" +#include "absl/log/log.h" #include "absl/random/internal/chi_square.h" #include "absl/random/internal/distribution_test_util.h" #include "absl/random/internal/pcg_engine.h" @@ -146,7 +146,7 @@ TEST(DiscreteDistributionTest, ChiSquaredTest50) { using absl::random_internal::kChiSquared; constexpr size_t kTrials = 10000; - constexpr int kBuckets = 50; // inclusive, so actally +1 + constexpr int kBuckets = 50; // inclusive, so actually +1 // 1-in-100000 threshold, but remember, there are about 8 tests // in this file. And the test could fail for other reasons. @@ -194,7 +194,7 @@ TEST(DiscreteDistributionTest, ChiSquaredTest50) { absl::StrAppend(&msg, kChiSquared, " p-value ", p_value, "\n"); absl::StrAppend(&msg, "High ", kChiSquared, " value: ", chi_square, " > ", kThreshold); - ABSL_RAW_LOG(INFO, "%s", msg.c_str()); + LOG(INFO) << msg; FAIL() << msg; } } diff --git a/absl/random/distributions.h b/absl/random/distributions.h index 37fc3aa7..4e3b332e 100644 --- a/absl/random/distributions.h +++ b/absl/random/distributions.h @@ -362,7 +362,7 @@ RealType Gaussian(URBG&& urbg, // NOLINT(runtime/references) // If `lo` is nonzero then this distribution is shifted to the desired interval, // so LogUniform(lo, hi, b) is equivalent to LogUniform(0, hi-lo, b)+lo. // -// See http://ecolego.facilia.se/ecolego/show/Log-Uniform%20Distribution +// See https://en.wikipedia.org/wiki/Log-normal_distribution // // Example: // diff --git a/absl/random/exponential_distribution_test.cc b/absl/random/exponential_distribution_test.cc index 3c44d9ec..fb9a0d16 100644 --- a/absl/random/exponential_distribution_test.cc +++ b/absl/random/exponential_distribution_test.cc @@ -29,8 +29,8 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "absl/base/internal/raw_logging.h" #include "absl/base/macros.h" +#include "absl/log/log.h" #include "absl/numeric/internal/representation.h" #include "absl/random/internal/chi_square.h" #include "absl/random/internal/distribution_test_util.h" @@ -115,9 +115,8 @@ TYPED_TEST(ExponentialDistributionTypedTest, SerializeTest) { if (sample < sample_min) sample_min = sample; } if (!std::is_same<TypeParam, long double>::value) { - ABSL_INTERNAL_LOG(INFO, - absl::StrFormat("Range {%f}: %f, %f, lambda=%f", lambda, - sample_min, sample_max, lambda)); + LOG(INFO) << "Range {" << lambda << "}: " << sample_min << ", " + << sample_max << ", lambda=" << lambda; } std::stringstream ss; @@ -219,17 +218,16 @@ bool ExponentialDistributionTests::SingleZTest(const double p, const bool pass = absl::random_internal::Near("z", z, 0.0, max_err); if (!pass) { - ABSL_INTERNAL_LOG( - INFO, absl::StrFormat("p=%f max_err=%f\n" - " lambda=%f\n" - " mean=%f vs. %f\n" - " stddev=%f vs. %f\n" - " skewness=%f vs. %f\n" - " kurtosis=%f vs. %f\n" - " z=%f vs. 0", - p, max_err, lambda(), m.mean, mean(), - std::sqrt(m.variance), stddev(), m.skewness, - skew(), m.kurtosis, kurtosis(), z)); + // clang-format off + LOG(INFO) + << "p=" << p << " max_err=" << max_err << "\n" + " lambda=" << lambda() << "\n" + " mean=" << m.mean << " vs. " << mean() << "\n" + " stddev=" << std::sqrt(m.variance) << " vs. " << stddev() << "\n" + " skewness=" << m.skewness << " vs. " << skew() << "\n" + " kurtosis=" << m.kurtosis << " vs. " << kurtosis() << "\n" + " z=" << z << " vs. 0"; + // clang-format on } return pass; } @@ -274,16 +272,16 @@ double ExponentialDistributionTests::SingleChiSquaredTest() { double p = absl::random_internal::ChiSquarePValue(chi_square, dof); if (chi_square > threshold) { - for (int i = 0; i < cutoffs.size(); i++) { - ABSL_INTERNAL_LOG( - INFO, absl::StrFormat("%d : (%f) = %d", i, cutoffs[i], counts[i])); + for (size_t i = 0; i < cutoffs.size(); i++) { + LOG(INFO) << i << " : (" << cutoffs[i] << ") = " << counts[i]; } - ABSL_INTERNAL_LOG(INFO, - absl::StrCat("lambda ", lambda(), "\n", // - " expected ", expected, "\n", // - kChiSquared, " ", chi_square, " (", p, ")\n", - kChiSquared, " @ 0.98 = ", threshold)); + // clang-format off + LOG(INFO) << "lambda " << lambda() << "\n" + " expected " << expected << "\n" + << kChiSquared << " " << chi_square << " (" << p << ")\n" + << kChiSquared << " @ 0.98 = " << threshold; + // clang-format on } return p; } diff --git a/absl/random/gaussian_distribution_test.cc b/absl/random/gaussian_distribution_test.cc index 4584ac92..bad3476f 100644 --- a/absl/random/gaussian_distribution_test.cc +++ b/absl/random/gaussian_distribution_test.cc @@ -26,8 +26,8 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "absl/base/internal/raw_logging.h" #include "absl/base/macros.h" +#include "absl/log/log.h" #include "absl/numeric/internal/representation.h" #include "absl/random/internal/chi_square.h" #include "absl/random/internal/distribution_test_util.h" @@ -116,9 +116,8 @@ TYPED_TEST(GaussianDistributionInterfaceTest, SerializeTest) { EXPECT_LE(sample, before.max()) << before; } if (!std::is_same<TypeParam, long double>::value) { - ABSL_INTERNAL_LOG( - INFO, absl::StrFormat("Range{%f, %f}: %f, %f", mean, stddev, - sample_min, sample_max)); + LOG(INFO) << "Range{" << mean << ", " << stddev << "}: " << sample_min + << ", " << sample_max; } std::stringstream ss; @@ -240,17 +239,16 @@ bool GaussianDistributionTests::SingleZTest(const double p, (std::pow(m.skewness, 2.0) + std::pow(m.kurtosis - 3.0, 2.0) / 4.0); if (!pass || jb > 9.21) { - ABSL_INTERNAL_LOG( - INFO, absl::StrFormat("p=%f max_err=%f\n" - " mean=%f vs. %f\n" - " stddev=%f vs. %f\n" - " skewness=%f vs. %f\n" - " kurtosis=%f vs. %f\n" - " z=%f vs. 0\n" - " jb=%f vs. 9.21", - p, max_err, m.mean, mean(), std::sqrt(m.variance), - stddev(), m.skewness, skew(), m.kurtosis, - kurtosis(), z, jb)); + // clang-format off + LOG(INFO) + << "p=" << p << " max_err=" << max_err << "\n" + " mean=" << m.mean << " vs. " << mean() << "\n" + " stddev=" << std::sqrt(m.variance) << " vs. " << stddev() << "\n" + " skewness=" << m.skewness << " vs. " << skew() << "\n" + " kurtosis=" << m.kurtosis << " vs. " << kurtosis() << "\n" + " z=" << z << " vs. 0\n" + " jb=" << jb << " vs. 9.21"; + // clang-format on } return pass; } @@ -297,16 +295,16 @@ double GaussianDistributionTests::SingleChiSquaredTest() { // Log if the chi_square value is above the threshold. if (chi_square > threshold) { - for (int i = 0; i < cutoffs.size(); i++) { - ABSL_INTERNAL_LOG( - INFO, absl::StrFormat("%d : (%f) = %d", i, cutoffs[i], counts[i])); + for (size_t i = 0; i < cutoffs.size(); i++) { + LOG(INFO) << i << " : (" << cutoffs[i] << ") = " << counts[i]; } - ABSL_INTERNAL_LOG( - INFO, absl::StrCat("mean=", mean(), " stddev=", stddev(), "\n", // - " expected ", expected, "\n", // - kChiSquared, " ", chi_square, " (", p, ")\n", // - kChiSquared, " @ 0.98 = ", threshold)); + // clang-format off + LOG(INFO) << "mean=" << mean() << " stddev=" << stddev() << "\n" + " expected " << expected << "\n" + << kChiSquared << " " << chi_square << " (" << p << ")\n" + << kChiSquared << " @ 0.98 = " << threshold; + // clang-format on } return p; } diff --git a/absl/random/generators_test.cc b/absl/random/generators_test.cc index 14fd24e9..20091309 100644 --- a/absl/random/generators_test.cc +++ b/absl/random/generators_test.cc @@ -49,7 +49,7 @@ void TestUniform(URBG* gen) { // (a, b) semantics, inferred types. absl::Uniform(absl::IntervalOpenOpen, *gen, 0, 1.0); // Promoted to double - // Explict overriding of types. + // Explicit overriding of types. absl::Uniform<int>(*gen, 0, 100); absl::Uniform<int8_t>(*gen, 0, 100); absl::Uniform<int16_t>(*gen, 0, 100); @@ -117,6 +117,7 @@ void TestBernoulli(URBG* gen) { absl::Bernoulli(*gen, 0.5); } + template <typename URBG> void TestZipf(URBG* gen) { absl::Zipf<int>(*gen, 100); diff --git a/absl/random/internal/BUILD.bazel b/absl/random/internal/BUILD.bazel index 81ca669b..71a742ee 100644 --- a/absl/random/internal/BUILD.bazel +++ b/absl/random/internal/BUILD.bazel @@ -28,7 +28,14 @@ default_package_visibility = [ "//absl/random:__pkg__", ] -package(default_visibility = default_package_visibility) +package( + default_visibility = default_package_visibility, + features = [ + "header_modules", + "layering_check", + "parse_headers", + ], +) licenses(["notice"]) @@ -82,6 +89,10 @@ cc_library( linkopts = ABSL_DEFAULT_LINKOPTS + select({ "//absl:msvc_compiler": ["-DEFAULTLIB:bcrypt.lib"], "//absl:clang-cl_compiler": ["-DEFAULTLIB:bcrypt.lib"], + "//absl:mingw_compiler": [ + "-DEFAULTLIB:bcrypt.lib", + "-lbcrypt", + ], "//conditions:default": [], }), deps = [ @@ -405,6 +416,7 @@ cc_test( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":traits", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -421,6 +433,7 @@ cc_test( ":generate_real", "//absl/flags:flag", "//absl/numeric:bits", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -433,6 +446,7 @@ cc_test( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":distribution_test_util", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -445,6 +459,7 @@ cc_test( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":fastmath", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -458,6 +473,7 @@ cc_test( deps = [ ":explicit_seed_seq", "//absl/random:seed_sequences", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -470,6 +486,7 @@ cc_test( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":salted_seed_seq", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -485,6 +502,7 @@ cc_test( deps = [ ":distribution_test_util", "//absl/base:core_headers", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -499,6 +517,7 @@ cc_test( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":fast_uniform_bits", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -539,6 +558,7 @@ cc_test( "//absl/random:distributions", "//absl/random:seed_sequences", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -551,6 +571,7 @@ cc_test( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":seed_material", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -567,6 +588,7 @@ cc_test( ":pool_urbg", "//absl/meta:type_traits", "//absl/types:span", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -582,6 +604,7 @@ cc_test( ":explicit_seed_seq", ":pcg_engine", "//absl/time", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -597,9 +620,10 @@ cc_test( deps = [ ":explicit_seed_seq", ":randen_engine", - "//absl/base:raw_logging_internal", + "//absl/log", "//absl/strings", "//absl/time", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -613,6 +637,7 @@ cc_test( deps = [ ":randen", "//absl/meta:type_traits", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -627,6 +652,7 @@ cc_test( ":platform", ":randen_slow", "//absl/base:endian", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -642,7 +668,7 @@ cc_test( ":platform", ":randen_hwaes", ":randen_hwaes_impl", # build_cleaner: keep - "//absl/base:raw_logging_internal", + "//absl/log", "//absl/strings:str_format", "@com_google_googletest//:gtest", ], @@ -658,6 +684,7 @@ cc_test( ":wide_multiply", "//absl/numeric:bits", "//absl/numeric:int128", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -703,8 +730,10 @@ cc_test( ], deps = [ ":nanobenchmark", - "//absl/base:raw_logging_internal", + "//absl/log", + "//absl/log:check", "//absl/strings", + "//absl/strings:str_format", ], ) @@ -736,6 +765,7 @@ cc_test( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":iostream_state_saver", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -748,6 +778,7 @@ cc_test( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":uniform_helper", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/random/internal/distribution_test_util.cc b/absl/random/internal/distribution_test_util.cc index e9005658..9fa37bd6 100644 --- a/absl/random/internal/distribution_test_util.cc +++ b/absl/random/internal/distribution_test_util.cc @@ -213,7 +213,7 @@ double BetaIncompleteImpl(const double x, const double p, const double q, double result = 1.; int ns = static_cast<int>(q + xc * psq); - // Use the soper reduction forumla. + // Use the soper reduction formula. double rx = (ns == 0) ? x : x / xc; double temp = q - ai; for (;;) { @@ -236,7 +236,7 @@ double BetaIncompleteImpl(const double x, const double p, const double q, } } - // NOTE: See also TOMS Alogrithm 708. + // NOTE: See also TOMS Algorithm 708. // http://www.netlib.org/toms/index.html // // NOTE: The NWSC library also includes BRATIO / ISUBX (p87) @@ -247,7 +247,7 @@ double BetaIncompleteImpl(const double x, const double p, const double q, // https://www.jstor.org/stable/2346798?read-now=1&seq=4#page_scan_tab_contents // https://www.jstor.org/stable/2346887?seq=1#page_scan_tab_contents // -// XINBTA(p, q, beta, alhpa) +// XINBTA(p, q, beta, alpha) // p: the value of the parameter p. // q: the value of the parameter q. // beta: the value of ln B(p, q) diff --git a/absl/random/internal/fast_uniform_bits.h b/absl/random/internal/fast_uniform_bits.h index 8d8ed045..83ee5c0f 100644 --- a/absl/random/internal/fast_uniform_bits.h +++ b/absl/random/internal/fast_uniform_bits.h @@ -57,9 +57,10 @@ constexpr UIntType IntegerLog2(UIntType n) { // `PowerOfTwoVariate(urbg)`. template <typename URBG> constexpr size_t NumBits() { - return RangeSize<URBG>() == 0 - ? std::numeric_limits<typename URBG::result_type>::digits - : IntegerLog2(RangeSize<URBG>()); + return static_cast<size_t>( + RangeSize<URBG>() == 0 + ? std::numeric_limits<typename URBG::result_type>::digits + : IntegerLog2(RangeSize<URBG>())); } // Given a shift value `n`, constructs a mask with exactly the low `n` bits set. diff --git a/absl/random/internal/fast_uniform_bits_test.cc b/absl/random/internal/fast_uniform_bits_test.cc index cee702df..34c25206 100644 --- a/absl/random/internal/fast_uniform_bits_test.cc +++ b/absl/random/internal/fast_uniform_bits_test.cc @@ -167,7 +167,7 @@ TEST(FastUniformBitsTest, RangeSize) { FakeUrbg<uint64_t, 0, (std::numeric_limits<uint64_t>::max)()>>())); } -// The constants need to be choosen so that an infinite rejection loop doesn't +// The constants need to be chosen so that an infinite rejection loop doesn't // happen... using Urng1_5bit = FakeUrbg<uint8_t, 0, 2, 0>; // ~1.5 bits (range 3) using Urng4bits = FakeUrbg<uint8_t, 1, 0x10, 2>; diff --git a/absl/random/internal/generate_real.h b/absl/random/internal/generate_real.h index b569450c..9a6f4005 100644 --- a/absl/random/internal/generate_real.h +++ b/absl/random/internal/generate_real.h @@ -78,7 +78,7 @@ inline RealType GenerateRealFromBits(uint64_t bits, int exp_bias = 0) { "GenerateRealFromBits must be parameterized by either float or double."); static_assert(sizeof(uint_type) == sizeof(real_type), - "Mismatched unsinged and real types."); + "Mismatched unsigned and real types."); static_assert((std::numeric_limits<real_type>::is_iec559 && std::numeric_limits<real_type>::radix == 2), diff --git a/absl/random/internal/iostream_state_saver_test.cc b/absl/random/internal/iostream_state_saver_test.cc index 6e66266c..ea9d2af0 100644 --- a/absl/random/internal/iostream_state_saver_test.cc +++ b/absl/random/internal/iostream_state_saver_test.cc @@ -345,8 +345,9 @@ TEST(IOStreamStateSaver, RoundTripLongDoubles) { } // Avoid undefined behavior (overflow/underflow). - if (dd <= std::numeric_limits<int64_t>::max() && - dd >= std::numeric_limits<int64_t>::lowest()) { + if (dd <= static_cast<long double>(std::numeric_limits<int64_t>::max()) && + dd >= + static_cast<long double>(std::numeric_limits<int64_t>::lowest())) { int64_t x = static_cast<int64_t>(dd); EXPECT_EQ(x, StreamRoundTrip<int64_t>(x)); } diff --git a/absl/random/internal/mock_helpers.h b/absl/random/internal/mock_helpers.h index 882b0518..a7a97bfc 100644 --- a/absl/random/internal/mock_helpers.h +++ b/absl/random/internal/mock_helpers.h @@ -101,7 +101,7 @@ class MockHelpers { template <typename KeyT, typename URBG, typename... Args> static auto MaybeInvokeMock(URBG* urbg, Args&&... args) -> absl::optional<typename KeySignature<KeyT>::result_type> { - // Use function overloading to dispatch to the implemenation since + // Use function overloading to dispatch to the implementation since // more modern patterns (e.g. require + constexpr) are not supported in all // compiler configurations. return InvokeMockImpl<KeyT, typename KeySignature<KeyT>::result_type, diff --git a/absl/random/internal/nanobenchmark.cc b/absl/random/internal/nanobenchmark.cc index c9181813..0f31a7d5 100644 --- a/absl/random/internal/nanobenchmark.cc +++ b/absl/random/internal/nanobenchmark.cc @@ -361,7 +361,7 @@ void CountingSort(T* values, size_t num_values) { // Write that many copies of each unique value to the array. T* ABSL_RANDOM_INTERNAL_RESTRICT p = values; for (const auto& value_count : unique) { - std::fill(p, p + value_count.second, value_count.first); + std::fill_n(p, value_count.second, value_count.first); p += value_count.second; } ABSL_RAW_CHECK(p == values + num_values, "Did not produce enough output"); diff --git a/absl/random/internal/nanobenchmark_test.cc b/absl/random/internal/nanobenchmark_test.cc index f1571e26..d4f1028d 100644 --- a/absl/random/internal/nanobenchmark_test.cc +++ b/absl/random/internal/nanobenchmark_test.cc @@ -14,8 +14,10 @@ #include "absl/random/internal/nanobenchmark.h" -#include "absl/base/internal/raw_logging.h" +#include "absl/log/check.h" +#include "absl/log/log.h" #include "absl/strings/numbers.h" +#include "absl/strings/str_format.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -36,16 +38,16 @@ void MeasureDiv(const FuncInput (&inputs)[N]) { params.max_evals = 6; // avoid test timeout const size_t num_results = Measure(&Div, nullptr, inputs, N, results, params); if (num_results == 0) { - ABSL_RAW_LOG( - WARNING, - "WARNING: Measurement failed, should not happen when using " - "PinThreadToCPU unless the region to measure takes > 1 second.\n"); + LOG(WARNING) + << "WARNING: Measurement failed, should not happen when using " + "PinThreadToCPU unless the region to measure takes > 1 second."; return; } for (size_t i = 0; i < num_results; ++i) { - ABSL_RAW_LOG(INFO, "%5zu: %6.2f ticks; MAD=%4.2f%%\n", results[i].input, - results[i].ticks, results[i].variability * 100.0); - ABSL_RAW_CHECK(results[i].ticks != 0.0f, "Zero duration"); + LOG(INFO) << absl::StreamFormat("%5u: %6.2f ticks; MAD=%4.2f%%\n", + results[i].input, results[i].ticks, + results[i].variability * 100.0); + CHECK_NE(results[i].ticks, 0.0f) << "Zero duration"; } } @@ -54,7 +56,7 @@ void RunAll(const int argc, char* argv[]) { int cpu = -1; if (argc == 2) { if (!absl::SimpleAtoi(argv[1], &cpu)) { - ABSL_RAW_LOG(FATAL, "The optional argument must be a CPU number >= 0.\n"); + LOG(FATAL) << "The optional argument must be a CPU number >= 0."; } } PinThreadToCPU(cpu); diff --git a/absl/random/internal/platform.h b/absl/random/internal/platform.h index bbdb4e62..d779f481 100644 --- a/absl/random/internal/platform.h +++ b/absl/random/internal/platform.h @@ -131,7 +131,7 @@ // ABSL_RANDOM_INTERNAL_AES_DISPATCH indicates whether the currently active // platform has, or should use run-time dispatch for selecting the -// acclerated Randen implementation. +// accelerated Randen implementation. #define ABSL_RANDOM_INTERNAL_AES_DISPATCH 0 #if defined(ABSL_ARCH_X86_64) diff --git a/absl/random/internal/randen_benchmarks.cc b/absl/random/internal/randen_benchmarks.cc index f589172c..ec086cea 100644 --- a/absl/random/internal/randen_benchmarks.cc +++ b/absl/random/internal/randen_benchmarks.cc @@ -47,8 +47,10 @@ static constexpr size_t kSeedSizeT = Randen::kSeedBytes / sizeof(uint32_t); // Randen implementation benchmarks. template <typename T> struct AbsorbFn : public T { - mutable uint64_t state[kStateSizeT] = {}; - mutable uint32_t seed[kSeedSizeT] = {}; + // These are both cast to uint128* in the RandenHwAes implementation, so + // ensure they are 16 byte aligned. + alignas(16) mutable uint64_t state[kStateSizeT] = {}; + alignas(16) mutable uint32_t seed[kSeedSizeT] = {}; static constexpr size_t bytes() { return sizeof(seed); } diff --git a/absl/random/internal/randen_detect.cc b/absl/random/internal/randen_detect.cc index 6dababa3..bdeab877 100644 --- a/absl/random/internal/randen_detect.cc +++ b/absl/random/internal/randen_detect.cc @@ -45,6 +45,10 @@ #if defined(ABSL_INTERNAL_USE_X86_CPUID) #if defined(_WIN32) || defined(_WIN64) #include <intrin.h> // NOLINT(build/include_order) +#elif ABSL_HAVE_BUILTIN(__cpuid) +// MSVC-equivalent __cpuid intrinsic declaration for clang-like compilers +// for non-Windows build environments. +extern void __cpuid(int[4], int); #else // MSVC-equivalent __cpuid intrinsic function. static void __cpuid(int cpu_info[4], int info_type) { diff --git a/absl/random/internal/randen_engine.h b/absl/random/internal/randen_engine.h index b4708664..fe2d9f6c 100644 --- a/absl/random/internal/randen_engine.h +++ b/absl/random/internal/randen_engine.h @@ -142,7 +142,7 @@ class alignas(8) randen_engine { // The Randen paper suggests preferentially initializing even-numbered // 128-bit vectors of the randen state (there are 16 such vectors). // The seed data is merged into the state offset by 128-bits, which - // implies prefering seed bytes [16..31, ..., 208..223]. Since the + // implies preferring seed bytes [16..31, ..., 208..223]. Since the // buffer is 32-bit values, we swap the corresponding buffer positions in // 128-bit chunks. size_t dst = kBufferSize; diff --git a/absl/random/internal/randen_engine_test.cc b/absl/random/internal/randen_engine_test.cc index c8e7685b..a94f4916 100644 --- a/absl/random/internal/randen_engine_test.cc +++ b/absl/random/internal/randen_engine_test.cc @@ -21,7 +21,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "absl/base/internal/raw_logging.h" +#include "absl/log/log.h" #include "absl/random/internal/explicit_seed_seq.h" #include "absl/strings/str_cat.h" #include "absl/time/clock.h" @@ -645,9 +645,8 @@ TEST(RandenTest, IsFastOrSlow) { } auto duration = absl::GetCurrentTimeNanos() - start; - ABSL_INTERNAL_LOG(INFO, absl::StrCat(static_cast<double>(duration) / - static_cast<double>(kCount), - "ns")); + LOG(INFO) << static_cast<double>(duration) / static_cast<double>(kCount) + << "ns"; EXPECT_GT(sum, 0); EXPECT_GE(duration, kCount); // Should be slower than 1ns per call. diff --git a/absl/random/internal/randen_hwaes.cc b/absl/random/internal/randen_hwaes.cc index fee6677c..f535f4c5 100644 --- a/absl/random/internal/randen_hwaes.cc +++ b/absl/random/internal/randen_hwaes.cc @@ -31,7 +31,7 @@ // a hardware accelerated implementation of randen, or whether it // will contain stubs that exit the process. #if ABSL_HAVE_ACCELERATED_AES -// The following plaforms have implemented RandenHwAes. +// The following platforms have implemented RandenHwAes. #if defined(ABSL_ARCH_X86_64) || defined(ABSL_ARCH_X86_32) || \ defined(ABSL_ARCH_PPC) || defined(ABSL_ARCH_ARM) || \ defined(ABSL_ARCH_AARCH64) diff --git a/absl/random/internal/randen_hwaes_test.cc b/absl/random/internal/randen_hwaes_test.cc index 2348b55c..00d96efd 100644 --- a/absl/random/internal/randen_hwaes_test.cc +++ b/absl/random/internal/randen_hwaes_test.cc @@ -16,7 +16,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "absl/base/internal/raw_logging.h" +#include "absl/log/log.h" #include "absl/random/internal/platform.h" #include "absl/random/internal/randen_detect.h" #include "absl/random/internal/randen_traits.h" @@ -67,32 +67,32 @@ TEST(RandenHwAesTest, Default) { int main(int argc, char* argv[]) { testing::InitGoogleTest(&argc, argv); - ABSL_RAW_LOG(INFO, "ABSL_HAVE_ACCELERATED_AES=%d", ABSL_HAVE_ACCELERATED_AES); - ABSL_RAW_LOG(INFO, "ABSL_RANDOM_INTERNAL_AES_DISPATCH=%d", - ABSL_RANDOM_INTERNAL_AES_DISPATCH); + LOG(INFO) << "ABSL_HAVE_ACCELERATED_AES=" << ABSL_HAVE_ACCELERATED_AES; + LOG(INFO) << "ABSL_RANDOM_INTERNAL_AES_DISPATCH=" + << ABSL_RANDOM_INTERNAL_AES_DISPATCH; #if defined(ABSL_ARCH_X86_64) - ABSL_RAW_LOG(INFO, "ABSL_ARCH_X86_64"); + LOG(INFO) << "ABSL_ARCH_X86_64"; #elif defined(ABSL_ARCH_X86_32) - ABSL_RAW_LOG(INFO, "ABSL_ARCH_X86_32"); + LOG(INFO) << "ABSL_ARCH_X86_32"; #elif defined(ABSL_ARCH_AARCH64) - ABSL_RAW_LOG(INFO, "ABSL_ARCH_AARCH64"); + LOG(INFO) << "ABSL_ARCH_AARCH64"; #elif defined(ABSL_ARCH_ARM) - ABSL_RAW_LOG(INFO, "ABSL_ARCH_ARM"); + LOG(INFO) << "ABSL_ARCH_ARM"; #elif defined(ABSL_ARCH_PPC) - ABSL_RAW_LOG(INFO, "ABSL_ARCH_PPC"); + LOG(INFO) << "ABSL_ARCH_PPC"; #else - ABSL_RAW_LOG(INFO, "ARCH Unknown"); + LOG(INFO) << "ARCH Unknown"; #endif int x = absl::random_internal::HasRandenHwAesImplementation(); - ABSL_RAW_LOG(INFO, "HasRandenHwAesImplementation = %d", x); + LOG(INFO) << "HasRandenHwAesImplementation = " << x; int y = absl::random_internal::CPUSupportsRandenHwAes(); - ABSL_RAW_LOG(INFO, "CPUSupportsRandenHwAes = %d", x); + LOG(INFO) << "CPUSupportsRandenHwAes = " << x; if (!x || !y) { - ABSL_RAW_LOG(INFO, "Skipping Randen HWAES tests."); + LOG(INFO) << "Skipping Randen HWAES tests."; return 0; } return RUN_ALL_TESTS(); diff --git a/absl/random/internal/uniform_helper.h b/absl/random/internal/uniform_helper.h index e68b82ee..db737e13 100644 --- a/absl/random/internal/uniform_helper.h +++ b/absl/random/internal/uniform_helper.h @@ -217,7 +217,7 @@ using UniformDistribution = // UniformDistributionWrapper is used as the underlying distribution type // by the absl::Uniform template function. It selects the proper Abseil // uniform distribution and provides constructor overloads that match the -// expected parameter order as well as adjusting distribtuion bounds based +// expected parameter order as well as adjusting distribution bounds based // on the tag. template <typename NumType> struct UniformDistributionWrapper : public UniformDistribution<NumType> { diff --git a/absl/random/log_uniform_int_distribution_test.cc b/absl/random/log_uniform_int_distribution_test.cc index 0d0fcb95..5df3edac 100644 --- a/absl/random/log_uniform_int_distribution_test.cc +++ b/absl/random/log_uniform_int_distribution_test.cc @@ -24,7 +24,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "absl/base/internal/raw_logging.h" +#include "absl/log/log.h" #include "absl/random/internal/chi_square.h" #include "absl/random/internal/distribution_test_util.h" #include "absl/random/internal/pcg_engine.h" @@ -108,8 +108,7 @@ TYPED_TEST(LogUniformIntDistributionTypeTest, SerializeTest) { if (sample > sample_max) sample_max = sample; if (sample < sample_min) sample_min = sample; } - ABSL_INTERNAL_LOG(INFO, - absl::StrCat("Range: ", +sample_min, ", ", +sample_max)); + LOG(INFO) << "Range: " << sample_min << ", " << sample_max; } } @@ -182,16 +181,14 @@ double LogUniformIntChiSquaredTest::ChiSquaredTestImpl() { const double p = absl::random_internal::ChiSquarePValue(chi_square, dof); if (chi_square > threshold) { - ABSL_INTERNAL_LOG(INFO, "values"); + LOG(INFO) << "values"; for (size_t i = 0; i < buckets.size(); i++) { - ABSL_INTERNAL_LOG(INFO, absl::StrCat(i, ": ", buckets[i])); + LOG(INFO) << i << ": " << buckets[i]; } - ABSL_INTERNAL_LOG(INFO, - absl::StrFormat("trials=%d\n" - "%s(data, %d) = %f (%f)\n" - "%s @ 0.98 = %f", - trials, kChiSquared, dof, chi_square, p, - kChiSquared, threshold)); + LOG(INFO) << "trials=" << trials << "\n" + << kChiSquared << "(data, " << dof << ") = " << chi_square << " (" + << p << ")\n" + << kChiSquared << " @ 0.98 = " << threshold; } return p; } diff --git a/absl/random/poisson_distribution_test.cc b/absl/random/poisson_distribution_test.cc index 4f585b9b..54755960 100644 --- a/absl/random/poisson_distribution_test.cc +++ b/absl/random/poisson_distribution_test.cc @@ -25,9 +25,9 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "absl/base/internal/raw_logging.h" #include "absl/base/macros.h" #include "absl/container/flat_hash_map.h" +#include "absl/log/log.h" #include "absl/random/internal/chi_square.h" #include "absl/random/internal/distribution_test_util.h" #include "absl/random/internal/pcg_engine.h" @@ -134,8 +134,8 @@ TYPED_TEST(PoissonDistributionInterfaceTest, SerializeTest) { if (sample < sample_min) sample_min = sample; } - ABSL_INTERNAL_LOG(INFO, absl::StrCat("Range {", param.mean(), "}: ", - +sample_min, ", ", +sample_max)); + LOG(INFO) << "Range {" << param.mean() << "}: " << sample_min << ", " + << sample_max; // Validate stream serialization. std::stringstream ss; @@ -188,10 +188,9 @@ class PoissonModel { } void LogCDF() { - ABSL_INTERNAL_LOG(INFO, absl::StrCat("CDF (mean = ", mean_, ")")); + LOG(INFO) << "CDF (mean = " << mean_ << ")"; for (const auto c : cdf_) { - ABSL_INTERNAL_LOG(INFO, - absl::StrCat(c.index, ": pmf=", c.pmf, " cdf=", c.cdf)); + LOG(INFO) << c.index << ": pmf=" << c.pmf << " cdf=" << c.cdf; } } @@ -286,16 +285,15 @@ bool PoissonDistributionZTest::SingleZTest(const double p, const bool pass = absl::random_internal::Near("z", z, 0.0, max_err); if (!pass) { - ABSL_INTERNAL_LOG( - INFO, absl::StrFormat("p=%f max_err=%f\n" - " mean=%f vs. %f\n" - " stddev=%f vs. %f\n" - " skewness=%f vs. %f\n" - " kurtosis=%f vs. %f\n" - " z=%f", - p, max_err, m.mean, mean(), std::sqrt(m.variance), - stddev(), m.skewness, skew(), m.kurtosis, - kurtosis(), z)); + // clang-format off + LOG(INFO) + << "p=" << p << " max_err=" << max_err << "\n" + " mean=" << m.mean << " vs. " << mean() << "\n" + " stddev=" << std::sqrt(m.variance) << " vs. " << stddev() << "\n" + " skewness=" << m.skewness << " vs. " << skew() << "\n" + " kurtosis=" << m.kurtosis << " vs. " << kurtosis() << "\n" + " z=" << z; + // clang-format on } return pass; } @@ -439,17 +437,16 @@ double PoissonDistributionChiSquaredTest::ChiSquaredTestImpl() { if (chi_square > threshold) { LogCDF(); - ABSL_INTERNAL_LOG(INFO, absl::StrCat("VALUES buckets=", counts.size(), - " samples=", kSamples)); + LOG(INFO) << "VALUES buckets=" << counts.size() + << " samples=" << kSamples; for (size_t i = 0; i < counts.size(); i++) { - ABSL_INTERNAL_LOG( - INFO, absl::StrCat(cutoffs_[i], ": ", counts[i], " vs. E=", e[i])); + LOG(INFO) << cutoffs_[i] << ": " << counts[i] << " vs. E=" << e[i]; } - ABSL_INTERNAL_LOG( - INFO, - absl::StrCat(kChiSquared, "(data, dof=", dof, ") = ", chi_square, " (", - p, ")\n", " vs.\n", kChiSquared, " @ 0.98 = ", threshold)); + LOG(INFO) << kChiSquared << "(data, dof=" << dof << ") = " << chi_square + << " (" << p << ")\n" + << " vs.\n" + << kChiSquared << " @ 0.98 = " << threshold; } return p; } diff --git a/absl/random/uniform_int_distribution_test.cc b/absl/random/uniform_int_distribution_test.cc index a830117a..b40d6185 100644 --- a/absl/random/uniform_int_distribution_test.cc +++ b/absl/random/uniform_int_distribution_test.cc @@ -24,7 +24,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "absl/base/internal/raw_logging.h" +#include "absl/log/log.h" #include "absl/random/internal/chi_square.h" #include "absl/random/internal/distribution_test_util.h" #include "absl/random/internal/pcg_engine.h" @@ -107,8 +107,7 @@ TYPED_TEST(UniformIntDistributionTest, ParamSerializeTest) { sample_min = sample; } } - std::string msg = absl::StrCat("Range: ", +sample_min, ", ", +sample_max); - ABSL_RAW_LOG(INFO, "%s", msg.c_str()); + LOG(INFO) << "Range: " << sample_min << ", " << sample_max; } } @@ -210,7 +209,7 @@ TYPED_TEST(UniformIntDistributionTest, ChiSquaredTest50) { absl::StrAppend(&msg, kChiSquared, " p-value ", p_value, "\n"); absl::StrAppend(&msg, "High ", kChiSquared, " value: ", chi_square, " > ", kThreshold); - ABSL_RAW_LOG(INFO, "%s", msg.c_str()); + LOG(INFO) << msg; FAIL() << msg; } } diff --git a/absl/random/uniform_real_distribution_test.cc b/absl/random/uniform_real_distribution_test.cc index 07f199d3..260aac96 100644 --- a/absl/random/uniform_real_distribution_test.cc +++ b/absl/random/uniform_real_distribution_test.cc @@ -26,7 +26,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "absl/base/internal/raw_logging.h" +#include "absl/log/log.h" #include "absl/numeric/internal/representation.h" #include "absl/random/internal/chi_square.h" #include "absl/random/internal/distribution_test_util.h" @@ -182,9 +182,8 @@ TYPED_TEST(UniformRealDistributionTest, ParamSerializeTest) { if (!std::is_same<real_type, long double>::value) { // static_cast<double>(long double) can overflow. - std::string msg = absl::StrCat("Range: ", static_cast<double>(sample_min), - ", ", static_cast<double>(sample_max)); - ABSL_RAW_LOG(INFO, "%s", msg.c_str()); + LOG(INFO) << "Range: " << static_cast<double>(sample_min) << ", " + << static_cast<double>(sample_max); } } } @@ -324,7 +323,7 @@ TYPED_TEST(UniformRealDistributionTest, ChiSquaredTest50) { absl::StrAppend(&msg, kChiSquared, " p-value ", p_value, "\n"); absl::StrAppend(&msg, "High ", kChiSquared, " value: ", chi_square, " > ", kThreshold); - ABSL_RAW_LOG(INFO, "%s", msg.c_str()); + LOG(INFO) << msg; FAIL() << msg; } } diff --git a/absl/random/zipf_distribution_test.cc b/absl/random/zipf_distribution_test.cc index c8bb89db..801ec4f6 100644 --- a/absl/random/zipf_distribution_test.cc +++ b/absl/random/zipf_distribution_test.cc @@ -25,7 +25,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "absl/base/internal/raw_logging.h" +#include "absl/log/log.h" #include "absl/random/internal/chi_square.h" #include "absl/random/internal/pcg_engine.h" #include "absl/random/internal/sequence_urbg.h" @@ -102,8 +102,7 @@ TYPED_TEST(ZipfDistributionTypedTest, SerializeTest) { if (sample > sample_max) sample_max = sample; if (sample < sample_min) sample_min = sample; } - ABSL_INTERNAL_LOG(INFO, - absl::StrCat("Range: ", +sample_min, ", ", +sample_max)); + LOG(INFO) << "Range: " << sample_min << ", " << sample_max; } } @@ -303,18 +302,15 @@ TEST_P(ZipfTest, ChiSquaredTest) { // Log if the chi_squared value is above the threshold. if (chi_square > threshold) { - ABSL_INTERNAL_LOG(INFO, "values"); + LOG(INFO) << "values"; for (size_t i = 0; i < expected.size(); i++) { - ABSL_INTERNAL_LOG(INFO, absl::StrCat(points[i], ": ", buckets[i], - " vs. E=", expected[i])); + LOG(INFO) << points[i] << ": " << buckets[i] << " vs. E=" << expected[i]; } - ABSL_INTERNAL_LOG(INFO, absl::StrCat("trials ", trials)); - ABSL_INTERNAL_LOG(INFO, - absl::StrCat("mean ", avg, " vs. expected ", mean())); - ABSL_INTERNAL_LOG(INFO, absl::StrCat(kChiSquared, "(data, ", dof, ") = ", - chi_square, " (", p_actual, ")")); - ABSL_INTERNAL_LOG(INFO, - absl::StrCat(kChiSquared, " @ 0.9995 = ", threshold)); + LOG(INFO) << "trials " << trials; + LOG(INFO) << "mean " << avg << " vs. expected " << mean(); + LOG(INFO) << kChiSquared << "(data, " << dof << ") = " << chi_square << " (" + << p_actual << ")"; + LOG(INFO) << kChiSquared << " @ 0.9995 = " << threshold; FAIL() << kChiSquared << " value of " << chi_square << " is above the threshold."; } diff --git a/absl/status/BUILD.bazel b/absl/status/BUILD.bazel index 1f58b307..981b37fd 100644 --- a/absl/status/BUILD.bazel +++ b/absl/status/BUILD.bazel @@ -24,13 +24,21 @@ load( "ABSL_TEST_COPTS", ) -package(default_visibility = ["//visibility:public"]) +package( + default_visibility = ["//visibility:public"], + features = [ + "header_modules", + "layering_check", + "parse_headers", + ], +) licenses(["notice"]) cc_library( name = "status", srcs = [ + "internal/status_internal.cc", "internal/status_internal.h", "status.cc", "status_payload_printer.cc", @@ -43,17 +51,22 @@ cc_library( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ "//absl/base:atomic_hook", + "//absl/base:config", "//absl/base:core_headers", + "//absl/base:no_destructor", + "//absl/base:nullability", "//absl/base:raw_logging_internal", "//absl/base:strerror", "//absl/container:inlined_vector", "//absl/debugging:stacktrace", "//absl/debugging:symbolize", "//absl/functional:function_ref", + "//absl/memory", "//absl/strings", "//absl/strings:cord", "//absl/strings:str_format", "//absl/types:optional", + "//absl/types:span", ], ) @@ -65,6 +78,9 @@ cc_test( deps = [ ":status", "//absl/strings", + "//absl/strings:cord", + "//absl/strings:str_format", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -83,10 +99,14 @@ cc_library( deps = [ ":status", "//absl/base", + "//absl/base:config", "//absl/base:core_headers", + "//absl/base:nullability", "//absl/base:raw_logging_internal", "//absl/meta:type_traits", "//absl/strings", + "//absl/strings:has_ostream_operator", + "//absl/strings:str_format", "//absl/types:variant", "//absl/utility", ], @@ -103,7 +123,9 @@ cc_test( "//absl/memory", "//absl/strings", "//absl/types:any", + "//absl/types:variant", "//absl/utility", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/status/CMakeLists.txt b/absl/status/CMakeLists.txt index 15db36af..00415ab9 100644 --- a/absl/status/CMakeLists.txt +++ b/absl/status/CMakeLists.txt @@ -20,11 +20,14 @@ absl_cc_library( "status.h" SRCS "internal/status_internal.h" + "internal/status_internal.cc" "status.cc" "status_payload_printer.h" "status_payload_printer.cc" COPTS ${ABSL_DEFAULT_COPTS} + DEFINES + "$<$<PLATFORM_ID:AIX>:_LINUX_SOURCE_COMPAT>" DEPS absl::atomic_hook absl::config @@ -32,11 +35,15 @@ absl_cc_library( absl::core_headers absl::function_ref absl::inlined_vector + absl::memory + absl::no_destructor + absl::nullability absl::optional absl::raw_logging_internal + absl::span absl::stacktrace - absl::str_format absl::strerror + absl::str_format absl::strings absl::symbolize PUBLIC @@ -51,6 +58,7 @@ absl_cc_test( ${ABSL_TEST_COPTS} DEPS absl::status + absl::str_format absl::strings GTest::gmock_main ) @@ -67,11 +75,15 @@ absl_cc_library( ${ABSL_DEFAULT_COPTS} DEPS absl::base - absl::status + absl::config absl::core_headers + absl::has_ostream_operator + absl::nullability absl::raw_logging_internal - absl::type_traits + absl::status + absl::str_format absl::strings + absl::type_traits absl::utility absl::variant PUBLIC @@ -87,5 +99,6 @@ absl_cc_test( DEPS absl::status absl::statusor + absl::strings GTest::gmock_main ) diff --git a/absl/status/internal/status_internal.cc b/absl/status/internal/status_internal.cc new file mode 100644 index 00000000..a9156754 --- /dev/null +++ b/absl/status/internal/status_internal.cc @@ -0,0 +1,248 @@ +// Copyright 2023 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/status/internal/status_internal.h" + +#include <atomic> +#include <cassert> +#include <cstddef> +#include <cstdint> +#include <cstdio> +#include <cstring> +#include <memory> +#include <string> +#include <utility> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/macros.h" +#include "absl/base/nullability.h" +#include "absl/debugging/stacktrace.h" +#include "absl/debugging/symbolize.h" +#include "absl/memory/memory.h" +#include "absl/status/status.h" +#include "absl/status/status_payload_printer.h" +#include "absl/strings/cord.h" +#include "absl/strings/escaping.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_split.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace status_internal { + +void StatusRep::Unref() const { + // Fast path: if ref==1, there is no need for a RefCountDec (since + // this is the only reference and therefore no other thread is + // allowed to be mucking with r). + if (ref_.load(std::memory_order_acquire) == 1 || + ref_.fetch_sub(1, std::memory_order_acq_rel) - 1 == 0) { + delete this; + } +} + +static absl::optional<size_t> FindPayloadIndexByUrl( + const Payloads* payloads, absl::string_view type_url) { + if (payloads == nullptr) return absl::nullopt; + + for (size_t i = 0; i < payloads->size(); ++i) { + if ((*payloads)[i].type_url == type_url) return i; + } + + return absl::nullopt; +} + +absl::optional<absl::Cord> StatusRep::GetPayload( + absl::string_view type_url) const { + absl::optional<size_t> index = + status_internal::FindPayloadIndexByUrl(payloads_.get(), type_url); + if (index.has_value()) return (*payloads_)[index.value()].payload; + + return absl::nullopt; +} + +void StatusRep::SetPayload(absl::string_view type_url, absl::Cord payload) { + if (payloads_ == nullptr) { + payloads_ = absl::make_unique<status_internal::Payloads>(); + } + + absl::optional<size_t> index = + status_internal::FindPayloadIndexByUrl(payloads_.get(), type_url); + if (index.has_value()) { + (*payloads_)[index.value()].payload = std::move(payload); + return; + } + + payloads_->push_back({std::string(type_url), std::move(payload)}); +} + +StatusRep::EraseResult StatusRep::ErasePayload(absl::string_view type_url) { + absl::optional<size_t> index = + status_internal::FindPayloadIndexByUrl(payloads_.get(), type_url); + if (!index.has_value()) return {false, Status::PointerToRep(this)}; + payloads_->erase(payloads_->begin() + index.value()); + if (payloads_->empty() && message_.empty()) { + // Special case: If this can be represented inlined, it MUST be inlined + // (== depends on this behavior). + EraseResult result = {true, Status::CodeToInlinedRep(code_)}; + Unref(); + return result; + } + return {true, Status::PointerToRep(this)}; +} + +void StatusRep::ForEachPayload( + absl::FunctionRef<void(absl::string_view, const absl::Cord&)> visitor) + const { + if (auto* payloads = payloads_.get()) { + bool in_reverse = + payloads->size() > 1 && reinterpret_cast<uintptr_t>(payloads) % 13 > 6; + + for (size_t index = 0; index < payloads->size(); ++index) { + const auto& elem = + (*payloads)[in_reverse ? payloads->size() - 1 - index : index]; + +#ifdef NDEBUG + visitor(elem.type_url, elem.payload); +#else + // In debug mode invalidate the type url to prevent users from relying on + // this string lifetime. + + // NOLINTNEXTLINE intentional extra conversion to force temporary. + visitor(std::string(elem.type_url), elem.payload); +#endif // NDEBUG + } + } +} + +std::string StatusRep::ToString(StatusToStringMode mode) const { + std::string text; + absl::StrAppend(&text, absl::StatusCodeToString(code()), ": ", message()); + + const bool with_payload = (mode & StatusToStringMode::kWithPayload) == + StatusToStringMode::kWithPayload; + + if (with_payload) { + status_internal::StatusPayloadPrinter printer = + status_internal::GetStatusPayloadPrinter(); + this->ForEachPayload([&](absl::string_view type_url, + const absl::Cord& payload) { + absl::optional<std::string> result; + if (printer) result = printer(type_url, payload); + absl::StrAppend( + &text, " [", type_url, "='", + result.has_value() ? *result : absl::CHexEscape(std::string(payload)), + "']"); + }); + } + + return text; +} + +bool StatusRep::operator==(const StatusRep& other) const { + assert(this != &other); + if (code_ != other.code_) return false; + if (message_ != other.message_) return false; + const status_internal::Payloads* this_payloads = payloads_.get(); + const status_internal::Payloads* other_payloads = other.payloads_.get(); + + const status_internal::Payloads no_payloads; + const status_internal::Payloads* larger_payloads = + this_payloads ? this_payloads : &no_payloads; + const status_internal::Payloads* smaller_payloads = + other_payloads ? other_payloads : &no_payloads; + if (larger_payloads->size() < smaller_payloads->size()) { + std::swap(larger_payloads, smaller_payloads); + } + if ((larger_payloads->size() - smaller_payloads->size()) > 1) return false; + // Payloads can be ordered differently, so we can't just compare payload + // vectors. + for (const auto& payload : *larger_payloads) { + + bool found = false; + for (const auto& other_payload : *smaller_payloads) { + if (payload.type_url == other_payload.type_url) { + if (payload.payload != other_payload.payload) { + return false; + } + found = true; + break; + } + } + if (!found) return false; + } + return true; +} + +absl::Nonnull<StatusRep*> StatusRep::CloneAndUnref() const { + // Optimization: no need to create a clone if we already have a refcount of 1. + if (ref_.load(std::memory_order_acquire) == 1) { + // All StatusRep instances are heap allocated and mutable, therefore this + // const_cast will never cast away const from a stack instance. + // + // CloneAndUnref is the only method that doesn't involve an external cast to + // get a mutable StatusRep* from the uintptr_t rep stored in Status. + return const_cast<StatusRep*>(this); + } + std::unique_ptr<status_internal::Payloads> payloads; + if (payloads_) { + payloads = absl::make_unique<status_internal::Payloads>(*payloads_); + } + auto* new_rep = new StatusRep(code_, message_, std::move(payloads)); + Unref(); + return new_rep; +} + +// Convert canonical code to a value known to this binary. +absl::StatusCode MapToLocalCode(int value) { + absl::StatusCode code = static_cast<absl::StatusCode>(value); + switch (code) { + case absl::StatusCode::kOk: + case absl::StatusCode::kCancelled: + case absl::StatusCode::kUnknown: + case absl::StatusCode::kInvalidArgument: + case absl::StatusCode::kDeadlineExceeded: + case absl::StatusCode::kNotFound: + case absl::StatusCode::kAlreadyExists: + case absl::StatusCode::kPermissionDenied: + case absl::StatusCode::kResourceExhausted: + case absl::StatusCode::kFailedPrecondition: + case absl::StatusCode::kAborted: + case absl::StatusCode::kOutOfRange: + case absl::StatusCode::kUnimplemented: + case absl::StatusCode::kInternal: + case absl::StatusCode::kUnavailable: + case absl::StatusCode::kDataLoss: + case absl::StatusCode::kUnauthenticated: + return code; + default: + return absl::StatusCode::kUnknown; + } +} + +absl::Nonnull<std::string*> MakeCheckFailString( + absl::Nonnull<const absl::Status*> status, + absl::Nonnull<const char*> prefix) { + return new std::string( + absl::StrCat(prefix, " (", + status->ToString(StatusToStringMode::kWithEverything), ")")); +} + +} // namespace status_internal + +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/status/internal/status_internal.h b/absl/status/internal/status_internal.h index 873eb5c2..c9f43832 100644 --- a/absl/status/internal/status_internal.h +++ b/absl/status/internal/status_internal.h @@ -14,13 +14,19 @@ #ifndef ABSL_STATUS_INTERNAL_STATUS_INTERNAL_H_ #define ABSL_STATUS_INTERNAL_STATUS_INTERNAL_H_ +#include <atomic> +#include <cstdint> #include <memory> #include <string> #include <utility> #include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/nullability.h" #include "absl/container/inlined_vector.h" #include "absl/strings/cord.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" #ifndef SWIG // Disabled for SWIG as it doesn't parse attributes correctly. @@ -32,9 +38,9 @@ ABSL_NAMESPACE_BEGIN // TODO(b/176172494): ABSL_MUST_USE_RESULT should expand to the more strict // [[nodiscard]]. For now, just use [[nodiscard]] directly when it is available. #if ABSL_HAVE_CPP_ATTRIBUTE(nodiscard) -class [[nodiscard]] Status; +class [[nodiscard]] ABSL_ATTRIBUTE_TRIVIAL_ABI Status; #else -class ABSL_MUST_USE_RESULT Status; +class ABSL_MUST_USE_RESULT ABSL_ATTRIBUTE_TRIVIAL_ABI Status; #endif ABSL_NAMESPACE_END } // namespace absl @@ -44,6 +50,7 @@ namespace absl { ABSL_NAMESPACE_BEGIN enum class StatusCode : int; +enum class StatusToStringMode : int; namespace status_internal { @@ -56,18 +63,54 @@ struct Payload { using Payloads = absl::InlinedVector<Payload, 1>; // Reference-counted representation of Status data. -struct StatusRep { +class StatusRep { + public: StatusRep(absl::StatusCode code_arg, absl::string_view message_arg, std::unique_ptr<status_internal::Payloads> payloads_arg) - : ref(int32_t{1}), - code(code_arg), - message(message_arg), - payloads(std::move(payloads_arg)) {} - - std::atomic<int32_t> ref; - absl::StatusCode code; - std::string message; - std::unique_ptr<status_internal::Payloads> payloads; + : ref_(int32_t{1}), + code_(code_arg), + message_(message_arg), + payloads_(std::move(payloads_arg)) {} + + absl::StatusCode code() const { return code_; } + const std::string& message() const { return message_; } + + // Ref and unref are const to allow access through a const pointer, and are + // used during copying operations. + void Ref() const { ref_.fetch_add(1, std::memory_order_relaxed); } + void Unref() const; + + // Payload methods correspond to the same methods in absl::Status. + absl::optional<absl::Cord> GetPayload(absl::string_view type_url) const; + void SetPayload(absl::string_view type_url, absl::Cord payload); + struct EraseResult { + bool erased; + uintptr_t new_rep; + }; + EraseResult ErasePayload(absl::string_view type_url); + void ForEachPayload( + absl::FunctionRef<void(absl::string_view, const absl::Cord&)> visitor) + const; + + std::string ToString(StatusToStringMode mode) const; + + bool operator==(const StatusRep& other) const; + bool operator!=(const StatusRep& other) const { return !(*this == other); } + + // Returns an equivalent heap allocated StatusRep with refcount 1. + // + // `this` is not safe to be used after calling as it may have been deleted. + absl::Nonnull<StatusRep*> CloneAndUnref() const; + + private: + mutable std::atomic<int32_t> ref_; + absl::StatusCode code_; + + // As an internal implementation detail, we guarantee that if status.message() + // is non-empty, then the resulting string_view is null terminated. + // This is required to implement 'StatusMessageAsCStr(...)' + std::string message_; + std::unique_ptr<status_internal::Payloads> payloads_; }; absl::StatusCode MapToLocalCode(int value); @@ -76,8 +119,10 @@ absl::StatusCode MapToLocalCode(int value); // suitable for output as an error message in assertion/`CHECK()` failures. // // This is an internal implementation detail for Abseil logging. -std::string* MakeCheckFailString(const absl::Status* status, - const char* prefix); +ABSL_ATTRIBUTE_PURE_FUNCTION +absl::Nonnull<std::string*> MakeCheckFailString( + absl::Nonnull<const absl::Status*> status, + absl::Nonnull<const char*> prefix); } // namespace status_internal diff --git a/absl/status/internal/statusor_internal.h b/absl/status/internal/statusor_internal.h index eaac2c0b..5be94903 100644 --- a/absl/status/internal/statusor_internal.h +++ b/absl/status/internal/statusor_internal.h @@ -14,12 +14,15 @@ #ifndef ABSL_STATUS_INTERNAL_STATUSOR_INTERNAL_H_ #define ABSL_STATUS_INTERNAL_STATUSOR_INTERNAL_H_ +#include <cstdint> #include <type_traits> #include <utility> #include "absl/base/attributes.h" +#include "absl/base/nullability.h" #include "absl/meta/type_traits.h" #include "absl/status/status.h" +#include "absl/strings/string_view.h" #include "absl/utility/utility.h" namespace absl { @@ -69,11 +72,8 @@ using IsConstructibleOrConvertibleOrAssignableFromStatusOr = template <typename T, typename U> struct IsDirectInitializationAmbiguous : public absl::conditional_t< - std::is_same<absl::remove_cv_t<absl::remove_reference_t<U>>, - U>::value, - std::false_type, - IsDirectInitializationAmbiguous< - T, absl::remove_cv_t<absl::remove_reference_t<U>>>> {}; + std::is_same<absl::remove_cvref_t<U>, U>::value, std::false_type, + IsDirectInitializationAmbiguous<T, absl::remove_cvref_t<U>>> {}; template <typename T, typename V> struct IsDirectInitializationAmbiguous<T, absl::StatusOr<V>> @@ -84,14 +84,11 @@ struct IsDirectInitializationAmbiguous<T, absl::StatusOr<V>> template <typename T, typename U> using IsDirectInitializationValid = absl::disjunction< // Short circuits if T is basically U. - std::is_same<T, absl::remove_cv_t<absl::remove_reference_t<U>>>, + std::is_same<T, absl::remove_cvref_t<U>>, absl::negation<absl::disjunction< - std::is_same<absl::StatusOr<T>, - absl::remove_cv_t<absl::remove_reference_t<U>>>, - std::is_same<absl::Status, - absl::remove_cv_t<absl::remove_reference_t<U>>>, - std::is_same<absl::in_place_t, - absl::remove_cv_t<absl::remove_reference_t<U>>>, + std::is_same<absl::StatusOr<T>, absl::remove_cvref_t<U>>, + std::is_same<absl::Status, absl::remove_cvref_t<U>>, + std::is_same<absl::in_place_t, absl::remove_cvref_t<U>>, IsDirectInitializationAmbiguous<T, U>>>>; // This trait detects whether `StatusOr<T>::operator=(U&&)` is ambiguous, which @@ -107,11 +104,8 @@ using IsDirectInitializationValid = absl::disjunction< template <typename T, typename U> struct IsForwardingAssignmentAmbiguous : public absl::conditional_t< - std::is_same<absl::remove_cv_t<absl::remove_reference_t<U>>, - U>::value, - std::false_type, - IsForwardingAssignmentAmbiguous< - T, absl::remove_cv_t<absl::remove_reference_t<U>>>> {}; + std::is_same<absl::remove_cvref_t<U>, U>::value, std::false_type, + IsForwardingAssignmentAmbiguous<T, absl::remove_cvref_t<U>>> {}; template <typename T, typename U> struct IsForwardingAssignmentAmbiguous<T, absl::StatusOr<U>> @@ -122,20 +116,17 @@ struct IsForwardingAssignmentAmbiguous<T, absl::StatusOr<U>> template <typename T, typename U> using IsForwardingAssignmentValid = absl::disjunction< // Short circuits if T is basically U. - std::is_same<T, absl::remove_cv_t<absl::remove_reference_t<U>>>, + std::is_same<T, absl::remove_cvref_t<U>>, absl::negation<absl::disjunction< - std::is_same<absl::StatusOr<T>, - absl::remove_cv_t<absl::remove_reference_t<U>>>, - std::is_same<absl::Status, - absl::remove_cv_t<absl::remove_reference_t<U>>>, - std::is_same<absl::in_place_t, - absl::remove_cv_t<absl::remove_reference_t<U>>>, + std::is_same<absl::StatusOr<T>, absl::remove_cvref_t<U>>, + std::is_same<absl::Status, absl::remove_cvref_t<U>>, + std::is_same<absl::in_place_t, absl::remove_cvref_t<U>>, IsForwardingAssignmentAmbiguous<T, U>>>>; class Helper { public: // Move type-agnostic error handling to the .cc. - static void HandleInvalidStatusCtorArg(Status*); + static void HandleInvalidStatusCtorArg(absl::Nonnull<Status*>); ABSL_ATTRIBUTE_NORETURN static void Crash(const absl::Status& status); }; @@ -143,7 +134,8 @@ class Helper { // the constructor. // This abstraction is here mostly for the gcc performance fix. template <typename T, typename... Args> -ABSL_ATTRIBUTE_NONNULL(1) void PlacementNew(void* p, Args&&... args) { +ABSL_ATTRIBUTE_NONNULL(1) +void PlacementNew(absl::Nonnull<void*> p, Args&&... args) { new (p) T(std::forward<Args>(args)...); } @@ -389,6 +381,53 @@ struct MoveAssignBase<T, false> { ABSL_ATTRIBUTE_NORETURN void ThrowBadStatusOrAccess(absl::Status status); +// Used to introduce jitter into the output of printing functions for +// `StatusOr` (i.e. `AbslStringify` and `operator<<`). +class StringifyRandom { + enum BracesType { + kBareParens = 0, + kSpaceParens, + kBareBrackets, + kSpaceBrackets, + }; + + // Returns a random `BracesType` determined once per binary load. + static BracesType RandomBraces() { + static const BracesType kRandomBraces = static_cast<BracesType>( + (reinterpret_cast<uintptr_t>(&kRandomBraces) >> 4) % 4); + return kRandomBraces; + } + + public: + static inline absl::string_view OpenBrackets() { + switch (RandomBraces()) { + case kBareParens: + return "("; + case kSpaceParens: + return "( "; + case kBareBrackets: + return "["; + case kSpaceBrackets: + return "[ "; + } + return "("; + } + + static inline absl::string_view CloseBrackets() { + switch (RandomBraces()) { + case kBareParens: + return ")"; + case kSpaceParens: + return " )"; + case kBareBrackets: + return "]"; + case kSpaceBrackets: + return " ]"; + } + return ")"; + } +}; + } // namespace internal_statusor ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/status/status.cc b/absl/status/status.cc index 160eb417..4dd5ae06 100644 --- a/absl/status/status.cc +++ b/absl/status/status.cc @@ -15,23 +15,37 @@ #include <errno.h> -#include <cassert> -#include <utility> - +#include <atomic> +#include <cstddef> +#include <cstdint> +#include <cstring> +#include <memory> +#include <ostream> +#include <string> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" #include "absl/base/internal/strerror.h" #include "absl/base/macros.h" +#include "absl/base/no_destructor.h" +#include "absl/base/nullability.h" #include "absl/debugging/stacktrace.h" #include "absl/debugging/symbolize.h" -#include "absl/status/status_payload_printer.h" -#include "absl/strings/escaping.h" +#include "absl/status/internal/status_internal.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/str_split.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" namespace absl { ABSL_NAMESPACE_BEGIN +static_assert( + alignof(status_internal::StatusRep) >= 4, + "absl::Status assumes it can use the bottom 2 bits of a StatusRep*."); + std::string StatusCodeToString(StatusCode code) { switch (code) { case StatusCode::kOk: @@ -77,149 +91,18 @@ std::ostream& operator<<(std::ostream& os, StatusCode code) { return os << StatusCodeToString(code); } -namespace status_internal { - -static absl::optional<size_t> FindPayloadIndexByUrl( - const Payloads* payloads, - absl::string_view type_url) { - if (payloads == nullptr) - return absl::nullopt; - - for (size_t i = 0; i < payloads->size(); ++i) { - if ((*payloads)[i].type_url == type_url) return i; - } - - return absl::nullopt; -} - -// Convert canonical code to a value known to this binary. -absl::StatusCode MapToLocalCode(int value) { - absl::StatusCode code = static_cast<absl::StatusCode>(value); - switch (code) { - case absl::StatusCode::kOk: - case absl::StatusCode::kCancelled: - case absl::StatusCode::kUnknown: - case absl::StatusCode::kInvalidArgument: - case absl::StatusCode::kDeadlineExceeded: - case absl::StatusCode::kNotFound: - case absl::StatusCode::kAlreadyExists: - case absl::StatusCode::kPermissionDenied: - case absl::StatusCode::kResourceExhausted: - case absl::StatusCode::kFailedPrecondition: - case absl::StatusCode::kAborted: - case absl::StatusCode::kOutOfRange: - case absl::StatusCode::kUnimplemented: - case absl::StatusCode::kInternal: - case absl::StatusCode::kUnavailable: - case absl::StatusCode::kDataLoss: - case absl::StatusCode::kUnauthenticated: - return code; - default: - return absl::StatusCode::kUnknown; - } -} -} // namespace status_internal - -absl::optional<absl::Cord> Status::GetPayload( - absl::string_view type_url) const { - const auto* payloads = GetPayloads(); - absl::optional<size_t> index = - status_internal::FindPayloadIndexByUrl(payloads, type_url); - if (index.has_value()) - return (*payloads)[index.value()].payload; - - return absl::nullopt; -} - -void Status::SetPayload(absl::string_view type_url, absl::Cord payload) { - if (ok()) return; - - PrepareToModify(); - - status_internal::StatusRep* rep = RepToPointer(rep_); - if (!rep->payloads) { - rep->payloads = absl::make_unique<status_internal::Payloads>(); - } - - absl::optional<size_t> index = - status_internal::FindPayloadIndexByUrl(rep->payloads.get(), type_url); - if (index.has_value()) { - (*rep->payloads)[index.value()].payload = std::move(payload); - return; - } - - rep->payloads->push_back({std::string(type_url), std::move(payload)}); -} - -bool Status::ErasePayload(absl::string_view type_url) { - absl::optional<size_t> index = - status_internal::FindPayloadIndexByUrl(GetPayloads(), type_url); - if (index.has_value()) { - PrepareToModify(); - GetPayloads()->erase(GetPayloads()->begin() + index.value()); - if (GetPayloads()->empty() && message().empty()) { - // Special case: If this can be represented inlined, it MUST be - // inlined (EqualsSlow depends on this behavior). - StatusCode c = static_cast<StatusCode>(raw_code()); - Unref(rep_); - rep_ = CodeToInlinedRep(c); - } - return true; - } - - return false; -} - -void Status::ForEachPayload( - absl::FunctionRef<void(absl::string_view, const absl::Cord&)> visitor) - const { - if (auto* payloads = GetPayloads()) { - bool in_reverse = - payloads->size() > 1 && reinterpret_cast<uintptr_t>(payloads) % 13 > 6; - - for (size_t index = 0; index < payloads->size(); ++index) { - const auto& elem = - (*payloads)[in_reverse ? payloads->size() - 1 - index : index]; - -#ifdef NDEBUG - visitor(elem.type_url, elem.payload); -#else - // In debug mode invalidate the type url to prevent users from relying on - // this string lifetime. - - // NOLINTNEXTLINE intentional extra conversion to force temporary. - visitor(std::string(elem.type_url), elem.payload); -#endif // NDEBUG - } - } -} - -const std::string* Status::EmptyString() { - static union EmptyString { - std::string str; - ~EmptyString() {} - } empty = {{}}; - return &empty.str; +absl::Nonnull<const std::string*> Status::EmptyString() { + static const absl::NoDestructor<std::string> kEmpty; + return kEmpty.get(); } #ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL constexpr const char Status::kMovedFromString[]; #endif -const std::string* Status::MovedFromString() { - static std::string* moved_from_string = new std::string(kMovedFromString); - return moved_from_string; -} - -void Status::UnrefNonInlined(uintptr_t rep) { - status_internal::StatusRep* r = RepToPointer(rep); - // Fast path: if ref==1, there is no need for a RefCountDec (since - // this is the only reference and therefore no other thread is - // allowed to be mucking with r). - if (r->ref.load(std::memory_order_acquire) == 1 || - r->ref.fetch_sub(1, std::memory_order_acq_rel) - 1 == 0) { - delete r; - } +absl::Nonnull<const std::string*> Status::MovedFromString() { + static const absl::NoDestructor<std::string> kMovedFrom(kMovedFromString); + return kMovedFrom.get(); } Status::Status(absl::StatusCode code, absl::string_view msg) @@ -229,97 +112,20 @@ Status::Status(absl::StatusCode code, absl::string_view msg) } } -int Status::raw_code() const { - if (IsInlined(rep_)) { - return static_cast<int>(InlinedRepToCode(rep_)); - } - status_internal::StatusRep* rep = RepToPointer(rep_); - return static_cast<int>(rep->code); -} - -absl::StatusCode Status::code() const { - return status_internal::MapToLocalCode(raw_code()); -} - -void Status::PrepareToModify() { - ABSL_RAW_CHECK(!ok(), "PrepareToModify shouldn't be called on OK status."); - if (IsInlined(rep_)) { - rep_ = PointerToRep(new status_internal::StatusRep( - static_cast<absl::StatusCode>(raw_code()), absl::string_view(), - nullptr)); - return; - } - - uintptr_t rep_i = rep_; - status_internal::StatusRep* rep = RepToPointer(rep_); - if (rep->ref.load(std::memory_order_acquire) != 1) { - std::unique_ptr<status_internal::Payloads> payloads; - if (rep->payloads) { - payloads = absl::make_unique<status_internal::Payloads>(*rep->payloads); - } - status_internal::StatusRep* const new_rep = new status_internal::StatusRep( - rep->code, message(), std::move(payloads)); - rep_ = PointerToRep(new_rep); - UnrefNonInlined(rep_i); +absl::Nonnull<status_internal::StatusRep*> Status::PrepareToModify( + uintptr_t rep) { + if (IsInlined(rep)) { + return new status_internal::StatusRep(InlinedRepToCode(rep), + absl::string_view(), nullptr); } + return RepToPointer(rep)->CloneAndUnref(); } -bool Status::EqualsSlow(const absl::Status& a, const absl::Status& b) { - if (IsInlined(a.rep_) != IsInlined(b.rep_)) return false; - if (a.message() != b.message()) return false; - if (a.raw_code() != b.raw_code()) return false; - if (a.GetPayloads() == b.GetPayloads()) return true; - - const status_internal::Payloads no_payloads; - const status_internal::Payloads* larger_payloads = - a.GetPayloads() ? a.GetPayloads() : &no_payloads; - const status_internal::Payloads* smaller_payloads = - b.GetPayloads() ? b.GetPayloads() : &no_payloads; - if (larger_payloads->size() < smaller_payloads->size()) { - std::swap(larger_payloads, smaller_payloads); - } - if ((larger_payloads->size() - smaller_payloads->size()) > 1) return false; - // Payloads can be ordered differently, so we can't just compare payload - // vectors. - for (const auto& payload : *larger_payloads) { - - bool found = false; - for (const auto& other_payload : *smaller_payloads) { - if (payload.type_url == other_payload.type_url) { - if (payload.payload != other_payload.payload) { - return false; - } - found = true; - break; - } - } - if (!found) return false; - } - return true; -} - -std::string Status::ToStringSlow(StatusToStringMode mode) const { - std::string text; - absl::StrAppend(&text, absl::StatusCodeToString(code()), ": ", message()); - - const bool with_payload = (mode & StatusToStringMode::kWithPayload) == - StatusToStringMode::kWithPayload; - - if (with_payload) { - status_internal::StatusPayloadPrinter printer = - status_internal::GetStatusPayloadPrinter(); - this->ForEachPayload([&](absl::string_view type_url, - const absl::Cord& payload) { - absl::optional<std::string> result; - if (printer) result = printer(type_url, payload); - absl::StrAppend( - &text, " [", type_url, "='", - result.has_value() ? *result : absl::CHexEscape(std::string(payload)), - "']"); - }); +std::string Status::ToStringSlow(uintptr_t rep, StatusToStringMode mode) { + if (IsInlined(rep)) { + return absl::StrCat(absl::StatusCodeToString(InlinedRepToCode(rep)), ": "); } - - return text; + return RepToPointer(rep)->ToString(mode); } std::ostream& operator<<(std::ostream& os, const Status& x) { @@ -608,16 +414,12 @@ Status ErrnoToStatus(int error_number, absl::string_view message) { MessageForErrnoToStatus(error_number, message)); } -namespace status_internal { - -std::string* MakeCheckFailString(const absl::Status* status, - const char* prefix) { - return new std::string( - absl::StrCat(prefix, " (", - status->ToString(StatusToStringMode::kWithEverything), ")")); +absl::Nonnull<const char*> StatusMessageAsCStr(const Status& status) { + // As an internal implementation detail, we guarantee that if status.message() + // is non-empty, then the resulting string_view is null terminated. + auto sv_message = status.message(); + return sv_message.empty() ? "" : sv_message.data(); } -} // namespace status_internal - ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/status/status.h b/absl/status/status.h index 4e8292fc..9ce16db9 100644 --- a/absl/status/status.h +++ b/absl/status/status.h @@ -51,10 +51,17 @@ #ifndef ABSL_STATUS_STATUS_H_ #define ABSL_STATUS_STATUS_H_ +#include <cassert> +#include <cstdint> #include <ostream> #include <string> #include <utility> +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/macros.h" +#include "absl/base/nullability.h" +#include "absl/base/optimization.h" #include "absl/functional/function_ref.h" #include "absl/status/internal/status_internal.h" #include "absl/strings/cord.h" @@ -398,7 +405,7 @@ inline StatusToStringMode& operator^=(StatusToStringMode& lhs, // // * It may provide more fine-grained semantic information about the error to // facilitate actionable remedies. -// * It may provide human-readable contexual information that is more +// * It may provide human-readable contextual information that is more // appropriate to display to an end user. // // Example: @@ -421,7 +428,7 @@ inline StatusToStringMode& operator^=(StatusToStringMode& lhs, // Returned Status objects may not be ignored. status_internal.h has a forward // declaration of the form // class ABSL_MUST_USE_RESULT Status; -class Status final { +class ABSL_ATTRIBUTE_TRIVIAL_ABI Status final { public: // Constructors @@ -516,6 +523,12 @@ class Status final { std::string ToString( StatusToStringMode mode = StatusToStringMode::kDefault) const; + // Support `absl::StrCat`, `absl::StrFormat`, etc. + template <typename Sink> + friend void AbslStringify(Sink& sink, const Status& status) { + sink.Append(status.ToString(StatusToStringMode::kWithEverything)); + } + // Status::IgnoreError() // // Ignores any errors. This method does nothing except potentially suppress @@ -538,7 +551,7 @@ class Status final { // // * It may provide more fine-grained semantic information about the error // to facilitate actionable remedies. - // * It may provide human-readable contexual information that is more + // * It may provide human-readable contextual information that is more // appropriate to display to an end user. // // A payload consists of a [key,value] pair, where the key is a string @@ -602,56 +615,57 @@ class Status final { // code, and an empty error message. explicit Status(absl::StatusCode code); - static void UnrefNonInlined(uintptr_t rep); + // Underlying constructor for status from a rep_. + explicit Status(uintptr_t rep) : rep_(rep) {} + static void Ref(uintptr_t rep); static void Unref(uintptr_t rep); // REQUIRES: !ok() - // Ensures rep_ is not shared with any other Status. - void PrepareToModify(); - - const status_internal::Payloads* GetPayloads() const; - status_internal::Payloads* GetPayloads(); - - static bool EqualsSlow(const absl::Status& a, const absl::Status& b); + // Ensures rep is not inlined or shared with any other Status. + static absl::Nonnull<status_internal::StatusRep*> PrepareToModify( + uintptr_t rep); // MSVC 14.0 limitation requires the const. static constexpr const char kMovedFromString[] = "Status accessed after move."; - static const std::string* EmptyString(); - static const std::string* MovedFromString(); + static absl::Nonnull<const std::string*> EmptyString(); + static absl::Nonnull<const std::string*> MovedFromString(); // Returns whether rep contains an inlined representation. // See rep_ for details. - static bool IsInlined(uintptr_t rep); + static constexpr bool IsInlined(uintptr_t rep); // Indicates whether this Status was the rhs of a move operation. See rep_ // for details. - static bool IsMovedFrom(uintptr_t rep); - static uintptr_t MovedFromRep(); + static constexpr bool IsMovedFrom(uintptr_t rep); + static constexpr uintptr_t MovedFromRep(); // Convert between error::Code and the inlined uintptr_t representation used // by rep_. See rep_ for details. - static uintptr_t CodeToInlinedRep(absl::StatusCode code); - static absl::StatusCode InlinedRepToCode(uintptr_t rep); + static constexpr uintptr_t CodeToInlinedRep(absl::StatusCode code); + static constexpr absl::StatusCode InlinedRepToCode(uintptr_t rep); // Converts between StatusRep* and the external uintptr_t representation used // by rep_. See rep_ for details. static uintptr_t PointerToRep(status_internal::StatusRep* r); - static status_internal::StatusRep* RepToPointer(uintptr_t r); + static absl::Nonnull<const status_internal::StatusRep*> RepToPointer( + uintptr_t r); - std::string ToStringSlow(StatusToStringMode mode) const; + static std::string ToStringSlow(uintptr_t rep, StatusToStringMode mode); // Status supports two different representations. - // - When the low bit is off it is an inlined representation. + // - When the low bit is set it is an inlined representation. // It uses the canonical error space, no message or payload. // The error code is (rep_ >> 2). // The (rep_ & 2) bit is the "moved from" indicator, used in IsMovedFrom(). - // - When the low bit is on it is an external representation. + // - When the low bit is off it is an external representation. // In this case all the data comes from a heap allocated Rep object. - // (rep_ - 1) is a status_internal::StatusRep* pointer to that structure. + // rep_ is a status_internal::StatusRep* pointer to that structure. uintptr_t rep_; + + friend class status_internal::StatusRep; }; // OkStatus() @@ -755,11 +769,11 @@ Status ErrnoToStatus(int error_number, absl::string_view message); // Implementation details follow //------------------------------------------------------------------------------ -inline Status::Status() : rep_(CodeToInlinedRep(absl::StatusCode::kOk)) {} +inline Status::Status() : Status(absl::StatusCode::kOk) {} -inline Status::Status(absl::StatusCode code) : rep_(CodeToInlinedRep(code)) {} +inline Status::Status(absl::StatusCode code) : Status(CodeToInlinedRep(code)) {} -inline Status::Status(const Status& x) : rep_(x.rep_) { Ref(rep_); } +inline Status::Status(const Status& x) : Status(x.rep_) { Ref(rep_); } inline Status& Status::operator=(const Status& x) { uintptr_t old_rep = rep_; @@ -771,7 +785,7 @@ inline Status& Status::operator=(const Status& x) { return *this; } -inline Status::Status(Status&& x) noexcept : rep_(x.rep_) { +inline Status::Status(Status&& x) noexcept : Status(x.rep_) { x.rep_ = MovedFromRep(); } @@ -803,15 +817,27 @@ inline bool Status::ok() const { return rep_ == CodeToInlinedRep(absl::StatusCode::kOk); } +inline absl::StatusCode Status::code() const { + return status_internal::MapToLocalCode(raw_code()); +} + +inline int Status::raw_code() const { + if (IsInlined(rep_)) return static_cast<int>(InlinedRepToCode(rep_)); + return static_cast<int>(RepToPointer(rep_)->code()); +} + inline absl::string_view Status::message() const { return !IsInlined(rep_) - ? RepToPointer(rep_)->message + ? RepToPointer(rep_)->message() : (IsMovedFrom(rep_) ? absl::string_view(kMovedFromString) : absl::string_view()); } inline bool operator==(const Status& lhs, const Status& rhs) { - return lhs.rep_ == rhs.rep_ || Status::EqualsSlow(lhs, rhs); + if (lhs.rep_ == rhs.rep_) return true; + if (Status::IsInlined(lhs.rep_)) return false; + if (Status::IsInlined(rhs.rep_)) return false; + return *Status::RepToPointer(lhs.rep_) == *Status::RepToPointer(rhs.rep_); } inline bool operator!=(const Status& lhs, const Status& rhs) { @@ -819,7 +845,7 @@ inline bool operator!=(const Status& lhs, const Status& rhs) { } inline std::string Status::ToString(StatusToStringMode mode) const { - return ok() ? "OK" : ToStringSlow(mode); + return ok() ? "OK" : ToStringSlow(rep_, mode); } inline void Status::IgnoreError() const { @@ -831,52 +857,68 @@ inline void swap(absl::Status& a, absl::Status& b) { swap(a.rep_, b.rep_); } -inline const status_internal::Payloads* Status::GetPayloads() const { - return IsInlined(rep_) ? nullptr : RepToPointer(rep_)->payloads.get(); +inline absl::optional<absl::Cord> Status::GetPayload( + absl::string_view type_url) const { + if (IsInlined(rep_)) return absl::nullopt; + return RepToPointer(rep_)->GetPayload(type_url); } -inline status_internal::Payloads* Status::GetPayloads() { - return IsInlined(rep_) ? nullptr : RepToPointer(rep_)->payloads.get(); +inline void Status::SetPayload(absl::string_view type_url, absl::Cord payload) { + if (ok()) return; + status_internal::StatusRep* rep = PrepareToModify(rep_); + rep->SetPayload(type_url, std::move(payload)); + rep_ = PointerToRep(rep); } -inline bool Status::IsInlined(uintptr_t rep) { return (rep & 1) == 0; } - -inline bool Status::IsMovedFrom(uintptr_t rep) { - return IsInlined(rep) && (rep & 2) != 0; +inline bool Status::ErasePayload(absl::string_view type_url) { + if (IsInlined(rep_)) return false; + status_internal::StatusRep* rep = PrepareToModify(rep_); + auto res = rep->ErasePayload(type_url); + rep_ = res.new_rep; + return res.erased; } -inline uintptr_t Status::MovedFromRep() { - return CodeToInlinedRep(absl::StatusCode::kInternal) | 2; +inline void Status::ForEachPayload( + absl::FunctionRef<void(absl::string_view, const absl::Cord&)> visitor) + const { + if (IsInlined(rep_)) return; + RepToPointer(rep_)->ForEachPayload(visitor); } -inline uintptr_t Status::CodeToInlinedRep(absl::StatusCode code) { - return static_cast<uintptr_t>(code) << 2; +constexpr bool Status::IsInlined(uintptr_t rep) { return (rep & 1) != 0; } + +constexpr bool Status::IsMovedFrom(uintptr_t rep) { return (rep & 2) != 0; } + +constexpr uintptr_t Status::CodeToInlinedRep(absl::StatusCode code) { + return (static_cast<uintptr_t>(code) << 2) + 1; } -inline absl::StatusCode Status::InlinedRepToCode(uintptr_t rep) { - assert(IsInlined(rep)); +constexpr absl::StatusCode Status::InlinedRepToCode(uintptr_t rep) { + ABSL_ASSERT(IsInlined(rep)); return static_cast<absl::StatusCode>(rep >> 2); } -inline status_internal::StatusRep* Status::RepToPointer(uintptr_t rep) { +constexpr uintptr_t Status::MovedFromRep() { + return CodeToInlinedRep(absl::StatusCode::kInternal) | 2; +} + +inline absl::Nonnull<const status_internal::StatusRep*> Status::RepToPointer( + uintptr_t rep) { assert(!IsInlined(rep)); - return reinterpret_cast<status_internal::StatusRep*>(rep - 1); + return reinterpret_cast<const status_internal::StatusRep*>(rep); } -inline uintptr_t Status::PointerToRep(status_internal::StatusRep* rep) { - return reinterpret_cast<uintptr_t>(rep) + 1; +inline uintptr_t Status::PointerToRep( + absl::Nonnull<status_internal::StatusRep*> rep) { + return reinterpret_cast<uintptr_t>(rep); } inline void Status::Ref(uintptr_t rep) { - if (!IsInlined(rep)) { - RepToPointer(rep)->ref.fetch_add(1, std::memory_order_relaxed); - } + if (!IsInlined(rep)) RepToPointer(rep)->Ref(); } inline void Status::Unref(uintptr_t rep) { - if (!IsInlined(rep)) { - UnrefNonInlined(rep); - } + if (!IsInlined(rep)) RepToPointer(rep)->Unref(); } inline Status OkStatus() { return Status(); } @@ -886,6 +928,15 @@ inline Status OkStatus() { return Status(); } // message-less kCancelled errors are common in the infrastructure. inline Status CancelledError() { return Status(absl::StatusCode::kCancelled); } +// Retrieves a message's status as a null terminated C string. The lifetime of +// this string is tied to the lifetime of the status object itself. +// +// If the status's message is empty, the empty string is returned. +// +// StatusMessageAsCStr exists for C support. Use `status.message()` in C++. +absl::Nonnull<const char*> StatusMessageAsCStr( + const Status& status ABSL_ATTRIBUTE_LIFETIME_BOUND); + ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/status/status_payload_printer.cc b/absl/status/status_payload_printer.cc index a47aea11..98401e90 100644 --- a/absl/status/status_payload_printer.cc +++ b/absl/status/status_payload_printer.cc @@ -13,9 +13,7 @@ // limitations under the License. #include "absl/status/status_payload_printer.h" -#include <atomic> - -#include "absl/base/attributes.h" +#include "absl/base/config.h" #include "absl/base/internal/atomic_hook.h" namespace absl { diff --git a/absl/status/status_payload_printer.h b/absl/status/status_payload_printer.h index 5e0937f6..f22255e1 100644 --- a/absl/status/status_payload_printer.h +++ b/absl/status/status_payload_printer.h @@ -16,6 +16,7 @@ #include <string> +#include "absl/base/nullability.h" #include "absl/strings/cord.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" @@ -34,8 +35,8 @@ namespace status_internal { // NOTE: This is an internal API and the design is subject to change in the // future in a non-backward-compatible way. Since it's only meant for debugging // purpose, you should not rely on it in any critical logic. -using StatusPayloadPrinter = absl::optional<std::string> (*)(absl::string_view, - const absl::Cord&); +using StatusPayloadPrinter = absl::Nullable<absl::optional<std::string> (*)( + absl::string_view, const absl::Cord&)>; // Sets the global payload printer. Only one printer should be set per process. // If multiple printers are set, it's undefined which one will be used. diff --git a/absl/status/status_test.cc b/absl/status/status_test.cc index 74a64ace..585e7807 100644 --- a/absl/status/status_test.cc +++ b/absl/status/status_test.cc @@ -16,9 +16,16 @@ #include <errno.h> +#include <array> +#include <cstddef> +#include <sstream> +#include <utility> + #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/strings/cord.h" #include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" namespace { @@ -132,6 +139,29 @@ TEST(Status, ConstructorWithCodeMessage) { } } +TEST(Status, StatusMessageCStringTest) { + { + absl::Status status = absl::OkStatus(); + EXPECT_EQ(status.message(), ""); + EXPECT_STREQ(absl::StatusMessageAsCStr(status), ""); + EXPECT_EQ(status.message(), absl::StatusMessageAsCStr(status)); + EXPECT_NE(absl::StatusMessageAsCStr(status), nullptr); + } + { + absl::Status status; + EXPECT_EQ(status.message(), ""); + EXPECT_NE(absl::StatusMessageAsCStr(status), nullptr); + EXPECT_STREQ(absl::StatusMessageAsCStr(status), ""); + } + { + absl::Status status(absl::StatusCode::kInternal, "message"); + EXPECT_FALSE(status.ok()); + EXPECT_EQ(absl::StatusCode::kInternal, status.code()); + EXPECT_EQ("message", status.message()); + EXPECT_STREQ("message", absl::StatusMessageAsCStr(status)); + } +} + TEST(Status, ConstructOutOfRangeCode) { const int kRawCode = 9999; absl::Status status(static_cast<absl::StatusCode>(kRawCode), ""); @@ -276,37 +306,77 @@ TEST(Status, TestForEachPayload) { } TEST(Status, ToString) { - absl::Status s(absl::StatusCode::kInternal, "fail"); - EXPECT_EQ("INTERNAL: fail", s.ToString()); - s.SetPayload("foo", absl::Cord("bar")); - EXPECT_EQ("INTERNAL: fail [foo='bar']", s.ToString()); - s.SetPayload("bar", absl::Cord("\377")); - EXPECT_THAT(s.ToString(), + absl::Status status(absl::StatusCode::kInternal, "fail"); + EXPECT_EQ("INTERNAL: fail", status.ToString()); + status.SetPayload("foo", absl::Cord("bar")); + EXPECT_EQ("INTERNAL: fail [foo='bar']", status.ToString()); + status.SetPayload("bar", absl::Cord("\377")); + EXPECT_THAT(status.ToString(), AllOf(HasSubstr("INTERNAL: fail"), HasSubstr("[foo='bar']"), HasSubstr("[bar='\\xff']"))); } TEST(Status, ToStringMode) { - absl::Status s(absl::StatusCode::kInternal, "fail"); - s.SetPayload("foo", absl::Cord("bar")); - s.SetPayload("bar", absl::Cord("\377")); + absl::Status status(absl::StatusCode::kInternal, "fail"); + status.SetPayload("foo", absl::Cord("bar")); + status.SetPayload("bar", absl::Cord("\377")); EXPECT_EQ("INTERNAL: fail", - s.ToString(absl::StatusToStringMode::kWithNoExtraData)); + status.ToString(absl::StatusToStringMode::kWithNoExtraData)); - EXPECT_THAT(s.ToString(absl::StatusToStringMode::kWithPayload), + EXPECT_THAT(status.ToString(absl::StatusToStringMode::kWithPayload), AllOf(HasSubstr("INTERNAL: fail"), HasSubstr("[foo='bar']"), HasSubstr("[bar='\\xff']"))); - EXPECT_THAT(s.ToString(absl::StatusToStringMode::kWithEverything), + EXPECT_THAT(status.ToString(absl::StatusToStringMode::kWithEverything), AllOf(HasSubstr("INTERNAL: fail"), HasSubstr("[foo='bar']"), HasSubstr("[bar='\\xff']"))); - EXPECT_THAT(s.ToString(~absl::StatusToStringMode::kWithPayload), + EXPECT_THAT(status.ToString(~absl::StatusToStringMode::kWithPayload), AllOf(HasSubstr("INTERNAL: fail"), Not(HasSubstr("[foo='bar']")), Not(HasSubstr("[bar='\\xff']")))); } +TEST(Status, OstreamOperator) { + absl::Status status(absl::StatusCode::kInternal, "fail"); + { std::stringstream stream; + stream << status; + EXPECT_EQ("INTERNAL: fail", stream.str()); + } + status.SetPayload("foo", absl::Cord("bar")); + { std::stringstream stream; + stream << status; + EXPECT_EQ("INTERNAL: fail [foo='bar']", stream.str()); + } + status.SetPayload("bar", absl::Cord("\377")); + { std::stringstream stream; + stream << status; + EXPECT_THAT(stream.str(), + AllOf(HasSubstr("INTERNAL: fail"), HasSubstr("[foo='bar']"), + HasSubstr("[bar='\\xff']"))); + } +} + +TEST(Status, AbslStringify) { + absl::Status status(absl::StatusCode::kInternal, "fail"); + EXPECT_EQ("INTERNAL: fail", absl::StrCat(status)); + EXPECT_EQ("INTERNAL: fail", absl::StrFormat("%v", status)); + status.SetPayload("foo", absl::Cord("bar")); + EXPECT_EQ("INTERNAL: fail [foo='bar']", absl::StrCat(status)); + status.SetPayload("bar", absl::Cord("\377")); + EXPECT_THAT(absl::StrCat(status), + AllOf(HasSubstr("INTERNAL: fail"), HasSubstr("[foo='bar']"), + HasSubstr("[bar='\\xff']"))); +} + +TEST(Status, OstreamEqStringify) { + absl::Status status(absl::StatusCode::kUnknown, "fail"); + status.SetPayload("foo", absl::Cord("bar")); + std::stringstream stream; + stream << status; + EXPECT_EQ(stream.str(), absl::StrCat(status)); +} + absl::Status EraseAndReturn(const absl::Status& base) { absl::Status copy = base; EXPECT_TRUE(copy.ErasePayload(kUrl1)); diff --git a/absl/status/statusor.cc b/absl/status/statusor.cc index 96642b34..7e6b334c 100644 --- a/absl/status/statusor.cc +++ b/absl/status/statusor.cc @@ -17,7 +17,10 @@ #include <utility> #include "absl/base/call_once.h" +#include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" +#include "absl/base/nullability.h" +#include "absl/status/internal/statusor_internal.h" #include "absl/status/status.h" #include "absl/strings/str_cat.h" @@ -52,7 +55,7 @@ BadStatusOrAccess& BadStatusOrAccess::operator=(BadStatusOrAccess&& other) { BadStatusOrAccess::BadStatusOrAccess(BadStatusOrAccess&& other) : status_(std::move(other.status_)) {} -const char* BadStatusOrAccess::what() const noexcept { +absl::Nonnull<const char*> BadStatusOrAccess::what() const noexcept { InitWhat(); return what_.c_str(); } @@ -67,7 +70,7 @@ void BadStatusOrAccess::InitWhat() const { namespace internal_statusor { -void Helper::HandleInvalidStatusCtorArg(absl::Status* status) { +void Helper::HandleInvalidStatusCtorArg(absl::Nonnull<absl::Status*> status) { const char* kMessage = "An OK status is not a valid constructor argument to StatusOr<T>"; #ifdef NDEBUG diff --git a/absl/status/statusor.h b/absl/status/statusor.h index a76e7201..cd35e5b4 100644 --- a/absl/status/statusor.h +++ b/absl/status/statusor.h @@ -39,15 +39,20 @@ #include <exception> #include <initializer_list> #include <new> +#include <ostream> #include <string> #include <type_traits> #include <utility> #include "absl/base/attributes.h" +#include "absl/base/nullability.h" #include "absl/base/call_once.h" #include "absl/meta/type_traits.h" #include "absl/status/internal/statusor_internal.h" #include "absl/status/status.h" +#include "absl/strings/has_absl_stringify.h" +#include "absl/strings/has_ostream_operator.h" +#include "absl/strings/str_format.h" #include "absl/types/variant.h" #include "absl/utility/utility.h" @@ -88,7 +93,7 @@ class BadStatusOrAccess : public std::exception { // // The pointer of this string is guaranteed to be valid until any non-const // function is invoked on the exception object. - const char* what() const noexcept override; + absl::Nonnull<const char*> what() const noexcept override; // BadStatusOrAccess::status() // @@ -146,7 +151,7 @@ class ABSL_MUST_USE_RESULT StatusOr; // // absl::StatusOr<int> i = GetCount(); // if (i.ok()) { -// updated_total += *i +// updated_total += *i; // } // // NOTE: using `absl::StatusOr<T>::value()` when no valid value is present will @@ -411,7 +416,7 @@ class StatusOr : private internal_statusor::StatusOrData<T>, typename = typename std::enable_if<absl::conjunction< std::is_constructible<T, U&&>, std::is_assignable<T&, U&&>, absl::disjunction< - std::is_same<absl::remove_cv_t<absl::remove_reference_t<U>>, T>, + std::is_same<absl::remove_cvref_t<U>, T>, absl::conjunction< absl::negation<std::is_convertible<U&&, absl::Status>>, absl::negation<internal_statusor:: @@ -444,8 +449,7 @@ class StatusOr : private internal_statusor::StatusOrData<T>, internal_statusor::IsDirectInitializationValid<T, U&&>, std::is_constructible<T, U&&>, std::is_convertible<U&&, T>, absl::disjunction< - std::is_same<absl::remove_cv_t<absl::remove_reference_t<U>>, - T>, + std::is_same<absl::remove_cvref_t<U>, T>, absl::conjunction< absl::negation<std::is_convertible<U&&, absl::Status>>, absl::negation< @@ -461,8 +465,7 @@ class StatusOr : private internal_statusor::StatusOrData<T>, absl::conjunction< internal_statusor::IsDirectInitializationValid<T, U&&>, absl::disjunction< - std::is_same<absl::remove_cv_t<absl::remove_reference_t<U>>, - T>, + std::is_same<absl::remove_cvref_t<U>, T>, absl::conjunction< absl::negation<std::is_constructible<absl::Status, U&&>>, absl::negation< @@ -584,7 +587,7 @@ class StatusOr : private internal_statusor::StatusOrData<T>, // Reconstructs the inner value T in-place using the provided args, using the // T(args...) constructor. Returns reference to the reconstructed `T`. template <typename... Args> - T& emplace(Args&&... args) { + T& emplace(Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND { if (ok()) { this->Clear(); this->MakeValue(std::forward<Args>(args)...); @@ -600,7 +603,8 @@ class StatusOr : private internal_statusor::StatusOrData<T>, absl::enable_if_t< std::is_constructible<T, std::initializer_list<U>&, Args&&...>::value, int> = 0> - T& emplace(std::initializer_list<U> ilist, Args&&... args) { + T& emplace(std::initializer_list<U> ilist, + Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND { if (ok()) { this->Clear(); this->MakeValue(ilist, std::forward<Args>(args)...); @@ -611,6 +615,21 @@ class StatusOr : private internal_statusor::StatusOrData<T>, return this->data_; } + // StatusOr<T>::AssignStatus() + // + // Sets the status of `absl::StatusOr<T>` to the given non-ok status value. + // + // NOTE: We recommend using the constructor and `operator=` where possible. + // This method is intended for use in generic programming, to enable setting + // the status of a `StatusOr<T>` when `T` may be `Status`. In that case, the + // constructor and `operator=` would assign into the inner value of type + // `Status`, rather than status of the `StatusOr` (b/280392796). + // + // REQUIRES: !Status(std::forward<U>(v)).ok(). This requirement is DCHECKed. + // In optimized builds, passing absl::OkStatus() here will have the effect + // of passing absl::StatusCode::kInternal as a fallback. + using internal_statusor::StatusOrData<T>::AssignStatus; + private: using internal_statusor::StatusOrData<T>::Assign; template <typename U> @@ -636,6 +655,41 @@ bool operator!=(const StatusOr<T>& lhs, const StatusOr<T>& rhs) { return !(lhs == rhs); } +// Prints the `value` or the status in brackets to `os`. +// +// Requires `T` supports `operator<<`. Do not rely on the output format which +// may change without notice. +template <typename T, typename std::enable_if< + absl::HasOstreamOperator<T>::value, int>::type = 0> +std::ostream& operator<<(std::ostream& os, const StatusOr<T>& status_or) { + if (status_or.ok()) { + os << status_or.value(); + } else { + os << internal_statusor::StringifyRandom::OpenBrackets() + << status_or.status() + << internal_statusor::StringifyRandom::CloseBrackets(); + } + return os; +} + +// As above, but supports `StrCat`, `StrFormat`, etc. +// +// Requires `T` has `AbslStringify`. Do not rely on the output format which +// may change without notice. +template < + typename Sink, typename T, + typename std::enable_if<absl::HasAbslStringify<T>::value, int>::type = 0> +void AbslStringify(Sink& sink, const StatusOr<T>& status_or) { + if (status_or.ok()) { + absl::Format(&sink, "%v", status_or.value()); + } else { + absl::Format(&sink, "%s%v%s", + internal_statusor::StringifyRandom::OpenBrackets(), + status_or.status(), + internal_statusor::StringifyRandom::CloseBrackets()); + } +} + //------------------------------------------------------------------------------ // Implementation details for StatusOr<T> //------------------------------------------------------------------------------ @@ -736,13 +790,13 @@ T&& StatusOr<T>::operator*() && { } template <typename T> -const T* StatusOr<T>::operator->() const { +absl::Nonnull<const T*> StatusOr<T>::operator->() const { this->EnsureOk(); return &this->data_; } template <typename T> -T* StatusOr<T>::operator->() { +absl::Nonnull<T*> StatusOr<T>::operator->() { this->EnsureOk(); return &this->data_; } diff --git a/absl/status/statusor_test.cc b/absl/status/statusor_test.cc index 29021543..09ffc658 100644 --- a/absl/status/statusor_test.cc +++ b/absl/status/statusor_test.cc @@ -15,31 +15,41 @@ #include "absl/status/statusor.h" #include <array> +#include <cstddef> #include <initializer_list> +#include <map> #include <memory> +#include <ostream> +#include <sstream> #include <string> #include <type_traits> #include <utility> +#include <vector> #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/casts.h" #include "absl/memory/memory.h" #include "absl/status/status.h" +#include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/types/any.h" +#include "absl/types/variant.h" #include "absl/utility/utility.h" namespace { using ::testing::AllOf; +using ::testing::AnyOf; using ::testing::AnyWith; using ::testing::ElementsAre; +using ::testing::EndsWith; using ::testing::Field; using ::testing::HasSubstr; using ::testing::Ne; using ::testing::Not; using ::testing::Pointee; +using ::testing::StartsWith; using ::testing::VariantWith; #ifdef GTEST_HAS_STATUS_MATCHERS @@ -1844,4 +1854,68 @@ TEST(StatusOr, AssignmentFromTypeConvertibleToStatus) { } } +TEST(StatusOr, StatusAssignmentFromStatusError) { + absl::StatusOr<absl::Status> statusor; + statusor.AssignStatus(absl::CancelledError()); + + EXPECT_FALSE(statusor.ok()); + EXPECT_EQ(statusor.status(), absl::CancelledError()); +} + +#if GTEST_HAS_DEATH_TEST +TEST(StatusOr, StatusAssignmentFromStatusOk) { + EXPECT_DEBUG_DEATH( + { + absl::StatusOr<absl::Status> statusor; + // This will DCHECK. + statusor.AssignStatus(absl::OkStatus()); + // In optimized mode, we are actually going to get error::INTERNAL for + // status here, rather than crashing, so check that. + EXPECT_FALSE(statusor.ok()); + EXPECT_EQ(statusor.status().code(), absl::StatusCode::kInternal); + }, + "An OK status is not a valid constructor argument to StatusOr<T>"); +} +#endif + +TEST(StatusOr, StatusAssignmentFromTypeConvertibleToStatus) { + CustomType<MyType, kConvToStatus> v; + absl::StatusOr<MyType> statusor; + statusor.AssignStatus(v); + + EXPECT_FALSE(statusor.ok()); + EXPECT_EQ(statusor.status(), static_cast<absl::Status>(v)); +} + +struct PrintTestStruct { + friend std::ostream& operator<<(std::ostream& os, const PrintTestStruct&) { + return os << "ostream"; + } + + template <typename Sink> + friend void AbslStringify(Sink& sink, const PrintTestStruct&) { + sink.Append("stringify"); + } +}; + +TEST(StatusOr, OkPrinting) { + absl::StatusOr<PrintTestStruct> print_me = PrintTestStruct{}; + std::stringstream stream; + stream << print_me; + EXPECT_EQ(stream.str(), "ostream"); + EXPECT_EQ(absl::StrCat(print_me), "stringify"); +} + +TEST(StatusOr, ErrorPrinting) { + absl::StatusOr<PrintTestStruct> print_me = absl::UnknownError("error"); + std::stringstream stream; + stream << print_me; + const auto error_matcher = + AllOf(HasSubstr("UNKNOWN"), HasSubstr("error"), + AnyOf(AllOf(StartsWith("("), EndsWith(")")), + AllOf(StartsWith("["), EndsWith("]")))); + EXPECT_THAT(stream.str(), error_matcher); + EXPECT_THAT(absl::StrCat(print_me), error_matcher); +} + } // namespace diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index 53c57718..8e8371b3 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel @@ -22,12 +22,31 @@ load( package( default_visibility = ["//visibility:public"], - features = ["parse_headers"], + features = [ + "header_modules", + "layering_check", + "parse_headers", + ], ) licenses(["notice"]) cc_library( + name = "string_view", + srcs = ["string_view.cc"], + hdrs = ["string_view.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:nullability", + "//absl/base:throw_delegate", + ], +) + +cc_library( name = "strings", srcs = [ "ascii.cc", @@ -50,13 +69,13 @@ cc_library( "str_cat.cc", "str_replace.cc", "str_split.cc", - "string_view.cc", "substitute.cc", ], hdrs = [ "ascii.h", "charconv.h", "escaping.h", + "has_absl_stringify.h", "internal/damerau_levenshtein_distance.h", "internal/has_absl_stringify.h", "internal/string_constant.h", @@ -72,12 +91,21 @@ cc_library( ], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + textual_hdrs = [ + # string_view.h was once part of :strings, so string_view.h is + # re-exported for backwards compatibility. + # New code should directly depend on :string_view. + "string_view.h", + ], deps = [ + ":charset", ":internal", + ":string_view", "//absl/base", "//absl/base:config", "//absl/base:core_headers", "//absl/base:endian", + "//absl/base:nullability", "//absl/base:raw_logging_internal", "//absl/base:throw_delegate", "//absl/memory", @@ -95,7 +123,6 @@ cc_library( "internal/utf8.cc", ], hdrs = [ - "internal/char_map.h", "internal/escaping.h", "internal/ostringstream.h", "internal/resize_uninitialized.h", @@ -120,6 +147,7 @@ cc_test( visibility = ["//visibility:private"], deps = [ ":strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -138,6 +166,8 @@ cc_test( ":strings", "//absl/base:core_headers", "//absl/container:fixed_array", + "//absl/log:check", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -155,6 +185,45 @@ cc_test( ":strings", "//absl/base:raw_logging_internal", "@com_github_google_benchmark//:benchmark_main", + "@com_google_googletest//:gtest", + ], +) + +cc_test( + name = "has_absl_stringify_test", + size = "small", + srcs = ["has_absl_stringify_test.cc"], + copts = ABSL_TEST_COPTS, + visibility = ["//visibility:private"], + deps = [ + ":strings", + "//absl/types:optional", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "has_ostream_operator", + hdrs = ["has_ostream_operator.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base:config", + ], +) + +cc_test( + name = "has_ostream_operator_test", + size = "small", + srcs = ["has_ostream_operator_test.cc"], + copts = ABSL_TEST_COPTS, + visibility = ["//visibility:private"], + deps = [ + ":has_ostream_operator", + "//absl/types:optional", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", ], ) @@ -167,6 +236,7 @@ cc_test( deps = [ ":strings", "//absl/base:core_headers", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -180,6 +250,7 @@ cc_test( deps = [ ":strings", "@com_github_google_benchmark//:benchmark_main", + "@com_google_googletest//:gtest", ], ) @@ -192,6 +263,7 @@ cc_test( copts = ABSL_TEST_COPTS, deps = [ "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -209,6 +281,7 @@ cc_test( ":strings", "//absl/base:core_headers", "@com_github_google_benchmark//:benchmark_main", + "@com_google_googletest//:gtest", ], ) @@ -224,6 +297,7 @@ cc_test( deps = [ ":strings", "//absl/base:core_headers", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -239,6 +313,7 @@ cc_test( deps = [ ":internal", "//absl/base:core_headers", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -252,6 +327,7 @@ cc_test( deps = [ ":strings", "//absl/meta:type_traits", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -263,10 +339,12 @@ cc_test( tags = ["benchmark"], visibility = ["//visibility:private"], deps = [ + ":string_view", ":strings", "//absl/base:core_headers", "//absl/base:raw_logging_internal", "@com_github_google_benchmark//:benchmark_main", + "@com_google_googletest//:gtest", ], ) @@ -277,10 +355,57 @@ cc_test( copts = ABSL_TEST_COPTS, visibility = ["//visibility:private"], deps = [ - ":strings", + ":string_view", "//absl/base:config", "//absl/base:core_headers", "//absl/base:dynamic_annotations", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "charset_benchmark", + size = "small", + srcs = [ + "charset_benchmark.cc", + ], + copts = ABSL_TEST_COPTS, + tags = [ + "benchmark", + ], + visibility = ["//visibility:private"], + deps = [ + ":charset", + "//absl/log:check", + "@com_github_google_benchmark//:benchmark_main", + "@com_google_googletest//:gtest", + ], +) + +cc_library( + name = "charset", + hdrs = [ + "charset.h", + ], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":string_view", + "//absl/base:core_headers", + ], +) + +cc_test( + name = "charset_test", + size = "small", + srcs = ["charset_test.cc"], + copts = ABSL_TEST_COPTS, + visibility = ["//visibility:private"], + deps = [ + ":charset", + ":strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -294,7 +419,6 @@ cc_library( "internal/cord_rep_btree_reader.cc", "internal/cord_rep_consume.cc", "internal/cord_rep_crc.cc", - "internal/cord_rep_ring.cc", ], hdrs = [ "internal/cord_data_edge.h", @@ -305,8 +429,6 @@ cc_library( "internal/cord_rep_consume.h", "internal/cord_rep_crc.h", "internal/cord_rep_flat.h", - "internal/cord_rep_ring.h", - "internal/cord_rep_ring_reader.h", ], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, @@ -343,6 +465,7 @@ cc_test( ":cord_rep_test_util", ":strings", "//absl/base:config", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -350,6 +473,7 @@ cc_test( cc_test( name = "cord_rep_btree_test", size = "medium", + timeout = "long", srcs = ["internal/cord_rep_btree_test.cc"], copts = ABSL_TEST_COPTS, visibility = ["//visibility:private"], @@ -360,6 +484,7 @@ cc_test( "//absl/base:config", "//absl/base:raw_logging_internal", "//absl/cleanup", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -376,6 +501,7 @@ cc_test( ":strings", "//absl/base:config", "//absl/base:raw_logging_internal", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -393,6 +519,7 @@ cc_test( ":strings", "//absl/base:config", "//absl/base:raw_logging_internal", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -408,6 +535,7 @@ cc_test( ":cord_rep_test_util", "//absl/base:config", "//absl/crc:crc_cord_state", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -431,6 +559,7 @@ cc_test( "//absl/base:config", "//absl/base:core_headers", "//absl/synchronization", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -457,15 +586,15 @@ cc_library( ":cordz_update_scope", ":cordz_update_tracker", ":internal", - ":str_format", ":strings", "//absl/base", "//absl/base:config", "//absl/base:core_headers", "//absl/base:endian", + "//absl/base:nullability", "//absl/base:raw_logging_internal", - "//absl/container:fixed_array", "//absl/container:inlined_vector", + "//absl/crc:crc32c", "//absl/crc:crc_cord_state", "//absl/functional:function_ref", "//absl/meta:type_traits", @@ -514,6 +643,7 @@ cc_library( "//absl/container:inlined_vector", "//absl/debugging:stacktrace", "//absl/synchronization", + "//absl/time", "//absl/types:span", ], ) @@ -546,6 +676,7 @@ cc_test( ":cordz_update_scope", ":cordz_update_tracker", "//absl/base:config", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -606,6 +737,7 @@ cc_test( ":cordz_functions", ":cordz_test_helpers", "//absl/base:config", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -624,6 +756,7 @@ cc_test( "//absl/synchronization", "//absl/synchronization:thread_pool", "//absl/time", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -645,6 +778,7 @@ cc_test( "//absl/debugging:stacktrace", "//absl/debugging:symbolize", "//absl/types:span", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -666,6 +800,7 @@ cc_test( "//absl/crc:crc_cord_state", "//absl/synchronization", "//absl/synchronization:thread_pool", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -687,6 +822,7 @@ cc_test( "//absl/synchronization", "//absl/synchronization:thread_pool", "//absl/time", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -737,6 +873,7 @@ cc_library( ":strings", "//absl/base:config", "//absl/base:core_headers", + "//absl/base:nullability", "@com_google_googletest//:gtest", ], ) @@ -751,8 +888,10 @@ cc_test( ":cord", ":cord_internal", ":cord_rep_test_util", + ":string_view", "//absl/base:config", "//absl/types:span", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -765,19 +904,25 @@ cc_test( visibility = ["//visibility:private"], deps = [ ":cord", + ":cord_internal", ":cord_test_helpers", ":cordz_functions", + ":cordz_statistics", ":cordz_test_helpers", + ":cordz_update_tracker", ":str_format", ":strings", "//absl/base:config", "//absl/base:core_headers", "//absl/base:endian", - "//absl/base:raw_logging_internal", "//absl/container:fixed_array", + "//absl/functional:function_ref", "//absl/hash", "//absl/log", + "//absl/log:check", "//absl/random", + "//absl/types:optional", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -799,6 +944,7 @@ cc_test( visibility = ["//visibility:private"], deps = [ ":cord", + ":cord_internal", ":cord_test_helpers", ":cordz_functions", ":cordz_info", @@ -810,38 +956,7 @@ cc_test( "//absl/base:config", "//absl/base:core_headers", "//absl/base:raw_logging_internal", - "@com_google_googletest//:gtest_main", - ], -) - -cc_test( - name = "cord_ring_test", - size = "medium", - srcs = ["cord_ring_test.cc"], - copts = ABSL_TEST_COPTS, - visibility = ["//visibility:private"], - deps = [ - ":cord_internal", - ":strings", - "//absl/base:config", - "//absl/base:core_headers", - "//absl/base:raw_logging_internal", - "//absl/debugging:leak_check", - "@com_google_googletest//:gtest_main", - ], -) - -cc_test( - name = "cord_ring_reader_test", - size = "medium", - srcs = ["cord_ring_reader_test.cc"], - copts = ABSL_TEST_COPTS, - visibility = ["//visibility:private"], - deps = [ - ":cord_internal", - ":strings", - "//absl/base:core_headers", - "//absl/debugging:leak_check", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -855,6 +970,7 @@ cc_test( deps = [ ":strings", "//absl/base:core_headers", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -869,6 +985,7 @@ cc_test( ":strings", "//absl/base:raw_logging_internal", "@com_github_google_benchmark//:benchmark_main", + "@com_google_googletest//:gtest", ], ) @@ -880,6 +997,7 @@ cc_test( visibility = ["//visibility:private"], deps = [ ":strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -896,6 +1014,7 @@ cc_test( "//absl/container:btree", "//absl/container:flat_hash_map", "//absl/container:node_hash_map", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -910,6 +1029,7 @@ cc_test( ":strings", "//absl/base:raw_logging_internal", "@com_github_google_benchmark//:benchmark_main", + "@com_google_googletest//:gtest", ], ) @@ -921,6 +1041,7 @@ cc_test( visibility = ["//visibility:private"], deps = [ ":internal", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -934,6 +1055,7 @@ cc_test( deps = [ ":internal", "@com_github_google_benchmark//:benchmark_main", + "@com_google_googletest//:gtest", ], ) @@ -949,6 +1071,7 @@ cc_test( deps = [ "//absl/base:core_headers", "//absl/meta:type_traits", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -963,6 +1086,7 @@ cc_test( ":strings", "//absl/base:core_headers", "//absl/memory", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -976,6 +1100,7 @@ cc_test( deps = [ ":strings", "@com_github_google_benchmark//:benchmark_main", + "@com_google_googletest//:gtest", ], ) @@ -988,6 +1113,7 @@ cc_test( deps = [ ":str_format", ":strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -1000,7 +1126,10 @@ cc_test( visibility = ["//visibility:private"], deps = [ ":strings", + "//absl/random", + "//absl/random:distributions", "@com_github_google_benchmark//:benchmark_main", + "@com_google_googletest//:gtest", ], ) @@ -1018,9 +1147,11 @@ cc_test( ":pow10_helper", ":strings", "//absl/base:config", - "//absl/base:raw_logging_internal", + "//absl/log", + "//absl/numeric:int128", "//absl/random", "//absl/random:distributions", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -1037,6 +1168,7 @@ cc_test( "//absl/random", "//absl/random:distributions", "@com_github_google_benchmark//:benchmark_main", + "@com_google_googletest//:gtest", ], ) @@ -1048,32 +1180,12 @@ cc_test( visibility = ["//visibility:private"], deps = [ ":strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) cc_test( - name = "char_map_test", - srcs = ["internal/char_map_test.cc"], - copts = ABSL_TEST_COPTS, - deps = [ - ":internal", - "@com_google_googletest//:gtest_main", - ], -) - -cc_test( - name = "char_map_benchmark", - srcs = ["internal/char_map_benchmark.cc"], - copts = ABSL_TEST_COPTS, - tags = ["benchmark"], - deps = [ - ":internal", - "@com_github_google_benchmark//:benchmark_main", - ], -) - -cc_test( name = "charconv_test", srcs = ["charconv_test.cc"], copts = ABSL_TEST_COPTS, @@ -1081,6 +1193,7 @@ cc_test( ":pow10_helper", ":str_format", ":strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -1095,7 +1208,8 @@ cc_test( deps = [ ":strings", "//absl/base:config", - "//absl/base:raw_logging_internal", + "//absl/log:check", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -1111,6 +1225,7 @@ cc_test( deps = [ ":strings", "//absl/base:config", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -1126,6 +1241,7 @@ cc_test( deps = [ ":strings", "@com_github_google_benchmark//:benchmark_main", + "@com_google_googletest//:gtest", ], ) @@ -1138,6 +1254,11 @@ cc_library( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":str_format_internal", + ":string_view", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:nullability", + "//absl/types:span", ], ) @@ -1168,6 +1289,8 @@ cc_library( ":strings", "//absl/base:config", "//absl/base:core_headers", + "//absl/container:fixed_array", + "//absl/container:inlined_vector", "//absl/functional:function_ref", "//absl/meta:type_traits", "//absl/numeric:bits", @@ -1188,6 +1311,10 @@ cc_test( ":cord", ":str_format", ":strings", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/types:span", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -1203,6 +1330,7 @@ cc_test( ":str_format", ":str_format_internal", ":strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -1215,6 +1343,8 @@ cc_test( deps = [ ":str_format", ":str_format_internal", + "//absl/base:config", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -1226,6 +1356,7 @@ cc_test( visibility = ["//visibility:private"], deps = [ ":str_format_internal", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -1237,6 +1368,7 @@ cc_test( visibility = ["//visibility:private"], deps = [ ":str_format", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -1248,11 +1380,17 @@ cc_test( copts = ABSL_TEST_COPTS, visibility = ["//visibility:private"], deps = [ + ":str_format", ":str_format_internal", ":strings", + "//absl/base:config", "//absl/base:core_headers", "//absl/base:raw_logging_internal", + "//absl/log", + "//absl/numeric:int128", "//absl/types:optional", + "//absl/types:span", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -1265,6 +1403,7 @@ cc_test( deps = [ ":cord", ":str_format_internal", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -1276,7 +1415,10 @@ cc_test( visibility = ["//visibility:private"], deps = [ ":str_format_internal", + ":string_view", + "//absl/base:config", "//absl/base:core_headers", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -1299,6 +1441,7 @@ cc_test( deps = [ ":pow10_helper", ":str_format", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -1317,3 +1460,16 @@ cc_binary( "//absl/types:optional", ], ) + +cc_test( + name = "char_formatting_test", + srcs = [ + "char_formatting_test.cc", + ], + deps = [ + ":str_format", + ":strings", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index a0f7cc54..1b245362 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt @@ -16,11 +16,30 @@ absl_cc_library( NAME + string_view + HDRS + string_view.h + SRCS + string_view.cc + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::base + absl::config + absl::core_headers + absl::nullability + absl::throw_delegate + PUBLIC +) + +absl_cc_library( + NAME strings HDRS "ascii.h" "charconv.h" "escaping.h" + "has_absl_stringify.h" "internal/damerau_levenshtein_distance.h" "internal/string_constant.h" "internal/has_absl_stringify.h" @@ -30,7 +49,6 @@ absl_cc_library( "str_join.h" "str_replace.h" "str_split.h" - "string_view.h" "strip.h" "substitute.h" SRCS @@ -54,31 +72,57 @@ absl_cc_library( "str_cat.cc" "str_replace.cc" "str_split.cc" - "string_view.cc" "substitute.cc" COPTS ${ABSL_DEFAULT_COPTS} DEPS + absl::string_view absl::strings_internal absl::base absl::bits + absl::charset absl::config absl::core_headers absl::endian absl::int128 absl::memory + absl::nullability absl::raw_logging_internal absl::throw_delegate absl::type_traits PUBLIC ) +absl_cc_library( + NAME + charset + HDRS + charset.h + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::core_headers + absl::string_view + PUBLIC +) + +absl_cc_library( + NAME + has_ostream_operator + HDRS + "has_ostream_operator.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::config + PUBLIC +) + # Internal-only target, do not depend on directly. absl_cc_library( NAME strings_internal HDRS - "internal/char_map.h" "internal/escaping.cc" "internal/escaping.h" "internal/ostringstream.h" @@ -122,6 +166,33 @@ absl_cc_test( absl::core_headers absl::fixed_array GTest::gmock_main + absl::check +) + +absl_cc_test( + NAME + has_absl_stringify_test + SRCS + "has_absl_stringify_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::optional + absl::strings + GTest::gmock_main +) + +absl_cc_test( + NAME + has_ostream_operator_test + SRCS + "has_ostream_operator_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::has_ostream_operator + absl::optional + GTest::gmock_main ) absl_cc_test( @@ -313,13 +384,14 @@ absl_cc_test( COPTS ${ABSL_TEST_COPTS} DEPS - absl::strings + absl::config absl::core_headers + absl::int128 + absl::log absl::pow10_helper - absl::config - absl::raw_logging_internal - absl::random_random absl::random_distributions + absl::random_random + absl::strings absl::strings_internal GTest::gmock_main ) @@ -339,13 +411,13 @@ absl_cc_test( absl_cc_test( NAME - char_map_test + charset_test SRCS - "internal/char_map_test.cc" + "charset_test.cc" COPTS ${ABSL_TEST_COPTS} DEPS - absl::strings_internal + absl::strings GTest::gmock_main ) @@ -372,9 +444,9 @@ absl_cc_test( COPTS ${ABSL_TEST_COPTS} DEPS - absl::strings + absl::check absl::config - absl::raw_logging_internal + absl::strings GTest::gmock_main ) @@ -401,7 +473,12 @@ absl_cc_library( COPTS ${ABSL_DEFAULT_COPTS} DEPS + absl::config + absl::core_headers + absl::nullability + absl::span absl::str_format_internal + absl::string_view PUBLIC ) @@ -432,6 +509,8 @@ absl_cc_library( absl::strings absl::config absl::core_headers + absl::fixed_array + absl::inlined_vector absl::numeric_representation absl::type_traits absl::utility @@ -447,10 +526,12 @@ absl_cc_test( COPTS ${ABSL_TEST_COPTS} DEPS - absl::str_format + absl::config absl::cord - absl::strings absl::core_headers + absl::span + absl::str_format + absl::strings GTest::gmock_main ) @@ -476,6 +557,7 @@ absl_cc_test( COPTS ${ABSL_TEST_COPTS} DEPS + absl::config absl::str_format absl::str_format_internal GTest::gmock_main @@ -513,11 +595,15 @@ absl_cc_test( COPTS ${ABSL_TEST_COPTS} DEPS - absl::strings - absl::str_format_internal + absl::config absl::core_headers - absl::raw_logging_internal absl::int128 + absl::log + absl::raw_logging_internal + absl::span + absl::str_format + absl::str_format_internal + absl::strings GTest::gmock_main ) @@ -543,10 +629,26 @@ absl_cc_test( ${ABSL_TEST_COPTS} DEPS absl::str_format_internal + absl::string_view + absl::config absl::core_headers GTest::gmock_main ) +absl_cc_test( + NAME + char_formatting_test + SRCS + "char_formatting_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::base + absl::str_format + absl::strings + GTest::gmock_main +) + # Internal-only target, do not depend on directly. absl_cc_library( NAME @@ -588,8 +690,6 @@ absl_cc_library( "internal/cord_rep_crc.h" "internal/cord_rep_consume.h" "internal/cord_rep_flat.h" - "internal/cord_rep_ring.h" - "internal/cord_rep_ring_reader.h" SRCS "internal/cord_internal.cc" "internal/cord_rep_btree.cc" @@ -597,7 +697,6 @@ absl_cc_library( "internal/cord_rep_btree_reader.cc" "internal/cord_rep_crc.cc" "internal/cord_rep_consume.cc" - "internal/cord_rep_ring.cc" COPTS ${ABSL_DEFAULT_COPTS} DEPS @@ -745,6 +844,7 @@ absl_cc_library( absl::raw_logging_internal absl::stacktrace absl::synchronization + absl::time ) absl_cc_test( @@ -885,11 +985,12 @@ absl_cc_library( absl::cordz_update_scope absl::cordz_update_tracker absl::core_headers + absl::crc32c absl::crc_cord_state absl::endian - absl::fixed_array absl::function_ref absl::inlined_vector + absl::nullability absl::optional absl::raw_logging_internal absl::span @@ -947,6 +1048,7 @@ absl_cc_library( absl::cordz_statistics absl::cordz_update_tracker absl::core_headers + absl::nullability absl::strings TESTONLY ) @@ -959,19 +1061,22 @@ absl_cc_test( COPTS ${ABSL_TEST_COPTS} DEPS - absl::cord - absl::str_format - absl::strings absl::base + absl::check absl::config + absl::cord absl::cord_test_helpers absl::cordz_test_helpers absl::core_headers absl::endian + absl::fixed_array + absl::function_ref absl::hash + absl::log + absl::optional absl::random_random - absl::raw_logging_internal - absl::fixed_array + absl::str_format + absl::strings GTest::gmock_main ) @@ -1064,38 +1169,6 @@ absl_cc_test( absl_cc_test( NAME - cord_ring_test - SRCS - "cord_ring_test.cc" - COPTS - ${ABSL_TEST_COPTS} - DEPS - absl::base - absl::config - absl::cord_internal - absl::core_headers - absl::raw_logging_internal - absl::strings - GTest::gmock_main -) - -absl_cc_test( - NAME - cord_ring_reader_test - SRCS - "cord_ring_reader_test.cc" - COPTS - ${ABSL_TEST_COPTS} - DEPS - absl::base - absl::cord_internal - absl::core_headers - absl::strings - GTest::gmock_main -) - -absl_cc_test( - NAME cordz_test SRCS "cordz_test.cc" @@ -1103,6 +1176,7 @@ absl_cc_test( ${ABSL_TEST_COPTS} DEPS absl::cord + absl::cord_internal absl::cord_test_helpers absl::cordz_test_helpers absl::cordz_functions diff --git a/absl/strings/ascii.cc b/absl/strings/ascii.cc index 868df2d1..5460b2c6 100644 --- a/absl/strings/ascii.cc +++ b/absl/strings/ascii.cc @@ -14,6 +14,15 @@ #include "absl/strings/ascii.h" +#include <climits> +#include <cstdint> +#include <cstring> +#include <string> +#include <type_traits> + +#include "absl/base/config.h" +#include "absl/base/nullability.h" + namespace absl { ABSL_NAMESPACE_BEGIN namespace ascii_internal { @@ -153,21 +162,132 @@ ABSL_DLL const char kToUpper[256] = { }; // clang-format on -} // namespace ascii_internal +template <class T> +static constexpr T BroadcastByte(unsigned char value) { + static_assert(std::is_integral<T>::value && sizeof(T) <= sizeof(uint64_t) && + std::is_unsigned<T>::value, + "only unsigned integers up to 64-bit allowed"); + T result = value; + constexpr size_t result_bit_width = sizeof(result) * CHAR_BIT; + result |= result << ((CHAR_BIT << 0) & (result_bit_width - 1)); + result |= result << ((CHAR_BIT << 1) & (result_bit_width - 1)); + result |= result << ((CHAR_BIT << 2) & (result_bit_width - 1)); + return result; +} + +// Returns whether `c` is in the a-z/A-Z range (w.r.t. `ToUpper`). +// Implemented by: +// 1. Pushing the a-z/A-Z range to [SCHAR_MIN, SCHAR_MIN + 26). +// 2. Comparing to SCHAR_MIN + 26. +template <bool ToUpper> +constexpr bool AsciiInAZRange(unsigned char c) { + constexpr unsigned char sub = (ToUpper ? 'a' : 'A') - SCHAR_MIN; + constexpr signed char threshold = SCHAR_MIN + 26; // 26 = alphabet size. + // Using unsigned arithmetic as overflows/underflows are well defined. + unsigned char u = c - sub; + // Using signed cmp, as SIMD unsigned cmp isn't available in many platforms. + return static_cast<signed char>(u) < threshold; +} + +template <bool ToUpper> +static constexpr char* PartialAsciiStrCaseFold(absl::Nonnull<char*> p, + absl::Nonnull<char*> end) { + using vec_t = size_t; + const size_t n = static_cast<size_t>(end - p); + + // SWAR algorithm: http://0x80.pl/notesen/2016-01-06-swar-swap-case.html + constexpr char ch_a = ToUpper ? 'a' : 'A', ch_z = ToUpper ? 'z' : 'Z'; + char* const swar_end = p + (n / sizeof(vec_t)) * sizeof(vec_t); + while (p < swar_end) { + vec_t v = vec_t(); + + // memcpy the vector, but constexpr + for (size_t i = 0; i < sizeof(vec_t); ++i) { + v |= static_cast<vec_t>(static_cast<unsigned char>(p[i])) + << (i * CHAR_BIT); + } + + constexpr unsigned int msb = 1u << (CHAR_BIT - 1); + const vec_t v_msb = v & BroadcastByte<vec_t>(msb); + const vec_t v_nonascii_mask = (v_msb << 1) - (v_msb >> (CHAR_BIT - 1)); + const vec_t v_nonascii = v & v_nonascii_mask; + const vec_t v_ascii = v & ~v_nonascii_mask; + const vec_t a = v_ascii + BroadcastByte<vec_t>(msb - ch_a - 0), + z = v_ascii + BroadcastByte<vec_t>(msb - ch_z - 1); + v = v_nonascii | (v_ascii ^ ((a ^ z) & BroadcastByte<vec_t>(msb)) >> 2); + + // memcpy the vector, but constexpr + for (size_t i = 0; i < sizeof(vec_t); ++i) { + p[i] = static_cast<char>(v >> (i * CHAR_BIT)); + } + + p += sizeof(v); + } + + return p; +} + +template <bool ToUpper> +static constexpr void AsciiStrCaseFold(absl::Nonnull<char*> p, + absl::Nonnull<char*> end) { + // The upper- and lowercase versions of ASCII characters differ by only 1 bit. + // When we need to flip the case, we can xor with this bit to achieve the + // desired result. Note that the choice of 'a' and 'A' here is arbitrary. We + // could have chosen 'z' and 'Z', or any other pair of characters as they all + // have the same single bit difference. + constexpr unsigned char kAsciiCaseBitFlip = 'a' ^ 'A'; -void AsciiStrToLower(std::string* s) { - for (auto& ch : *s) { - ch = absl::ascii_tolower(static_cast<unsigned char>(ch)); + using vec_t = size_t; + // TODO(b/316380338): When FDO becomes able to vectorize these, + // revert this manual optimization and just leave the naive loop. + if (static_cast<size_t>(end - p) >= sizeof(vec_t)) { + p = ascii_internal::PartialAsciiStrCaseFold<ToUpper>(p, end); + } + while (p < end) { + unsigned char v = static_cast<unsigned char>(*p); + v ^= AsciiInAZRange<ToUpper>(v) ? kAsciiCaseBitFlip : 0; + *p = static_cast<char>(v); + ++p; } } -void AsciiStrToUpper(std::string* s) { - for (auto& ch : *s) { - ch = absl::ascii_toupper(static_cast<unsigned char>(ch)); +static constexpr size_t ValidateAsciiCasefold() { + constexpr size_t num_chars = 1 + CHAR_MAX - CHAR_MIN; + size_t incorrect_index = 0; + char lowered[num_chars] = {}; + char uppered[num_chars] = {}; + for (unsigned int i = 0; i < num_chars; ++i) { + uppered[i] = lowered[i] = static_cast<char>(i); + } + AsciiStrCaseFold<false>(&lowered[0], &lowered[num_chars]); + AsciiStrCaseFold<true>(&uppered[0], &uppered[num_chars]); + for (size_t i = 0; i < num_chars; ++i) { + const char ch = static_cast<char>(i), + ch_upper = ('a' <= ch && ch <= 'z' ? 'A' + (ch - 'a') : ch), + ch_lower = ('A' <= ch && ch <= 'Z' ? 'a' + (ch - 'A') : ch); + if (uppered[i] != ch_upper || lowered[i] != ch_lower) { + incorrect_index = i > 0 ? i : num_chars; + break; + } } + return incorrect_index; +} + +static_assert(ValidateAsciiCasefold() == 0, "error in case conversion"); + +} // namespace ascii_internal + +void AsciiStrToLower(absl::Nonnull<std::string*> s) { + char* p = &(*s)[0]; // Guaranteed to be valid for empty strings + return ascii_internal::AsciiStrCaseFold<false>(p, p + s->size()); +} + +void AsciiStrToUpper(absl::Nonnull<std::string*> s) { + char* p = &(*s)[0]; // Guaranteed to be valid for empty strings + return ascii_internal::AsciiStrCaseFold<true>(p, p + s->size()); } -void RemoveExtraAsciiWhitespace(std::string* str) { +void RemoveExtraAsciiWhitespace(absl::Nonnull<std::string*> str) { auto stripped = StripAsciiWhitespace(*str); if (stripped.empty()) { diff --git a/absl/strings/ascii.h b/absl/strings/ascii.h index 42eadaea..c238f4de 100644 --- a/absl/strings/ascii.h +++ b/absl/strings/ascii.h @@ -53,10 +53,12 @@ #define ABSL_STRINGS_ASCII_H_ #include <algorithm> +#include <cstddef> #include <string> #include "absl/base/attributes.h" #include "absl/base/config.h" +#include "absl/base/nullability.h" #include "absl/strings/string_view.h" namespace absl { @@ -165,7 +167,7 @@ inline char ascii_tolower(unsigned char c) { } // Converts the characters in `s` to lowercase, changing the contents of `s`. -void AsciiStrToLower(std::string* s); +void AsciiStrToLower(absl::Nonnull<std::string*> s); // Creates a lowercase string from a given absl::string_view. ABSL_MUST_USE_RESULT inline std::string AsciiStrToLower(absl::string_view s) { @@ -183,7 +185,7 @@ inline char ascii_toupper(unsigned char c) { } // Converts the characters in `s` to uppercase, changing the contents of `s`. -void AsciiStrToUpper(std::string* s); +void AsciiStrToUpper(absl::Nonnull<std::string*> s); // Creates an uppercase string from a given absl::string_view. ABSL_MUST_USE_RESULT inline std::string AsciiStrToUpper(absl::string_view s) { @@ -201,7 +203,7 @@ ABSL_MUST_USE_RESULT inline absl::string_view StripLeadingAsciiWhitespace( } // Strips in place whitespace from the beginning of the given string. -inline void StripLeadingAsciiWhitespace(std::string* str) { +inline void StripLeadingAsciiWhitespace(absl::Nonnull<std::string*> str) { auto it = std::find_if_not(str->begin(), str->end(), absl::ascii_isspace); str->erase(str->begin(), it); } @@ -215,7 +217,7 @@ ABSL_MUST_USE_RESULT inline absl::string_view StripTrailingAsciiWhitespace( } // Strips in place whitespace from the end of the given string -inline void StripTrailingAsciiWhitespace(std::string* str) { +inline void StripTrailingAsciiWhitespace(absl::Nonnull<std::string*> str) { auto it = std::find_if_not(str->rbegin(), str->rend(), absl::ascii_isspace); str->erase(static_cast<size_t>(str->rend() - it)); } @@ -228,13 +230,13 @@ ABSL_MUST_USE_RESULT inline absl::string_view StripAsciiWhitespace( } // Strips in place whitespace from both ends of the given string -inline void StripAsciiWhitespace(std::string* str) { +inline void StripAsciiWhitespace(absl::Nonnull<std::string*> str) { StripTrailingAsciiWhitespace(str); StripLeadingAsciiWhitespace(str); } // Removes leading, trailing, and consecutive internal whitespace. -void RemoveExtraAsciiWhitespace(std::string*); +void RemoveExtraAsciiWhitespace(absl::Nonnull<std::string*> str); ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/strings/ascii_benchmark.cc b/absl/strings/ascii_benchmark.cc index aca458c8..4ae73174 100644 --- a/absl/strings/ascii_benchmark.cc +++ b/absl/strings/ascii_benchmark.cc @@ -14,7 +14,9 @@ #include "absl/strings/ascii.h" +#include <algorithm> #include <cctype> +#include <cstddef> #include <string> #include <array> #include <random> @@ -103,18 +105,28 @@ static void BM_StrToLower(benchmark::State& state) { const int size = state.range(0); std::string s(size, 'X'); for (auto _ : state) { - benchmark::DoNotOptimize(absl::AsciiStrToLower(s)); + benchmark::DoNotOptimize(s); + std::string res = absl::AsciiStrToLower(s); + benchmark::DoNotOptimize(res); } } -BENCHMARK(BM_StrToLower)->Range(1, 1 << 20); +BENCHMARK(BM_StrToLower) + ->DenseRange(0, 32) + ->RangeMultiplier(2) + ->Range(64, 1 << 26); static void BM_StrToUpper(benchmark::State& state) { const int size = state.range(0); std::string s(size, 'x'); for (auto _ : state) { - benchmark::DoNotOptimize(absl::AsciiStrToUpper(s)); + benchmark::DoNotOptimize(s); + std::string res = absl::AsciiStrToUpper(s); + benchmark::DoNotOptimize(res); } } -BENCHMARK(BM_StrToUpper)->Range(1, 1 << 20); +BENCHMARK(BM_StrToUpper) + ->DenseRange(0, 32) + ->RangeMultiplier(2) + ->Range(64, 1 << 26); } // namespace diff --git a/absl/strings/ascii_test.cc b/absl/strings/ascii_test.cc index dfed114c..117140c1 100644 --- a/absl/strings/ascii_test.cc +++ b/absl/strings/ascii_test.cc @@ -14,6 +14,7 @@ #include "absl/strings/ascii.h" +#include <algorithm> #include <cctype> #include <clocale> #include <cstring> @@ -21,7 +22,7 @@ #include "gtest/gtest.h" #include "absl/base/macros.h" -#include "absl/base/port.h" +#include "absl/strings/string_view.h" namespace { @@ -189,14 +190,14 @@ TEST(AsciiStrTo, Lower) { const std::string str("GHIJKL"); const std::string str2("MNOPQR"); const absl::string_view sp(str2); - std::string mutable_str("STUVWX"); + std::string mutable_str("_`?@[{AMNOPQRSTUVWXYZ"); EXPECT_EQ("abcdef", absl::AsciiStrToLower(buf)); EXPECT_EQ("ghijkl", absl::AsciiStrToLower(str)); EXPECT_EQ("mnopqr", absl::AsciiStrToLower(sp)); absl::AsciiStrToLower(&mutable_str); - EXPECT_EQ("stuvwx", mutable_str); + EXPECT_EQ("_`?@[{amnopqrstuvwxyz", mutable_str); char mutable_buf[] = "Mutable"; std::transform(mutable_buf, mutable_buf + strlen(mutable_buf), @@ -207,12 +208,12 @@ TEST(AsciiStrTo, Lower) { TEST(AsciiStrTo, Upper) { const char buf[] = "abcdef"; const std::string str("ghijkl"); - const std::string str2("mnopqr"); + const std::string str2("_`?@[{amnopqrstuvwxyz"); const absl::string_view sp(str2); EXPECT_EQ("ABCDEF", absl::AsciiStrToUpper(buf)); EXPECT_EQ("GHIJKL", absl::AsciiStrToUpper(str)); - EXPECT_EQ("MNOPQR", absl::AsciiStrToUpper(sp)); + EXPECT_EQ("_`?@[{AMNOPQRSTUVWXYZ", absl::AsciiStrToUpper(sp)); char mutable_buf[] = "Mutable"; std::transform(mutable_buf, mutable_buf + strlen(mutable_buf), diff --git a/absl/strings/char_formatting_test.cc b/absl/strings/char_formatting_test.cc new file mode 100644 index 00000000..1692da70 --- /dev/null +++ b/absl/strings/char_formatting_test.cc @@ -0,0 +1,169 @@ +// Copyright 2023 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 <cstddef> + +#include "gtest/gtest.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/substitute.h" + +namespace { + +TEST(CharFormatting, Char) { + const char v = 'A'; + + // Desired behavior: does not compile: + // EXPECT_EQ(absl::StrCat(v, "B"), "AB"); + // EXPECT_EQ(absl::StrFormat("%vB", v), "AB"); + + // Legacy behavior: format as char: + EXPECT_EQ(absl::Substitute("$0B", v), "AB"); +} + +enum CharEnum : char {}; +TEST(CharFormatting, CharEnum) { + auto v = static_cast<CharEnum>('A'); + + // Desired behavior: format as decimal + EXPECT_EQ(absl::StrFormat("%vB", v), "65B"); + EXPECT_EQ(absl::StrCat(v, "B"), "65B"); + + // Legacy behavior: format as character: + + // Some older versions of gcc behave differently in this one case +#if !defined(__GNUC__) || defined(__clang__) + EXPECT_EQ(absl::Substitute("$0B", v), "AB"); +#endif +} + +enum class CharEnumClass: char {}; +TEST(CharFormatting, CharEnumClass) { + auto v = static_cast<CharEnumClass>('A'); + + // Desired behavior: format as decimal: + EXPECT_EQ(absl::StrFormat("%vB", v), "65B"); + EXPECT_EQ(absl::StrCat(v, "B"), "65B"); + + // Legacy behavior: format as character: + EXPECT_EQ(absl::Substitute("$0B", v), "AB"); +} + +TEST(CharFormatting, UnsignedChar) { + const unsigned char v = 'A'; + + // Desired behavior: format as decimal: + EXPECT_EQ(absl::StrCat(v, "B"), "65B"); + EXPECT_EQ(absl::Substitute("$0B", v), "65B"); + EXPECT_EQ(absl::StrFormat("%vB", v), "65B"); + + // Signedness check + const unsigned char w = 255; + EXPECT_EQ(absl::StrCat(w, "B"), "255B"); + EXPECT_EQ(absl::Substitute("$0B", w), "255B"); + // EXPECT_EQ(absl::StrFormat("%vB", v), "255B"); +} + +TEST(CharFormatting, SignedChar) { + const signed char v = 'A'; + + // Desired behavior: format as decimal: + EXPECT_EQ(absl::StrCat(v, "B"), "65B"); + EXPECT_EQ(absl::Substitute("$0B", v), "65B"); + EXPECT_EQ(absl::StrFormat("%vB", v), "65B"); + + // Signedness check + const signed char w = -128; + EXPECT_EQ(absl::StrCat(w, "B"), "-128B"); + EXPECT_EQ(absl::Substitute("$0B", w), "-128B"); +} + +enum UnsignedCharEnum : unsigned char {}; +TEST(CharFormatting, UnsignedCharEnum) { + auto v = static_cast<UnsignedCharEnum>('A'); + + // Desired behavior: format as decimal: + EXPECT_EQ(absl::StrCat(v, "B"), "65B"); + EXPECT_EQ(absl::Substitute("$0B", v), "65B"); + EXPECT_EQ(absl::StrFormat("%vB", v), "65B"); + + // Signedness check + auto w = static_cast<UnsignedCharEnum>(255); + EXPECT_EQ(absl::StrCat(w, "B"), "255B"); + EXPECT_EQ(absl::Substitute("$0B", w), "255B"); + EXPECT_EQ(absl::StrFormat("%vB", w), "255B"); +} + +enum SignedCharEnum : signed char {}; +TEST(CharFormatting, SignedCharEnum) { + auto v = static_cast<SignedCharEnum>('A'); + + // Desired behavior: format as decimal: + EXPECT_EQ(absl::StrCat(v, "B"), "65B"); + EXPECT_EQ(absl::Substitute("$0B", v), "65B"); + EXPECT_EQ(absl::StrFormat("%vB", v), "65B"); + + // Signedness check + auto w = static_cast<SignedCharEnum>(-128); + EXPECT_EQ(absl::StrCat(w, "B"), "-128B"); + EXPECT_EQ(absl::Substitute("$0B", w), "-128B"); + EXPECT_EQ(absl::StrFormat("%vB", w), "-128B"); +} + +enum class UnsignedCharEnumClass : unsigned char {}; +TEST(CharFormatting, UnsignedCharEnumClass) { + auto v = static_cast<UnsignedCharEnumClass>('A'); + + // Desired behavior: format as decimal: + EXPECT_EQ(absl::StrCat(v, "B"), "65B"); + EXPECT_EQ(absl::Substitute("$0B", v), "65B"); + EXPECT_EQ(absl::StrFormat("%vB", v), "65B"); + + // Signedness check + auto w = static_cast<UnsignedCharEnumClass>(255); + EXPECT_EQ(absl::StrCat(w, "B"), "255B"); + EXPECT_EQ(absl::Substitute("$0B", w), "255B"); + EXPECT_EQ(absl::StrFormat("%vB", w), "255B"); +} + +enum SignedCharEnumClass : signed char {}; +TEST(CharFormatting, SignedCharEnumClass) { + auto v = static_cast<SignedCharEnumClass>('A'); + + // Desired behavior: format as decimal: + EXPECT_EQ(absl::StrCat(v, "B"), "65B"); + EXPECT_EQ(absl::Substitute("$0B", v), "65B"); + EXPECT_EQ(absl::StrFormat("%vB", v), "65B"); + + // Signedness check + auto w = static_cast<SignedCharEnumClass>(-128); + EXPECT_EQ(absl::StrCat(w, "B"), "-128B"); + EXPECT_EQ(absl::Substitute("$0B", w), "-128B"); + EXPECT_EQ(absl::StrFormat("%vB", w), "-128B"); +} + +#ifdef __cpp_lib_byte +TEST(CharFormatting, StdByte) { + auto v = static_cast<std::byte>('A'); + // Desired behavior: format as 0xff + // (No APIs do this today.) + + // Legacy behavior: format as decimal: + EXPECT_EQ(absl::StrCat(v, "B"), "65B"); + EXPECT_EQ(absl::Substitute("$0B", v), "65B"); + EXPECT_EQ(absl::StrFormat("%vB", v), "65B"); +} +#endif // _cpp_lib_byte + +} // namespace diff --git a/absl/strings/charconv.cc b/absl/strings/charconv.cc index 69d420bc..0c9227f8 100644 --- a/absl/strings/charconv.cc +++ b/absl/strings/charconv.cc @@ -16,11 +16,14 @@ #include <algorithm> #include <cassert> -#include <cmath> -#include <cstring> +#include <cstddef> +#include <cstdint> #include <limits> +#include <system_error> // NOLINT(build/c++11) #include "absl/base/casts.h" +#include "absl/base/config.h" +#include "absl/base/nullability.h" #include "absl/numeric/bits.h" #include "absl/numeric/int128.h" #include "absl/strings/internal/charconv_bigint.h" @@ -117,11 +120,18 @@ struct FloatTraits<double> { // Parsing a smaller N will produce something finite. static constexpr int kEiselLemireMaxExclusiveExp10 = 309; - static double MakeNan(const char* tagp) { + static double MakeNan(absl::Nonnull<const char*> tagp) { +#if ABSL_HAVE_BUILTIN(__builtin_nan) + // Use __builtin_nan() if available since it has a fix for + // https://bugs.llvm.org/show_bug.cgi?id=37778 + // std::nan may use the glibc implementation. + return __builtin_nan(tagp); +#else // Support nan no matter which namespace it's in. Some platforms // incorrectly don't put it in namespace std. using namespace std; // NOLINT return nan(tagp); +#endif } // Builds a nonzero floating point number out of the provided parts. @@ -183,11 +193,18 @@ struct FloatTraits<float> { static constexpr int kEiselLemireMinInclusiveExp10 = -46 - 18; static constexpr int kEiselLemireMaxExclusiveExp10 = 39; - static float MakeNan(const char* tagp) { + static float MakeNan(absl::Nonnull<const char*> tagp) { +#if ABSL_HAVE_BUILTIN(__builtin_nanf) + // Use __builtin_nanf() if available since it has a fix for + // https://bugs.llvm.org/show_bug.cgi?id=37778 + // std::nanf may use the glibc implementation. + return __builtin_nanf(tagp); +#else // Support nanf no matter which namespace it's in. Some platforms // incorrectly don't put it in namespace std. using namespace std; // NOLINT - return nanf(tagp); + return std::nanf(tagp); +#endif } static float Make(mantissa_t mantissa, int exponent, bool sign) { @@ -203,7 +220,8 @@ struct FloatTraits<float> { if (mantissa > kMantissaMask) { // Normal value. // Adjust by 127 for the exponent representation bias, and an additional - // 23 due to the implied decimal point in the IEEE mantissa represenation. + // 23 due to the implied decimal point in the IEEE mantissa + // representation. flt += static_cast<uint32_t>(exponent + 127 + kTargetMantissaBits - 1) << 23; mantissa &= kMantissaMask; @@ -327,7 +345,7 @@ int NormalizedShiftSize(int mantissa_width, int binary_exponent) { // `value` must be wider than the requested bit width. // // Returns the number of bits shifted. -int TruncateToBitWidth(int bit_width, uint128* value) { +int TruncateToBitWidth(int bit_width, absl::Nonnull<uint128*> value) { const int current_bit_width = BitWidth(*value); const int shift = current_bit_width - bit_width; *value >>= shift; @@ -339,7 +357,7 @@ int TruncateToBitWidth(int bit_width, uint128* value) { // the appropriate double, and returns true. template <typename FloatType> bool HandleEdgeCase(const strings_internal::ParsedFloat& input, bool negative, - FloatType* value) { + absl::Nonnull<FloatType*> value) { if (input.type == strings_internal::FloatType::kNan) { // 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 @@ -349,7 +367,8 @@ bool HandleEdgeCase(const strings_internal::ParsedFloat& input, bool negative, // 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) +#if (defined(__GNUC__) && !defined(__clang__)) || \ + (defined(__clang__) && __clang_major__ < 7) volatile char n_char_sequence[kNanBufferSize]; #else char n_char_sequence[kNanBufferSize]; @@ -387,7 +406,8 @@ bool HandleEdgeCase(const strings_internal::ParsedFloat& input, bool negative, // number is stored in *value. template <typename FloatType> void EncodeResult(const CalculatedFloat& calculated, bool negative, - absl::from_chars_result* result, FloatType* value) { + absl::Nonnull<absl::from_chars_result*> result, + absl::Nonnull<FloatType*> value) { if (calculated.exponent == kOverflow) { result->ec = std::errc::result_out_of_range; *value = negative ? -std::numeric_limits<FloatType>::max() @@ -433,7 +453,7 @@ void EncodeResult(const CalculatedFloat& calculated, bool negative, // Zero and negative values of `shift` are accepted, in which case the word is // shifted left, as necessary. uint64_t ShiftRightAndRound(uint128 value, int shift, bool input_exact, - bool* output_exact) { + absl::Nonnull<bool*> output_exact) { if (shift <= 0) { *output_exact = input_exact; return static_cast<uint64_t>(value << -shift); @@ -462,7 +482,7 @@ uint64_t ShiftRightAndRound(uint128 value, int shift, bool input_exact, // the low bit of `value` is set. // // In inexact mode, the nonzero error means the actual value is greater - // than the halfway point and we must alway round up. + // than the halfway point and we must always round up. if ((value & 1) == 1 || !input_exact) { ++value; } @@ -667,7 +687,8 @@ CalculatedFloat CalculateFromParsedDecimal( // this function returns false) is both fast and correct. template <typename FloatType> bool EiselLemire(const strings_internal::ParsedFloat& input, bool negative, - FloatType* value, std::errc* ec) { + absl::Nonnull<FloatType*> value, + absl::Nonnull<std::errc*> ec) { uint64_t man = input.mantissa; int exp10 = input.exponent; if (exp10 < FloatTraits<FloatType>::kEiselLemireMinInclusiveExp10) { @@ -840,7 +861,8 @@ bool EiselLemire(const strings_internal::ParsedFloat& input, bool negative, } template <typename FloatType> -from_chars_result FromCharsImpl(const char* first, const char* last, +from_chars_result FromCharsImpl(absl::Nonnull<const char*> first, + absl::Nonnull<const char*> last, FloatType& value, chars_format fmt_flags) { from_chars_result result; result.ptr = first; // overwritten on successful parse @@ -926,12 +948,14 @@ from_chars_result FromCharsImpl(const char* first, const char* last, } } // namespace -from_chars_result from_chars(const char* first, const char* last, double& value, +from_chars_result from_chars(absl::Nonnull<const char*> first, + absl::Nonnull<const char*> last, double& value, chars_format fmt) { return FromCharsImpl(first, last, value, fmt); } -from_chars_result from_chars(const char* first, const char* last, float& value, +from_chars_result from_chars(absl::Nonnull<const char*> first, + absl::Nonnull<const char*> last, float& value, chars_format fmt) { return FromCharsImpl(first, last, value, fmt); } diff --git a/absl/strings/charconv.h b/absl/strings/charconv.h index 7c509812..be250902 100644 --- a/absl/strings/charconv.h +++ b/absl/strings/charconv.h @@ -18,11 +18,12 @@ #include <system_error> // NOLINT(build/c++11) #include "absl/base/config.h" +#include "absl/base/nullability.h" namespace absl { ABSL_NAMESPACE_BEGIN -// Workalike compatibilty version of std::chars_format from C++17. +// Workalike compatibility version of std::chars_format from C++17. // // This is an bitfield enumerator which can be passed to absl::from_chars to // configure the string-to-float conversion. @@ -44,11 +45,11 @@ enum class chars_format { // characters that were successfully parsed. If none was found, `ptr` is set // to the `first` argument to from_chars. struct from_chars_result { - const char* ptr; + absl::Nonnull<const char*> ptr; std::errc ec; }; -// Workalike compatibilty version of std::from_chars from C++17. Currently +// Workalike compatibility version of std::from_chars from C++17. Currently // this only supports the `double` and `float` types. // // This interface incorporates the proposed resolutions for library issues @@ -76,11 +77,13 @@ struct from_chars_result { // format that strtod() accepts, except that a "0x" prefix is NOT matched. // (In particular, in `hex` mode, the input "0xff" results in the largest // matching pattern "0".) -absl::from_chars_result from_chars(const char* first, const char* last, +absl::from_chars_result from_chars(absl::Nonnull<const char*> first, + absl::Nonnull<const char*> last, double& value, // NOLINT chars_format fmt = chars_format::general); -absl::from_chars_result from_chars(const char* first, const char* last, +absl::from_chars_result from_chars(absl::Nonnull<const char*> first, + absl::Nonnull<const char*> last, float& value, // NOLINT chars_format fmt = chars_format::general); diff --git a/absl/strings/charconv_test.cc b/absl/strings/charconv_test.cc index b83de5a0..c16c735c 100644 --- a/absl/strings/charconv_test.cc +++ b/absl/strings/charconv_test.cc @@ -14,14 +14,19 @@ #include "absl/strings/charconv.h" +#include <cfloat> +#include <cmath> #include <cstdlib> +#include <functional> +#include <limits> #include <string> +#include <system_error> // NOLINT(build/c++11) -#include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/strings/internal/pow10_helper.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" #ifdef _MSC_FULL_VER #define ABSL_COMPILER_DOES_EXACT_ROUNDING 0 diff --git a/absl/strings/charset.h b/absl/strings/charset.h new file mode 100644 index 00000000..ff4e81a4 --- /dev/null +++ b/absl/strings/charset.h @@ -0,0 +1,164 @@ +// 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. +// +// ----------------------------------------------------------------------------- +// File: charset.h +// ----------------------------------------------------------------------------- +// +// This file contains absl::CharSet, a fast, bit-vector set of 8-bit unsigned +// characters. +// +// Instances can be initialized as constexpr constants. For example: +// +// constexpr absl::CharSet kJustX = absl::CharSet::Char('x'); +// constexpr absl::CharSet kMySymbols = absl::CharSet("$@!"); +// constexpr absl::CharSet kLetters = absl::CharSet::Range('a', 'z'); +// +// Multiple instances can be combined that still forms a constexpr expression. +// For example: +// +// constexpr absl::CharSet kLettersAndNumbers = +// absl::CharSet::Range('a', 'z') | absl::CharSet::Range('0', '9'); +// +// Several pre-defined character classes are available that mirror the methods +// from <cctype>. For example: +// +// constexpr absl::CharSet kLettersAndWhitespace = +// absl::CharSet::AsciiAlphabet() | absl::CharSet::AsciiWhitespace(); +// +// To check membership, use the .contains method, e.g. +// +// absl::CharSet hex_letters("abcdef"); +// hex_letters.contains('a'); // true +// hex_letters.contains('g'); // false + +#ifndef ABSL_STRINGS_CHARSET_H_ +#define ABSL_STRINGS_CHARSET_H_ + +#include <cstddef> +#include <cstdint> +#include <cstring> + +#include "absl/base/macros.h" +#include "absl/base/port.h" +#include "absl/strings/string_view.h" + +namespace absl { + +class CharSet { + public: + constexpr CharSet() : m_() {} + + // Initializes with a given string_view. + constexpr explicit CharSet(absl::string_view str) : m_() { + for (char c : str) { + SetChar(static_cast<unsigned char>(c)); + } + } + + constexpr bool contains(char c) const { + return ((m_[static_cast<unsigned char>(c) / 64] >> + (static_cast<unsigned char>(c) % 64)) & + 0x1) == 0x1; + } + + constexpr bool empty() const { + for (uint64_t c : m_) { + if (c != 0) return false; + } + return true; + } + + // Containing only a single specified char. + static constexpr CharSet Char(char x) { + return CharSet(CharMaskForWord(x, 0), CharMaskForWord(x, 1), + CharMaskForWord(x, 2), CharMaskForWord(x, 3)); + } + + // Containing all the chars in the closed interval [lo,hi]. + static constexpr CharSet Range(char lo, char hi) { + return CharSet(RangeForWord(lo, hi, 0), RangeForWord(lo, hi, 1), + RangeForWord(lo, hi, 2), RangeForWord(lo, hi, 3)); + } + + friend constexpr CharSet operator&(const CharSet& a, const CharSet& b) { + return CharSet(a.m_[0] & b.m_[0], a.m_[1] & b.m_[1], a.m_[2] & b.m_[2], + a.m_[3] & b.m_[3]); + } + + friend constexpr CharSet operator|(const CharSet& a, const CharSet& b) { + return CharSet(a.m_[0] | b.m_[0], a.m_[1] | b.m_[1], a.m_[2] | b.m_[2], + a.m_[3] | b.m_[3]); + } + + friend constexpr CharSet operator~(const CharSet& a) { + return CharSet(~a.m_[0], ~a.m_[1], ~a.m_[2], ~a.m_[3]); + } + + // Mirrors the char-classifying predicates in <cctype>. + static constexpr CharSet AsciiUppercase() { return CharSet::Range('A', 'Z'); } + static constexpr CharSet AsciiLowercase() { return CharSet::Range('a', 'z'); } + static constexpr CharSet AsciiDigits() { return CharSet::Range('0', '9'); } + static constexpr CharSet AsciiAlphabet() { + return AsciiLowercase() | AsciiUppercase(); + } + static constexpr CharSet AsciiAlphanumerics() { + return AsciiDigits() | AsciiAlphabet(); + } + static constexpr CharSet AsciiHexDigits() { + return AsciiDigits() | CharSet::Range('A', 'F') | CharSet::Range('a', 'f'); + } + static constexpr CharSet AsciiPrintable() { + return CharSet::Range(0x20, 0x7e); + } + static constexpr CharSet AsciiWhitespace() { return CharSet("\t\n\v\f\r "); } + static constexpr CharSet AsciiPunctuation() { + return AsciiPrintable() & ~AsciiWhitespace() & ~AsciiAlphanumerics(); + } + + private: + constexpr CharSet(uint64_t b0, uint64_t b1, uint64_t b2, uint64_t b3) + : m_{b0, b1, b2, b3} {} + + static constexpr uint64_t RangeForWord(char lo, char hi, uint64_t word) { + return OpenRangeFromZeroForWord(static_cast<unsigned char>(hi) + 1, word) & + ~OpenRangeFromZeroForWord(static_cast<unsigned char>(lo), word); + } + + // All the chars in the specified word of the range [0, upper). + static constexpr uint64_t OpenRangeFromZeroForWord(uint64_t upper, + uint64_t word) { + return (upper <= 64 * word) ? 0 + : (upper >= 64 * (word + 1)) + ? ~static_cast<uint64_t>(0) + : (~static_cast<uint64_t>(0) >> (64 - upper % 64)); + } + + static constexpr uint64_t CharMaskForWord(char x, uint64_t word) { + return (static_cast<unsigned char>(x) / 64 == word) + ? (static_cast<uint64_t>(1) + << (static_cast<unsigned char>(x) % 64)) + : 0; + } + + constexpr void SetChar(unsigned char c) { + m_[c / 64] |= static_cast<uint64_t>(1) << (c % 64); + } + + uint64_t m_[4]; +}; + +} // namespace absl + +#endif // ABSL_STRINGS_CHARSET_H_ diff --git a/absl/strings/internal/char_map_benchmark.cc b/absl/strings/charset_benchmark.cc index 5cef967b..bf7ae560 100644 --- a/absl/strings/internal/char_map_benchmark.cc +++ b/absl/strings/charset_benchmark.cc @@ -1,4 +1,4 @@ -// Copyright 2017 The Abseil Authors. +// Copyright 2020 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. @@ -12,30 +12,30 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "absl/strings/internal/char_map.h" - #include <cstdint> #include "benchmark/benchmark.h" +#include "absl/log/check.h" +#include "absl/strings/charset.h" namespace { -absl::strings_internal::Charmap MakeBenchmarkMap() { - absl::strings_internal::Charmap m; +absl::CharSet MakeBenchmarkMap() { + absl::CharSet m; uint32_t x[] = {0x0, 0x1, 0x2, 0x3, 0xf, 0xe, 0xd, 0xc}; for (uint32_t& t : x) t *= static_cast<uint32_t>(0x11111111UL); for (uint32_t i = 0; i < 256; ++i) { - if ((x[i / 32] >> (i % 32)) & 1) - m = m | absl::strings_internal::Charmap::Char(i); + if ((x[i / 32] >> (i % 32)) & 1) m = m | absl::CharSet::Char(i); } return m; } // Micro-benchmark for Charmap::contains. -void BM_Contains(benchmark::State& state) { +static void BM_Contains(benchmark::State& state) { // Loop-body replicated 10 times to increase time per iteration. // Argument continuously changed to avoid generating common subexpressions. - const absl::strings_internal::Charmap benchmark_map = MakeBenchmarkMap(); + // Final CHECK used to discourage unwanted optimization. + const absl::CharSet benchmark_map = MakeBenchmarkMap(); unsigned char c = 0; int ops = 0; for (auto _ : state) { @@ -50,12 +50,8 @@ void BM_Contains(benchmark::State& state) { ops += benchmark_map.contains(c++); ops += benchmark_map.contains(c++); } - benchmark::DoNotOptimize(ops); + CHECK_NE(ops, -1); } BENCHMARK(BM_Contains); -// We don't bother benchmarking Charmap::IsZero or Charmap::IntersectsWith; -// their running time is data-dependent and it is not worth characterizing -// "typical" data. - } // namespace diff --git a/absl/strings/charset_test.cc b/absl/strings/charset_test.cc new file mode 100644 index 00000000..fff943ae --- /dev/null +++ b/absl/strings/charset_test.cc @@ -0,0 +1,181 @@ +// Copyright 2020 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/charset.h" + +#include <stdio.h> +#include <stdlib.h> + +#include <string> +#include <vector> + +#include "gtest/gtest.h" +#include "absl/strings/ascii.h" +#include "absl/strings/string_view.h" + +namespace { + +constexpr absl::CharSet everything_map = ~absl::CharSet(); +constexpr absl::CharSet nothing_map = absl::CharSet(); + +TEST(Charmap, AllTests) { + const absl::CharSet also_nothing_map(""); + EXPECT_TRUE(everything_map.contains('\0')); + EXPECT_FALSE(nothing_map.contains('\0')); + EXPECT_FALSE(also_nothing_map.contains('\0')); + for (unsigned char ch = 1; ch != 0; ++ch) { + SCOPED_TRACE(ch); + EXPECT_TRUE(everything_map.contains(ch)); + EXPECT_FALSE(nothing_map.contains(ch)); + EXPECT_FALSE(also_nothing_map.contains(ch)); + } + + const absl::CharSet symbols(absl::string_view("&@#@^!@?", 5)); + EXPECT_TRUE(symbols.contains('&')); + EXPECT_TRUE(symbols.contains('@')); + EXPECT_TRUE(symbols.contains('#')); + EXPECT_TRUE(symbols.contains('^')); + EXPECT_FALSE(symbols.contains('!')); + EXPECT_FALSE(symbols.contains('?')); + int cnt = 0; + for (unsigned char ch = 1; ch != 0; ++ch) cnt += symbols.contains(ch); + EXPECT_EQ(cnt, 4); + + const absl::CharSet lets(absl::string_view("^abcde", 3)); + const absl::CharSet lets2(absl::string_view("fghij\0klmnop", 10)); + const absl::CharSet lets3("fghij\0klmnop"); + EXPECT_TRUE(lets2.contains('k')); + EXPECT_FALSE(lets3.contains('k')); + + EXPECT_FALSE((symbols & lets).empty()); + EXPECT_TRUE((lets2 & lets).empty()); + EXPECT_FALSE((lets & symbols).empty()); + EXPECT_TRUE((lets & lets2).empty()); + + EXPECT_TRUE(nothing_map.empty()); + EXPECT_FALSE(lets.empty()); +} + +std::string Members(const absl::CharSet& m) { + std::string r; + for (size_t i = 0; i < 256; ++i) + if (m.contains(i)) r.push_back(i); + return r; +} + +std::string ClosedRangeString(unsigned char lo, unsigned char hi) { + // Don't depend on lo<hi. Just increment until lo==hi. + std::string s; + while (true) { + s.push_back(lo); + if (lo == hi) break; + ++lo; + } + return s; +} + +TEST(Charmap, Constexpr) { + constexpr absl::CharSet kEmpty = absl::CharSet(); + EXPECT_EQ(Members(kEmpty), ""); + constexpr absl::CharSet kA = absl::CharSet::Char('A'); + EXPECT_EQ(Members(kA), "A"); + constexpr absl::CharSet kAZ = absl::CharSet::Range('A', 'Z'); + EXPECT_EQ(Members(kAZ), "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); + constexpr absl::CharSet kIdentifier = + absl::CharSet::Range('0', '9') | absl::CharSet::Range('A', 'Z') | + absl::CharSet::Range('a', 'z') | absl::CharSet::Char('_'); + EXPECT_EQ(Members(kIdentifier), + "0123456789" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "_" + "abcdefghijklmnopqrstuvwxyz"); + constexpr absl::CharSet kAll = ~absl::CharSet(); + for (size_t i = 0; i < 256; ++i) { + SCOPED_TRACE(i); + EXPECT_TRUE(kAll.contains(i)); + } + constexpr absl::CharSet kHello = absl::CharSet("Hello, world!"); + EXPECT_EQ(Members(kHello), " !,Hdelorw"); + + // test negation and intersection + constexpr absl::CharSet kABC = + absl::CharSet::Range('A', 'Z') & ~absl::CharSet::Range('D', 'Z'); + EXPECT_EQ(Members(kABC), "ABC"); + + // contains + constexpr bool kContainsA = absl::CharSet("abc").contains('a'); + EXPECT_TRUE(kContainsA); + constexpr bool kContainsD = absl::CharSet("abc").contains('d'); + EXPECT_FALSE(kContainsD); + + // empty + constexpr bool kEmptyIsEmpty = absl::CharSet().empty(); + EXPECT_TRUE(kEmptyIsEmpty); + constexpr bool kNotEmptyIsEmpty = absl::CharSet("abc").empty(); + EXPECT_FALSE(kNotEmptyIsEmpty); +} + +TEST(Charmap, Range) { + // Exhaustive testing takes too long, so test some of the boundaries that + // are perhaps going to cause trouble. + std::vector<size_t> poi = {0, 1, 2, 3, 4, 7, 8, 9, 15, + 16, 17, 30, 31, 32, 33, 63, 64, 65, + 127, 128, 129, 223, 224, 225, 254, 255}; + for (auto lo = poi.begin(); lo != poi.end(); ++lo) { + SCOPED_TRACE(*lo); + for (auto hi = lo; hi != poi.end(); ++hi) { + SCOPED_TRACE(*hi); + EXPECT_EQ(Members(absl::CharSet::Range(*lo, *hi)), + ClosedRangeString(*lo, *hi)); + } + } +} + +TEST(Charmap, NullByteWithStringView) { + char characters[5] = {'a', 'b', '\0', 'd', 'x'}; + absl::string_view view(characters, 5); + absl::CharSet tester(view); + EXPECT_TRUE(tester.contains('a')); + EXPECT_TRUE(tester.contains('b')); + EXPECT_TRUE(tester.contains('\0')); + EXPECT_TRUE(tester.contains('d')); + EXPECT_TRUE(tester.contains('x')); + EXPECT_FALSE(tester.contains('c')); +} + +TEST(CharmapCtype, Match) { + for (int c = 0; c < 256; ++c) { + SCOPED_TRACE(c); + SCOPED_TRACE(static_cast<char>(c)); + EXPECT_EQ(absl::ascii_isupper(c), + absl::CharSet::AsciiUppercase().contains(c)); + EXPECT_EQ(absl::ascii_islower(c), + absl::CharSet::AsciiLowercase().contains(c)); + EXPECT_EQ(absl::ascii_isdigit(c), absl::CharSet::AsciiDigits().contains(c)); + EXPECT_EQ(absl::ascii_isalpha(c), + absl::CharSet::AsciiAlphabet().contains(c)); + EXPECT_EQ(absl::ascii_isalnum(c), + absl::CharSet::AsciiAlphanumerics().contains(c)); + EXPECT_EQ(absl::ascii_isxdigit(c), + absl::CharSet::AsciiHexDigits().contains(c)); + EXPECT_EQ(absl::ascii_isprint(c), + absl::CharSet::AsciiPrintable().contains(c)); + EXPECT_EQ(absl::ascii_isspace(c), + absl::CharSet::AsciiWhitespace().contains(c)); + EXPECT_EQ(absl::ascii_ispunct(c), + absl::CharSet::AsciiPunctuation().contains(c)); + } +} + +} // namespace diff --git a/absl/strings/cord.cc b/absl/strings/cord.cc index 1d33dd83..f67326fd 100644 --- a/absl/strings/cord.cc +++ b/absl/strings/cord.cc @@ -15,27 +15,33 @@ #include "absl/strings/cord.h" #include <algorithm> -#include <atomic> +#include <cassert> #include <cstddef> +#include <cstdint> #include <cstdio> #include <cstdlib> +#include <cstring> #include <iomanip> #include <ios> #include <iostream> #include <limits> +#include <memory> #include <ostream> #include <sstream> -#include <type_traits> -#include <unordered_set> -#include <vector> +#include <string> +#include <utility> -#include "absl/base/casts.h" +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/internal/endian.h" #include "absl/base/internal/raw_logging.h" #include "absl/base/macros.h" -#include "absl/base/port.h" -#include "absl/container/fixed_array.h" +#include "absl/base/optimization.h" +#include "absl/base/nullability.h" #include "absl/container/inlined_vector.h" +#include "absl/crc/crc32c.h" #include "absl/crc/internal/crc_cord_state.h" +#include "absl/functional/function_ref.h" #include "absl/strings/cord_buffer.h" #include "absl/strings/escaping.h" #include "absl/strings/internal/cord_data_edge.h" @@ -43,14 +49,14 @@ #include "absl/strings/internal/cord_rep_btree.h" #include "absl/strings/internal/cord_rep_crc.h" #include "absl/strings/internal/cord_rep_flat.h" -#include "absl/strings/internal/cordz_statistics.h" -#include "absl/strings/internal/cordz_update_scope.h" #include "absl/strings/internal/cordz_update_tracker.h" #include "absl/strings/internal/resize_uninitialized.h" +#include "absl/strings/match.h" #include "absl/strings/str_cat.h" -#include "absl/strings/str_format.h" -#include "absl/strings/str_join.h" #include "absl/strings/string_view.h" +#include "absl/strings/strip.h" +#include "absl/types/optional.h" +#include "absl/types/span.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -69,29 +75,21 @@ using ::absl::cord_internal::kMinFlatLength; using ::absl::cord_internal::kInlinedVectorSize; using ::absl::cord_internal::kMaxBytesToCopy; -static void DumpNode(CordRep* rep, bool include_data, std::ostream* os, - int indent = 0); -static bool VerifyNode(CordRep* root, CordRep* start_node, - bool full_validation); - -static inline CordRep* VerifyTree(CordRep* node) { - // Verification is expensive, so only do it in debug mode. - // Even in debug mode we normally do only light validation. - // If you are debugging Cord itself, you should define the - // macro EXTRA_CORD_VALIDATION, e.g. by adding - // --copt=-DEXTRA_CORD_VALIDATION to the blaze line. -#ifdef EXTRA_CORD_VALIDATION - assert(node == nullptr || VerifyNode(node, node, /*full_validation=*/true)); -#else // EXTRA_CORD_VALIDATION - assert(node == nullptr || VerifyNode(node, node, /*full_validation=*/false)); -#endif // EXTRA_CORD_VALIDATION - static_cast<void>(&VerifyNode); +static void DumpNode(absl::Nonnull<CordRep*> rep, bool include_data, + absl::Nonnull<std::ostream*> os, int indent = 0); +static bool VerifyNode(absl::Nonnull<CordRep*> root, + absl::Nonnull<CordRep*> start_node); +static inline absl::Nullable<CordRep*> VerifyTree( + absl::Nullable<CordRep*> node) { + assert(node == nullptr || VerifyNode(node, node)); + static_cast<void>(&VerifyNode); return node; } -static CordRepFlat* CreateFlat(const char* data, size_t length, - size_t alloc_hint) { +static absl::Nonnull<CordRepFlat*> CreateFlat(absl::Nonnull<const char*> data, + size_t length, + size_t alloc_hint) { CordRepFlat* flat = CordRepFlat::New(length + alloc_hint); flat->length = length; memcpy(flat->Data(), data, length); @@ -100,7 +98,8 @@ static CordRepFlat* CreateFlat(const char* data, size_t length, // Creates a new flat or Btree out of the specified array. // The returned node has a refcount of 1. -static CordRep* NewBtree(const char* data, size_t length, size_t alloc_hint) { +static absl::Nonnull<CordRep*> NewBtree(absl::Nonnull<const char*> data, + size_t length, size_t alloc_hint) { if (length <= kMaxFlatLength) { return CreateFlat(data, length, alloc_hint); } @@ -113,14 +112,16 @@ static CordRep* NewBtree(const char* data, size_t length, size_t alloc_hint) { // Create a new tree out of the specified array. // The returned node has a refcount of 1. -static CordRep* NewTree(const char* data, size_t length, size_t alloc_hint) { +static absl::Nullable<CordRep*> NewTree(absl::Nullable<const char*> data, + size_t length, size_t alloc_hint) { if (length == 0) return nullptr; return NewBtree(data, length, alloc_hint); } namespace cord_internal { -void InitializeCordRepExternal(absl::string_view data, CordRepExternal* rep) { +void InitializeCordRepExternal(absl::string_view data, + absl::Nonnull<CordRepExternal*> rep) { assert(!data.empty()); rep->length = data.size(); rep->tag = EXTERNAL; @@ -134,7 +135,7 @@ void InitializeCordRepExternal(absl::string_view data, CordRepExternal* rep) { // and not wasteful, we move the string into an external cord rep, preserving // the already allocated string contents. // Requires the provided string length to be larger than `kMaxInline`. -static CordRep* CordRepFromString(std::string&& src) { +static absl::Nonnull<CordRep*> CordRepFromString(std::string&& src) { assert(src.length() > cord_internal::kMaxInline); if ( // String is short: copy data to avoid external block overhead. @@ -166,12 +167,13 @@ static CordRep* CordRepFromString(std::string&& src) { constexpr unsigned char Cord::InlineRep::kMaxInline; #endif -inline void Cord::InlineRep::set_data(const char* data, size_t n) { +inline void Cord::InlineRep::set_data(absl::Nonnull<const char*> data, + size_t n) { static_assert(kMaxInline == 15, "set_data is hard-coded for a length of 15"); data_.set_inline_data(data, n); } -inline char* Cord::InlineRep::set_data(size_t n) { +inline absl::Nonnull<char*> Cord::InlineRep::set_data(size_t n) { assert(n <= kMaxInline); ResetToEmpty(); set_inline_size(n); @@ -195,13 +197,13 @@ inline void Cord::InlineRep::remove_prefix(size_t n) { // Returns `rep` converted into a CordRepBtree. // Directly returns `rep` if `rep` is already a CordRepBtree. -static CordRepBtree* ForceBtree(CordRep* rep) { +static absl::Nonnull<CordRepBtree*> ForceBtree(CordRep* rep) { return rep->IsBtree() ? rep->btree() : CordRepBtree::Create(cord_internal::RemoveCrcNode(rep)); } -void Cord::InlineRep::AppendTreeToInlined(CordRep* tree, +void Cord::InlineRep::AppendTreeToInlined(absl::Nonnull<CordRep*> tree, MethodIdentifier method) { assert(!is_tree()); if (!data_.is_empty()) { @@ -211,14 +213,16 @@ void Cord::InlineRep::AppendTreeToInlined(CordRep* tree, EmplaceTree(tree, method); } -void Cord::InlineRep::AppendTreeToTree(CordRep* tree, MethodIdentifier method) { +void Cord::InlineRep::AppendTreeToTree(absl::Nonnull<CordRep*> tree, + MethodIdentifier method) { assert(is_tree()); const CordzUpdateScope scope(data_.cordz_info(), method); tree = CordRepBtree::Append(ForceBtree(data_.as_tree()), tree); SetTree(tree, scope); } -void Cord::InlineRep::AppendTree(CordRep* tree, MethodIdentifier method) { +void Cord::InlineRep::AppendTree(absl::Nonnull<CordRep*> tree, + MethodIdentifier method) { assert(tree != nullptr); assert(tree->length != 0); assert(!tree->IsCrc()); @@ -229,7 +233,7 @@ void Cord::InlineRep::AppendTree(CordRep* tree, MethodIdentifier method) { } } -void Cord::InlineRep::PrependTreeToInlined(CordRep* tree, +void Cord::InlineRep::PrependTreeToInlined(absl::Nonnull<CordRep*> tree, MethodIdentifier method) { assert(!is_tree()); if (!data_.is_empty()) { @@ -239,7 +243,7 @@ void Cord::InlineRep::PrependTreeToInlined(CordRep* tree, EmplaceTree(tree, method); } -void Cord::InlineRep::PrependTreeToTree(CordRep* tree, +void Cord::InlineRep::PrependTreeToTree(absl::Nonnull<CordRep*> tree, MethodIdentifier method) { assert(is_tree()); const CordzUpdateScope scope(data_.cordz_info(), method); @@ -247,7 +251,8 @@ void Cord::InlineRep::PrependTreeToTree(CordRep* tree, SetTree(tree, scope); } -void Cord::InlineRep::PrependTree(CordRep* tree, MethodIdentifier method) { +void Cord::InlineRep::PrependTree(absl::Nonnull<CordRep*> tree, + MethodIdentifier method) { assert(tree != nullptr); assert(tree->length != 0); assert(!tree->IsCrc()); @@ -262,8 +267,9 @@ void Cord::InlineRep::PrependTree(CordRep* tree, MethodIdentifier method) { // suitable leaf is found, the function will update the length field for all // nodes to account for the size increase. The append region address will be // written to region and the actual size increase will be written to size. -static inline bool PrepareAppendRegion(CordRep* root, char** region, - size_t* size, size_t max_length) { +static inline bool PrepareAppendRegion( + absl::Nonnull<CordRep*> root, absl::Nonnull<absl::Nullable<char*>*> region, + absl::Nonnull<size_t*> size, size_t max_length) { if (root->IsBtree() && root->refcount.IsOne()) { Span<char> span = root->btree()->GetAppendBuffer(max_length); if (!span.empty()) { @@ -466,11 +472,11 @@ void Cord::InlineRep::AppendArray(absl::string_view src, CommitTree(root, rep, scope, method); } -inline CordRep* Cord::TakeRep() const& { +inline absl::Nonnull<CordRep*> Cord::TakeRep() const& { return CordRep::Ref(contents_.tree()); } -inline CordRep* Cord::TakeRep() && { +inline absl::Nonnull<CordRep*> Cord::TakeRep() && { CordRep* rep = contents_.tree(); contents_.clear(); return rep; @@ -528,7 +534,7 @@ inline void Cord::AppendImpl(C&& src) { contents_.AppendTree(rep, CordzUpdateTracker::kAppendCord); } -static CordRep::ExtractResult ExtractAppendBuffer(CordRep* rep, +static CordRep::ExtractResult ExtractAppendBuffer(absl::Nonnull<CordRep*> rep, size_t min_capacity) { switch (rep->tag) { case cord_internal::BTREE: @@ -574,13 +580,9 @@ CordBuffer Cord::GetAppendBufferSlowPath(size_t block_size, size_t capacity, return CreateAppendBuffer(contents_.data_, block_size, capacity); } -void Cord::Append(const Cord& src) { - AppendImpl(src); -} +void Cord::Append(const Cord& src) { AppendImpl(src); } -void Cord::Append(Cord&& src) { - AppendImpl(std::move(src)); -} +void Cord::Append(Cord&& src) { AppendImpl(std::move(src)); } template <typename T, Cord::EnableIfString<T>> void Cord::Append(T&& src) { @@ -779,8 +781,9 @@ int ClampResult(int memcmp_res) { return static_cast<int>(memcmp_res > 0) - static_cast<int>(memcmp_res < 0); } -int CompareChunks(absl::string_view* lhs, absl::string_view* rhs, - size_t* size_to_compare) { +int CompareChunks(absl::Nonnull<absl::string_view*> lhs, + absl::Nonnull<absl::string_view*> rhs, + absl::Nonnull<size_t*> size_to_compare) { size_t compared_size = std::min(lhs->size(), rhs->size()); assert(*size_to_compare >= compared_size); *size_to_compare -= compared_size; @@ -795,7 +798,7 @@ int CompareChunks(absl::string_view* lhs, absl::string_view* rhs, } // This overload set computes comparison results from memcmp result. This -// interface is used inside GenericCompare below. Differet implementations +// interface is used inside GenericCompare below. Different implementations // are specialized for int and bool. For int we clamp result to {-1, 0, 1} // set. For bool we just interested in "value == 0". template <typename ResultType> @@ -878,7 +881,8 @@ void Cord::SetExpectedChecksum(uint32_t crc) { SetCrcCordState(std::move(state)); } -const crc_internal::CrcCordState* Cord::MaybeGetCrcCordState() const { +absl::Nullable<const crc_internal::CrcCordState*> Cord::MaybeGetCrcCordState() + const { if (!contents_.is_tree() || !contents_.tree()->IsCrc()) { return nullptr; } @@ -895,7 +899,8 @@ absl::optional<uint32_t> Cord::ExpectedChecksum() const { inline int Cord::CompareSlowPath(absl::string_view rhs, size_t compared_size, size_t size_to_compare) const { - auto advance = [](Cord::ChunkIterator* it, absl::string_view* chunk) { + auto advance = [](absl::Nonnull<Cord::ChunkIterator*> it, + absl::Nonnull<absl::string_view*> chunk) { if (!chunk->empty()) return true; ++*it; if (it->bytes_remaining_ == 0) return false; @@ -925,7 +930,8 @@ inline int Cord::CompareSlowPath(absl::string_view rhs, size_t compared_size, inline int Cord::CompareSlowPath(const Cord& rhs, size_t compared_size, size_t size_to_compare) const { - auto advance = [](Cord::ChunkIterator* it, absl::string_view* chunk) { + auto advance = [](absl::Nonnull<Cord::ChunkIterator*> it, + absl::Nonnull<absl::string_view*> chunk) { if (!chunk->empty()) return true; ++*it; if (it->bytes_remaining_ == 0) return false; @@ -1047,7 +1053,7 @@ Cord::operator std::string() const { return s; } -void CopyCordToString(const Cord& src, std::string* dst) { +void CopyCordToString(const Cord& src, absl::Nonnull<std::string*> dst) { if (!src.contents_.is_tree()) { src.contents_.CopyTo(dst); } else { @@ -1056,7 +1062,7 @@ void CopyCordToString(const Cord& src, std::string* dst) { } } -void Cord::CopyToArraySlowPath(char* dst) const { +void Cord::CopyToArraySlowPath(absl::Nonnull<char*> dst) const { assert(contents_.is_tree()); absl::string_view fragment; if (GetFlatAux(contents_.tree(), &fragment)) { @@ -1166,6 +1172,194 @@ char Cord::operator[](size_t i) const { } } +namespace { + +// Tests whether the sequence of chunks beginning at `position` starts with +// `needle`. +// +// REQUIRES: remaining `absl::Cord` starting at `position` is greater than or +// equal to `needle.size()`. +bool IsSubstringInCordAt(absl::Cord::CharIterator position, + absl::string_view needle) { + auto haystack_chunk = absl::Cord::ChunkRemaining(position); + while (true) { + // Precondition is that `absl::Cord::ChunkRemaining(position)` is not + // empty. This assert will trigger if that is not true. + assert(!haystack_chunk.empty()); + auto min_length = std::min(haystack_chunk.size(), needle.size()); + if (!absl::ConsumePrefix(&needle, haystack_chunk.substr(0, min_length))) { + return false; + } + if (needle.empty()) { + return true; + } + absl::Cord::Advance(&position, min_length); + haystack_chunk = absl::Cord::ChunkRemaining(position); + } +} + +} // namespace + +// A few options how this could be implemented: +// (a) Flatten the Cord and find, i.e. +// haystack.Flatten().find(needle) +// For large 'haystack' (where Cord makes sense to be used), this copies +// the whole 'haystack' and can be slow. +// (b) Use std::search, i.e. +// std::search(haystack.char_begin(), haystack.char_end(), +// needle.begin(), needle.end()) +// This avoids the copy, but compares one byte at a time, and branches a +// lot every time it has to advance. It is also not possible to use +// std::search as is, because CharIterator is only an input iterator, not a +// forward iterator. +// (c) Use string_view::find in each fragment, and specifically handle fragment +// boundaries. +// +// This currently implements option (b). +absl::Cord::CharIterator absl::Cord::FindImpl(CharIterator it, + absl::string_view needle) const { + // Ensure preconditions are met by callers first. + + // Needle must not be empty. + assert(!needle.empty()); + // Haystack must be at least as large as needle. + assert(it.chunk_iterator_.bytes_remaining_ >= needle.size()); + + // Cord is a sequence of chunks. To find `needle` we go chunk by chunk looking + // for the first char of needle, up until we have advanced `N` defined as + // `haystack.size() - needle.size()`. If we find the first char of needle at + // `P` and `P` is less than `N`, we then call `IsSubstringInCordAt` to + // see if this is the needle. If not, we advance to `P + 1` and try again. + while (it.chunk_iterator_.bytes_remaining_ >= needle.size()) { + auto haystack_chunk = Cord::ChunkRemaining(it); + assert(!haystack_chunk.empty()); + // Look for the first char of `needle` in the current chunk. + auto idx = haystack_chunk.find(needle.front()); + if (idx == absl::string_view::npos) { + // No potential match in this chunk, advance past it. + Cord::Advance(&it, haystack_chunk.size()); + continue; + } + // We found the start of a potential match in the chunk. Advance the + // iterator and haystack chunk to the match the position. + Cord::Advance(&it, idx); + // Check if there is enough haystack remaining to actually have a match. + if (it.chunk_iterator_.bytes_remaining_ < needle.size()) { + break; + } + // Check if this is `needle`. + if (IsSubstringInCordAt(it, needle)) { + return it; + } + // No match, increment the iterator for the next attempt. + Cord::Advance(&it, 1); + } + // If we got here, we did not find `needle`. + return char_end(); +} + +absl::Cord::CharIterator absl::Cord::Find(absl::string_view needle) const { + if (needle.empty()) { + return char_begin(); + } + if (needle.size() > size()) { + return char_end(); + } + if (needle.size() == size()) { + return *this == needle ? char_begin() : char_end(); + } + return FindImpl(char_begin(), needle); +} + +namespace { + +// Tests whether the sequence of chunks beginning at `haystack` starts with the +// sequence of chunks beginning at `needle_begin` and extending to `needle_end`. +// +// REQUIRES: remaining `absl::Cord` starting at `position` is greater than or +// equal to `needle_end - needle_begin` and `advance`. +bool IsSubcordInCordAt(absl::Cord::CharIterator haystack, + absl::Cord::CharIterator needle_begin, + absl::Cord::CharIterator needle_end) { + while (needle_begin != needle_end) { + auto haystack_chunk = absl::Cord::ChunkRemaining(haystack); + assert(!haystack_chunk.empty()); + auto needle_chunk = absl::Cord::ChunkRemaining(needle_begin); + auto min_length = std::min(haystack_chunk.size(), needle_chunk.size()); + if (haystack_chunk.substr(0, min_length) != + needle_chunk.substr(0, min_length)) { + return false; + } + absl::Cord::Advance(&haystack, min_length); + absl::Cord::Advance(&needle_begin, min_length); + } + return true; +} + +// Tests whether the sequence of chunks beginning at `position` starts with the +// cord `needle`. +// +// REQUIRES: remaining `absl::Cord` starting at `position` is greater than or +// equal to `needle.size()`. +bool IsSubcordInCordAt(absl::Cord::CharIterator position, + const absl::Cord& needle) { + return IsSubcordInCordAt(position, needle.char_begin(), needle.char_end()); +} + +} // namespace + +absl::Cord::CharIterator absl::Cord::Find(const absl::Cord& needle) const { + if (needle.empty()) { + return char_begin(); + } + const auto needle_size = needle.size(); + if (needle_size > size()) { + return char_end(); + } + if (needle_size == size()) { + return *this == needle ? char_begin() : char_end(); + } + const auto needle_chunk = Cord::ChunkRemaining(needle.char_begin()); + auto haystack_it = char_begin(); + while (true) { + haystack_it = FindImpl(haystack_it, needle_chunk); + if (haystack_it == char_end() || + haystack_it.chunk_iterator_.bytes_remaining_ < needle_size) { + break; + } + // We found the first chunk of `needle` at `haystack_it` but not the entire + // subcord. Advance past the first chunk and check for the remainder. + auto haystack_advanced_it = haystack_it; + auto needle_it = needle.char_begin(); + Cord::Advance(&haystack_advanced_it, needle_chunk.size()); + Cord::Advance(&needle_it, needle_chunk.size()); + if (IsSubcordInCordAt(haystack_advanced_it, needle_it, needle.char_end())) { + return haystack_it; + } + Cord::Advance(&haystack_it, 1); + if (haystack_it.chunk_iterator_.bytes_remaining_ < needle_size) { + break; + } + if (haystack_it.chunk_iterator_.bytes_remaining_ == needle_size) { + // Special case, if there is exactly `needle_size` bytes remaining, the + // subcord is either at `haystack_it` or not at all. + if (IsSubcordInCordAt(haystack_it, needle)) { + return haystack_it; + } + break; + } + } + return char_end(); +} + +bool Cord::Contains(absl::string_view rhs) const { + return rhs.empty() || Find(rhs) != char_end(); +} + +bool Cord::Contains(const absl::Cord& rhs) const { + return rhs.empty() || Find(rhs) != char_end(); +} + absl::string_view Cord::FlattenSlowPath() { assert(contents_.is_tree()); size_t total_size = size(); @@ -1194,7 +1388,8 @@ absl::string_view Cord::FlattenSlowPath() { return absl::string_view(new_buffer, total_size); } -/* static */ bool Cord::GetFlatAux(CordRep* rep, absl::string_view* fragment) { +/* static */ bool Cord::GetFlatAux(absl::Nonnull<CordRep*> rep, + absl::Nonnull<absl::string_view*> fragment) { assert(rep != nullptr); if (rep->length == 0) { *fragment = absl::string_view(); @@ -1228,7 +1423,7 @@ absl::string_view Cord::FlattenSlowPath() { } /* static */ void Cord::ForEachChunkAux( - absl::cord_internal::CordRep* rep, + absl::Nonnull<absl::cord_internal::CordRep*> rep, absl::FunctionRef<void(absl::string_view)> callback) { assert(rep != nullptr); if (rep->length == 0) return; @@ -1253,8 +1448,8 @@ absl::string_view Cord::FlattenSlowPath() { } } -static void DumpNode(CordRep* rep, bool include_data, std::ostream* os, - int indent) { +static void DumpNode(absl::Nonnull<CordRep*> rep, bool include_data, + absl::Nonnull<std::ostream*> os, int indent) { const int kIndentStep = 1; absl::InlinedVector<CordRep*, kInlinedVectorSize> stack; absl::InlinedVector<int, kInlinedVectorSize> indents; @@ -1290,7 +1485,7 @@ static void DumpNode(CordRep* rep, bool include_data, std::ostream* os, *os << absl::CEscape(std::string(rep->flat()->Data(), rep->length)); *os << "]\n"; } else { - CordRepBtree::Dump(rep, /*label=*/ "", include_data, *os); + CordRepBtree::Dump(rep, /*label=*/"", include_data, *os); } } if (leaf) { @@ -1304,16 +1499,17 @@ static void DumpNode(CordRep* rep, bool include_data, std::ostream* os, ABSL_INTERNAL_CHECK(indents.empty(), ""); } -static std::string ReportError(CordRep* root, CordRep* node) { +static std::string ReportError(absl::Nonnull<CordRep*> root, + absl::Nonnull<CordRep*> node) { std::ostringstream buf; buf << "Error at node " << node << " in:"; DumpNode(root, true, &buf); return buf.str(); } -static bool VerifyNode(CordRep* root, CordRep* start_node, - bool /* full_validation */) { - absl::InlinedVector<CordRep*, 2> worklist; +static bool VerifyNode(absl::Nonnull<CordRep*> root, + absl::Nonnull<CordRep*> start_node) { + absl::InlinedVector<absl::Nonnull<CordRep*>, 2> worklist; worklist.push_back(start_node); do { CordRep* node = worklist.back(); diff --git a/absl/strings/cord.h b/absl/strings/cord.h index c4a0d5aa..b3e556b6 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -74,6 +74,7 @@ #include "absl/base/internal/endian.h" #include "absl/base/internal/per_thread_tls.h" #include "absl/base/macros.h" +#include "absl/base/nullability.h" #include "absl/base/port.h" #include "absl/container/inlined_vector.h" #include "absl/crc/internal/crc_cord_state.h" @@ -86,7 +87,6 @@ #include "absl/strings/internal/cord_rep_btree.h" #include "absl/strings/internal/cord_rep_btree_reader.h" #include "absl/strings/internal/cord_rep_crc.h" -#include "absl/strings/internal/cord_rep_ring.h" #include "absl/strings/internal/cordz_functions.h" #include "absl/strings/internal/cordz_info.h" #include "absl/strings/internal/cordz_statistics.h" @@ -103,16 +103,37 @@ class Cord; class CordTestPeer; template <typename Releaser> Cord MakeCordFromExternal(absl::string_view, Releaser&&); -void CopyCordToString(const Cord& src, std::string* dst); +void CopyCordToString(const Cord& src, absl::Nonnull<std::string*> dst); // Cord memory accounting modes enum class CordMemoryAccounting { // Counts the *approximate* number of bytes held in full or in part by this // Cord (which may not remain the same between invocations). Cords that share // memory could each be "charged" independently for the same shared memory. + // See also comment on `kTotalMorePrecise` on internally shared memory. kTotal, // Counts the *approximate* number of bytes held in full or in part by this + // Cord for the distinct memory held by this cord. This option is similar + // to `kTotal`, except that if the cord has multiple references to the same + // memory, that memory is only counted once. + // + // For example: + // absl::Cord cord; + // cord.Append(some_other_cord); + // cord.Append(some_other_cord); + // // Counts `some_other_cord` twice: + // cord.EstimatedMemoryUsage(kTotal); + // // Counts `some_other_cord` once: + // cord.EstimatedMemoryUsage(kTotalMorePrecise); + // + // The `kTotalMorePrecise` number is more expensive to compute as it requires + // deduplicating all memory references. Applications should prefer to use + // `kFairShare` or `kTotal` unless they really need a more precise estimate + // on "how much memory is potentially held / kept alive by this cord?" + kTotalMorePrecise, + + // Counts the *approximate* number of bytes held in full or in part by this // Cord weighted by the sharing ratio of that data. For example, if some data // edge is shared by 4 different Cords, then each cord is attributed 1/4th of // the total memory usage as a 'fair share' of the total memory usage. @@ -341,7 +362,7 @@ class Cord { // Cord::empty() // - // Determines whether the given Cord is empty, returning `true` is so. + // Determines whether the given Cord is empty, returning `true` if so. bool empty() const; // Cord::EstimatedMemoryUsage() @@ -375,6 +396,12 @@ class Cord { bool EndsWith(absl::string_view rhs) const; bool EndsWith(const Cord& rhs) const; + // Cord::Contains() + // + // Determines whether the Cord contains the passed string data `rhs`. + bool Contains(absl::string_view rhs) const; + bool Contains(const Cord& rhs) const; + // Cord::operator std::string() // // Converts a Cord into a `std::string()`. This operator is marked explicit to @@ -390,7 +417,8 @@ class Cord { // guarantee that pointers previously returned by `dst->data()` remain valid // even if `*dst` had enough capacity to hold `src`. If `*dst` is a new // object, prefer to simply use the conversion operator to `std::string`. - friend void CopyCordToString(const Cord& src, std::string* dst); + friend void CopyCordToString(const Cord& src, + absl::Nonnull<std::string*> dst); class CharIterator; @@ -427,7 +455,7 @@ class Cord { using iterator_category = std::input_iterator_tag; using value_type = absl::string_view; using difference_type = ptrdiff_t; - using pointer = const value_type*; + using pointer = absl::Nonnull<const value_type*>; using reference = value_type; ChunkIterator() = default; @@ -447,14 +475,14 @@ class Cord { using CordRepBtree = absl::cord_internal::CordRepBtree; using CordRepBtreeReader = absl::cord_internal::CordRepBtreeReader; - // Constructs a `begin()` iterator from `tree`. `tree` must not be null. - explicit ChunkIterator(cord_internal::CordRep* tree); + // Constructs a `begin()` iterator from `tree`. + explicit ChunkIterator(absl::Nonnull<cord_internal::CordRep*> tree); // Constructs a `begin()` iterator from `cord`. - explicit ChunkIterator(const Cord* cord); + explicit ChunkIterator(absl::Nonnull<const Cord*> cord); // Initializes this instance from a tree. Invoked by constructors. - void InitTree(cord_internal::CordRep* tree); + void InitTree(absl::Nonnull<cord_internal::CordRep*> tree); // Removes `n` bytes from `current_chunk_`. Expects `n` to be smaller than // `current_chunk_.size()`. @@ -472,7 +500,7 @@ class Cord { // The current leaf, or `nullptr` if the iterator points to short data. // If the current chunk is a substring node, current_leaf_ points to the // underlying flat or external node. - absl::cord_internal::CordRep* current_leaf_ = nullptr; + absl::Nullable<absl::cord_internal::CordRep*> current_leaf_ = nullptr; // The number of bytes left in the `Cord` over which we are iterating. size_t bytes_remaining_ = 0; @@ -494,7 +522,7 @@ class Cord { // absl::string_view s) { // return std::find(c.chunk_begin(), c.chunk_end(), s); // } - ChunkIterator chunk_begin() const; + ChunkIterator chunk_begin() const ABSL_ATTRIBUTE_LIFETIME_BOUND; // Cord::chunk_end() // @@ -503,7 +531,7 @@ class Cord { // Generally, prefer using `Cord::Chunks()` within a range-based for loop for // iterating over the chunks of a Cord. This method may be useful for getting // a `ChunkIterator` where range-based for-loops may not be available. - ChunkIterator chunk_end() const; + ChunkIterator chunk_end() const ABSL_ATTRIBUTE_LIFETIME_BOUND; //---------------------------------------------------------------------------- // Cord::ChunkRange @@ -529,13 +557,13 @@ class Cord { using iterator = ChunkIterator; using const_iterator = ChunkIterator; - explicit ChunkRange(const Cord* cord) : cord_(cord) {} + explicit ChunkRange(absl::Nonnull<const Cord*> cord) : cord_(cord) {} ChunkIterator begin() const; ChunkIterator end() const; private: - const Cord* cord_; + absl::Nonnull<const Cord*> cord_; }; // Cord::Chunks() @@ -557,7 +585,7 @@ class Cord { // // The temporary Cord returned by CordFactory has been destroyed! // } // } - ChunkRange Chunks() const; + ChunkRange Chunks() const ABSL_ATTRIBUTE_LIFETIME_BOUND; //---------------------------------------------------------------------------- // Cord::CharIterator @@ -588,7 +616,7 @@ class Cord { using iterator_category = std::input_iterator_tag; using value_type = char; using difference_type = ptrdiff_t; - using pointer = const char*; + using pointer = absl::Nonnull<const char*>; using reference = const char&; CharIterator() = default; @@ -603,7 +631,8 @@ class Cord { friend Cord; private: - explicit CharIterator(const Cord* cord) : chunk_iterator_(cord) {} + explicit CharIterator(absl::Nonnull<const Cord*> cord) + : chunk_iterator_(cord) {} ChunkIterator chunk_iterator_; }; @@ -614,14 +643,14 @@ class Cord { // advanced as a separate `Cord`. `n_bytes` must be less than or equal to the // number of bytes within the Cord; otherwise, behavior is undefined. It is // valid to pass `char_end()` and `0`. - static Cord AdvanceAndRead(CharIterator* it, size_t n_bytes); + static Cord AdvanceAndRead(absl::Nonnull<CharIterator*> it, size_t n_bytes); // Cord::Advance() // // Advances the `Cord::CharIterator` by `n_bytes`. `n_bytes` must be less than // or equal to the number of bytes remaining within the Cord; otherwise, // behavior is undefined. It is valid to pass `char_end()` and `0`. - static void Advance(CharIterator* it, size_t n_bytes); + static void Advance(absl::Nonnull<CharIterator*> it, size_t n_bytes); // Cord::ChunkRemaining() // @@ -637,7 +666,7 @@ class Cord { // Generally, prefer using `Cord::Chars()` within a range-based for loop for // iterating over the chunks of a Cord. This method may be useful for getting // a `CharIterator` where range-based for-loops may not be available. - CharIterator char_begin() const; + CharIterator char_begin() const ABSL_ATTRIBUTE_LIFETIME_BOUND; // Cord::char_end() // @@ -646,7 +675,7 @@ class Cord { // Generally, prefer using `Cord::Chars()` within a range-based for loop for // iterating over the chunks of a Cord. This method may be useful for getting // a `CharIterator` where range-based for-loops are not useful. - CharIterator char_end() const; + CharIterator char_end() const ABSL_ATTRIBUTE_LIFETIME_BOUND; // Cord::CharRange // @@ -661,7 +690,7 @@ class Cord { class CharRange { public: // Fulfill minimum c++ container requirements [container.requirements] - // Theses (partial) container type definitions allow CharRange to be used + // These (partial) container type definitions allow CharRange to be used // in various utilities expecting a subset of [container.requirements]. // For example, the below enables using `::testing::ElementsAre(...)` using value_type = char; @@ -670,13 +699,13 @@ class Cord { using iterator = CharIterator; using const_iterator = CharIterator; - explicit CharRange(const Cord* cord) : cord_(cord) {} + explicit CharRange(absl::Nonnull<const Cord*> cord) : cord_(cord) {} CharIterator begin() const; CharIterator end() const; private: - const Cord* cord_; + absl::Nonnull<const Cord*> cord_; }; // Cord::Chars() @@ -698,7 +727,7 @@ class Cord { // // The temporary Cord returned by CordFactory has been destroyed! // } // } - CharRange Chars() const; + CharRange Chars() const ABSL_ATTRIBUTE_LIFETIME_BOUND; // Cord::operator[] // @@ -716,20 +745,38 @@ class Cord { // // If this cord's representation is a single flat array, returns a // string_view referencing that array. Otherwise returns nullopt. - absl::optional<absl::string_view> TryFlat() const; + absl::optional<absl::string_view> TryFlat() const + ABSL_ATTRIBUTE_LIFETIME_BOUND; // Cord::Flatten() // // Flattens the cord into a single array and returns a view of the data. // // If the cord was already flat, the contents are not modified. - absl::string_view Flatten(); + absl::string_view Flatten() ABSL_ATTRIBUTE_LIFETIME_BOUND; + + // Cord::Find() + // + // Returns an iterator to the first occurrance of the substring `needle`. + // + // If the substring `needle` does not occur, `Cord::char_end()` is returned. + CharIterator Find(absl::string_view needle) const; + CharIterator Find(const absl::Cord& needle) const; // Supports absl::Cord as a sink object for absl::Format(). - friend void AbslFormatFlush(absl::Cord* cord, absl::string_view part) { + friend void AbslFormatFlush(absl::Nonnull<absl::Cord*> cord, + absl::string_view part) { cord->Append(part); } + // Support automatic stringification with absl::StrCat and absl::StrFormat. + template <typename Sink> + friend void AbslStringify(Sink& sink, const absl::Cord& cord) { + for (absl::string_view chunk : cord.Chunks()) { + sink.Append(chunk); + } + } + // Cord::SetExpectedChecksum() // // Stores a checksum value with this non-empty cord instance, for later @@ -788,7 +835,8 @@ class Cord { friend bool operator==(const Cord& lhs, const Cord& rhs); friend bool operator==(const Cord& lhs, absl::string_view rhs); - friend const CordzInfo* GetCordzInfoForTesting(const Cord& cord); + friend absl::Nullable<const CordzInfo*> GetCordzInfoForTesting( + const Cord& cord); // Calls the provided function once for each cord chunk, in order. Unlike // Chunks(), this API will not allocate memory. @@ -815,20 +863,22 @@ class Cord { InlineRep& operator=(const InlineRep& src); InlineRep& operator=(InlineRep&& src) noexcept; - explicit constexpr InlineRep(absl::string_view sv, CordRep* rep); + explicit constexpr InlineRep(absl::string_view sv, + absl::Nullable<CordRep*> rep); - void Swap(InlineRep* rhs); - bool empty() const; + void Swap(absl::Nonnull<InlineRep*> rhs); size_t size() const; - const char* data() const; // Returns nullptr if holding pointer - void set_data(const char* data, size_t n); // Discards pointer, if any - char* set_data(size_t n); // Write data to the result + // Returns nullptr if holding pointer + absl::Nullable<const char*> data() const; + // Discards pointer, if any + void set_data(absl::Nonnull<const char*> data, size_t n); + absl::Nonnull<char*> set_data(size_t n); // Write data to the result // Returns nullptr if holding bytes - absl::cord_internal::CordRep* tree() const; - absl::cord_internal::CordRep* as_tree() const; - const char* as_chars() const; + absl::Nullable<absl::cord_internal::CordRep*> tree() const; + absl::Nonnull<absl::cord_internal::CordRep*> as_tree() const; + absl::Nonnull<const char*> as_chars() const; // Returns non-null iff was holding a pointer - absl::cord_internal::CordRep* clear(); + absl::Nullable<absl::cord_internal::CordRep*> clear(); // Converts to pointer if necessary. void reduce_size(size_t n); // REQUIRES: holding data void remove_prefix(size_t n); // REQUIRES: holding data @@ -837,46 +887,52 @@ class Cord { // Creates a CordRepFlat instance from the current inlined data with `extra' // bytes of desired additional capacity. - CordRepFlat* MakeFlatWithExtraCapacity(size_t extra); + absl::Nonnull<CordRepFlat*> MakeFlatWithExtraCapacity(size_t extra); // Sets the tree value for this instance. `rep` must not be null. // Requires the current instance to hold a tree, and a lock to be held on // any CordzInfo referenced by this instance. The latter is enforced through // the CordzUpdateScope argument. If the current instance is sampled, then // the CordzInfo instance is updated to reference the new `rep` value. - void SetTree(CordRep* rep, const CordzUpdateScope& scope); + void SetTree(absl::Nonnull<CordRep*> rep, const CordzUpdateScope& scope); // Identical to SetTree(), except that `rep` is allowed to be null, in // which case the current instance is reset to an empty value. - void SetTreeOrEmpty(CordRep* rep, const CordzUpdateScope& scope); + void SetTreeOrEmpty(absl::Nullable<CordRep*> rep, + const CordzUpdateScope& scope); // Sets the tree value for this instance, and randomly samples this cord. // This function disregards existing contents in `data_`, and should be // called when a Cord is 'promoted' from an 'uninitialized' or 'inlined' // value to a non-inlined (tree / ring) value. - void EmplaceTree(CordRep* rep, MethodIdentifier method); + void EmplaceTree(absl::Nonnull<CordRep*> rep, MethodIdentifier method); // Identical to EmplaceTree, except that it copies the parent stack from // the provided `parent` data if the parent is sampled. - void EmplaceTree(CordRep* rep, const InlineData& parent, + void EmplaceTree(absl::Nonnull<CordRep*> rep, const InlineData& parent, MethodIdentifier method); // Commits the change of a newly created, or updated `rep` root value into // this cord. `old_rep` indicates the old (inlined or tree) value of the // cord, and determines if the commit invokes SetTree() or EmplaceTree(). - void CommitTree(const CordRep* old_rep, CordRep* rep, - const CordzUpdateScope& scope, MethodIdentifier method); - - void AppendTreeToInlined(CordRep* tree, MethodIdentifier method); - void AppendTreeToTree(CordRep* tree, MethodIdentifier method); - void AppendTree(CordRep* tree, MethodIdentifier method); - void PrependTreeToInlined(CordRep* tree, MethodIdentifier method); - void PrependTreeToTree(CordRep* tree, MethodIdentifier method); - void PrependTree(CordRep* tree, MethodIdentifier method); + void CommitTree(absl::Nullable<const CordRep*> old_rep, + absl::Nonnull<CordRep*> rep, const CordzUpdateScope& scope, + MethodIdentifier method); + + void AppendTreeToInlined(absl::Nonnull<CordRep*> tree, + MethodIdentifier method); + void AppendTreeToTree(absl::Nonnull<CordRep*> tree, + MethodIdentifier method); + void AppendTree(absl::Nonnull<CordRep*> tree, MethodIdentifier method); + void PrependTreeToInlined(absl::Nonnull<CordRep*> tree, + MethodIdentifier method); + void PrependTreeToTree(absl::Nonnull<CordRep*> tree, + MethodIdentifier method); + void PrependTree(absl::Nonnull<CordRep*> tree, MethodIdentifier method); bool IsSame(const InlineRep& other) const { return data_ == other.data_; } - void CopyTo(std::string* dst) const { + void CopyTo(absl::Nonnull<std::string*> dst) const { // memcpy is much faster when operating on a known size. On most supported // platforms, the small string optimization is large enough that resizing // to 15 bytes does not cause a memory allocation. @@ -888,7 +944,7 @@ class Cord { } // Copies the inline contents into `dst`. Assumes the cord is not empty. - void CopyToArray(char* dst) const; + void CopyToArray(absl::Nonnull<char*> dst) const; bool is_tree() const { return data_.is_tree(); } @@ -901,12 +957,12 @@ class Cord { } // Returns the profiled CordzInfo, or nullptr if not sampled. - absl::cord_internal::CordzInfo* cordz_info() const { + absl::Nullable<absl::cord_internal::CordzInfo*> cordz_info() const { return data_.cordz_info(); } - // Sets the profiled CordzInfo. `cordz_info` must not be null. - void set_cordz_info(cord_internal::CordzInfo* cordz_info) { + // Sets the profiled CordzInfo. + void set_cordz_info(absl::Nonnull<cord_internal::CordzInfo*> cordz_info) { assert(cordz_info != nullptr); data_.set_cordz_info(cordz_info); } @@ -938,19 +994,19 @@ class Cord { InlineRep contents_; // Helper for GetFlat() and TryFlat(). - static bool GetFlatAux(absl::cord_internal::CordRep* rep, - absl::string_view* fragment); + static bool GetFlatAux(absl::Nonnull<absl::cord_internal::CordRep*> rep, + absl::Nonnull<absl::string_view*> fragment); // Helper for ForEachChunk(). static void ForEachChunkAux( - absl::cord_internal::CordRep* rep, + absl::Nonnull<absl::cord_internal::CordRep*> rep, absl::FunctionRef<void(absl::string_view)> callback); // The destructor for non-empty Cords. void DestroyCordSlow(); // Out-of-line implementation of slower parts of logic. - void CopyToArraySlowPath(char* dst) const; + void CopyToArraySlowPath(absl::Nonnull<char*> dst) const; int CompareSlowPath(absl::string_view rhs, size_t compared_size, size_t size_to_compare) const; int CompareSlowPath(const Cord& rhs, size_t compared_size, @@ -967,8 +1023,8 @@ class Cord { // Returns a new reference to contents_.tree(), or steals an existing // reference if called on an rvalue. - absl::cord_internal::CordRep* TakeRep() const&; - absl::cord_internal::CordRep* TakeRep() &&; + absl::Nonnull<absl::cord_internal::CordRep*> TakeRep() const&; + absl::Nonnull<absl::cord_internal::CordRep*> TakeRep() &&; // Helper for Append(). template <typename C> @@ -1005,7 +1061,10 @@ class Cord { friend class CrcCord; void SetCrcCordState(crc_internal::CrcCordState state); - const crc_internal::CrcCordState* MaybeGetCrcCordState() const; + absl::Nullable<const crc_internal::CrcCordState*> MaybeGetCrcCordState() + const; + + CharIterator FindImpl(CharIterator it, absl::string_view needle) const; }; ABSL_NAMESPACE_END @@ -1024,13 +1083,15 @@ namespace cord_internal { // Does non-template-specific `CordRepExternal` initialization. // Requires `data` to be non-empty. -void InitializeCordRepExternal(absl::string_view data, CordRepExternal* rep); +void InitializeCordRepExternal(absl::string_view data, + absl::Nonnull<CordRepExternal*> rep); // Creates a new `CordRep` that owns `data` and `releaser` and returns a pointer // to it. Requires `data` to be non-empty. template <typename Releaser> // NOLINTNEXTLINE - suppress clang-tidy raw pointer return. -CordRep* NewExternalRep(absl::string_view data, Releaser&& releaser) { +absl::Nonnull<CordRep*> NewExternalRep(absl::string_view data, + Releaser&& releaser) { assert(!data.empty()); using ReleaserType = absl::decay_t<Releaser>; CordRepExternal* rep = new CordRepExternalImpl<ReleaserType>( @@ -1042,7 +1103,7 @@ CordRep* NewExternalRep(absl::string_view data, Releaser&& releaser) { // Overload for function reference types that dispatches using a function // pointer because there are no `alignof()` or `sizeof()` a function reference. // NOLINTNEXTLINE - suppress clang-tidy raw pointer return. -inline CordRep* NewExternalRep(absl::string_view data, +inline absl::Nonnull<CordRep*> NewExternalRep(absl::string_view data, void (&releaser)(absl::string_view)) { return NewExternalRep(data, &releaser); } @@ -1065,7 +1126,8 @@ Cord MakeCordFromExternal(absl::string_view data, Releaser&& releaser) { return cord; } -constexpr Cord::InlineRep::InlineRep(absl::string_view sv, CordRep* rep) +constexpr Cord::InlineRep::InlineRep(absl::string_view sv, + absl::Nullable<CordRep*> rep) : data_(sv, rep) {} inline Cord::InlineRep::InlineRep(const Cord::InlineRep& src) @@ -1104,28 +1166,30 @@ inline Cord::InlineRep& Cord::InlineRep::operator=( return *this; } -inline void Cord::InlineRep::Swap(Cord::InlineRep* rhs) { +inline void Cord::InlineRep::Swap(absl::Nonnull<Cord::InlineRep*> rhs) { if (rhs == this) { return; } std::swap(data_, rhs->data_); } -inline const char* Cord::InlineRep::data() const { +inline absl::Nullable<const char*> Cord::InlineRep::data() const { return is_tree() ? nullptr : data_.as_chars(); } -inline const char* Cord::InlineRep::as_chars() const { +inline absl::Nonnull<const char*> Cord::InlineRep::as_chars() const { assert(!data_.is_tree()); return data_.as_chars(); } -inline absl::cord_internal::CordRep* Cord::InlineRep::as_tree() const { +inline absl::Nonnull<absl::cord_internal::CordRep*> Cord::InlineRep::as_tree() + const { assert(data_.is_tree()); return data_.as_tree(); } -inline absl::cord_internal::CordRep* Cord::InlineRep::tree() const { +inline absl::Nullable<absl::cord_internal::CordRep*> Cord::InlineRep::tree() + const { if (is_tree()) { return as_tree(); } else { @@ -1133,14 +1197,12 @@ inline absl::cord_internal::CordRep* Cord::InlineRep::tree() const { } } -inline bool Cord::InlineRep::empty() const { return data_.is_empty(); } - inline size_t Cord::InlineRep::size() const { return is_tree() ? as_tree()->length : inline_size(); } -inline cord_internal::CordRepFlat* Cord::InlineRep::MakeFlatWithExtraCapacity( - size_t extra) { +inline absl::Nonnull<cord_internal::CordRepFlat*> +Cord::InlineRep::MakeFlatWithExtraCapacity(size_t extra) { static_assert(cord_internal::kMinFlatLength >= sizeof(data_), ""); size_t len = data_.inline_size(); auto* result = CordRepFlat::New(len + extra); @@ -1149,20 +1211,21 @@ inline cord_internal::CordRepFlat* Cord::InlineRep::MakeFlatWithExtraCapacity( return result; } -inline void Cord::InlineRep::EmplaceTree(CordRep* rep, +inline void Cord::InlineRep::EmplaceTree(absl::Nonnull<CordRep*> rep, MethodIdentifier method) { assert(rep); data_.make_tree(rep); CordzInfo::MaybeTrackCord(data_, method); } -inline void Cord::InlineRep::EmplaceTree(CordRep* rep, const InlineData& parent, +inline void Cord::InlineRep::EmplaceTree(absl::Nonnull<CordRep*> rep, + const InlineData& parent, MethodIdentifier method) { data_.make_tree(rep); CordzInfo::MaybeTrackCord(data_, parent, method); } -inline void Cord::InlineRep::SetTree(CordRep* rep, +inline void Cord::InlineRep::SetTree(absl::Nonnull<CordRep*> rep, const CordzUpdateScope& scope) { assert(rep); assert(data_.is_tree()); @@ -1170,7 +1233,7 @@ inline void Cord::InlineRep::SetTree(CordRep* rep, scope.SetCordRep(rep); } -inline void Cord::InlineRep::SetTreeOrEmpty(CordRep* rep, +inline void Cord::InlineRep::SetTreeOrEmpty(absl::Nullable<CordRep*> rep, const CordzUpdateScope& scope) { assert(data_.is_tree()); if (rep) { @@ -1181,7 +1244,8 @@ inline void Cord::InlineRep::SetTreeOrEmpty(CordRep* rep, scope.SetCordRep(rep); } -inline void Cord::InlineRep::CommitTree(const CordRep* old_rep, CordRep* rep, +inline void Cord::InlineRep::CommitTree(absl::Nullable<const CordRep*> old_rep, + absl::Nonnull<CordRep*> rep, const CordzUpdateScope& scope, MethodIdentifier method) { if (old_rep) { @@ -1191,7 +1255,7 @@ inline void Cord::InlineRep::CommitTree(const CordRep* old_rep, CordRep* rep, } } -inline absl::cord_internal::CordRep* Cord::InlineRep::clear() { +inline absl::Nullable<absl::cord_internal::CordRep*> Cord::InlineRep::clear() { if (is_tree()) { CordzInfo::MaybeUntrackCord(cordz_info()); } @@ -1200,7 +1264,7 @@ inline absl::cord_internal::CordRep* Cord::InlineRep::clear() { return result; } -inline void Cord::InlineRep::CopyToArray(char* dst) const { +inline void Cord::InlineRep::CopyToArray(absl::Nonnull<char*> dst) const { assert(!is_tree()); size_t n = inline_size(); assert(n != 0); @@ -1273,10 +1337,16 @@ inline size_t Cord::EstimatedMemoryUsage( CordMemoryAccounting accounting_method) const { size_t result = sizeof(Cord); if (const absl::cord_internal::CordRep* rep = contents_.tree()) { - if (accounting_method == CordMemoryAccounting::kFairShare) { - result += cord_internal::GetEstimatedFairShareMemoryUsage(rep); - } else { - result += cord_internal::GetEstimatedMemoryUsage(rep); + switch (accounting_method) { + case CordMemoryAccounting::kFairShare: + result += cord_internal::GetEstimatedFairShareMemoryUsage(rep); + break; + case CordMemoryAccounting::kTotalMorePrecise: + result += cord_internal::GetMorePreciseMemoryUsage(rep); + break; + case CordMemoryAccounting::kTotal: + result += cord_internal::GetEstimatedMemoryUsage(rep); + break; } } return result; @@ -1375,7 +1445,8 @@ inline bool Cord::StartsWith(absl::string_view rhs) const { return EqualsImpl(rhs, rhs_size); } -inline void Cord::ChunkIterator::InitTree(cord_internal::CordRep* tree) { +inline void Cord::ChunkIterator::InitTree( + absl::Nonnull<cord_internal::CordRep*> tree) { tree = cord_internal::SkipCrcNode(tree); if (tree->tag == cord_internal::BTREE) { current_chunk_ = btree_reader_.Init(tree->btree()); @@ -1385,12 +1456,13 @@ inline void Cord::ChunkIterator::InitTree(cord_internal::CordRep* tree) { } } -inline Cord::ChunkIterator::ChunkIterator(cord_internal::CordRep* tree) { +inline Cord::ChunkIterator::ChunkIterator( + absl::Nonnull<cord_internal::CordRep*> tree) { bytes_remaining_ = tree->length; InitTree(tree); } -inline Cord::ChunkIterator::ChunkIterator(const Cord* cord) { +inline Cord::ChunkIterator::ChunkIterator(absl::Nonnull<const Cord*> cord) { if (CordRep* tree = cord->contents_.tree()) { bytes_remaining_ = tree->length; if (ABSL_PREDICT_TRUE(bytes_remaining_ != 0)) { @@ -1530,12 +1602,13 @@ inline Cord::CharIterator::pointer Cord::CharIterator::operator->() const { return chunk_iterator_->data(); } -inline Cord Cord::AdvanceAndRead(CharIterator* it, size_t n_bytes) { +inline Cord Cord::AdvanceAndRead(absl::Nonnull<CharIterator*> it, + size_t n_bytes) { assert(it != nullptr); return it->chunk_iterator_.AdvanceAndReadBytes(n_bytes); } -inline void Cord::Advance(CharIterator* it, size_t n_bytes) { +inline void Cord::Advance(absl::Nonnull<CharIterator*> it, size_t n_bytes) { assert(it != nullptr); it->chunk_iterator_.AdvanceBytes(n_bytes); } @@ -1591,7 +1664,7 @@ inline bool operator>=(const Cord& x, const Cord& y) { // Nonmember Cord-to-absl::string_view relational operators. // // Due to implicit conversions, these also enable comparisons of Cord with -// with std::string, ::string, and const char*. +// std::string and const char*. inline bool operator==(const Cord& lhs, absl::string_view rhs) { size_t lhs_size = lhs.size(); size_t rhs_size = rhs.size(); diff --git a/absl/strings/cord_analysis.cc b/absl/strings/cord_analysis.cc index 73d3c4e6..19b0fa44 100644 --- a/absl/strings/cord_analysis.cc +++ b/absl/strings/cord_analysis.cc @@ -14,22 +14,17 @@ #include "absl/strings/cord_analysis.h" +#include <cassert> #include <cstddef> #include <cstdint> +#include <unordered_set> -#include "absl/base/attributes.h" #include "absl/base/config.h" -#include "absl/container/inlined_vector.h" +#include "absl/base/nullability.h" #include "absl/strings/internal/cord_data_edge.h" #include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cord_rep_btree.h" #include "absl/strings/internal/cord_rep_crc.h" -#include "absl/strings/internal/cord_rep_flat.h" -#include "absl/strings/internal/cord_rep_ring.h" -// -#include "absl/base/macros.h" -#include "absl/base/port.h" -#include "absl/functional/function_ref.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -37,20 +32,22 @@ namespace cord_internal { namespace { // Accounting mode for analyzing memory usage. -enum class Mode { kTotal, kFairShare }; +enum class Mode { kFairShare, kTotal, kTotalMorePrecise }; // CordRepRef holds a `const CordRep*` reference in rep, and depending on mode, // holds a 'fraction' representing a cumulative inverse refcount weight. template <Mode mode> struct CordRepRef { // Instantiates a CordRepRef instance. - explicit CordRepRef(const CordRep* r) : rep(r) {} + explicit CordRepRef(absl::Nonnull<const CordRep*> r) : rep(r) {} // Creates a child reference holding the provided child. // Overloaded to add cumulative reference count for kFairShare. - CordRepRef Child(const CordRep* child) const { return CordRepRef(child); } + CordRepRef Child(absl::Nonnull<const CordRep*> child) const { + return CordRepRef(child); + } - const CordRep* rep; + absl::Nonnull<const CordRep*> rep; }; // RawUsage holds the computed total number of bytes. @@ -62,6 +59,22 @@ struct RawUsage { void Add(size_t size, CordRepRef<mode>) { total += size; } }; +// Overloaded representation of RawUsage that tracks the set of objects +// counted, and avoids double-counting objects referenced more than once +// by the same Cord. +template <> +struct RawUsage<Mode::kTotalMorePrecise> { + size_t total = 0; + // TODO(b/289250880): Replace this with a flat_hash_set. + std::unordered_set<absl::Nonnull<const CordRep*>> counted; + + void Add(size_t size, CordRepRef<Mode::kTotalMorePrecise> repref) { + if (counted.insert(repref.rep).second) { + total += size; + } + } +}; + // Returns n / refcount avoiding a div for the common refcount == 1. template <typename refcount_t> double MaybeDiv(double d, refcount_t refcount) { @@ -77,15 +90,15 @@ double MaybeDiv(double d, refcount_t refcount) { template <> struct CordRepRef<Mode::kFairShare> { // Creates a CordRepRef with the provided rep and top (parent) fraction. - explicit CordRepRef(const CordRep* r, double frac = 1.0) + explicit CordRepRef(absl::Nonnull<const CordRep*> r, double frac = 1.0) : rep(r), fraction(MaybeDiv(frac, r->refcount.Get())) {} // Returns a CordRepRef with a fraction of `this->fraction / child.refcount` - CordRepRef Child(const CordRep* child) const { + CordRepRef Child(absl::Nonnull<const CordRep*> child) const { return CordRepRef(child, fraction); } - const CordRep* rep; + absl::Nonnull<const CordRep*> rep; double fraction; }; @@ -120,16 +133,6 @@ void AnalyzeDataEdge(CordRepRef<mode> rep, RawUsage<mode>& raw_usage) { raw_usage.Add(size, rep); } -// Computes the memory size of the provided Ring tree. -template <Mode mode> -void AnalyzeRing(CordRepRef<mode> rep, RawUsage<mode>& raw_usage) { - const CordRepRing* ring = rep.rep->ring(); - raw_usage.Add(CordRepRing::AllocSize(ring->capacity()), rep); - ring->ForEach([&](CordRepRing::index_type pos) { - AnalyzeDataEdge(rep.Child(ring->entry_child(pos)), raw_usage); - }); -} - // Computes the memory size of the provided Btree tree. template <Mode mode> void AnalyzeBtree(CordRepRef<mode> rep, RawUsage<mode>& raw_usage) { @@ -147,7 +150,7 @@ void AnalyzeBtree(CordRepRef<mode> rep, RawUsage<mode>& raw_usage) { } template <Mode mode> -size_t GetEstimatedUsage(const CordRep* rep) { +size_t GetEstimatedUsage(absl::Nonnull<const CordRep*> rep) { // Zero initialized memory usage totals. RawUsage<mode> raw_usage; @@ -157,6 +160,9 @@ size_t GetEstimatedUsage(const CordRep* rep) { // Consume the top level CRC node if present. if (repref.rep->tag == CRC) { raw_usage.Add(sizeof(CordRepCrc), repref); + if (repref.rep->crc()->child == nullptr) { + return static_cast<size_t>(raw_usage.total); + } repref = repref.Child(repref.rep->crc()->child); } @@ -164,8 +170,6 @@ size_t GetEstimatedUsage(const CordRep* rep) { AnalyzeDataEdge(repref, raw_usage); } else if (repref.rep->tag == BTREE) { AnalyzeBtree(repref, raw_usage); - } else if (repref.rep->tag == RING) { - AnalyzeRing(repref, raw_usage); } else { assert(false); } @@ -175,14 +179,18 @@ size_t GetEstimatedUsage(const CordRep* rep) { } // namespace -size_t GetEstimatedMemoryUsage(const CordRep* rep) { +size_t GetEstimatedMemoryUsage(absl::Nonnull<const CordRep*> rep) { return GetEstimatedUsage<Mode::kTotal>(rep); } -size_t GetEstimatedFairShareMemoryUsage(const CordRep* rep) { +size_t GetEstimatedFairShareMemoryUsage(absl::Nonnull<const CordRep*> rep) { return GetEstimatedUsage<Mode::kFairShare>(rep); } +size_t GetMorePreciseMemoryUsage(absl::Nonnull<const CordRep*> rep) { + return GetEstimatedUsage<Mode::kTotalMorePrecise>(rep); +} + } // namespace cord_internal ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/strings/cord_analysis.h b/absl/strings/cord_analysis.h index 7041ad1a..f8ce3489 100644 --- a/absl/strings/cord_analysis.h +++ b/absl/strings/cord_analysis.h @@ -19,6 +19,7 @@ #include <cstdint> #include "absl/base/config.h" +#include "absl/base/nullability.h" #include "absl/strings/internal/cord_internal.h" namespace absl { @@ -28,13 +29,31 @@ namespace cord_internal { // Returns the *approximate* number of bytes held in full or in part by this // Cord (which may not remain the same between invocations). Cords that share // memory could each be "charged" independently for the same shared memory. -size_t GetEstimatedMemoryUsage(const CordRep* rep); +size_t GetEstimatedMemoryUsage(absl::Nonnull<const CordRep*> rep); + +// Returns the *approximate* number of bytes held in full or in part by this +// Cord for the distinct memory held by this cord. This is similar to +// `GetEstimatedMemoryUsage()`, except that if the cord has multiple references +// to the same memory, that memory is only counted once. +// +// For example: +// absl::Cord cord; +// cord.append(some_other_cord); +// cord.append(some_other_cord); +// // Calls GetEstimatedMemoryUsage() and counts `other_cord` twice: +// cord.EstimatedMemoryUsage(kTotal); +// // Calls GetMorePreciseMemoryUsage() and counts `other_cord` once: +// cord.EstimatedMemoryUsage(kTotalMorePrecise); +// +// This is more expensive than `GetEstimatedMemoryUsage()` as it requires +// deduplicating all memory references. +size_t GetMorePreciseMemoryUsage(absl::Nonnull<const CordRep*> rep); // Returns the *approximate* number of bytes held in full or in part by this // CordRep weighted by the sharing ratio of that data. For example, if some data // edge is shared by 4 different Cords, then each cord is attribute 1/4th of // the total memory usage as a 'fair share' of the total memory usage. -size_t GetEstimatedFairShareMemoryUsage(const CordRep* rep); +size_t GetEstimatedFairShareMemoryUsage(absl::Nonnull<const CordRep*> rep); } // namespace cord_internal ABSL_NAMESPACE_END diff --git a/absl/strings/cord_buffer.h b/absl/strings/cord_buffer.h index 15494b31..bc0e4e45 100644 --- a/absl/strings/cord_buffer.h +++ b/absl/strings/cord_buffer.h @@ -160,7 +160,6 @@ class CordBuffer { // for more information on buffer capacities and intended usage. static CordBuffer CreateWithDefaultLimit(size_t capacity); - // CordBuffer::CreateWithCustomLimit() // // Creates a CordBuffer instance of the desired `capacity` rounded to an @@ -336,7 +335,7 @@ class CordBuffer { } // Returns the available area of the internal SSO data - absl::Span<char> long_available() { + absl::Span<char> long_available() const { assert(!is_short()); const size_t length = long_rep.rep->length; return absl::Span<char>(long_rep.rep->Data() + length, @@ -460,9 +459,7 @@ inline constexpr size_t CordBuffer::MaximumPayload() { } inline constexpr size_t CordBuffer::MaximumPayload(size_t block_size) { - // TODO(absl-team): Use std::min when C++11 support is dropped. - return (kCustomLimit < block_size ? kCustomLimit : block_size) - - cord_internal::kFlatOverhead; + return (std::min)(kCustomLimit, block_size) - cord_internal::kFlatOverhead; } inline CordBuffer CordBuffer::CreateWithDefaultLimit(size_t capacity) { diff --git a/absl/strings/cord_buffer_test.cc b/absl/strings/cord_buffer_test.cc index 5c7437ae..ab628081 100644 --- a/absl/strings/cord_buffer_test.cc +++ b/absl/strings/cord_buffer_test.cc @@ -16,16 +16,18 @@ #include <algorithm> -#include <climits> #include <cstring> +#include <limits> #include <string> #include <utility> #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/config.h" +#include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cord_rep_flat.h" #include "absl/strings/internal/cord_rep_test_util.h" +#include "absl/strings/string_view.h" #include "absl/types/span.h" using testing::Eq; diff --git a/absl/strings/cord_ring_reader_test.cc b/absl/strings/cord_ring_reader_test.cc deleted file mode 100644 index 8e7183bf..00000000 --- a/absl/strings/cord_ring_reader_test.cc +++ /dev/null @@ -1,180 +0,0 @@ -// Copyright 2020 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 <cstdlib> -#include <ctime> -#include <memory> -#include <random> -#include <sstream> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/debugging/leak_check.h" -#include "absl/strings/internal/cord_internal.h" -#include "absl/strings/internal/cord_rep_ring.h" -#include "absl/strings/internal/cord_rep_ring_reader.h" -#include "absl/strings/string_view.h" - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace cord_internal { -namespace { - -using testing::Eq; - -// Creates a flat for testing -CordRep* MakeFlat(absl::string_view s) { - CordRepFlat* flat = CordRepFlat::New(s.length()); - memcpy(flat->Data(), s.data(), s.length()); - flat->length = s.length(); - return flat; -} - -CordRepRing* FromFlats(Span<absl::string_view const> flats) { - CordRepRing* ring = CordRepRing::Create(MakeFlat(flats[0]), flats.size() - 1); - for (int i = 1; i < flats.size(); ++i) { - ring = CordRepRing::Append(ring, MakeFlat(flats[i])); - } - return ring; -} - -std::array<absl::string_view, 12> TestFlats() { - return {"abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ", - "KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_", - "+-=", "[]\\{}|;':", ",/<>?", "."}; -} - -TEST(CordRingReaderTest, DefaultInstance) { - CordRepRingReader reader; - EXPECT_FALSE(static_cast<bool>(reader)); - EXPECT_THAT(reader.ring(), Eq(nullptr)); -#ifndef NDEBUG - EXPECT_DEATH_IF_SUPPORTED(reader.length(), ".*"); - EXPECT_DEATH_IF_SUPPORTED(reader.consumed(), ".*"); - EXPECT_DEATH_IF_SUPPORTED(reader.remaining(), ".*"); - EXPECT_DEATH_IF_SUPPORTED(reader.Next(), ".*"); - EXPECT_DEATH_IF_SUPPORTED(reader.Seek(0), ".*"); -#endif -} - -TEST(CordRingReaderTest, Reset) { - CordRepRingReader reader; - auto flats = TestFlats(); - CordRepRing* ring = FromFlats(flats); - - absl::string_view first = reader.Reset(ring); - EXPECT_THAT(first, Eq(flats[0])); - EXPECT_TRUE(static_cast<bool>(reader)); - EXPECT_THAT(reader.ring(), Eq(ring)); - EXPECT_THAT(reader.index(), Eq(ring->head())); - EXPECT_THAT(reader.node(), Eq(ring->entry_child(ring->head()))); - EXPECT_THAT(reader.length(), Eq(ring->length)); - EXPECT_THAT(reader.consumed(), Eq(flats[0].length())); - EXPECT_THAT(reader.remaining(), Eq(ring->length - reader.consumed())); - - reader.Reset(); - EXPECT_FALSE(static_cast<bool>(reader)); - EXPECT_THAT(reader.ring(), Eq(nullptr)); - - CordRep::Unref(ring); -} - -TEST(CordRingReaderTest, Next) { - CordRepRingReader reader; - auto flats = TestFlats(); - CordRepRing* ring = FromFlats(flats); - CordRepRing::index_type head = ring->head(); - - reader.Reset(ring); - size_t consumed = reader.consumed(); - size_t remaining = reader.remaining(); - for (int i = 1; i < flats.size(); ++i) { - CordRepRing::index_type index = ring->advance(head, i); - consumed += flats[i].length(); - remaining -= flats[i].length(); - absl::string_view next = reader.Next(); - ASSERT_THAT(next, Eq(flats[i])); - ASSERT_THAT(reader.index(), Eq(index)); - ASSERT_THAT(reader.node(), Eq(ring->entry_child(index))); - ASSERT_THAT(reader.consumed(), Eq(consumed)); - ASSERT_THAT(reader.remaining(), Eq(remaining)); - } - -#ifndef NDEBUG - EXPECT_DEATH_IF_SUPPORTED(reader.Next(), ".*"); -#endif - - CordRep::Unref(ring); -} - -TEST(CordRingReaderTest, SeekForward) { - CordRepRingReader reader; - auto flats = TestFlats(); - CordRepRing* ring = FromFlats(flats); - CordRepRing::index_type head = ring->head(); - - reader.Reset(ring); - size_t consumed = 0; - size_t remaining = ring->length; - for (int i = 0; i < flats.size(); ++i) { - CordRepRing::index_type index = ring->advance(head, i); - size_t offset = consumed; - consumed += flats[i].length(); - remaining -= flats[i].length(); - for (int off = 0; off < flats[i].length(); ++off) { - absl::string_view chunk = reader.Seek(offset + off); - ASSERT_THAT(chunk, Eq(flats[i].substr(off))); - ASSERT_THAT(reader.index(), Eq(index)); - ASSERT_THAT(reader.node(), Eq(ring->entry_child(index))); - ASSERT_THAT(reader.consumed(), Eq(consumed)); - ASSERT_THAT(reader.remaining(), Eq(remaining)); - } - } - - CordRep::Unref(ring); -} - -TEST(CordRingReaderTest, SeekBackward) { - CordRepRingReader reader; - auto flats = TestFlats(); - CordRepRing* ring = FromFlats(flats); - CordRepRing::index_type head = ring->head(); - - reader.Reset(ring); - size_t consumed = ring->length; - size_t remaining = 0; - for (int i = flats.size() - 1; i >= 0; --i) { - CordRepRing::index_type index = ring->advance(head, i); - size_t offset = consumed - flats[i].length(); - for (int off = 0; off < flats[i].length(); ++off) { - absl::string_view chunk = reader.Seek(offset + off); - ASSERT_THAT(chunk, Eq(flats[i].substr(off))); - ASSERT_THAT(reader.index(), Eq(index)); - ASSERT_THAT(reader.node(), Eq(ring->entry_child(index))); - ASSERT_THAT(reader.consumed(), Eq(consumed)); - ASSERT_THAT(reader.remaining(), Eq(remaining)); - } - consumed -= flats[i].length(); - remaining += flats[i].length(); - } -#ifndef NDEBUG - EXPECT_DEATH_IF_SUPPORTED(reader.Seek(ring->length), ".*"); -#endif - CordRep::Unref(ring); -} - -} // namespace -} // namespace cord_internal -ABSL_NAMESPACE_END -} // namespace absl diff --git a/absl/strings/cord_ring_test.cc b/absl/strings/cord_ring_test.cc deleted file mode 100644 index f39a0a4f..00000000 --- a/absl/strings/cord_ring_test.cc +++ /dev/null @@ -1,1454 +0,0 @@ -// Copyright 2020 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 <cstdlib> -#include <ctime> -#include <memory> -#include <random> -#include <sstream> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" -#include "absl/base/config.h" -#include "absl/base/internal/raw_logging.h" -#include "absl/base/macros.h" -#include "absl/debugging/leak_check.h" -#include "absl/strings/internal/cord_internal.h" -#include "absl/strings/internal/cord_rep_ring.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/string_view.h" - -extern thread_local bool cord_ring; - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace { - -using RandomEngine = std::mt19937_64; - -using ::absl::cord_internal::CordRep; -using ::absl::cord_internal::CordRepConcat; -using ::absl::cord_internal::CordRepExternal; -using ::absl::cord_internal::CordRepFlat; -using ::absl::cord_internal::CordRepRing; -using ::absl::cord_internal::CordRepSubstring; - -using ::absl::cord_internal::EXTERNAL; -using ::absl::cord_internal::SUBSTRING; - -using testing::ElementsAre; -using testing::ElementsAreArray; -using testing::Eq; -using testing::Ge; -using testing::Le; -using testing::Lt; -using testing::Ne; -using testing::SizeIs; - -using index_type = CordRepRing::index_type; - -enum InputShareMode { kPrivate, kShared, kSharedIndirect }; - -// TestParam class used by all test fixtures. -// Not all fixtures use all possible input combinations -struct TestParam { - TestParam() = default; - explicit TestParam(InputShareMode input_share_mode) - : input_share_mode(input_share_mode) {} - - // Run the test with the 'rep under test' to be privately owned. - // Otherwise, the rep has a shared ref count of 2 or higher. - bool refcount_is_one = true; - - // Run the test with the 'rep under test' being allocated with enough capacity - // to accommodate any modifications made to it. Otherwise, the rep has zero - // extra (reserve) capacity. - bool with_capacity = true; - - // For test providing possibly shared input such as Append(.., CordpRep*), - // this field defines if that input is adopted with a refcount of one - // (privately owned / donated), or shared. For composite inputs such as - // 'substring of flat', we also have the 'shared indirect' value which means - // the top level node is not shared, but the contained child node is shared. - InputShareMode input_share_mode = kPrivate; - - std::string ToString() const { - return absl::StrCat(refcount_is_one ? "Private" : "Shared", - with_capacity ? "" : "_NoCapacity", - (input_share_mode == kPrivate) ? "" - : (input_share_mode == kShared) - ? "_SharedInput" - : "_IndirectSharedInput"); - } -}; -using TestParams = std::vector<TestParam>; - -// Matcher validating when mutable copies are required / performed. -MATCHER_P2(EqIfPrivate, param, rep, - absl::StrCat("Equal 0x", absl::Hex(rep), " if private")) { - return param.refcount_is_one ? arg == rep : true; -} - -// Matcher validating when mutable copies are required / performed. -MATCHER_P2(EqIfPrivateAndCapacity, param, rep, - absl::StrCat("Equal 0x", absl::Hex(rep), - " if private and capacity")) { - return (param.refcount_is_one && param.with_capacity) ? arg == rep : true; -} - -// Matcher validating a shared ring was re-allocated. Should only be used for -// tests doing exactly one update as subsequent updates could return the -// original (freed and re-used) pointer. -MATCHER_P2(NeIfShared, param, rep, - absl::StrCat("Not equal 0x", absl::Hex(rep), " if shared")) { - return param.refcount_is_one ? true : arg != rep; -} - -MATCHER_P2(EqIfInputPrivate, param, rep, "Equal if input is private") { - return param.input_share_mode == kPrivate ? arg == rep : arg != rep; -} - -// Matcher validating the core in-variants of the CordRepRing instance. -MATCHER(IsValidRingBuffer, "RingBuffer is valid") { - std::stringstream ss; - if (!arg->IsValid(ss)) { - *result_listener << "\nERROR: " << ss.str() << "\nRING = " << *arg; - return false; - } - return true; -} - -// Returns the flats contained in the provided CordRepRing -std::vector<string_view> ToFlats(const CordRepRing* r) { - std::vector<string_view> flats; - flats.reserve(r->entries()); - index_type pos = r->head(); - do { - flats.push_back(r->entry_data(pos)); - } while ((pos = r->advance(pos)) != r->tail()); - return flats; -} - -class not_a_string_view { - public: - explicit not_a_string_view(absl::string_view s) - : data_(s.data()), size_(s.size()) {} - explicit not_a_string_view(const void* data, size_t size) - : data_(data), size_(size) {} - - not_a_string_view remove_prefix(size_t n) const { - return not_a_string_view(static_cast<const char*>(data_) + n, size_ - n); - } - - not_a_string_view remove_suffix(size_t n) const { - return not_a_string_view(data_, size_ - n); - } - - const void* data() const { return data_; } - size_t size() const { return size_; } - - private: - const void* data_; - size_t size_; -}; - -bool operator==(not_a_string_view lhs, not_a_string_view rhs) { - return lhs.data() == rhs.data() && lhs.size() == rhs.size(); -} - -std::ostream& operator<<(std::ostream& s, not_a_string_view rhs) { - return s << "{ data: " << rhs.data() << " size: " << rhs.size() << "}"; -} - -std::vector<not_a_string_view> ToRawFlats(const CordRepRing* r) { - std::vector<not_a_string_view> flats; - flats.reserve(r->entries()); - index_type pos = r->head(); - do { - flats.emplace_back(r->entry_data(pos)); - } while ((pos = r->advance(pos)) != r->tail()); - return flats; -} - -// Returns the value contained in the provided CordRepRing -std::string ToString(const CordRepRing* r) { - std::string value; - value.reserve(r->length); - index_type pos = r->head(); - do { - absl::string_view sv = r->entry_data(pos); - value.append(sv.data(), sv.size()); - } while ((pos = r->advance(pos)) != r->tail()); - return value; -} - -// Creates a flat for testing -CordRep* MakeFlat(absl::string_view s, size_t extra = 0) { - CordRepFlat* flat = CordRepFlat::New(s.length() + extra); - memcpy(flat->Data(), s.data(), s.length()); - flat->length = s.length(); - return flat; -} - -// Creates an external node for testing -CordRepExternal* MakeExternal(absl::string_view s) { - struct Rep : public CordRepExternal { - std::string s; - explicit Rep(absl::string_view s) : s(s) { - this->tag = EXTERNAL; - this->base = s.data(); - this->length = s.length(); - this->releaser_invoker = [](CordRepExternal* self) { - delete static_cast<Rep*>(self); - }; - } - }; - return new Rep(s); -} - -CordRepExternal* MakeFakeExternal(size_t length) { - struct Rep : public CordRepExternal { - std::string s; - explicit Rep(size_t len) { - this->tag = EXTERNAL; - this->base = reinterpret_cast<const char*>(this->storage); - this->length = len; - this->releaser_invoker = [](CordRepExternal* self) { - delete static_cast<Rep*>(self); - }; - } - }; - return new Rep(length); -} - -// Creates a flat or an external node for testing depending on the size. -CordRep* MakeLeaf(absl::string_view s, size_t extra = 0) { - if (s.size() <= absl::cord_internal::kMaxFlatLength) { - return MakeFlat(s, extra); - } else { - return MakeExternal(s); - } -} - -// Creates a substring node -CordRepSubstring* MakeSubstring(size_t start, size_t len, CordRep* rep) { - auto* sub = new CordRepSubstring; - sub->tag = SUBSTRING; - sub->start = start; - sub->length = (len <= 0) ? rep->length - start + len : len; - sub->child = rep; - return sub; -} - -// Creates a substring node removing the specified prefix -CordRepSubstring* RemovePrefix(size_t start, CordRep* rep) { - return MakeSubstring(start, rep->length - start, rep); -} - -// Creates a substring node removing the specified suffix -CordRepSubstring* RemoveSuffix(size_t length, CordRep* rep) { - return MakeSubstring(0, rep->length - length, rep); -} - -enum Composition { kMix, kAppend, kPrepend }; - -Composition RandomComposition() { - RandomEngine rng(GTEST_FLAG_GET(random_seed)); - return (rng() & 1) ? kMix : ((rng() & 1) ? kAppend : kPrepend); -} - -absl::string_view ToString(Composition composition) { - switch (composition) { - case kAppend: - return "Append"; - case kPrepend: - return "Prepend"; - case kMix: - return "Mix"; - } - assert(false); - return "???"; -} - -constexpr const char* kFox = "The quick brown fox jumps over the lazy dog"; -constexpr const char* kFoxFlats[] = {"The ", "quick ", "brown ", - "fox ", "jumps ", "over ", - "the ", "lazy ", "dog"}; - -CordRepRing* FromFlats(Span<const char* const> flats, - Composition composition = kAppend) { - if (flats.empty()) return nullptr; - CordRepRing* ring = nullptr; - switch (composition) { - case kAppend: - ring = CordRepRing::Create(MakeLeaf(flats.front()), flats.size() - 1); - for (int i = 1; i < flats.size(); ++i) { - ring = CordRepRing::Append(ring, MakeLeaf(flats[i])); - } - break; - case kPrepend: - ring = CordRepRing::Create(MakeLeaf(flats.back()), flats.size() - 1); - for (int i = static_cast<int>(flats.size() - 2); i >= 0; --i) { - ring = CordRepRing::Prepend(ring, MakeLeaf(flats[i])); - } - break; - case kMix: - size_t middle1 = flats.size() / 2, middle2 = middle1; - ring = CordRepRing::Create(MakeLeaf(flats[middle1]), flats.size() - 1); - if (!flats.empty()) { - if ((flats.size() & 1) == 0) { - ring = CordRepRing::Prepend(ring, MakeLeaf(flats[--middle1])); - } - for (int i = 1; i <= middle1; ++i) { - ring = CordRepRing::Prepend(ring, MakeLeaf(flats[middle1 - i])); - ring = CordRepRing::Append(ring, MakeLeaf(flats[middle2 + i])); - } - } - break; - } - EXPECT_THAT(ToFlats(ring), ElementsAreArray(flats)); - return ring; -} - -std::ostream& operator<<(std::ostream& s, const TestParam& param) { - return s << param.ToString(); -} - -std::string TestParamToString(const testing::TestParamInfo<TestParam>& info) { - return info.param.ToString(); -} - -class CordRingTest : public testing::Test { - public: - ~CordRingTest() override { - for (CordRep* rep : unrefs_) { - CordRep::Unref(rep); - } - } - - template <typename CordRepType> - CordRepType* NeedsUnref(CordRepType* rep) { - assert(rep); - unrefs_.push_back(rep); - return rep; - } - - template <typename CordRepType> - CordRepType* Ref(CordRepType* rep) { - CordRep::Ref(rep); - return NeedsUnref(rep); - } - - private: - std::vector<CordRep*> unrefs_; -}; - -class CordRingTestWithParam : public testing::TestWithParam<TestParam> { - public: - ~CordRingTestWithParam() override { - for (CordRep* rep : unrefs_) { - CordRep::Unref(rep); - } - } - - CordRepRing* CreateWithCapacity(CordRep* child, size_t extra_capacity) { - if (!GetParam().with_capacity) extra_capacity = 0; - CordRepRing* ring = CordRepRing::Create(child, extra_capacity); - ring->SetCapacityForTesting(1 + extra_capacity); - return RefIfShared(ring); - } - - bool Shared() const { return !GetParam().refcount_is_one; } - bool InputShared() const { return GetParam().input_share_mode == kShared; } - bool InputSharedIndirect() const { - return GetParam().input_share_mode == kSharedIndirect; - } - - template <typename CordRepType> - CordRepType* NeedsUnref(CordRepType* rep) { - assert(rep); - unrefs_.push_back(rep); - return rep; - } - - template <typename CordRepType> - CordRepType* Ref(CordRepType* rep) { - CordRep::Ref(rep); - return NeedsUnref(rep); - } - - template <typename CordRepType> - CordRepType* RefIfShared(CordRepType* rep) { - return Shared() ? Ref(rep) : rep; - } - - template <typename CordRepType> - CordRepType* RefIfInputShared(CordRepType* rep) { - return InputShared() ? Ref(rep) : rep; - } - - template <typename CordRepType> - CordRepType* RefIfInputSharedIndirect(CordRepType* rep) { - return InputSharedIndirect() ? Ref(rep) : rep; - } - - private: - std::vector<CordRep*> unrefs_; -}; - -class CordRingCreateTest : public CordRingTestWithParam { - public: - static TestParams CreateTestParams() { - TestParams params; - params.emplace_back(InputShareMode::kPrivate); - params.emplace_back(InputShareMode::kShared); - return params; - } -}; - -class CordRingSubTest : public CordRingTestWithParam { - public: - static TestParams CreateTestParams() { - TestParams params; - for (bool refcount_is_one : {true, false}) { - TestParam param; - param.refcount_is_one = refcount_is_one; - params.push_back(param); - } - return params; - } -}; - -class CordRingBuildTest : public CordRingTestWithParam { - public: - static TestParams CreateTestParams() { - TestParams params; - for (bool refcount_is_one : {true, false}) { - for (bool with_capacity : {true, false}) { - TestParam param; - param.refcount_is_one = refcount_is_one; - param.with_capacity = with_capacity; - params.push_back(param); - } - } - return params; - } -}; - -class CordRingCreateFromTreeTest : public CordRingTestWithParam { - public: - static TestParams CreateTestParams() { - TestParams params; - params.emplace_back(InputShareMode::kPrivate); - params.emplace_back(InputShareMode::kShared); - params.emplace_back(InputShareMode::kSharedIndirect); - return params; - } -}; - -class CordRingBuildInputTest : public CordRingTestWithParam { - public: - static TestParams CreateTestParams() { - TestParams params; - for (bool refcount_is_one : {true, false}) { - for (bool with_capacity : {true, false}) { - for (InputShareMode share_mode : {kPrivate, kShared, kSharedIndirect}) { - TestParam param; - param.refcount_is_one = refcount_is_one; - param.with_capacity = with_capacity; - param.input_share_mode = share_mode; - params.push_back(param); - } - } - } - return params; - } -}; - -INSTANTIATE_TEST_SUITE_P(WithParam, CordRingSubTest, - testing::ValuesIn(CordRingSubTest::CreateTestParams()), - TestParamToString); - -INSTANTIATE_TEST_SUITE_P( - WithParam, CordRingCreateTest, - testing::ValuesIn(CordRingCreateTest::CreateTestParams()), - TestParamToString); - -INSTANTIATE_TEST_SUITE_P( - WithParam, CordRingCreateFromTreeTest, - testing::ValuesIn(CordRingCreateFromTreeTest::CreateTestParams()), - TestParamToString); - -INSTANTIATE_TEST_SUITE_P( - WithParam, CordRingBuildTest, - testing::ValuesIn(CordRingBuildTest::CreateTestParams()), - TestParamToString); - -INSTANTIATE_TEST_SUITE_P( - WithParam, CordRingBuildInputTest, - testing::ValuesIn(CordRingBuildInputTest::CreateTestParams()), - TestParamToString); - -TEST_P(CordRingCreateTest, CreateFromFlat) { - absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz"; - CordRepRing* result = NeedsUnref(CordRepRing::Create(MakeFlat(str1))); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result->length, Eq(str1.size())); - EXPECT_THAT(ToFlats(result), ElementsAre(str1)); -} - -TEST_P(CordRingCreateTest, CreateFromRing) { - CordRepRing* ring = RefIfShared(FromFlats(kFoxFlats)); - CordRepRing* result = NeedsUnref(CordRepRing::Create(ring)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivate(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(ToFlats(result), ElementsAreArray(kFoxFlats)); -} - -TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringRing) { - CordRepRing* ring = RefIfInputSharedIndirect(FromFlats(kFoxFlats)); - CordRep* sub = RefIfInputShared(MakeSubstring(2, 11, ring)); - CordRepRing* result = NeedsUnref(CordRepRing::Create(sub)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfInputPrivate(GetParam(), ring)); - EXPECT_THAT(ToString(result), string_view(kFox).substr(2, 11)); -} - -TEST_F(CordRingTest, CreateWithIllegalExtraCapacity) { -#if defined(ABSL_HAVE_EXCEPTIONS) - CordRep* flat = NeedsUnref(MakeFlat("Hello world")); - try { - CordRepRing::Create(flat, CordRepRing::kMaxCapacity); - GTEST_FAIL() << "expected std::length_error exception"; - } catch (const std::length_error&) { - } -#elif defined(GTEST_HAS_DEATH_TEST) - CordRep* flat = NeedsUnref(MakeFlat("Hello world")); - EXPECT_DEATH(CordRepRing::Create(flat, CordRepRing::kMaxCapacity), ".*"); -#endif -} - -TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringOfFlat) { - absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz"; - auto* flat = RefIfInputShared(MakeFlat(str1)); - auto* child = RefIfInputSharedIndirect(MakeSubstring(4, 20, flat)); - CordRepRing* result = NeedsUnref(CordRepRing::Create(child)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result->length, Eq(20)); - EXPECT_THAT(ToFlats(result), ElementsAre(str1.substr(4, 20))); -} - -TEST_P(CordRingCreateTest, CreateFromExternal) { - absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz"; - auto* child = RefIfInputShared(MakeExternal(str1)); - CordRepRing* result = NeedsUnref(CordRepRing::Create(child)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result->length, Eq(str1.size())); - EXPECT_THAT(ToFlats(result), ElementsAre(str1)); -} - -TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringOfExternal) { - absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz"; - auto* external = RefIfInputShared(MakeExternal(str1)); - auto* child = RefIfInputSharedIndirect(MakeSubstring(1, 24, external)); - CordRepRing* result = NeedsUnref(CordRepRing::Create(child)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result->length, Eq(24)); - EXPECT_THAT(ToFlats(result), ElementsAre(str1.substr(1, 24))); -} - -TEST_P(CordRingCreateFromTreeTest, CreateFromSubstringOfLargeExternal) { - auto* external = RefIfInputShared(MakeFakeExternal(1 << 20)); - auto str = not_a_string_view(external->base, 1 << 20) - .remove_prefix(1 << 19) - .remove_suffix(6); - auto* child = - RefIfInputSharedIndirect(MakeSubstring(1 << 19, (1 << 19) - 6, external)); - CordRepRing* result = NeedsUnref(CordRepRing::Create(child)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result->length, Eq(str.size())); - EXPECT_THAT(ToRawFlats(result), ElementsAre(str)); -} - -TEST_P(CordRingCreateTest, Properties) { - absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz"; - CordRepRing* result = NeedsUnref(CordRepRing::Create(MakeFlat(str1), 120)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result->head(), Eq(0)); - EXPECT_THAT(result->tail(), Eq(1)); - EXPECT_THAT(result->capacity(), Ge(120 + 1)); - EXPECT_THAT(result->capacity(), Le(2 * 120 + 1)); - EXPECT_THAT(result->entries(), Eq(1)); - EXPECT_THAT(result->begin_pos(), Eq(0)); -} - -TEST_P(CordRingCreateTest, EntryForNewFlat) { - absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz"; - CordRep* child = MakeFlat(str1); - CordRepRing* result = NeedsUnref(CordRepRing::Create(child, 120)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result->entry_child(0), Eq(child)); - EXPECT_THAT(result->entry_end_pos(0), Eq(str1.length())); - EXPECT_THAT(result->entry_data_offset(0), Eq(0)); -} - -TEST_P(CordRingCreateTest, EntryForNewFlatSubstring) { - absl::string_view str1 = "1234567890abcdefghijklmnopqrstuvwxyz"; - CordRep* child = MakeFlat(str1); - CordRep* substring = MakeSubstring(10, 26, child); - CordRepRing* result = NeedsUnref(CordRepRing::Create(substring, 1)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result->entry_child(0), Eq(child)); - EXPECT_THAT(result->entry_end_pos(0), Eq(26)); - EXPECT_THAT(result->entry_data_offset(0), Eq(10)); -} - -TEST_P(CordRingBuildTest, AppendFlat) { - absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz"; - absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1); - CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, MakeFlat(str2))); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(result->length, Eq(str1.size() + str2.size())); - EXPECT_THAT(ToFlats(result), ElementsAre(str1, str2)); -} - -TEST_P(CordRingBuildTest, PrependFlat) { - absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz"; - absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1); - CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, MakeFlat(str2))); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(result->length, Eq(str1.size() + str2.size())); - EXPECT_THAT(ToFlats(result), ElementsAre(str2, str1)); -} - -TEST_P(CordRingBuildTest, AppendString) { - absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz"; - absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1); - CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(result->length, Eq(str1.size() + str2.size())); - EXPECT_THAT(ToFlats(result), ElementsAre(str1, str2)); -} - -TEST_P(CordRingBuildTest, AppendStringHavingExtra) { - absl::string_view str1 = "1234"; - absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - CordRepRing* ring = CreateWithCapacity(MakeFlat(str1, 26), 0); - CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result->length, Eq(str1.size() + str2.size())); - EXPECT_THAT(result, EqIfPrivate(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); -} - -TEST_P(CordRingBuildTest, AppendStringHavingPartialExtra) { - absl::string_view str1 = "1234"; - absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - - // Create flat with at least one extra byte. We don't expect to have sized - // alloc and capacity rounding to grant us enough to not make it partial. - auto* flat = MakeFlat(str1, 1); - size_t avail = flat->flat()->Capacity() - flat->length; - ASSERT_THAT(avail, Lt(str2.size())) << " adjust test for larger flats!"; - - // Construct the flats we do expect using all of `avail`. - absl::string_view str1a = str2.substr(0, avail); - absl::string_view str2a = str2.substr(avail); - - CordRepRing* ring = CreateWithCapacity(flat, 1); - CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result->length, Eq(str1.size() + str2.size())); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - if (GetParam().refcount_is_one) { - EXPECT_THAT(ToFlats(result), ElementsAre(StrCat(str1, str1a), str2a)); - } else { - EXPECT_THAT(ToFlats(result), ElementsAre(str1, str2)); - } -} - -TEST_P(CordRingBuildTest, AppendStringHavingExtraInSubstring) { - absl::string_view str1 = "123456789_1234"; - absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - CordRep* flat = RemovePrefix(10, MakeFlat(str1, 26)); - CordRepRing* ring = CreateWithCapacity(flat, 0); - CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivate(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(result->length, Eq(4 + str2.size())); - if (GetParam().refcount_is_one) { - EXPECT_THAT(ToFlats(result), ElementsAre(StrCat("1234", str2))); - } else { - EXPECT_THAT(ToFlats(result), ElementsAre("1234", str2)); - } -} - -TEST_P(CordRingBuildTest, AppendStringHavingSharedExtra) { - absl::string_view str1 = "123456789_1234"; - absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - for (int shared_type = 0; shared_type < 2; ++shared_type) { - SCOPED_TRACE(absl::StrCat("Shared extra type ", shared_type)); - - // Create a flat that is shared in some way. - CordRep* flat = nullptr; - CordRep* flat1 = nullptr; - if (shared_type == 0) { - // Shared flat - flat = CordRep::Ref(MakeFlat(str1.substr(10), 100)); - } else if (shared_type == 1) { - // Shared flat inside private substring - flat1 = CordRep::Ref(MakeFlat(str1)); - flat = RemovePrefix(10, flat1); - } else { - // Private flat inside shared substring - flat = CordRep::Ref(RemovePrefix(10, MakeFlat(str1, 100))); - } - - CordRepRing* ring = CreateWithCapacity(flat, 1); - CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(result->length, Eq(4 + str2.size())); - EXPECT_THAT(ToFlats(result), ElementsAre("1234", str2)); - - CordRep::Unref(shared_type == 1 ? flat1 : flat); - } -} - -TEST_P(CordRingBuildTest, AppendStringWithExtra) { - absl::string_view str1 = "1234"; - absl::string_view str2 = "1234567890"; - absl::string_view str3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1); - CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, str2, 26)); - result = CordRepRing::Append(result, str3); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result->length, Eq(str1.size() + str2.size() + str3.size())); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(ToFlats(result), ElementsAre(str1, StrCat(str2, str3))); -} - -TEST_P(CordRingBuildTest, PrependString) { - absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz"; - absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - // Use external rep to avoid appending to first flat - CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1); - CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2)); - ASSERT_THAT(result, IsValidRingBuffer()); - if (GetParam().with_capacity && GetParam().refcount_is_one) { - EXPECT_THAT(result, Eq(ring)); - } else { - EXPECT_THAT(result, Ne(ring)); - } - EXPECT_THAT(result->length, Eq(str1.size() + str2.size())); - EXPECT_THAT(ToFlats(result), ElementsAre(str2, str1)); -} - -TEST_P(CordRingBuildTest, PrependStringHavingExtra) { - absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz1234"; - absl::string_view str2 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - CordRep* flat = RemovePrefix(26, MakeFlat(str1)); - CordRepRing* ring = CreateWithCapacity(flat, 0); - CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivate(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(result->length, Eq(4 + str2.size())); - if (GetParam().refcount_is_one) { - EXPECT_THAT(ToFlats(result), ElementsAre(StrCat(str2, "1234"))); - } else { - EXPECT_THAT(ToFlats(result), ElementsAre(str2, "1234")); - } -} - -TEST_P(CordRingBuildTest, PrependStringHavingSharedExtra) { - absl::string_view str1 = "123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - absl::string_view str2 = "abcdefghij"; - absl::string_view str1a = str1.substr(10); - for (int shared_type = 1; shared_type < 2; ++shared_type) { - SCOPED_TRACE(absl::StrCat("Shared extra type ", shared_type)); - - // Create a flat that is shared in some way. - CordRep* flat = nullptr; - CordRep* flat1 = nullptr; - if (shared_type == 1) { - // Shared flat inside private substring - flat = RemovePrefix(10, flat1 = CordRep::Ref(MakeFlat(str1))); - } else { - // Private flat inside shared substring - flat = CordRep::Ref(RemovePrefix(10, MakeFlat(str1, 100))); - } - - CordRepRing* ring = CreateWithCapacity(flat, 1); - CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result->length, Eq(str1a.size() + str2.size())); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(ToFlats(result), ElementsAre(str2, str1a)); - CordRep::Unref(shared_type == 1 ? flat1 : flat); - } -} - -TEST_P(CordRingBuildTest, PrependStringWithExtra) { - absl::string_view str1 = "1234"; - absl::string_view str2 = "1234567890"; - absl::string_view str3 = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; - CordRepRing* ring = CreateWithCapacity(MakeExternal(str1), 1); - CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, str2, 26)); - ASSERT_THAT(result, IsValidRingBuffer()); - result = CordRepRing::Prepend(result, str3); - EXPECT_THAT(result->length, Eq(str1.size() + str2.size() + str3.size())); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(ToFlats(result), ElementsAre(StrCat(str3, str2), str1)); -} - -TEST_P(CordRingBuildTest, AppendPrependStringMix) { - const auto& flats = kFoxFlats; - CordRepRing* ring = CreateWithCapacity(MakeFlat(flats[4]), 8); - CordRepRing* result = ring; - for (int i = 1; i <= 4; ++i) { - result = CordRepRing::Prepend(result, flats[4 - i]); - result = CordRepRing::Append(result, flats[4 + i]); - } - NeedsUnref(result); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(ToString(result), kFox); -} - -TEST_P(CordRingBuildTest, AppendPrependStringMixWithExtra) { - const auto& flats = kFoxFlats; - CordRepRing* ring = CreateWithCapacity(MakeFlat(flats[4], 100), 8); - CordRepRing* result = ring; - for (int i = 1; i <= 4; ++i) { - result = CordRepRing::Prepend(result, flats[4 - i], 100); - result = CordRepRing::Append(result, flats[4 + i], 100); - } - NeedsUnref(result); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - if (GetParam().refcount_is_one) { - EXPECT_THAT(ToFlats(result), - ElementsAre("The quick brown fox ", "jumps over the lazy dog")); - } else { - EXPECT_THAT(ToFlats(result), ElementsAre("The quick brown fox ", "jumps ", - "over the lazy dog")); - } -} - -TEST_P(CordRingBuildTest, AppendPrependStringMixWithPrependedExtra) { - const auto& flats = kFoxFlats; - CordRep* flat = MakeFlat(StrCat(std::string(50, '.'), flats[4]), 50); - CordRepRing* ring = CreateWithCapacity(RemovePrefix(50, flat), 0); - CordRepRing* result = ring; - for (int i = 1; i <= 4; ++i) { - result = CordRepRing::Prepend(result, flats[4 - i], 100); - result = CordRepRing::Append(result, flats[4 + i], 100); - } - result = NeedsUnref(result); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivate(GetParam(), ring)); - if (GetParam().refcount_is_one) { - EXPECT_THAT(ToFlats(result), ElementsAre(kFox)); - } else { - EXPECT_THAT(ToFlats(result), ElementsAre("The quick brown fox ", "jumps ", - "over the lazy dog")); - } -} - -TEST_P(CordRingSubTest, SubRing) { - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - auto flats = MakeSpan(kFoxFlats); - string_view all = kFox; - for (size_t offset = 0; offset < all.size() - 1; ++offset) { - CordRepRing* ring = RefIfShared(FromFlats(flats, composition)); - CordRepRing* result = CordRepRing::SubRing(ring, offset, 0); - EXPECT_THAT(result, nullptr); - - for (size_t len = 1; len < all.size() - offset; ++len) { - ring = RefIfShared(FromFlats(flats, composition)); - result = NeedsUnref(CordRepRing::SubRing(ring, offset, len)); - ASSERT_THAT(result, IsValidRingBuffer()); - ASSERT_THAT(result, EqIfPrivate(GetParam(), ring)); - ASSERT_THAT(result, NeIfShared(GetParam(), ring)); - ASSERT_THAT(ToString(result), Eq(all.substr(offset, len))); - } - } -} - -TEST_P(CordRingSubTest, SubRingFromLargeExternal) { - auto composition = RandomComposition(); - std::string large_string(1 << 20, '.'); - const char* flats[] = { - "abcdefghijklmnopqrstuvwxyz", - large_string.c_str(), - "ABCDEFGHIJKLMNOPQRSTUVWXYZ", - }; - std::string buffer = absl::StrCat(flats[0], flats[1], flats[2]); - absl::string_view all = buffer; - for (size_t offset = 0; offset < 30; ++offset) { - CordRepRing* ring = RefIfShared(FromFlats(flats, composition)); - CordRepRing* result = CordRepRing::SubRing(ring, offset, 0); - EXPECT_THAT(result, nullptr); - - for (size_t len = all.size() - 30; len < all.size() - offset; ++len) { - ring = RefIfShared(FromFlats(flats, composition)); - result = NeedsUnref(CordRepRing::SubRing(ring, offset, len)); - ASSERT_THAT(result, IsValidRingBuffer()); - ASSERT_THAT(result, EqIfPrivate(GetParam(), ring)); - ASSERT_THAT(result, NeIfShared(GetParam(), ring)); - auto str = ToString(result); - ASSERT_THAT(str, SizeIs(len)); - ASSERT_THAT(str, Eq(all.substr(offset, len))); - } - } -} - -TEST_P(CordRingSubTest, RemovePrefix) { - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - auto flats = MakeSpan(kFoxFlats); - string_view all = kFox; - CordRepRing* ring = RefIfShared(FromFlats(flats, composition)); - CordRepRing* result = CordRepRing::RemovePrefix(ring, all.size()); - EXPECT_THAT(result, nullptr); - - for (size_t len = 1; len < all.size(); ++len) { - ring = RefIfShared(FromFlats(flats, composition)); - result = NeedsUnref(CordRepRing::RemovePrefix(ring, len)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivate(GetParam(), ring)); - ASSERT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(ToString(result), Eq(all.substr(len))); - } -} - -TEST_P(CordRingSubTest, RemovePrefixFromLargeExternal) { - CordRepExternal* external1 = MakeFakeExternal(1 << 20); - CordRepExternal* external2 = MakeFakeExternal(1 << 20); - CordRepRing* ring = CordRepRing::Create(external1, 1); - ring = CordRepRing::Append(ring, external2); - CordRepRing* result = NeedsUnref(CordRepRing::RemovePrefix(ring, 1 << 16)); - EXPECT_THAT( - ToRawFlats(result), - ElementsAre( - not_a_string_view(external1->base, 1 << 20).remove_prefix(1 << 16), - not_a_string_view(external2->base, 1 << 20))); -} - -TEST_P(CordRingSubTest, RemoveSuffix) { - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - auto flats = MakeSpan(kFoxFlats); - string_view all = kFox; - CordRepRing* ring = RefIfShared(FromFlats(flats, composition)); - CordRepRing* result = CordRepRing::RemoveSuffix(ring, all.size()); - EXPECT_THAT(result, nullptr); - - for (size_t len = 1; len < all.size(); ++len) { - ring = RefIfShared(FromFlats(flats, composition)); - result = NeedsUnref(CordRepRing::RemoveSuffix(ring, len)); - ASSERT_THAT(result, IsValidRingBuffer()); - ASSERT_THAT(result, EqIfPrivate(GetParam(), ring)); - ASSERT_THAT(result, NeIfShared(GetParam(), ring)); - ASSERT_THAT(ToString(result), Eq(all.substr(0, all.size() - len))); - } -} - -TEST_P(CordRingSubTest, AppendRing) { - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - auto flats = MakeSpan(kFoxFlats).subspan(1); - CordRepRing* ring = CreateWithCapacity(MakeFlat(kFoxFlats[0]), flats.size()); - CordRepRing* child = FromFlats(flats, composition); - CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, child)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivate(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(ToFlats(result), ElementsAreArray(kFoxFlats)); -} - -TEST_P(CordRingBuildInputTest, AppendRingWithFlatOffset) { - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - auto flats = MakeSpan(kFoxFlats); - CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size()); - CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition)); - CordRep* stripped = RemovePrefix(10, child); - CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(ToFlats(result), ElementsAre("Head", "brown ", "fox ", "jumps ", - "over ", "the ", "lazy ", "dog")); -} - -TEST_P(CordRingBuildInputTest, AppendRingWithBrokenOffset) { - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - auto flats = MakeSpan(kFoxFlats); - CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size()); - CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition)); - CordRep* stripped = RemovePrefix(21, child); - CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(ToFlats(result), - ElementsAre("Head", "umps ", "over ", "the ", "lazy ", "dog")); -} - -TEST_P(CordRingBuildInputTest, AppendRingWithFlatLength) { - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - auto flats = MakeSpan(kFoxFlats); - CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size()); - CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition)); - CordRep* stripped = RemoveSuffix(8, child); - CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(ToFlats(result), ElementsAre("Head", "The ", "quick ", "brown ", - "fox ", "jumps ", "over ", "the ")); -} - -TEST_P(CordRingBuildTest, AppendRingWithBrokenFlatLength) { - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - auto flats = MakeSpan(kFoxFlats); - CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size()); - CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition)); - CordRep* stripped = RemoveSuffix(15, child); - CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(ToFlats(result), ElementsAre("Head", "The ", "quick ", "brown ", - "fox ", "jumps ", "ov")); -} - -TEST_P(CordRingBuildTest, AppendRingMiddlePiece) { - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - auto flats = MakeSpan(kFoxFlats); - CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size()); - CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition)); - CordRep* stripped = MakeSubstring(7, child->length - 27, child); - CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(ToFlats(result), - ElementsAre("Head", "ck ", "brown ", "fox ", "jum")); -} - -TEST_P(CordRingBuildTest, AppendRingSinglePiece) { - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - auto flats = MakeSpan(kFoxFlats); - CordRepRing* ring = CreateWithCapacity(MakeFlat("Head"), flats.size()); - CordRep* child = RefIfInputSharedIndirect(FromFlats(flats, composition)); - CordRep* stripped = RefIfInputShared(MakeSubstring(11, 3, child)); - CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(ToFlats(result), ElementsAre("Head", "row")); -} - -TEST_P(CordRingBuildInputTest, AppendRingSinglePieceWithPrefix) { - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - auto flats = MakeSpan(kFoxFlats); - size_t extra_capacity = 1 + (GetParam().with_capacity ? flats.size() : 0); - CordRepRing* ring = CordRepRing::Create(MakeFlat("Head"), extra_capacity); - ring->SetCapacityForTesting(1 + extra_capacity); - ring = RefIfShared(CordRepRing::Prepend(ring, MakeFlat("Prepend"))); - assert(ring->IsValid(std::cout)); - CordRepRing* child = RefIfInputSharedIndirect(FromFlats(flats, composition)); - CordRep* stripped = RefIfInputShared(MakeSubstring(11, 3, child)); - CordRepRing* result = NeedsUnref(CordRepRing::Append(ring, stripped)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(ToFlats(result), ElementsAre("Prepend", "Head", "row")); -} - -TEST_P(CordRingBuildInputTest, PrependRing) { - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - auto fox = MakeSpan(kFoxFlats); - auto flats = MakeSpan(fox).subspan(0, fox.size() - 1); - CordRepRing* ring = CreateWithCapacity(MakeFlat(fox.back()), flats.size()); - CordRepRing* child = RefIfInputShared(FromFlats(flats, composition)); - CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, child)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(ToFlats(result), ElementsAreArray(kFoxFlats)); -} - -TEST_P(CordRingBuildInputTest, PrependRingWithFlatOffset) { - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - auto flats = MakeSpan(kFoxFlats); - CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size()); - CordRep* child = RefIfInputShared(FromFlats(flats, composition)); - CordRep* stripped = RefIfInputSharedIndirect(RemovePrefix(10, child)); - CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(ToFlats(result), ElementsAre("brown ", "fox ", "jumps ", "over ", - "the ", "lazy ", "dog", "Tail")); -} - -TEST_P(CordRingBuildInputTest, PrependRingWithBrokenOffset) { - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - auto flats = MakeSpan(kFoxFlats); - CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size()); - CordRep* child = RefIfInputShared(FromFlats(flats, composition)); - CordRep* stripped = RefIfInputSharedIndirect(RemovePrefix(21, child)); - CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped)); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(ToFlats(result), - ElementsAre("umps ", "over ", "the ", "lazy ", "dog", "Tail")); -} - -TEST_P(CordRingBuildInputTest, PrependRingWithFlatLength) { - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - auto flats = MakeSpan(kFoxFlats); - CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size()); - CordRep* child = RefIfInputShared(FromFlats(flats, composition)); - CordRep* stripped = RefIfInputSharedIndirect(RemoveSuffix(8, child)); - CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(ToFlats(result), ElementsAre("The ", "quick ", "brown ", "fox ", - "jumps ", "over ", "the ", "Tail")); -} - -TEST_P(CordRingBuildInputTest, PrependRingWithBrokenFlatLength) { - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - auto flats = MakeSpan(kFoxFlats); - CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size()); - CordRep* child = RefIfInputShared(FromFlats(flats, composition)); - CordRep* stripped = RefIfInputSharedIndirect(RemoveSuffix(15, child)); - CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(ToFlats(result), ElementsAre("The ", "quick ", "brown ", "fox ", - "jumps ", "ov", "Tail")); -} - -TEST_P(CordRingBuildInputTest, PrependRingMiddlePiece) { - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - auto flats = MakeSpan(kFoxFlats); - CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size()); - CordRep* child = RefIfInputShared(FromFlats(flats, composition)); - CordRep* stripped = - RefIfInputSharedIndirect(MakeSubstring(7, child->length - 27, child)); - CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(ToFlats(result), - ElementsAre("ck ", "brown ", "fox ", "jum", "Tail")); -} - -TEST_P(CordRingBuildInputTest, PrependRingSinglePiece) { - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - auto flats = MakeSpan(kFoxFlats); - CordRepRing* ring = CreateWithCapacity(MakeFlat("Tail"), flats.size()); - CordRep* child = RefIfInputShared(FromFlats(flats, composition)); - CordRep* stripped = RefIfInputSharedIndirect(MakeSubstring(11, 3, child)); - CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(ToFlats(result), ElementsAre("row", "Tail")); -} - -TEST_P(CordRingBuildInputTest, PrependRingSinglePieceWithPrefix) { - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - auto flats = MakeSpan(kFoxFlats); - size_t extra_capacity = 1 + (GetParam().with_capacity ? flats.size() : 0); - CordRepRing* ring = CordRepRing::Create(MakeFlat("Tail"), extra_capacity); - ring->SetCapacityForTesting(1 + extra_capacity); - ring = RefIfShared(CordRepRing::Prepend(ring, MakeFlat("Prepend"))); - CordRep* child = RefIfInputShared(FromFlats(flats, composition)); - CordRep* stripped = RefIfInputSharedIndirect(MakeSubstring(11, 3, child)); - CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, stripped)); - ASSERT_THAT(result, IsValidRingBuffer()); - EXPECT_THAT(result, EqIfPrivateAndCapacity(GetParam(), ring)); - EXPECT_THAT(result, NeIfShared(GetParam(), ring)); - EXPECT_THAT(ToFlats(result), ElementsAre("row", "Prepend", "Tail")); -} - -TEST_F(CordRingTest, Find) { - constexpr const char* flats[] = { - "abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ", - "KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_", - "+-=", "[]\\{}|;':", ",/<>?", "."}; - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - CordRepRing* ring = NeedsUnref(FromFlats(flats, composition)); - std::string value = ToString(ring); - for (int i = 0; i < value.length(); ++i) { - CordRepRing::Position found = ring->Find(i); - auto data = ring->entry_data(found.index); - ASSERT_THAT(found.offset, Lt(data.length())); - ASSERT_THAT(data[found.offset], Eq(value[i])); - } -} - -TEST_F(CordRingTest, FindWithHint) { - constexpr const char* flats[] = { - "abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ", - "KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_", - "+-=", "[]\\{}|;':", ",/<>?", "."}; - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - CordRepRing* ring = NeedsUnref(FromFlats(flats, composition)); - std::string value = ToString(ring); - -#if defined(GTEST_HAS_DEATH_TEST) - // Test hint beyond valid position - index_type head = ring->head(); - EXPECT_DEBUG_DEATH(ring->Find(ring->advance(head), 0), ".*"); - EXPECT_DEBUG_DEATH(ring->Find(ring->advance(head), 9), ".*"); - EXPECT_DEBUG_DEATH(ring->Find(ring->advance(head, 3), 24), ".*"); -#endif - - int flat_pos = 0; - size_t flat_offset = 0; - for (auto sflat : flats) { - string_view flat(sflat); - for (int offset = 0; offset < flat.length(); ++offset) { - for (int start = 0; start <= flat_pos; ++start) { - index_type hint = ring->advance(ring->head(), start); - CordRepRing::Position found = ring->Find(hint, flat_offset + offset); - ASSERT_THAT(found.index, Eq(ring->advance(ring->head(), flat_pos))); - ASSERT_THAT(found.offset, Eq(offset)); - } - } - ++flat_pos; - flat_offset += flat.length(); - } -} - -TEST_F(CordRingTest, FindInLargeRing) { - constexpr const char* flats[] = { - "abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ", - "KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_", - "+-=", "[]\\{}|;':", ",/<>?", "."}; - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - CordRepRing* ring = FromFlats(flats, composition); - for (int i = 0; i < 13; ++i) { - ring = CordRepRing::Append(ring, FromFlats(flats, composition)); - } - NeedsUnref(ring); - std::string value = ToString(ring); - for (int i = 0; i < value.length(); ++i) { - CordRepRing::Position pos = ring->Find(i); - auto data = ring->entry_data(pos.index); - ASSERT_THAT(pos.offset, Lt(data.length())); - ASSERT_THAT(data[pos.offset], Eq(value[i])); - } -} - -TEST_F(CordRingTest, FindTail) { - constexpr const char* flats[] = { - "abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ", - "KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_", - "+-=", "[]\\{}|;':", ",/<>?", "."}; - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - CordRepRing* ring = NeedsUnref(FromFlats(flats, composition)); - std::string value = ToString(ring); - - for (int i = 0; i < value.length(); ++i) { - CordRepRing::Position pos = ring->FindTail(i + 1); - auto data = ring->entry_data(ring->retreat(pos.index)); - ASSERT_THAT(pos.offset, Lt(data.length())); - ASSERT_THAT(data[data.length() - pos.offset - 1], Eq(value[i])); - } -} - -TEST_F(CordRingTest, FindTailWithHint) { - constexpr const char* flats[] = { - "abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ", - "KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_", - "+-=", "[]\\{}|;':", ",/<>?", "."}; - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - CordRepRing* ring = NeedsUnref(FromFlats(flats, composition)); - std::string value = ToString(ring); - - // Test hint beyond valid position -#if defined(GTEST_HAS_DEATH_TEST) - index_type head = ring->head(); - EXPECT_DEBUG_DEATH(ring->FindTail(ring->advance(head), 1), ".*"); - EXPECT_DEBUG_DEATH(ring->FindTail(ring->advance(head), 10), ".*"); - EXPECT_DEBUG_DEATH(ring->FindTail(ring->advance(head, 3), 26), ".*"); -#endif - - for (int i = 0; i < value.length(); ++i) { - CordRepRing::Position pos = ring->FindTail(i + 1); - auto data = ring->entry_data(ring->retreat(pos.index)); - ASSERT_THAT(pos.offset, Lt(data.length())); - ASSERT_THAT(data[data.length() - pos.offset - 1], Eq(value[i])); - } -} - -TEST_F(CordRingTest, FindTailInLargeRing) { - constexpr const char* flats[] = { - "abcdefghij", "klmnopqrst", "uvwxyz", "ABCDEFGHIJ", - "KLMNOPQRST", "UVWXYZ", "1234567890", "~!@#$%^&*()_", - "+-=", "[]\\{}|;':", ",/<>?", "."}; - auto composition = RandomComposition(); - SCOPED_TRACE(ToString(composition)); - CordRepRing* ring = FromFlats(flats, composition); - for (int i = 0; i < 13; ++i) { - ring = CordRepRing::Append(ring, FromFlats(flats, composition)); - } - NeedsUnref(ring); - std::string value = ToString(ring); - for (int i = 0; i < value.length(); ++i) { - CordRepRing::Position pos = ring->FindTail(i + 1); - auto data = ring->entry_data(ring->retreat(pos.index)); - ASSERT_THAT(pos.offset, Lt(data.length())); - ASSERT_THAT(data[data.length() - pos.offset - 1], Eq(value[i])); - } -} - -TEST_F(CordRingTest, GetCharacter) { - auto flats = MakeSpan(kFoxFlats); - CordRepRing* ring = CordRepRing::Create(MakeFlat("Tail"), flats.size()); - CordRep* child = FromFlats(flats, kAppend); - CordRepRing* result = NeedsUnref(CordRepRing::Prepend(ring, child)); - std::string value = ToString(result); - for (int i = 0; i < value.length(); ++i) { - ASSERT_THAT(result->GetCharacter(i), Eq(value[i])); - } -} - -TEST_F(CordRingTest, GetCharacterWithSubstring) { - absl::string_view str1 = "abcdefghijklmnopqrstuvwxyz"; - auto* child = MakeSubstring(4, 20, MakeFlat(str1)); - CordRepRing* result = NeedsUnref(CordRepRing::Create(child)); - ASSERT_THAT(result, IsValidRingBuffer()); - std::string value = ToString(result); - for (int i = 0; i < value.length(); ++i) { - ASSERT_THAT(result->GetCharacter(i), Eq(value[i])); - } -} - -TEST_F(CordRingTest, IsFlatSingleFlat) { - for (bool external : {false, true}) { - SCOPED_TRACE(external ? "With External" : "With Flat"); - absl::string_view str = "Hello world"; - CordRep* rep = external ? MakeExternal(str) : MakeFlat(str); - CordRepRing* ring = NeedsUnref(CordRepRing::Create(rep)); - - // The ring is a single non-fragmented flat: - absl::string_view fragment; - EXPECT_TRUE(ring->IsFlat(nullptr)); - EXPECT_TRUE(ring->IsFlat(&fragment)); - EXPECT_THAT(fragment, Eq("Hello world")); - fragment = ""; - EXPECT_TRUE(ring->IsFlat(0, 11, nullptr)); - EXPECT_TRUE(ring->IsFlat(0, 11, &fragment)); - EXPECT_THAT(fragment, Eq("Hello world")); - - // Arbitrary ranges must check true as well. - EXPECT_TRUE(ring->IsFlat(1, 4, &fragment)); - EXPECT_THAT(fragment, Eq("ello")); - EXPECT_TRUE(ring->IsFlat(6, 5, &fragment)); - EXPECT_THAT(fragment, Eq("world")); - } -} - -TEST_F(CordRingTest, IsFlatMultiFlat) { - for (bool external : {false, true}) { - SCOPED_TRACE(external ? "With External" : "With Flat"); - absl::string_view str1 = "Hello world"; - absl::string_view str2 = "Halt and catch fire"; - CordRep* rep1 = external ? MakeExternal(str1) : MakeFlat(str1); - CordRep* rep2 = external ? MakeExternal(str2) : MakeFlat(str2); - CordRepRing* ring = CordRepRing::Append(CordRepRing::Create(rep1), rep2); - NeedsUnref(ring); - - // The ring is fragmented, IsFlat() on the entire cord must be false. - EXPECT_FALSE(ring->IsFlat(nullptr)); - absl::string_view fragment = "Don't touch this"; - EXPECT_FALSE(ring->IsFlat(&fragment)); - EXPECT_THAT(fragment, Eq("Don't touch this")); - - // Check for ranges exactly within both flats. - EXPECT_TRUE(ring->IsFlat(0, 11, &fragment)); - EXPECT_THAT(fragment, Eq("Hello world")); - EXPECT_TRUE(ring->IsFlat(11, 19, &fragment)); - EXPECT_THAT(fragment, Eq("Halt and catch fire")); - - // Check for arbitrary partial range inside each flat. - EXPECT_TRUE(ring->IsFlat(1, 4, &fragment)); - EXPECT_THAT(fragment, "ello"); - EXPECT_TRUE(ring->IsFlat(26, 4, &fragment)); - EXPECT_THAT(fragment, "fire"); - - // Check ranges spanning across both flats - fragment = "Don't touch this"; - EXPECT_FALSE(ring->IsFlat(1, 18, &fragment)); - EXPECT_FALSE(ring->IsFlat(10, 2, &fragment)); - EXPECT_THAT(fragment, Eq("Don't touch this")); - } -} - -TEST_F(CordRingTest, Dump) { - std::stringstream ss; - auto flats = MakeSpan(kFoxFlats); - CordRepRing* ring = NeedsUnref(FromFlats(flats, kPrepend)); - ss << *ring; -} - -} // namespace -ABSL_NAMESPACE_END -} // namespace absl diff --git a/absl/strings/cord_test.cc b/absl/strings/cord_test.cc index 5603e94c..f1a5f39c 100644 --- a/absl/strings/cord_test.cc +++ b/absl/strings/cord_test.cc @@ -15,32 +15,50 @@ #include "absl/strings/cord.h" #include <algorithm> -#include <climits> +#include <array> +#include <cassert> +#include <cstddef> +#include <cstdint> #include <cstdio> +#include <cstring> +#include <iostream> #include <iterator> -#include <map> -#include <numeric> +#include <limits> #include <random> +#include <set> #include <sstream> +#include <string> #include <type_traits> #include <utility> #include <vector> #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/internal/endian.h" -#include "absl/base/internal/raw_logging.h" #include "absl/base/macros.h" +#include "absl/base/options.h" #include "absl/container/fixed_array.h" +#include "absl/functional/function_ref.h" #include "absl/hash/hash.h" +#include "absl/log/check.h" +#include "absl/log/log.h" #include "absl/random/random.h" +#include "absl/strings/cord_buffer.h" #include "absl/strings/cord_test_helpers.h" #include "absl/strings/cordz_test_helpers.h" +#include "absl/strings/internal/cord_internal.h" +#include "absl/strings/internal/cord_rep_crc.h" +#include "absl/strings/internal/cord_rep_flat.h" +#include "absl/strings/internal/cordz_statistics.h" +#include "absl/strings/internal/cordz_update_tracker.h" +#include "absl/strings/internal/string_constant.h" #include "absl/strings/match.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" +#include "absl/types/optional.h" // convenience local constants static constexpr auto FLAT = absl::cord_internal::FLAT; @@ -58,6 +76,8 @@ using absl::cord_internal::CordRepSubstring; using absl::cord_internal::CordzUpdateTracker; using absl::cord_internal::kFlatOverhead; using absl::cord_internal::kMaxFlatLength; +using ::testing::ElementsAre; +using ::testing::Le; static std::string RandomLowercaseString(RandomEngine* rng); static std::string RandomLowercaseString(RandomEngine* rng, size_t length); @@ -208,9 +228,8 @@ class CordTestPeer { } static Cord MakeSubstring(Cord src, size_t offset, size_t length) { - ABSL_RAW_CHECK(src.contents_.is_tree(), "Can not be inlined"); - ABSL_RAW_CHECK(src.ExpectedChecksum() == absl::nullopt, - "Can not be hardened"); + CHECK(src.contents_.is_tree()) << "Can not be inlined"; + CHECK(!src.ExpectedChecksum().has_value()) << "Can not be hardened"; Cord cord; auto* tree = cord_internal::SkipCrcNode(src.contents_.tree()); auto* rep = CordRepSubstring::Create(CordRep::Ref(tree), offset, length); @@ -372,7 +391,7 @@ TEST_P(CordTest, GigabyteCordFromExternal) { for (int i = 0; i < 1024; ++i) { c.Append(from); } - ABSL_RAW_LOG(INFO, "Made a Cord with %zu bytes!", c.size()); + LOG(INFO) << "Made a Cord with " << c.size() << " bytes!"; // Note: on a 32-bit build, this comes out to 2,818,048,000 bytes. // Note: on a 64-bit build, this comes out to 171,932,385,280 bytes. } @@ -482,6 +501,93 @@ TEST_P(CordTest, StartsEndsWith) { ASSERT_TRUE(!empty.EndsWith("xyz")); } +TEST_P(CordTest, Contains) { + auto flat_haystack = absl::Cord("this is a flat cord"); + auto fragmented_haystack = absl::MakeFragmentedCord( + {"this", " ", "is", " ", "a", " ", "fragmented", " ", "cord"}); + + EXPECT_TRUE(flat_haystack.Contains("")); + EXPECT_TRUE(fragmented_haystack.Contains("")); + EXPECT_TRUE(flat_haystack.Contains(absl::Cord(""))); + EXPECT_TRUE(fragmented_haystack.Contains(absl::Cord(""))); + EXPECT_TRUE(absl::Cord("").Contains("")); + EXPECT_TRUE(absl::Cord("").Contains(absl::Cord(""))); + EXPECT_FALSE(absl::Cord("").Contains(flat_haystack)); + EXPECT_FALSE(absl::Cord("").Contains(fragmented_haystack)); + + EXPECT_FALSE(flat_haystack.Contains("z")); + EXPECT_FALSE(fragmented_haystack.Contains("z")); + EXPECT_FALSE(flat_haystack.Contains(absl::Cord("z"))); + EXPECT_FALSE(fragmented_haystack.Contains(absl::Cord("z"))); + + EXPECT_FALSE(flat_haystack.Contains("is an")); + EXPECT_FALSE(fragmented_haystack.Contains("is an")); + EXPECT_FALSE(flat_haystack.Contains(absl::Cord("is an"))); + EXPECT_FALSE(fragmented_haystack.Contains(absl::Cord("is an"))); + EXPECT_FALSE( + flat_haystack.Contains(absl::MakeFragmentedCord({"is", " ", "an"}))); + EXPECT_FALSE(fragmented_haystack.Contains( + absl::MakeFragmentedCord({"is", " ", "an"}))); + + EXPECT_TRUE(flat_haystack.Contains("is a")); + EXPECT_TRUE(fragmented_haystack.Contains("is a")); + EXPECT_TRUE(flat_haystack.Contains(absl::Cord("is a"))); + EXPECT_TRUE(fragmented_haystack.Contains(absl::Cord("is a"))); + EXPECT_TRUE( + flat_haystack.Contains(absl::MakeFragmentedCord({"is", " ", "a"}))); + EXPECT_TRUE( + fragmented_haystack.Contains(absl::MakeFragmentedCord({"is", " ", "a"}))); +} + +TEST_P(CordTest, Find) { + auto flat_haystack = absl::Cord("this is a flat cord"); + auto fragmented_haystack = absl::MakeFragmentedCord( + {"this", " ", "is", " ", "a", " ", "fragmented", " ", "cord"}); + auto empty_haystack = absl::Cord(""); + + EXPECT_EQ(flat_haystack.Find(""), flat_haystack.char_begin()); + EXPECT_EQ(fragmented_haystack.Find(""), fragmented_haystack.char_begin()); + EXPECT_EQ(flat_haystack.Find(absl::Cord("")), flat_haystack.char_begin()); + EXPECT_EQ(fragmented_haystack.Find(absl::Cord("")), + fragmented_haystack.char_begin()); + EXPECT_EQ(empty_haystack.Find(""), empty_haystack.char_begin()); + EXPECT_EQ(empty_haystack.Find(absl::Cord("")), empty_haystack.char_begin()); + EXPECT_EQ(empty_haystack.Find(flat_haystack), empty_haystack.char_end()); + EXPECT_EQ(empty_haystack.Find(fragmented_haystack), + empty_haystack.char_end()); + + EXPECT_EQ(flat_haystack.Find("z"), flat_haystack.char_end()); + EXPECT_EQ(fragmented_haystack.Find("z"), fragmented_haystack.char_end()); + EXPECT_EQ(flat_haystack.Find(absl::Cord("z")), flat_haystack.char_end()); + EXPECT_EQ(fragmented_haystack.Find(absl::Cord("z")), + fragmented_haystack.char_end()); + + EXPECT_EQ(flat_haystack.Find("is an"), flat_haystack.char_end()); + EXPECT_EQ(fragmented_haystack.Find("is an"), fragmented_haystack.char_end()); + EXPECT_EQ(flat_haystack.Find(absl::Cord("is an")), flat_haystack.char_end()); + EXPECT_EQ(fragmented_haystack.Find(absl::Cord("is an")), + fragmented_haystack.char_end()); + EXPECT_EQ(flat_haystack.Find(absl::MakeFragmentedCord({"is", " ", "an"})), + flat_haystack.char_end()); + EXPECT_EQ( + fragmented_haystack.Find(absl::MakeFragmentedCord({"is", " ", "an"})), + fragmented_haystack.char_end()); + + EXPECT_EQ(flat_haystack.Find("is a"), + std::next(flat_haystack.char_begin(), 5)); + EXPECT_EQ(fragmented_haystack.Find("is a"), + std::next(fragmented_haystack.char_begin(), 5)); + EXPECT_EQ(flat_haystack.Find(absl::Cord("is a")), + std::next(flat_haystack.char_begin(), 5)); + EXPECT_EQ(fragmented_haystack.Find(absl::Cord("is a")), + std::next(fragmented_haystack.char_begin(), 5)); + EXPECT_EQ(flat_haystack.Find(absl::MakeFragmentedCord({"is", " ", "a"})), + std::next(flat_haystack.char_begin(), 5)); + EXPECT_EQ( + fragmented_haystack.Find(absl::MakeFragmentedCord({"is", " ", "a"})), + std::next(fragmented_haystack.char_begin(), 5)); +} + TEST_P(CordTest, Subcord) { RandomEngine rng(GTEST_FLAG_GET(random_seed)); const std::string s = RandomLowercaseString(&rng, 1024); @@ -618,7 +724,7 @@ TEST_P(CordTest, AppendEmptyBufferToTree) { TEST_P(CordTest, AppendSmallBuffer) { absl::Cord cord; absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(3); - ASSERT_THAT(buffer.capacity(), ::testing::Le(15)); + ASSERT_THAT(buffer.capacity(), Le(15)); memcpy(buffer.data(), "Abc", 3); buffer.SetLength(3); cord.Append(std::move(buffer)); @@ -632,7 +738,7 @@ TEST_P(CordTest, AppendSmallBuffer) { EXPECT_EQ(buffer.length(), 0); // NOLINT EXPECT_GT(buffer.capacity(), 0); // NOLINT - EXPECT_THAT(cord.Chunks(), ::testing::ElementsAre("Abcdefgh")); + EXPECT_THAT(cord.Chunks(), ElementsAre("Abcdefgh")); } TEST_P(CordTest, AppendAndPrependBufferArePrecise) { @@ -671,7 +777,7 @@ TEST_P(CordTest, AppendAndPrependBufferArePrecise) { TEST_P(CordTest, PrependSmallBuffer) { absl::Cord cord; absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(3); - ASSERT_THAT(buffer.capacity(), ::testing::Le(15)); + ASSERT_THAT(buffer.capacity(), Le(15)); memcpy(buffer.data(), "Abc", 3); buffer.SetLength(3); cord.Prepend(std::move(buffer)); @@ -685,7 +791,7 @@ TEST_P(CordTest, PrependSmallBuffer) { EXPECT_EQ(buffer.length(), 0); // NOLINT EXPECT_GT(buffer.capacity(), 0); // NOLINT - EXPECT_THAT(cord.Chunks(), ::testing::ElementsAre("defghAbc")); + EXPECT_THAT(cord.Chunks(), ElementsAre("defghAbc")); } TEST_P(CordTest, AppendLargeBuffer) { @@ -707,7 +813,7 @@ TEST_P(CordTest, AppendLargeBuffer) { EXPECT_EQ(buffer.length(), 0); // NOLINT EXPECT_GT(buffer.capacity(), 0); // NOLINT - EXPECT_THAT(cord.Chunks(), ::testing::ElementsAre(s1, s2)); + EXPECT_THAT(cord.Chunks(), ElementsAre(s1, s2)); } TEST_P(CordTest, PrependLargeBuffer) { @@ -729,7 +835,7 @@ TEST_P(CordTest, PrependLargeBuffer) { EXPECT_EQ(buffer.length(), 0); // NOLINT EXPECT_GT(buffer.capacity(), 0); // NOLINT - EXPECT_THAT(cord.Chunks(), ::testing::ElementsAre(s2, s1)); + EXPECT_THAT(cord.Chunks(), ElementsAre(s2, s1)); } class CordAppendBufferTest : public testing::TestWithParam<bool> { @@ -1245,15 +1351,15 @@ absl::Cord BigCord(size_t len, char v) { // Splice block into cord. absl::Cord SpliceCord(const absl::Cord& blob, int64_t offset, const absl::Cord& block) { - ABSL_RAW_CHECK(offset >= 0, ""); - ABSL_RAW_CHECK(offset + block.size() <= blob.size(), ""); + CHECK_GE(offset, 0); + CHECK_LE(static_cast<size_t>(offset) + block.size(), blob.size()); absl::Cord result(blob); result.RemoveSuffix(blob.size() - offset); result.Append(block); absl::Cord suffix(blob); suffix.RemovePrefix(offset + block.size()); result.Append(suffix); - ABSL_RAW_CHECK(blob.size() == result.size(), ""); + CHECK_EQ(blob.size(), result.size()); return result; } @@ -1763,6 +1869,8 @@ TEST_P(CordTest, ExternalMemoryGet) { // of empty and inlined cords, and flat nodes. constexpr auto kFairShare = absl::CordMemoryAccounting::kFairShare; +constexpr auto kTotalMorePrecise = + absl::CordMemoryAccounting::kTotalMorePrecise; // Creates a cord of `n` `c` values, making sure no string stealing occurs. absl::Cord MakeCord(size_t n, char c) { @@ -1774,12 +1882,14 @@ TEST(CordTest, CordMemoryUsageEmpty) { absl::Cord cord; EXPECT_EQ(sizeof(absl::Cord), cord.EstimatedMemoryUsage()); EXPECT_EQ(sizeof(absl::Cord), cord.EstimatedMemoryUsage(kFairShare)); + EXPECT_EQ(sizeof(absl::Cord), cord.EstimatedMemoryUsage(kTotalMorePrecise)); } TEST(CordTest, CordMemoryUsageInlined) { absl::Cord a("hello"); EXPECT_EQ(a.EstimatedMemoryUsage(), sizeof(absl::Cord)); EXPECT_EQ(a.EstimatedMemoryUsage(kFairShare), sizeof(absl::Cord)); + EXPECT_EQ(a.EstimatedMemoryUsage(kTotalMorePrecise), sizeof(absl::Cord)); } TEST(CordTest, CordMemoryUsageExternalMemory) { @@ -1789,6 +1899,7 @@ TEST(CordTest, CordMemoryUsageExternalMemory) { sizeof(absl::Cord) + 1000 + sizeof(CordRepExternal) + sizeof(intptr_t); EXPECT_EQ(cord.EstimatedMemoryUsage(), expected); EXPECT_EQ(cord.EstimatedMemoryUsage(kFairShare), expected); + EXPECT_EQ(cord.EstimatedMemoryUsage(kTotalMorePrecise), expected); } TEST(CordTest, CordMemoryUsageFlat) { @@ -1798,6 +1909,8 @@ TEST(CordTest, CordMemoryUsageFlat) { EXPECT_EQ(cord.EstimatedMemoryUsage(), sizeof(absl::Cord) + flat_size); EXPECT_EQ(cord.EstimatedMemoryUsage(kFairShare), sizeof(absl::Cord) + flat_size); + EXPECT_EQ(cord.EstimatedMemoryUsage(kTotalMorePrecise), + sizeof(absl::Cord) + flat_size); } TEST(CordTest, CordMemoryUsageSubStringSharedFlat) { @@ -1807,6 +1920,8 @@ TEST(CordTest, CordMemoryUsageSubStringSharedFlat) { absl::Cord cord = flat.Subcord(500, 1000); EXPECT_EQ(cord.EstimatedMemoryUsage(), sizeof(absl::Cord) + sizeof(CordRepSubstring) + flat_size); + EXPECT_EQ(cord.EstimatedMemoryUsage(kTotalMorePrecise), + sizeof(absl::Cord) + sizeof(CordRepSubstring) + flat_size); EXPECT_EQ(cord.EstimatedMemoryUsage(kFairShare), sizeof(absl::Cord) + sizeof(CordRepSubstring) + flat_size / 2); } @@ -1817,6 +1932,8 @@ TEST(CordTest, CordMemoryUsageFlatShared) { const size_t flat_size = absl::CordTestPeer::Tree(cord)->flat()->AllocatedSize(); EXPECT_EQ(cord.EstimatedMemoryUsage(), sizeof(absl::Cord) + flat_size); + EXPECT_EQ(cord.EstimatedMemoryUsage(kTotalMorePrecise), + sizeof(absl::Cord) + flat_size); EXPECT_EQ(cord.EstimatedMemoryUsage(kFairShare), sizeof(absl::Cord) + flat_size / 2); } @@ -1835,6 +1952,8 @@ TEST(CordTest, CordMemoryUsageFlatHardenedAndShared) { absl::Cord cord2(cord); EXPECT_EQ(cord2.EstimatedMemoryUsage(), sizeof(absl::Cord) + sizeof(CordRepCrc) + flat_size); + EXPECT_EQ(cord2.EstimatedMemoryUsage(kTotalMorePrecise), + sizeof(absl::Cord) + sizeof(CordRepCrc) + flat_size); EXPECT_EQ(cord2.EstimatedMemoryUsage(kFairShare), sizeof(absl::Cord) + (sizeof(CordRepCrc) + flat_size / 2) / 2); } @@ -1853,7 +1972,7 @@ TEST(CordTest, CordMemoryUsageBTree) { // windows DLL, we may have ODR like effects on the flag, meaning the DLL // code will run with the picked up default. if (!absl::CordTestPeer::Tree(cord1)->IsBtree()) { - ABSL_RAW_LOG(WARNING, "Cord library code not respecting btree flag"); + LOG(WARNING) << "Cord library code not respecting btree flag"; return; } @@ -1861,6 +1980,8 @@ TEST(CordTest, CordMemoryUsageBTree) { size_t rep1_shared_size = sizeof(CordRepBtree) + flats1_size / 2; EXPECT_EQ(cord1.EstimatedMemoryUsage(), sizeof(absl::Cord) + rep1_size); + EXPECT_EQ(cord1.EstimatedMemoryUsage(kTotalMorePrecise), + sizeof(absl::Cord) + rep1_size); EXPECT_EQ(cord1.EstimatedMemoryUsage(kFairShare), sizeof(absl::Cord) + rep1_shared_size); @@ -1875,6 +1996,8 @@ TEST(CordTest, CordMemoryUsageBTree) { size_t rep2_size = sizeof(CordRepBtree) + flats2_size; EXPECT_EQ(cord2.EstimatedMemoryUsage(), sizeof(absl::Cord) + rep2_size); + EXPECT_EQ(cord2.EstimatedMemoryUsage(kTotalMorePrecise), + sizeof(absl::Cord) + rep2_size); EXPECT_EQ(cord2.EstimatedMemoryUsage(kFairShare), sizeof(absl::Cord) + rep2_size); @@ -1883,6 +2006,8 @@ TEST(CordTest, CordMemoryUsageBTree) { EXPECT_EQ(cord.EstimatedMemoryUsage(), sizeof(absl::Cord) + sizeof(CordRepBtree) + rep1_size + rep2_size); + EXPECT_EQ(cord.EstimatedMemoryUsage(kTotalMorePrecise), + sizeof(absl::Cord) + sizeof(CordRepBtree) + rep1_size + rep2_size); EXPECT_EQ(cord.EstimatedMemoryUsage(kFairShare), sizeof(absl::Cord) + sizeof(CordRepBtree) + rep1_shared_size / 2 + rep2_size); @@ -1901,6 +2026,66 @@ TEST_P(CordTest, CordMemoryUsageInlineRep) { EXPECT_EQ(c1.EstimatedMemoryUsage(), c2.EstimatedMemoryUsage()); } +TEST_P(CordTest, CordMemoryUsageTotalMorePreciseMode) { + constexpr size_t kChunkSize = 2000; + std::string tmp_str(kChunkSize, 'x'); + const absl::Cord flat(std::move(tmp_str)); + + // Construct `fragmented` with two references into the same + // underlying buffer shared with `flat`: + absl::Cord fragmented(flat); + fragmented.Append(flat); + + // Memory usage of `flat`, minus the top-level Cord object: + const size_t flat_internal_usage = + flat.EstimatedMemoryUsage() - sizeof(absl::Cord); + + // `fragmented` holds a Cord and a CordRepBtree. That tree points to two + // copies of flat's internals, which we expect to dedup: + EXPECT_EQ(fragmented.EstimatedMemoryUsage(kTotalMorePrecise), + sizeof(absl::Cord) + + sizeof(CordRepBtree) + + flat_internal_usage); + + // This is a case where kTotal produces an overestimate: + EXPECT_EQ(fragmented.EstimatedMemoryUsage(), + sizeof(absl::Cord) + + sizeof(CordRepBtree) + + 2 * flat_internal_usage); +} + +TEST_P(CordTest, CordMemoryUsageTotalMorePreciseModeWithSubstring) { + constexpr size_t kChunkSize = 2000; + std::string tmp_str(kChunkSize, 'x'); + const absl::Cord flat(std::move(tmp_str)); + + // Construct `fragmented` with two references into the same + // underlying buffer shared with `flat`. + // + // This time, each reference is through a Subcord(): + absl::Cord fragmented; + fragmented.Append(flat.Subcord(1, kChunkSize - 2)); + fragmented.Append(flat.Subcord(1, kChunkSize - 2)); + + // Memory usage of `flat`, minus the top-level Cord object: + const size_t flat_internal_usage = + flat.EstimatedMemoryUsage() - sizeof(absl::Cord); + + // `fragmented` holds a Cord and a CordRepBtree. That tree points to two + // CordRepSubstrings, each pointing at flat's internals. + EXPECT_EQ(fragmented.EstimatedMemoryUsage(kTotalMorePrecise), + sizeof(absl::Cord) + + sizeof(CordRepBtree) + + 2 * sizeof(CordRepSubstring) + + flat_internal_usage); + + // This is a case where kTotal produces an overestimate: + EXPECT_EQ(fragmented.EstimatedMemoryUsage(), + sizeof(absl::Cord) + + sizeof(CordRepBtree) + + 2 * sizeof(CordRepSubstring) + + 2 * flat_internal_usage); +} } // namespace // Regtest for 7510292 (fix a bug introduced by 7465150) @@ -1925,8 +2110,6 @@ TEST_P(CordTest, DiabolicalGrowth) { // This test exercises a diabolical Append(<one char>) on a cord, making the // cord shared before each Append call resulting in a terribly fragmented // resulting cord. - // TODO(b/183983616): Apply some minimum compaction when copying a shared - // source cord into a mutable copy for updates in CordRepRing. RandomEngine rng(GTEST_FLAG_GET(random_seed)); const std::string expected = RandomLowercaseString(&rng, 5000); absl::Cord cord; @@ -1938,8 +2121,7 @@ TEST_P(CordTest, DiabolicalGrowth) { std::string value; absl::CopyCordToString(cord, &value); EXPECT_EQ(value, expected); - ABSL_RAW_LOG(INFO, "Diabolical size allocated = %zu", - cord.EstimatedMemoryUsage()); + LOG(INFO) << "Diabolical size allocated = " << cord.EstimatedMemoryUsage(); } // The following tests check support for >4GB cords in 64-bit binaries, and @@ -2474,6 +2656,13 @@ TEST_P(CordTest, Format) { EXPECT_EQ(c, "There were 0003 little pigs.And 1 bad wolf!"); } +TEST_P(CordTest, Stringify) { + absl::Cord c = + absl::MakeFragmentedCord({"A ", "small ", "fragmented ", "Cord", "."}); + MaybeHarden(c); + EXPECT_EQ(absl::StrCat(c), "A small fragmented Cord."); +} + TEST_P(CordTest, Hardening) { absl::Cord cord("hello"); MaybeHarden(cord); @@ -3058,6 +3247,12 @@ TEST_P(CordTest, ChecksummedEmptyCord) { EXPECT_EQ(absl::HashOf(c3), absl::HashOf(absl::string_view())); } +TEST(CrcCordTest, ChecksummedEmptyCordEstimateMemoryUsage) { + absl::Cord cord; + cord.SetExpectedChecksum(0); + EXPECT_NE(cord.EstimatedMemoryUsage(), 0); +} + #if defined(GTEST_HAS_DEATH_TEST) && defined(ABSL_INTERNAL_CORD_HAVE_SANITIZER) // Returns an expected poison / uninitialized death message expression. diff --git a/absl/strings/cord_test_helpers.h b/absl/strings/cord_test_helpers.h index 31a1dc89..ca52240a 100644 --- a/absl/strings/cord_test_helpers.h +++ b/absl/strings/cord_test_helpers.h @@ -51,7 +51,7 @@ enum class TestCordSize { // existing inputs rather than copying contents of the input. kMedium = cord_internal::kMaxFlatLength / 2 + 1, - // A string value large enough to cause it to be stored in mutliple flats. + // A string value large enough to cause it to be stored in multiple flats. kLarge = cord_internal::kMaxFlatLength * 4 }; diff --git a/absl/strings/cordz_test.cc b/absl/strings/cordz_test.cc index 2b7d30b0..a35493e3 100644 --- a/absl/strings/cordz_test.cc +++ b/absl/strings/cordz_test.cc @@ -12,18 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include <cstdint> +#include <cstddef> +#include <cstring> +#include <ostream> #include <string> +#include <utility> #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/config.h" -#include "absl/base/internal/raw_logging.h" -#include "absl/base/macros.h" #include "absl/strings/cord.h" +#include "absl/strings/cord_buffer.h" #include "absl/strings/cord_test_helpers.h" #include "absl/strings/cordz_test_helpers.h" -#include "absl/strings/internal/cordz_functions.h" +#include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cordz_info.h" #include "absl/strings/internal/cordz_sample_token.h" #include "absl/strings/internal/cordz_statistics.h" diff --git a/absl/strings/cordz_test_helpers.h b/absl/strings/cordz_test_helpers.h index e410eecf..619f13c2 100644 --- a/absl/strings/cordz_test_helpers.h +++ b/absl/strings/cordz_test_helpers.h @@ -21,6 +21,7 @@ #include "gtest/gtest.h" #include "absl/base/config.h" #include "absl/base/macros.h" +#include "absl/base/nullability.h" #include "absl/strings/cord.h" #include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cordz_info.h" @@ -33,15 +34,16 @@ namespace absl { ABSL_NAMESPACE_BEGIN // Returns the CordzInfo for the cord, or nullptr if the cord is not sampled. -inline const cord_internal::CordzInfo* GetCordzInfoForTesting( +inline absl::Nullable<const cord_internal::CordzInfo*> GetCordzInfoForTesting( const Cord& cord) { if (!cord.contents_.is_tree()) return nullptr; return cord.contents_.cordz_info(); } // Returns true if the provided cordz_info is in the list of sampled cords. -inline bool CordzInfoIsListed(const cord_internal::CordzInfo* cordz_info, - cord_internal::CordzSampleToken token = {}) { +inline bool CordzInfoIsListed( + absl::Nonnull<const cord_internal::CordzInfo*> cordz_info, + cord_internal::CordzSampleToken token = {}) { for (const cord_internal::CordzInfo& info : token) { if (cordz_info == &info) return true; } @@ -119,7 +121,7 @@ class CordzSamplingIntervalHelper { // Wrapper struct managing a small CordRep `rep` struct TestCordRep { - cord_internal::CordRepFlat* rep; + absl::Nonnull<cord_internal::CordRepFlat*> rep; TestCordRep() { rep = cord_internal::CordRepFlat::New(100); diff --git a/absl/strings/escaping.cc b/absl/strings/escaping.cc index 93966846..1c0eac42 100644 --- a/absl/strings/escaping.cc +++ b/absl/strings/escaping.cc @@ -16,21 +16,22 @@ #include <algorithm> #include <cassert> +#include <cstddef> #include <cstdint> #include <cstring> -#include <iterator> #include <limits> #include <string> -#include "absl/base/internal/endian.h" +#include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" #include "absl/base/internal/unaligned_access.h" -#include "absl/strings/internal/char_map.h" +#include "absl/strings/ascii.h" +#include "absl/strings/charset.h" #include "absl/strings/internal/escaping.h" #include "absl/strings/internal/resize_uninitialized.h" #include "absl/strings/internal/utf8.h" +#include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" -#include "absl/strings/str_join.h" #include "absl/strings/string_view.h" namespace absl { @@ -443,6 +444,8 @@ void CEscapeAndAppendInternal(absl::string_view src, std::string* dest) { } } +// Reverses the mapping in Base64EscapeInternal; see that method's +// documentation for details of the mapping. bool Base64UnescapeInternal(const char* src_param, size_t szsrc, char* dest, size_t szdest, const signed char* unbase64, size_t* len) { @@ -676,7 +679,10 @@ bool Base64UnescapeInternal(const char* src_param, size_t szsrc, char* dest, return ok; } -// The arrays below were generated by the following code +// The arrays below map base64-escaped characters back to their original values. +// For the inverse case, see k(WebSafe)Base64Chars in the internal +// escaping.cc. +// These arrays were generated by the following inversion code: // #include <sys/time.h> // #include <stdlib.h> // #include <string.h> @@ -703,8 +709,8 @@ bool Base64UnescapeInternal(const char* src_param, size_t szsrc, char* dest, // } // } // -// where the value of "Base64[]" was replaced by one of the base-64 conversion -// tables from the functions below. +// where the value of "Base64[]" was replaced by one of k(WebSafe)Base64Chars +// in the internal escaping.cc. /* clang-format off */ constexpr signed char kUnBase64[] = { -1, -1, -1, -1, -1, -1, -1, -1, @@ -777,9 +783,6 @@ constexpr signed char kUnWebSafeBase64[] = { }; /* clang-format on */ -constexpr char kWebSafeBase64Chars[] = - "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; - template <typename String> bool Base64UnescapeInternal(const char* src, size_t slen, String* dest, const signed char* unbase64) { @@ -880,30 +883,6 @@ std::string Utf8SafeCHexEscape(absl::string_view src) { return CEscapeInternal(src, true, true); } -// ---------------------------------------------------------------------- -// Base64Unescape() - base64 decoder -// Base64Escape() - base64 encoder -// WebSafeBase64Unescape() - Google's variation of base64 decoder -// WebSafeBase64Escape() - Google's variation of base64 encoder -// -// Check out -// https://datatracker.ietf.org/doc/html/rfc2045 for formal description, but -// what we care about is that... -// Take the encoded stuff in groups of 4 characters and turn each -// character into a code 0 to 63 thus: -// A-Z map to 0 to 25 -// a-z map to 26 to 51 -// 0-9 map to 52 to 61 -// +(- for WebSafe) maps to 62 -// /(_ for WebSafe) maps to 63 -// There will be four numbers, all less than 64 which can be represented -// by a 6 digit binary number (aaaaaa, bbbbbb, cccccc, dddddd respectively). -// Arrange the 6 digit binary numbers into three bytes as such: -// aaaaaabb bbbbcccc ccdddddd -// Equals signs (one or two) are used at the end of the encoded block to -// indicate that the text was not an integer multiple of three bytes long. -// ---------------------------------------------------------------------- - bool Base64Unescape(absl::string_view src, std::string* dest) { return Base64UnescapeInternal(src.data(), src.size(), dest, kUnBase64); } @@ -921,7 +900,7 @@ void Base64Escape(absl::string_view src, std::string* dest) { void WebSafeBase64Escape(absl::string_view src, std::string* dest) { strings_internal::Base64EscapeInternal( reinterpret_cast<const unsigned char*>(src.data()), src.size(), dest, - false, kWebSafeBase64Chars); + false, strings_internal::kWebSafeBase64Chars); } std::string Base64Escape(absl::string_view src) { @@ -936,7 +915,7 @@ std::string WebSafeBase64Escape(absl::string_view src) { std::string dest; strings_internal::Base64EscapeInternal( reinterpret_cast<const unsigned char*>(src.data()), src.size(), &dest, - false, kWebSafeBase64Chars); + false, strings_internal::kWebSafeBase64Chars); return dest; } diff --git a/absl/strings/escaping.h b/absl/strings/escaping.h index 7c082fef..bf2a5898 100644 --- a/absl/strings/escaping.h +++ b/absl/strings/escaping.h @@ -121,7 +121,7 @@ std::string Utf8SafeCHexEscape(absl::string_view src); // // Encodes a `src` string into a base64-encoded 'dest' string with padding // characters. This function conforms with RFC 4648 section 4 (base64) and RFC -// 2045. See also CalculateBase64EscapedLen(). +// 2045. void Base64Escape(absl::string_view src, std::string* dest); std::string Base64Escape(absl::string_view src); diff --git a/absl/strings/escaping_benchmark.cc b/absl/strings/escaping_benchmark.cc index 10d5b033..f792226a 100644 --- a/absl/strings/escaping_benchmark.cc +++ b/absl/strings/escaping_benchmark.cc @@ -14,13 +14,17 @@ #include "absl/strings/escaping.h" +#include <cstdint> #include <cstdio> #include <cstring> +#include <memory> #include <random> +#include <string> #include "benchmark/benchmark.h" #include "absl/base/internal/raw_logging.h" #include "absl/strings/internal/escaping_test_common.h" +#include "absl/strings/str_cat.h" namespace { diff --git a/absl/strings/escaping_test.cc b/absl/strings/escaping_test.cc index 44ffcba7..ca1ee45c 100644 --- a/absl/strings/escaping_test.cc +++ b/absl/strings/escaping_test.cc @@ -15,17 +15,20 @@ #include "absl/strings/escaping.h" #include <array> +#include <cstddef> #include <cstdio> #include <cstring> +#include <initializer_list> #include <memory> +#include <string> #include <vector> -#include "gmock/gmock.h" #include "gtest/gtest.h" -#include "absl/container/fixed_array.h" +#include "absl/log/check.h" #include "absl/strings/str_cat.h" #include "absl/strings/internal/escaping_test_common.h" +#include "absl/strings/string_view.h" namespace { @@ -562,6 +565,7 @@ template <typename StringType> void TestEscapeAndUnescape() { // Check the short strings; this tests the math (and boundaries) for (const auto& tc : base64_tests) { + // Test plain base64. StringType encoded("this junk should be ignored"); absl::Base64Escape(tc.plaintext, &encoded); EXPECT_EQ(encoded, tc.cyphertext); @@ -571,22 +575,26 @@ void TestEscapeAndUnescape() { EXPECT_TRUE(absl::Base64Unescape(encoded, &decoded)); EXPECT_EQ(decoded, tc.plaintext); - StringType websafe(tc.cyphertext); - for (int c = 0; c < websafe.size(); ++c) { - if ('+' == websafe[c]) websafe[c] = '-'; - if ('/' == websafe[c]) websafe[c] = '_'; + StringType websafe_with_padding(tc.cyphertext); + for (unsigned int c = 0; c < websafe_with_padding.size(); ++c) { + if ('+' == websafe_with_padding[c]) websafe_with_padding[c] = '-'; + if ('/' == websafe_with_padding[c]) websafe_with_padding[c] = '_'; + // Intentionally keeping padding aka '='. + } + + // Test plain websafe (aka without padding). + StringType websafe(websafe_with_padding); + for (unsigned int c = 0; c < websafe.size(); ++c) { if ('=' == websafe[c]) { websafe.resize(c); break; } } - encoded = "this junk should be ignored"; absl::WebSafeBase64Escape(tc.plaintext, &encoded); EXPECT_EQ(encoded, websafe); EXPECT_EQ(absl::WebSafeBase64Escape(tc.plaintext), websafe); - // Let's try the string version of the decoder decoded = "this junk should be ignored"; EXPECT_TRUE(absl::WebSafeBase64Unescape(websafe, &decoded)); EXPECT_EQ(decoded, tc.plaintext); diff --git a/absl/strings/has_absl_stringify.h b/absl/strings/has_absl_stringify.h new file mode 100644 index 00000000..274a7865 --- /dev/null +++ b/absl/strings/has_absl_stringify.h @@ -0,0 +1,63 @@ +// 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_HAS_ABSL_STRINGIFY_H_ +#define ABSL_STRINGS_HAS_ABSL_STRINGIFY_H_ + +#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); +}; + +} // namespace strings_internal + +// `HasAbslStringify<T>` detects if type `T` supports the `AbslStringify()` +// customization point (see +// https://abseil.io/docs/cpp/guides/format#abslstringify for the +// documentation). +// +// Note that there are types that can be `StrCat`-ed that do not use the +// `AbslStringify` customization point (for example, `int`). + +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 {}; + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_STRINGS_HAS_ABSL_STRINGIFY_H_ diff --git a/absl/strings/has_absl_stringify_test.cc b/absl/strings/has_absl_stringify_test.cc new file mode 100644 index 00000000..59e7e1d3 --- /dev/null +++ b/absl/strings/has_absl_stringify_test.cc @@ -0,0 +1,40 @@ +// Copyright 2023 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/has_absl_stringify.h" + +#include <string> + +#include "gtest/gtest.h" +#include "absl/types/optional.h" + +namespace { + +struct TypeWithoutAbslStringify {}; + +struct TypeWithAbslStringify { + template <typename Sink> + friend void AbslStringify(Sink&, const TypeWithAbslStringify&) {} +}; + +TEST(HasAbslStringifyTest, Works) { + EXPECT_FALSE(absl::HasAbslStringify<int>::value); + EXPECT_FALSE(absl::HasAbslStringify<std::string>::value); + EXPECT_FALSE(absl::HasAbslStringify<TypeWithoutAbslStringify>::value); + EXPECT_TRUE(absl::HasAbslStringify<TypeWithAbslStringify>::value); + EXPECT_FALSE( + absl::HasAbslStringify<absl::optional<TypeWithAbslStringify>>::value); +} + +} // namespace diff --git a/absl/strings/has_ostream_operator.h b/absl/strings/has_ostream_operator.h new file mode 100644 index 00000000..156ffc74 --- /dev/null +++ b/absl/strings/has_ostream_operator.h @@ -0,0 +1,42 @@ +// Copyright 2023 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_HAS_OSTREAM_OPERATOR_H_ +#define ABSL_STRINGS_HAS_OSTREAM_OPERATOR_H_ + +#include <ostream> +#include <type_traits> +#include <utility> + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +// Detects if type `T` supports streaming to `std::ostream`s with `operator<<`. + +template <typename T, typename = void> +struct HasOstreamOperator : std::false_type {}; + +template <typename T> +struct HasOstreamOperator< + T, std::enable_if_t<std::is_same< + std::ostream&, decltype(std::declval<std::ostream&>() + << std::declval<const T&>())>::value>> + : std::true_type {}; + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_STRINGS_HAS_OSTREAM_OPERATOR_H_ diff --git a/absl/strings/has_ostream_operator_test.cc b/absl/strings/has_ostream_operator_test.cc new file mode 100644 index 00000000..9ef41366 --- /dev/null +++ b/absl/strings/has_ostream_operator_test.cc @@ -0,0 +1,41 @@ +// Copyright 2023 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/has_ostream_operator.h" + +#include <ostream> +#include <string> + +#include "gtest/gtest.h" +#include "absl/types/optional.h" + +namespace { + +struct TypeWithoutOstreamOp {}; + +struct TypeWithOstreamOp { + friend std::ostream& operator<<(std::ostream& os, const TypeWithOstreamOp&) { + return os; + } +}; + +TEST(HasOstreamOperatorTest, Works) { + EXPECT_TRUE(absl::HasOstreamOperator<int>::value); + EXPECT_TRUE(absl::HasOstreamOperator<std::string>::value); + EXPECT_FALSE(absl::HasOstreamOperator<absl::optional<int>>::value); + EXPECT_FALSE(absl::HasOstreamOperator<TypeWithoutOstreamOp>::value); + EXPECT_TRUE(absl::HasOstreamOperator<TypeWithOstreamOp>::value); +} + +} // namespace diff --git a/absl/strings/internal/char_map.h b/absl/strings/internal/char_map.h deleted file mode 100644 index 70a90343..00000000 --- a/absl/strings/internal/char_map.h +++ /dev/null @@ -1,158 +0,0 @@ -// Copyright 2017 The Abseil Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// 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. -// -// Character Map Class -// -// A fast, bit-vector map for 8-bit unsigned characters. -// This class is useful for non-character purposes as well. - -#ifndef ABSL_STRINGS_INTERNAL_CHAR_MAP_H_ -#define ABSL_STRINGS_INTERNAL_CHAR_MAP_H_ - -#include <cstddef> -#include <cstdint> -#include <cstring> - -#include "absl/base/macros.h" -#include "absl/base/port.h" - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace strings_internal { - -class Charmap { - public: - constexpr Charmap() : m_() {} - - // Initializes with a given char*. Note that NUL is not treated as - // a terminator, but rather a char to be flicked. - Charmap(const char* str, int len) : m_() { - while (len--) SetChar(*str++); - } - - // Initializes with a given char*. NUL is treated as a terminator - // and will not be in the charmap. - explicit Charmap(const char* str) : m_() { - while (*str) SetChar(*str++); - } - - constexpr bool contains(unsigned char c) const { - return (m_[c / 64] >> (c % 64)) & 0x1; - } - - // Returns true if and only if a character exists in both maps. - bool IntersectsWith(const Charmap& c) const { - for (size_t i = 0; i < ABSL_ARRAYSIZE(m_); ++i) { - if ((m_[i] & c.m_[i]) != 0) return true; - } - return false; - } - - bool IsZero() const { - for (uint64_t c : m_) { - if (c != 0) return false; - } - return true; - } - - // Containing only a single specified char. - static constexpr Charmap Char(char x) { - return Charmap(CharMaskForWord(x, 0), CharMaskForWord(x, 1), - CharMaskForWord(x, 2), CharMaskForWord(x, 3)); - } - - // Containing all the chars in the C-string 's'. - static constexpr Charmap FromString(const char* s) { - Charmap ret; - while (*s) ret = ret | Char(*s++); - return ret; - } - - // Containing all the chars in the closed interval [lo,hi]. - static constexpr Charmap Range(char lo, char hi) { - return Charmap(RangeForWord(lo, hi, 0), RangeForWord(lo, hi, 1), - RangeForWord(lo, hi, 2), RangeForWord(lo, hi, 3)); - } - - friend constexpr Charmap operator&(const Charmap& a, const Charmap& b) { - return Charmap(a.m_[0] & b.m_[0], a.m_[1] & b.m_[1], a.m_[2] & b.m_[2], - a.m_[3] & b.m_[3]); - } - - friend constexpr Charmap operator|(const Charmap& a, const Charmap& b) { - return Charmap(a.m_[0] | b.m_[0], a.m_[1] | b.m_[1], a.m_[2] | b.m_[2], - a.m_[3] | b.m_[3]); - } - - friend constexpr Charmap operator~(const Charmap& a) { - return Charmap(~a.m_[0], ~a.m_[1], ~a.m_[2], ~a.m_[3]); - } - - private: - constexpr Charmap(uint64_t b0, uint64_t b1, uint64_t b2, uint64_t b3) - : m_{b0, b1, b2, b3} {} - - static constexpr uint64_t RangeForWord(char lo, char hi, uint64_t word) { - return OpenRangeFromZeroForWord(static_cast<unsigned char>(hi) + 1, word) & - ~OpenRangeFromZeroForWord(static_cast<unsigned char>(lo), word); - } - - // All the chars in the specified word of the range [0, upper). - static constexpr uint64_t OpenRangeFromZeroForWord(uint64_t upper, - uint64_t word) { - return (upper <= 64 * word) - ? 0 - : (upper >= 64 * (word + 1)) - ? ~static_cast<uint64_t>(0) - : (~static_cast<uint64_t>(0) >> (64 - upper % 64)); - } - - static constexpr uint64_t CharMaskForWord(char x, uint64_t word) { - const auto unsigned_x = static_cast<unsigned char>(x); - return (unsigned_x / 64 == word) - ? (static_cast<uint64_t>(1) << (unsigned_x % 64)) - : 0; - } - - void SetChar(char c) { - const auto unsigned_c = static_cast<unsigned char>(c); - m_[unsigned_c / 64] |= static_cast<uint64_t>(1) << (unsigned_c % 64); - } - - uint64_t m_[4]; -}; - -// Mirror the char-classifying predicates in <cctype> -constexpr Charmap UpperCharmap() { return Charmap::Range('A', 'Z'); } -constexpr Charmap LowerCharmap() { return Charmap::Range('a', 'z'); } -constexpr Charmap DigitCharmap() { return Charmap::Range('0', '9'); } -constexpr Charmap AlphaCharmap() { return LowerCharmap() | UpperCharmap(); } -constexpr Charmap AlnumCharmap() { return DigitCharmap() | AlphaCharmap(); } -constexpr Charmap XDigitCharmap() { - return DigitCharmap() | Charmap::Range('A', 'F') | Charmap::Range('a', 'f'); -} -constexpr Charmap PrintCharmap() { return Charmap::Range(0x20, 0x7e); } -constexpr Charmap SpaceCharmap() { return Charmap::FromString("\t\n\v\f\r "); } -constexpr Charmap CntrlCharmap() { - return Charmap::Range(0, 0x7f) & ~PrintCharmap(); -} -constexpr Charmap BlankCharmap() { return Charmap::FromString("\t "); } -constexpr Charmap GraphCharmap() { return PrintCharmap() & ~SpaceCharmap(); } -constexpr Charmap PunctCharmap() { return GraphCharmap() & ~AlnumCharmap(); } - -} // namespace strings_internal -ABSL_NAMESPACE_END -} // namespace absl - -#endif // ABSL_STRINGS_INTERNAL_CHAR_MAP_H_ diff --git a/absl/strings/internal/char_map_test.cc b/absl/strings/internal/char_map_test.cc deleted file mode 100644 index d3306241..00000000 --- a/absl/strings/internal/char_map_test.cc +++ /dev/null @@ -1,172 +0,0 @@ -// Copyright 2017 The Abseil Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// 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/char_map.h" - -#include <cctype> -#include <string> -#include <vector> - -#include "gmock/gmock.h" -#include "gtest/gtest.h" - -namespace { - -constexpr absl::strings_internal::Charmap everything_map = - ~absl::strings_internal::Charmap(); -constexpr absl::strings_internal::Charmap nothing_map{}; - -TEST(Charmap, AllTests) { - const absl::strings_internal::Charmap also_nothing_map("", 0); - ASSERT_TRUE(everything_map.contains('\0')); - ASSERT_TRUE(!nothing_map.contains('\0')); - ASSERT_TRUE(!also_nothing_map.contains('\0')); - for (unsigned char ch = 1; ch != 0; ++ch) { - ASSERT_TRUE(everything_map.contains(ch)); - ASSERT_TRUE(!nothing_map.contains(ch)); - ASSERT_TRUE(!also_nothing_map.contains(ch)); - } - - const absl::strings_internal::Charmap symbols("&@#@^!@?", 5); - ASSERT_TRUE(symbols.contains('&')); - ASSERT_TRUE(symbols.contains('@')); - ASSERT_TRUE(symbols.contains('#')); - ASSERT_TRUE(symbols.contains('^')); - ASSERT_TRUE(!symbols.contains('!')); - ASSERT_TRUE(!symbols.contains('?')); - int cnt = 0; - for (unsigned char ch = 1; ch != 0; ++ch) - cnt += symbols.contains(ch); - ASSERT_EQ(cnt, 4); - - const absl::strings_internal::Charmap lets("^abcde", 3); - const absl::strings_internal::Charmap lets2("fghij\0klmnop", 10); - const absl::strings_internal::Charmap lets3("fghij\0klmnop"); - ASSERT_TRUE(lets2.contains('k')); - ASSERT_TRUE(!lets3.contains('k')); - - ASSERT_TRUE(symbols.IntersectsWith(lets)); - ASSERT_TRUE(!lets2.IntersectsWith(lets)); - ASSERT_TRUE(lets.IntersectsWith(symbols)); - ASSERT_TRUE(!lets.IntersectsWith(lets2)); - - ASSERT_TRUE(nothing_map.IsZero()); - ASSERT_TRUE(!lets.IsZero()); -} - -namespace { -std::string Members(const absl::strings_internal::Charmap& m) { - std::string r; - for (size_t i = 0; i < 256; ++i) - if (m.contains(i)) r.push_back(i); - return r; -} - -std::string ClosedRangeString(unsigned char lo, unsigned char hi) { - // Don't depend on lo<hi. Just increment until lo==hi. - std::string s; - while (true) { - s.push_back(lo); - if (lo == hi) break; - ++lo; - } - return s; -} - -} // namespace - -TEST(Charmap, Constexpr) { - constexpr absl::strings_internal::Charmap kEmpty = nothing_map; - EXPECT_THAT(Members(kEmpty), ""); - constexpr absl::strings_internal::Charmap kA = - absl::strings_internal::Charmap::Char('A'); - EXPECT_THAT(Members(kA), "A"); - constexpr absl::strings_internal::Charmap kAZ = - absl::strings_internal::Charmap::Range('A', 'Z'); - EXPECT_THAT(Members(kAZ), "ABCDEFGHIJKLMNOPQRSTUVWXYZ"); - constexpr absl::strings_internal::Charmap kIdentifier = - absl::strings_internal::Charmap::Range('0', '9') | - absl::strings_internal::Charmap::Range('A', 'Z') | - absl::strings_internal::Charmap::Range('a', 'z') | - absl::strings_internal::Charmap::Char('_'); - EXPECT_THAT(Members(kIdentifier), - "0123456789" - "ABCDEFGHIJKLMNOPQRSTUVWXYZ" - "_" - "abcdefghijklmnopqrstuvwxyz"); - constexpr absl::strings_internal::Charmap kAll = everything_map; - for (size_t i = 0; i < 256; ++i) { - EXPECT_TRUE(kAll.contains(i)) << i; - } - constexpr absl::strings_internal::Charmap kHello = - absl::strings_internal::Charmap::FromString("Hello, world!"); - EXPECT_THAT(Members(kHello), " !,Hdelorw"); - - // test negation and intersection - constexpr absl::strings_internal::Charmap kABC = - absl::strings_internal::Charmap::Range('A', 'Z') & - ~absl::strings_internal::Charmap::Range('D', 'Z'); - EXPECT_THAT(Members(kABC), "ABC"); -} - -TEST(Charmap, Range) { - // Exhaustive testing takes too long, so test some of the boundaries that - // are perhaps going to cause trouble. - std::vector<size_t> poi = {0, 1, 2, 3, 4, 7, 8, 9, 15, - 16, 17, 30, 31, 32, 33, 63, 64, 65, - 127, 128, 129, 223, 224, 225, 254, 255}; - for (auto lo = poi.begin(); lo != poi.end(); ++lo) { - SCOPED_TRACE(*lo); - for (auto hi = lo; hi != poi.end(); ++hi) { - SCOPED_TRACE(*hi); - EXPECT_THAT(Members(absl::strings_internal::Charmap::Range(*lo, *hi)), - ClosedRangeString(*lo, *hi)); - } - } -} - -bool AsBool(int x) { return static_cast<bool>(x); } - -TEST(CharmapCtype, Match) { - for (int c = 0; c < 256; ++c) { - SCOPED_TRACE(c); - SCOPED_TRACE(static_cast<char>(c)); - EXPECT_EQ(AsBool(std::isupper(c)), - absl::strings_internal::UpperCharmap().contains(c)); - EXPECT_EQ(AsBool(std::islower(c)), - absl::strings_internal::LowerCharmap().contains(c)); - EXPECT_EQ(AsBool(std::isdigit(c)), - absl::strings_internal::DigitCharmap().contains(c)); - EXPECT_EQ(AsBool(std::isalpha(c)), - absl::strings_internal::AlphaCharmap().contains(c)); - EXPECT_EQ(AsBool(std::isalnum(c)), - absl::strings_internal::AlnumCharmap().contains(c)); - EXPECT_EQ(AsBool(std::isxdigit(c)), - absl::strings_internal::XDigitCharmap().contains(c)); - EXPECT_EQ(AsBool(std::isprint(c)), - absl::strings_internal::PrintCharmap().contains(c)); - EXPECT_EQ(AsBool(std::isspace(c)), - absl::strings_internal::SpaceCharmap().contains(c)); - EXPECT_EQ(AsBool(std::iscntrl(c)), - absl::strings_internal::CntrlCharmap().contains(c)); - EXPECT_EQ(AsBool(std::isblank(c)), - absl::strings_internal::BlankCharmap().contains(c)); - EXPECT_EQ(AsBool(std::isgraph(c)), - absl::strings_internal::GraphCharmap().contains(c)); - EXPECT_EQ(AsBool(std::ispunct(c)), - absl::strings_internal::PunctCharmap().contains(c)); - } -} - -} // namespace diff --git a/absl/strings/internal/charconv_bigint.cc b/absl/strings/internal/charconv_bigint.cc index 282b639e..46b5289a 100644 --- a/absl/strings/internal/charconv_bigint.cc +++ b/absl/strings/internal/charconv_bigint.cc @@ -296,10 +296,8 @@ template <int max_words> std::min(n / kLargePowerOfFiveStep, kLargestPowerOfFiveIndex); if (first_pass) { // just copy, rather than multiplying by 1 - std::copy( - LargePowerOfFiveData(big_power), - LargePowerOfFiveData(big_power) + LargePowerOfFiveSize(big_power), - answer.words_); + std::copy_n(LargePowerOfFiveData(big_power), + LargePowerOfFiveSize(big_power), answer.words_); answer.size_ = LargePowerOfFiveSize(big_power); first_pass = false; } else { diff --git a/absl/strings/internal/charconv_bigint.h b/absl/strings/internal/charconv_bigint.h index 8f702976..5c0c375d 100644 --- a/absl/strings/internal/charconv_bigint.h +++ b/absl/strings/internal/charconv_bigint.h @@ -92,7 +92,7 @@ class BigUnsigned { // numbers with this many decimal digits or fewer are representable by this // type. // - // Analagous to std::numeric_limits<BigUnsigned>::digits10. + // Analogous to std::numeric_limits<BigUnsigned>::digits10. static constexpr int Digits10() { // 9975007/1035508 is very slightly less than log10(2**32). return static_cast<uint64_t>(max_words) * 9975007 / 1035508; @@ -121,7 +121,7 @@ class BigUnsigned { ++size_; } } - std::fill(words_, words_ + word_shift, 0u); + std::fill_n(words_, word_shift, 0u); } } @@ -197,7 +197,7 @@ class BigUnsigned { } void SetToZero() { - std::fill(words_, words_ + size_, 0u); + std::fill_n(words_, size_, 0u); size_ = 0; } diff --git a/absl/strings/internal/charconv_parse_test.cc b/absl/strings/internal/charconv_parse_test.cc index bc2d1118..2b7b0821 100644 --- a/absl/strings/internal/charconv_parse_test.cc +++ b/absl/strings/internal/charconv_parse_test.cc @@ -19,7 +19,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "absl/base/internal/raw_logging.h" +#include "absl/log/check.h" #include "absl/strings/str_cat.h" using absl::chars_format; @@ -56,14 +56,14 @@ void ExpectParsedFloat(std::string s, absl::chars_format format_flags, begin_subrange = static_cast<int>(open_bracket_pos); s.replace(open_bracket_pos, 1, ""); std::string::size_type close_bracket_pos = s.find(']'); - ABSL_RAW_CHECK(close_bracket_pos != absl::string_view::npos, - "Test input contains [ without matching ]"); + CHECK_NE(close_bracket_pos, absl::string_view::npos) + << "Test input contains [ without matching ]"; end_subrange = static_cast<int>(close_bracket_pos); s.replace(close_bracket_pos, 1, ""); } const std::string::size_type expected_characters_matched = s.find('$'); - ABSL_RAW_CHECK(expected_characters_matched != std::string::npos, - "Input string must contain $"); + CHECK_NE(expected_characters_matched, std::string::npos) + << "Input string must contain $"; s.replace(expected_characters_matched, 1, ""); ParsedFloat parsed = diff --git a/absl/strings/internal/cord_internal.cc b/absl/strings/internal/cord_internal.cc index b6b06cfa..57d9d385 100644 --- a/absl/strings/internal/cord_internal.cc +++ b/absl/strings/internal/cord_internal.cc @@ -22,18 +22,14 @@ #include "absl/strings/internal/cord_rep_btree.h" #include "absl/strings/internal/cord_rep_crc.h" #include "absl/strings/internal/cord_rep_flat.h" -#include "absl/strings/internal/cord_rep_ring.h" #include "absl/strings/str_cat.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace cord_internal { -ABSL_CONST_INIT std::atomic<bool> cord_ring_buffer_enabled( - kCordEnableRingBufferDefault); ABSL_CONST_INIT std::atomic<bool> shallow_subcords_enabled( kCordShallowSubcordsDefault); -ABSL_CONST_INIT std::atomic<bool> cord_btree_exhaustive_validation(false); void LogFatalNodeType(CordRep* rep) { ABSL_INTERNAL_LOG(FATAL, absl::StrCat("Unexpected node type: ", @@ -48,9 +44,6 @@ void CordRep::Destroy(CordRep* rep) { if (rep->tag == BTREE) { CordRepBtree::Destroy(rep->btree()); return; - } else if (rep->tag == RING) { - CordRepRing::Destroy(rep->ring()); - return; } else if (rep->tag == EXTERNAL) { CordRepExternal::Delete(rep); return; diff --git a/absl/strings/internal/cord_internal.h b/absl/strings/internal/cord_internal.h index e6f0d544..8744540e 100644 --- a/absl/strings/internal/cord_internal.h +++ b/absl/strings/internal/cord_internal.h @@ -55,30 +55,15 @@ struct CordRepExternal; struct CordRepFlat; struct CordRepSubstring; struct CordRepCrc; -class CordRepRing; class CordRepBtree; class CordzInfo; // Default feature enable states for cord ring buffers -enum CordFeatureDefaults { - kCordEnableRingBufferDefault = false, - kCordShallowSubcordsDefault = false -}; +enum CordFeatureDefaults { kCordShallowSubcordsDefault = false }; -extern std::atomic<bool> cord_ring_buffer_enabled; extern std::atomic<bool> shallow_subcords_enabled; -// `cord_btree_exhaustive_validation` can be set to force exhaustive validation -// in debug assertions, and code that calls `IsValid()` explicitly. By default, -// assertions should be relatively cheap and AssertValid() can easily lead to -// O(n^2) complexity as recursive / full tree validation is O(n). -extern std::atomic<bool> cord_btree_exhaustive_validation; - -inline void enable_cord_ring_buffer(bool enable) { - cord_ring_buffer_enabled.store(enable, std::memory_order_relaxed); -} - inline void enable_shallow_subcords(bool enable) { shallow_subcords_enabled.store(enable, std::memory_order_relaxed); } @@ -116,8 +101,16 @@ inline void SmallMemmove(char* dst, const char* src, size_t n) { if (nullify_tail) { memset(dst + 7, 0, 8); } + // GCC 12 has a false-positive -Wstringop-overflow warning here. +#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wstringop-overflow" +#endif memcpy(dst, &buf1, 8); memcpy(dst + n - 8, &buf2, 8); +#if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(12, 0) +#pragma GCC diagnostic pop +#endif } else if (n >= 4) { uint32_t buf1; uint32_t buf2; @@ -163,18 +156,17 @@ class RefcountAndFlags { // false will be visible to a thread that just observed this method returning // false. Always returns false when the immortal bit is set. inline bool Decrement() { - int32_t refcount = count_.load(std::memory_order_acquire) & kRefcountMask; + int32_t refcount = count_.load(std::memory_order_acquire); assert(refcount > 0 || refcount & kImmortalFlag); return refcount != kRefIncrement && - (count_.fetch_sub(kRefIncrement, std::memory_order_acq_rel) & - kRefcountMask) != kRefIncrement; + count_.fetch_sub(kRefIncrement, std::memory_order_acq_rel) != + kRefIncrement; } // Same as Decrement but expect that refcount is greater than 1. inline bool DecrementExpectHighRefcount() { int32_t refcount = - count_.fetch_sub(kRefIncrement, std::memory_order_acq_rel) & - kRefcountMask; + count_.fetch_sub(kRefIncrement, std::memory_order_acq_rel); assert(refcount > 0 || refcount & kImmortalFlag); return refcount != kRefIncrement; } @@ -192,10 +184,9 @@ class RefcountAndFlags { // This call performs the test for a reference count of one, and // performs the memory barrier needed for the owning thread // to act on the object, knowing that it has exclusive access to the - // object. Always returns false when the immortal bit is set. + // object. Always returns false when the immortal bit is set. inline bool IsOne() { - return (count_.load(std::memory_order_acquire) & kRefcountMask) == - kRefIncrement; + return count_.load(std::memory_order_acquire) == kRefIncrement; } bool IsImmortal() const { @@ -203,23 +194,15 @@ class RefcountAndFlags { } private: - // We reserve the bottom bits for flags. + // We reserve the bottom bit for flag. // kImmortalBit indicates that this entity should never be collected; it is // used for the StringConstant constructor to avoid collecting immutable // constant cords. - // kReservedFlag is reserved for future use. enum Flags { - kNumFlags = 2, + kNumFlags = 1, kImmortalFlag = 0x1, - kReservedFlag = 0x2, kRefIncrement = (1 << kNumFlags), - - // Bitmask to use when checking refcount by equality. This masks out - // all flags except kImmortalFlag, which is part of the refcount for - // purposes of equality. (A refcount of 0 or 1 does not count as 0 or 1 - // if the immortal bit is set.) - kRefcountMask = ~kReservedFlag, }; std::atomic<int32_t> count_; @@ -231,7 +214,7 @@ enum CordRepKind { SUBSTRING = 1, CRC = 2, BTREE = 3, - RING = 4, + UNUSED_4 = 4, EXTERNAL = 5, // We have different tags for different sized flat arrays, @@ -250,12 +233,8 @@ enum CordRepKind { // There are various locations where we want to check if some rep is a 'plain' // data edge, i.e. an external or flat rep. By having FLAT == EXTERNAL + 1, we // can perform this check in a single branch as 'tag >= EXTERNAL' -// Likewise, we have some locations where we check for 'ring or external/flat', -// so likewise align RING to EXTERNAL. // Note that we can leave this optimization to the compiler. The compiler will // DTRT when it sees a condition like `tag == EXTERNAL || tag >= FLAT`. -static_assert(RING == BTREE + 1, "BTREE and RING not consecutive"); -static_assert(EXTERNAL == RING + 1, "BTREE and EXTERNAL not consecutive"); static_assert(FLAT == EXTERNAL + 1, "EXTERNAL and FLAT not consecutive"); struct CordRep { @@ -299,15 +278,12 @@ struct CordRep { // # LINT.ThenChange(cord_rep_btree.h:copy_raw) // Returns true if this instance's tag matches the requested type. - constexpr bool IsRing() const { return tag == RING; } constexpr bool IsSubstring() const { return tag == SUBSTRING; } constexpr bool IsCrc() const { return tag == CRC; } constexpr bool IsExternal() const { return tag == EXTERNAL; } constexpr bool IsFlat() const { return tag >= FLAT; } constexpr bool IsBtree() const { return tag == BTREE; } - inline CordRepRing* ring(); - inline const CordRepRing* ring() const; inline CordRepSubstring* substring(); inline const CordRepSubstring* substring() const; inline CordRepCrc* crc(); diff --git a/absl/strings/internal/cord_rep_btree.cc b/absl/strings/internal/cord_rep_btree.cc index a86fdc0b..05bd0e20 100644 --- a/absl/strings/internal/cord_rep_btree.cc +++ b/absl/strings/internal/cord_rep_btree.cc @@ -14,6 +14,7 @@ #include "absl/strings/internal/cord_rep_btree.h" +#include <atomic> #include <cassert> #include <cstdint> #include <iostream> @@ -49,9 +50,7 @@ using CopyResult = CordRepBtree::CopyResult; constexpr auto kFront = CordRepBtree::kFront; constexpr auto kBack = CordRepBtree::kBack; -inline bool exhaustive_validation() { - return cord_btree_exhaustive_validation.load(std::memory_order_relaxed); -} +ABSL_CONST_INIT std::atomic<bool> cord_btree_exhaustive_validation(false); // Implementation of the various 'Dump' functions. // Prints the entire tree structure or 'rep'. External callers should @@ -362,6 +361,15 @@ struct StackOperations { } // namespace +void SetCordBtreeExhaustiveValidation(bool do_exaustive_validation) { + cord_btree_exhaustive_validation.store(do_exaustive_validation, + std::memory_order_relaxed); +} + +bool IsCordBtreeExhaustiveValidationEnabled() { + return cord_btree_exhaustive_validation.load(std::memory_order_relaxed); +} + void CordRepBtree::Dump(const CordRep* rep, absl::string_view label, bool include_contents, std::ostream& stream) { stream << "===================================\n"; @@ -450,7 +458,8 @@ bool CordRepBtree::IsValid(const CordRepBtree* tree, bool shallow) { child_length += edge->length; } NODE_CHECK_EQ(child_length, tree->length); - if ((!shallow || exhaustive_validation()) && tree->height() > 0) { + if ((!shallow || IsCordBtreeExhaustiveValidationEnabled()) && + tree->height() > 0) { for (CordRep* edge : tree->Edges()) { if (!IsValid(edge->btree(), shallow)) return false; } diff --git a/absl/strings/internal/cord_rep_btree.h b/absl/strings/internal/cord_rep_btree.h index 4209e512..be94b62e 100644 --- a/absl/strings/internal/cord_rep_btree.h +++ b/absl/strings/internal/cord_rep_btree.h @@ -32,6 +32,14 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace cord_internal { +// `SetCordBtreeExhaustiveValidation()` can be set to force exhaustive +// validation in debug assertions, and code that calls `IsValid()` +// explicitly. By default, assertions should be relatively cheap and +// AssertValid() can easily lead to O(n^2) complexity as recursive / full tree +// validation is O(n). +void SetCordBtreeExhaustiveValidation(bool do_exaustive_validation); +bool IsCordBtreeExhaustiveValidationEnabled(); + class CordRepBtreeNavigator; // CordRepBtree is as the name implies a btree implementation of a Cordrep tree. diff --git a/absl/strings/internal/cord_rep_btree_test.cc b/absl/strings/internal/cord_rep_btree_test.cc index 9d6ce484..840acf9f 100644 --- a/absl/strings/internal/cord_rep_btree_test.cc +++ b/absl/strings/internal/cord_rep_btree_test.cc @@ -507,7 +507,7 @@ TEST_P(CordRepBtreeTest, AppendToTreeTwoDeep) { for (size_t i = max_cap * max_cap + 1; i < max_cap * max_cap * max_cap; ++i) { // Ref top level tree based on param. // Ref child node once every 16 iterations, and leaf node every 4 - // iterrations which which should not have an observable effect other than + // iterations which which should not have an observable effect other than // the node and/or the leaf below it being copied. refs.RefIf(shared(), tree); refs.RefIf(i % 16 == 0, tree->Edges().back()); @@ -568,7 +568,7 @@ TEST_P(CordRepBtreeTest, PrependToTreeTwoDeep) { for (size_t i = max_cap * max_cap + 1; i < max_cap * max_cap * max_cap; ++i) { // Ref top level tree based on param. // Ref child node once every 16 iterations, and leaf node every 4 - // iterrations which which should not have an observable effect other than + // iterations which which should not have an observable effect other than // the node and/or the leaf below it being copied. refs.RefIf(shared(), tree); refs.RefIf(i % 16 == 0, tree->Edges().back()); @@ -1355,9 +1355,9 @@ TEST(CordRepBtreeTest, AssertValid) { TEST(CordRepBtreeTest, CheckAssertValidShallowVsDeep) { // Restore exhaustive validation on any exit. - const bool exhaustive_validation = cord_btree_exhaustive_validation.load(); + const bool exhaustive_validation = IsCordBtreeExhaustiveValidationEnabled(); auto cleanup = absl::MakeCleanup([exhaustive_validation] { - cord_btree_exhaustive_validation.store(exhaustive_validation); + SetCordBtreeExhaustiveValidation(exhaustive_validation); }); // Create a tree of at least 2 levels, and mess with the original flat, which @@ -1372,7 +1372,7 @@ TEST(CordRepBtreeTest, CheckAssertValidShallowVsDeep) { } flat->length = 100; - cord_btree_exhaustive_validation.store(false); + SetCordBtreeExhaustiveValidation(false); EXPECT_FALSE(CordRepBtree::IsValid(tree)); EXPECT_TRUE(CordRepBtree::IsValid(tree, true)); EXPECT_FALSE(CordRepBtree::IsValid(tree, false)); @@ -1382,7 +1382,7 @@ TEST(CordRepBtreeTest, CheckAssertValidShallowVsDeep) { EXPECT_DEBUG_DEATH(CordRepBtree::AssertValid(tree, false), ".*"); #endif - cord_btree_exhaustive_validation.store(true); + SetCordBtreeExhaustiveValidation(true); EXPECT_FALSE(CordRepBtree::IsValid(tree)); EXPECT_FALSE(CordRepBtree::IsValid(tree, true)); EXPECT_FALSE(CordRepBtree::IsValid(tree, false)); diff --git a/absl/strings/internal/cord_rep_consume.cc b/absl/strings/internal/cord_rep_consume.cc index 20a55797..db7d4fef 100644 --- a/absl/strings/internal/cord_rep_consume.cc +++ b/absl/strings/internal/cord_rep_consume.cc @@ -42,7 +42,8 @@ CordRep* ClipSubstring(CordRepSubstring* substring) { } // namespace -void Consume(CordRep* rep, ConsumeFn consume_fn) { +void Consume(CordRep* rep, + FunctionRef<void(CordRep*, size_t, size_t)> consume_fn) { size_t offset = 0; size_t length = rep->length; @@ -53,8 +54,9 @@ void Consume(CordRep* rep, ConsumeFn consume_fn) { consume_fn(rep, offset, length); } -void ReverseConsume(CordRep* rep, ConsumeFn consume_fn) { - return Consume(rep, std::move(consume_fn)); +void ReverseConsume(CordRep* rep, + FunctionRef<void(CordRep*, size_t, size_t)> consume_fn) { + return Consume(rep, consume_fn); } } // namespace cord_internal diff --git a/absl/strings/internal/cord_rep_consume.h b/absl/strings/internal/cord_rep_consume.h index d46fca2b..bece1874 100644 --- a/absl/strings/internal/cord_rep_consume.h +++ b/absl/strings/internal/cord_rep_consume.h @@ -24,11 +24,6 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace cord_internal { -// Functor for the Consume() and ReverseConsume() functions: -// void ConsumeFunc(CordRep* rep, size_t offset, size_t length); -// See the Consume() and ReverseConsume() function comments for documentation. -using ConsumeFn = FunctionRef<void(CordRep*, size_t, size_t)>; - // Consume() and ReverseConsume() consume CONCAT based trees and invoke the // provided functor with the contained nodes in the proper forward or reverse // order, which is used to convert CONCAT trees into other tree or cord data. @@ -40,8 +35,10 @@ using ConsumeFn = FunctionRef<void(CordRep*, size_t, size_t)>; // violations, we can not 100% guarantee that all code respects 'new format' // settings and flags, so we need to be able to parse old data on the fly until // all old code is deprecated / no longer the default format. -void Consume(CordRep* rep, ConsumeFn consume_fn); -void ReverseConsume(CordRep* rep, ConsumeFn consume_fn); +void Consume(CordRep* rep, + FunctionRef<void(CordRep*, size_t, size_t)> consume_fn); +void ReverseConsume(CordRep* rep, + FunctionRef<void(CordRep*, size_t, size_t)> consume_fn); } // namespace cord_internal ABSL_NAMESPACE_END diff --git a/absl/strings/internal/cord_rep_flat.h b/absl/strings/internal/cord_rep_flat.h index e3e27fcd..27c4b21e 100644 --- a/absl/strings/internal/cord_rep_flat.h +++ b/absl/strings/internal/cord_rep_flat.h @@ -120,8 +120,16 @@ struct CordRepFlat : public CordRep { // Round size up so it matches a size we can exactly express in a tag. const size_t size = RoundUpForTag(len + kFlatOverhead); void* const raw_rep = ::operator new(size); + // GCC 13 has a false-positive -Wstringop-overflow warning here. + #if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(13, 0) + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wstringop-overflow" + #endif CordRepFlat* rep = new (raw_rep) CordRepFlat(); rep->tag = AllocatedSizeToTag(size); + #if ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(13, 0) + #pragma GCC diagnostic pop + #endif return rep; } diff --git a/absl/strings/internal/cord_rep_ring.cc b/absl/strings/internal/cord_rep_ring.cc deleted file mode 100644 index af2fc768..00000000 --- a/absl/strings/internal/cord_rep_ring.cc +++ /dev/null @@ -1,773 +0,0 @@ -// Copyright 2020 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/cord_rep_ring.h" - -#include <cassert> -#include <cstddef> -#include <cstdint> -#include <iostream> -#include <limits> -#include <memory> -#include <string> - -#include "absl/base/internal/raw_logging.h" -#include "absl/base/internal/throw_delegate.h" -#include "absl/base/macros.h" -#include "absl/container/inlined_vector.h" -#include "absl/strings/internal/cord_internal.h" -#include "absl/strings/internal/cord_rep_consume.h" -#include "absl/strings/internal/cord_rep_flat.h" - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace cord_internal { - -namespace { - -using index_type = CordRepRing::index_type; - -enum class Direction { kForward, kReversed }; - -inline bool IsFlatOrExternal(CordRep* rep) { - return rep->IsFlat() || rep->IsExternal(); -} - -// Verifies that n + extra <= kMaxCapacity: throws std::length_error otherwise. -inline void CheckCapacity(size_t n, size_t extra) { - if (ABSL_PREDICT_FALSE(extra > CordRepRing::kMaxCapacity - n)) { - base_internal::ThrowStdLengthError("Maximum capacity exceeded"); - } -} - -// Creates a flat from the provided string data, allocating up to `extra` -// capacity in the returned flat depending on kMaxFlatLength limitations. -// Requires `len` to be less or equal to `kMaxFlatLength` -CordRepFlat* CreateFlat(const char* s, size_t n, size_t extra = 0) { // NOLINT - assert(n <= kMaxFlatLength); - auto* rep = CordRepFlat::New(n + extra); - rep->length = n; - memcpy(rep->Data(), s, n); - return rep; -} - -// Unrefs the entries in `[head, tail)`. -// Requires all entries to be a FLAT or EXTERNAL node. -void UnrefEntries(const CordRepRing* rep, index_type head, index_type tail) { - rep->ForEach(head, tail, [rep](index_type ix) { - CordRep* child = rep->entry_child(ix); - if (!child->refcount.Decrement()) { - if (child->tag >= FLAT) { - CordRepFlat::Delete(child->flat()); - } else { - CordRepExternal::Delete(child->external()); - } - } - }); -} - -} // namespace - -std::ostream& operator<<(std::ostream& s, const CordRepRing& rep) { - // Note: 'pos' values are defined as size_t (for overflow reasons), but that - // prints really awkward for small prepended values such as -5. ssize_t is not - // portable (POSIX), so we use ptrdiff_t instead to cast to signed values. - s << " CordRepRing(" << &rep << ", length = " << rep.length - << ", head = " << rep.head_ << ", tail = " << rep.tail_ - << ", cap = " << rep.capacity_ << ", rc = " << rep.refcount.Get() - << ", begin_pos_ = " << static_cast<ptrdiff_t>(rep.begin_pos_) << ") {\n"; - CordRepRing::index_type head = rep.head(); - do { - CordRep* child = rep.entry_child(head); - s << " entry[" << head << "] length = " << rep.entry_length(head) - << ", child " << child << ", clen = " << child->length - << ", tag = " << static_cast<int>(child->tag) - << ", rc = " << child->refcount.Get() - << ", offset = " << rep.entry_data_offset(head) - << ", end_pos = " << static_cast<ptrdiff_t>(rep.entry_end_pos(head)) - << "\n"; - head = rep.advance(head); - } while (head != rep.tail()); - return s << "}\n"; -} - -void CordRepRing::AddDataOffset(index_type index, size_t n) { - entry_data_offset()[index] += static_cast<offset_type>(n); -} - -void CordRepRing::SubLength(index_type index, size_t n) { - entry_end_pos()[index] -= n; -} - -class CordRepRing::Filler { - public: - Filler(CordRepRing* rep, index_type pos) : rep_(rep), head_(pos), pos_(pos) {} - - index_type head() const { return head_; } - index_type pos() const { return pos_; } - - void Add(CordRep* child, size_t offset, pos_type end_pos) { - rep_->entry_end_pos()[pos_] = end_pos; - rep_->entry_child()[pos_] = child; - rep_->entry_data_offset()[pos_] = static_cast<offset_type>(offset); - pos_ = rep_->advance(pos_); - } - - private: - CordRepRing* rep_; - index_type head_; - index_type pos_; -}; - -#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL -constexpr size_t CordRepRing::kMaxCapacity; -#endif - -bool CordRepRing::IsValid(std::ostream& output) const { - if (capacity_ == 0) { - output << "capacity == 0"; - return false; - } - - if (head_ >= capacity_ || tail_ >= capacity_) { - output << "head " << head_ << " and/or tail " << tail_ << "exceed capacity " - << capacity_; - return false; - } - - const index_type back = retreat(tail_); - size_t pos_length = Distance(begin_pos_, entry_end_pos(back)); - if (pos_length != length) { - output << "length " << length << " does not match positional length " - << pos_length << " from begin_pos " << begin_pos_ << " and entry[" - << back << "].end_pos " << entry_end_pos(back); - return false; - } - - index_type head = head_; - pos_type begin_pos = begin_pos_; - do { - pos_type end_pos = entry_end_pos(head); - size_t entry_length = Distance(begin_pos, end_pos); - if (entry_length == 0) { - output << "entry[" << head << "] has an invalid length " << entry_length - << " from begin_pos " << begin_pos << " and end_pos " << end_pos; - return false; - } - - CordRep* child = entry_child(head); - if (child == nullptr) { - output << "entry[" << head << "].child == nullptr"; - return false; - } - if (child->tag < FLAT && child->tag != EXTERNAL) { - output << "entry[" << head << "].child has an invalid tag " - << static_cast<int>(child->tag); - return false; - } - - size_t offset = entry_data_offset(head); - if (offset >= child->length || entry_length > child->length - offset) { - output << "entry[" << head << "] has offset " << offset - << " and entry length " << entry_length - << " which are outside of the child's length of " << child->length; - return false; - } - - begin_pos = end_pos; - head = advance(head); - } while (head != tail_); - - return true; -} - -#ifdef EXTRA_CORD_RING_VALIDATION -CordRepRing* CordRepRing::Validate(CordRepRing* rep, const char* file, - int line) { - if (!rep->IsValid(std::cerr)) { - std::cerr << "\nERROR: CordRepRing corrupted"; - if (line) std::cerr << " at line " << line; - if (file) std::cerr << " in file " << file; - std::cerr << "\nContent = " << *rep; - abort(); - } - return rep; -} -#endif // EXTRA_CORD_RING_VALIDATION - -CordRepRing* CordRepRing::New(size_t capacity, size_t extra) { - CheckCapacity(capacity, extra); - - size_t size = AllocSize(capacity += extra); - void* mem = ::operator new(size); - auto* rep = new (mem) CordRepRing(static_cast<index_type>(capacity)); - rep->tag = RING; - rep->capacity_ = static_cast<index_type>(capacity); - rep->begin_pos_ = 0; - return rep; -} - -void CordRepRing::SetCapacityForTesting(size_t capacity) { - // Adjust for the changed layout - assert(capacity <= capacity_); - assert(head() == 0 || head() < tail()); - memmove(Layout::Partial(capacity).Pointer<1>(data_) + head(), - Layout::Partial(capacity_).Pointer<1>(data_) + head(), - entries() * sizeof(Layout::ElementType<1>)); - memmove(Layout::Partial(capacity, capacity).Pointer<2>(data_) + head(), - Layout::Partial(capacity_, capacity_).Pointer<2>(data_) + head(), - entries() * sizeof(Layout::ElementType<2>)); - capacity_ = static_cast<index_type>(capacity); -} - -void CordRepRing::Delete(CordRepRing* rep) { - assert(rep != nullptr && rep->IsRing()); -#if defined(__cpp_sized_deallocation) - size_t size = AllocSize(rep->capacity_); - rep->~CordRepRing(); - ::operator delete(rep, size); -#else - rep->~CordRepRing(); - ::operator delete(rep); -#endif -} - -void CordRepRing::Destroy(CordRepRing* rep) { - UnrefEntries(rep, rep->head(), rep->tail()); - Delete(rep); -} - -template <bool ref> -void CordRepRing::Fill(const CordRepRing* src, index_type head, - index_type tail) { - this->length = src->length; - head_ = 0; - tail_ = advance(0, src->entries(head, tail)); - begin_pos_ = src->begin_pos_; - - // TODO(mvels): there may be opportunities here for large buffers. - auto* dst_pos = entry_end_pos(); - auto* dst_child = entry_child(); - auto* dst_offset = entry_data_offset(); - src->ForEach(head, tail, [&](index_type index) { - *dst_pos++ = src->entry_end_pos(index); - CordRep* child = src->entry_child(index); - *dst_child++ = ref ? CordRep::Ref(child) : child; - *dst_offset++ = src->entry_data_offset(index); - }); -} - -CordRepRing* CordRepRing::Copy(CordRepRing* rep, index_type head, - index_type tail, size_t extra) { - CordRepRing* newrep = CordRepRing::New(rep->entries(head, tail), extra); - newrep->Fill<true>(rep, head, tail); - CordRep::Unref(rep); - return newrep; -} - -CordRepRing* CordRepRing::Mutable(CordRepRing* rep, size_t extra) { - // Get current number of entries, and check for max capacity. - size_t entries = rep->entries(); - - if (!rep->refcount.IsOne()) { - return Copy(rep, rep->head(), rep->tail(), extra); - } else if (entries + extra > rep->capacity()) { - const size_t min_grow = rep->capacity() + rep->capacity() / 2; - const size_t min_extra = (std::max)(extra, min_grow - entries); - CordRepRing* newrep = CordRepRing::New(entries, min_extra); - newrep->Fill<false>(rep, rep->head(), rep->tail()); - CordRepRing::Delete(rep); - return newrep; - } else { - return rep; - } -} - -Span<char> CordRepRing::GetAppendBuffer(size_t size) { - assert(refcount.IsOne()); - index_type back = retreat(tail_); - CordRep* child = entry_child(back); - if (child->tag >= FLAT && child->refcount.IsOne()) { - size_t capacity = child->flat()->Capacity(); - pos_type end_pos = entry_end_pos(back); - size_t data_offset = entry_data_offset(back); - size_t entry_length = Distance(entry_begin_pos(back), end_pos); - size_t used = data_offset + entry_length; - if (size_t n = (std::min)(capacity - used, size)) { - child->length = data_offset + entry_length + n; - entry_end_pos()[back] = end_pos + n; - this->length += n; - return {child->flat()->Data() + used, n}; - } - } - return {nullptr, 0}; -} - -Span<char> CordRepRing::GetPrependBuffer(size_t size) { - assert(refcount.IsOne()); - CordRep* child = entry_child(head_); - size_t data_offset = entry_data_offset(head_); - if (data_offset && child->refcount.IsOne() && child->tag >= FLAT) { - size_t n = (std::min)(data_offset, size); - this->length += n; - begin_pos_ -= n; - data_offset -= n; - entry_data_offset()[head_] = static_cast<offset_type>(data_offset); - return {child->flat()->Data() + data_offset, n}; - } - return {nullptr, 0}; -} - -CordRepRing* CordRepRing::CreateFromLeaf(CordRep* child, size_t offset, - size_t len, size_t extra) { - CordRepRing* rep = CordRepRing::New(1, extra); - rep->head_ = 0; - rep->tail_ = rep->advance(0); - rep->length = len; - rep->entry_end_pos()[0] = len; - rep->entry_child()[0] = child; - rep->entry_data_offset()[0] = static_cast<offset_type>(offset); - return Validate(rep); -} - -CordRepRing* CordRepRing::CreateSlow(CordRep* child, size_t extra) { - CordRepRing* rep = nullptr; - Consume(child, [&](CordRep* child_arg, size_t offset, size_t len) { - if (IsFlatOrExternal(child_arg)) { - rep = rep ? AppendLeaf(rep, child_arg, offset, len) - : CreateFromLeaf(child_arg, offset, len, extra); - } else if (rep) { - rep = AddRing<AddMode::kAppend>(rep, child_arg->ring(), offset, len); - } else if (offset == 0 && child_arg->length == len) { - rep = Mutable(child_arg->ring(), extra); - } else { - rep = SubRing(child_arg->ring(), offset, len, extra); - } - }); - return Validate(rep, nullptr, __LINE__); -} - -CordRepRing* CordRepRing::Create(CordRep* child, size_t extra) { - size_t length = child->length; - if (IsFlatOrExternal(child)) { - return CreateFromLeaf(child, 0, length, extra); - } - if (child->IsRing()) { - return Mutable(child->ring(), extra); - } - return CreateSlow(child, extra); -} - -template <CordRepRing::AddMode mode> -CordRepRing* CordRepRing::AddRing(CordRepRing* rep, CordRepRing* ring, - size_t offset, size_t len) { - assert(offset < ring->length); - constexpr bool append = mode == AddMode::kAppend; - Position head = ring->Find(offset); - Position tail = ring->FindTail(head.index, offset + len); - const index_type entries = ring->entries(head.index, tail.index); - - rep = Mutable(rep, entries); - - // The delta for making ring[head].end_pos into 'len - offset' - const pos_type delta_length = - (append ? rep->begin_pos_ + rep->length : rep->begin_pos_ - len) - - ring->entry_begin_pos(head.index) - head.offset; - - // Start filling at `tail`, or `entries` before `head` - Filler filler(rep, append ? rep->tail_ : rep->retreat(rep->head_, entries)); - - if (ring->refcount.IsOne()) { - // Copy entries from source stealing the ref and adjusting the end position. - // Commit the filler as this is no-op. - ring->ForEach(head.index, tail.index, [&](index_type ix) { - filler.Add(ring->entry_child(ix), ring->entry_data_offset(ix), - ring->entry_end_pos(ix) + delta_length); - }); - - // Unref entries we did not copy over, and delete source. - if (head.index != ring->head_) UnrefEntries(ring, ring->head_, head.index); - if (tail.index != ring->tail_) UnrefEntries(ring, tail.index, ring->tail_); - CordRepRing::Delete(ring); - } else { - ring->ForEach(head.index, tail.index, [&](index_type ix) { - CordRep* child = ring->entry_child(ix); - filler.Add(child, ring->entry_data_offset(ix), - ring->entry_end_pos(ix) + delta_length); - CordRep::Ref(child); - }); - CordRepRing::Unref(ring); - } - - if (head.offset) { - // Increase offset of first 'source' entry appended or prepended. - // This is always the entry in `filler.head()` - rep->AddDataOffset(filler.head(), head.offset); - } - - if (tail.offset) { - // Reduce length of last 'source' entry appended or prepended. - // This is always the entry tailed by `filler.pos()` - rep->SubLength(rep->retreat(filler.pos()), tail.offset); - } - - // Commit changes - rep->length += len; - if (append) { - rep->tail_ = filler.pos(); - } else { - rep->head_ = filler.head(); - rep->begin_pos_ -= len; - } - - return Validate(rep); -} - -CordRepRing* CordRepRing::AppendSlow(CordRepRing* rep, CordRep* child) { - Consume(child, [&rep](CordRep* child_arg, size_t offset, size_t len) { - if (child_arg->IsRing()) { - rep = AddRing<AddMode::kAppend>(rep, child_arg->ring(), offset, len); - } else { - rep = AppendLeaf(rep, child_arg, offset, len); - } - }); - return rep; -} - -CordRepRing* CordRepRing::AppendLeaf(CordRepRing* rep, CordRep* child, - size_t offset, size_t len) { - rep = Mutable(rep, 1); - index_type back = rep->tail_; - const pos_type begin_pos = rep->begin_pos_ + rep->length; - rep->tail_ = rep->advance(rep->tail_); - rep->length += len; - rep->entry_end_pos()[back] = begin_pos + len; - rep->entry_child()[back] = child; - rep->entry_data_offset()[back] = static_cast<offset_type>(offset); - return Validate(rep, nullptr, __LINE__); -} - -CordRepRing* CordRepRing::Append(CordRepRing* rep, CordRep* child) { - size_t length = child->length; - if (IsFlatOrExternal(child)) { - return AppendLeaf(rep, child, 0, length); - } - if (child->IsRing()) { - return AddRing<AddMode::kAppend>(rep, child->ring(), 0, length); - } - return AppendSlow(rep, child); -} - -CordRepRing* CordRepRing::PrependSlow(CordRepRing* rep, CordRep* child) { - ReverseConsume(child, [&](CordRep* child_arg, size_t offset, size_t len) { - if (IsFlatOrExternal(child_arg)) { - rep = PrependLeaf(rep, child_arg, offset, len); - } else { - rep = AddRing<AddMode::kPrepend>(rep, child_arg->ring(), offset, len); - } - }); - return Validate(rep); -} - -CordRepRing* CordRepRing::PrependLeaf(CordRepRing* rep, CordRep* child, - size_t offset, size_t len) { - rep = Mutable(rep, 1); - index_type head = rep->retreat(rep->head_); - pos_type end_pos = rep->begin_pos_; - rep->head_ = head; - rep->length += len; - rep->begin_pos_ -= len; - rep->entry_end_pos()[head] = end_pos; - rep->entry_child()[head] = child; - rep->entry_data_offset()[head] = static_cast<offset_type>(offset); - return Validate(rep); -} - -CordRepRing* CordRepRing::Prepend(CordRepRing* rep, CordRep* child) { - size_t length = child->length; - if (IsFlatOrExternal(child)) { - return PrependLeaf(rep, child, 0, length); - } - if (child->IsRing()) { - return AddRing<AddMode::kPrepend>(rep, child->ring(), 0, length); - } - return PrependSlow(rep, child); -} - -CordRepRing* CordRepRing::Append(CordRepRing* rep, absl::string_view data, - size_t extra) { - if (rep->refcount.IsOne()) { - Span<char> avail = rep->GetAppendBuffer(data.length()); - if (!avail.empty()) { - memcpy(avail.data(), data.data(), avail.length()); - data.remove_prefix(avail.length()); - } - } - if (data.empty()) return Validate(rep); - - const size_t flats = (data.length() - 1) / kMaxFlatLength + 1; - rep = Mutable(rep, flats); - - Filler filler(rep, rep->tail_); - pos_type pos = rep->begin_pos_ + rep->length; - - while (data.length() >= kMaxFlatLength) { - auto* flat = CreateFlat(data.data(), kMaxFlatLength); - filler.Add(flat, 0, pos += kMaxFlatLength); - data.remove_prefix(kMaxFlatLength); - } - - if (data.length()) { - auto* flat = CreateFlat(data.data(), data.length(), extra); - filler.Add(flat, 0, pos += data.length()); - } - - rep->length = pos - rep->begin_pos_; - rep->tail_ = filler.pos(); - - return Validate(rep); -} - -CordRepRing* CordRepRing::Prepend(CordRepRing* rep, absl::string_view data, - size_t extra) { - if (rep->refcount.IsOne()) { - Span<char> avail = rep->GetPrependBuffer(data.length()); - if (!avail.empty()) { - const char* tail = data.data() + data.length() - avail.length(); - memcpy(avail.data(), tail, avail.length()); - data.remove_suffix(avail.length()); - } - } - if (data.empty()) return rep; - - const size_t flats = (data.length() - 1) / kMaxFlatLength + 1; - rep = Mutable(rep, flats); - pos_type pos = rep->begin_pos_; - Filler filler(rep, rep->retreat(rep->head_, static_cast<index_type>(flats))); - - size_t first_size = data.size() - (flats - 1) * kMaxFlatLength; - CordRepFlat* flat = CordRepFlat::New(first_size + extra); - flat->length = first_size + extra; - memcpy(flat->Data() + extra, data.data(), first_size); - data.remove_prefix(first_size); - filler.Add(flat, extra, pos); - pos -= first_size; - - while (!data.empty()) { - assert(data.size() >= kMaxFlatLength); - flat = CreateFlat(data.data(), kMaxFlatLength); - filler.Add(flat, 0, pos); - pos -= kMaxFlatLength; - data.remove_prefix(kMaxFlatLength); - } - - rep->head_ = filler.head(); - rep->length += rep->begin_pos_ - pos; - rep->begin_pos_ = pos; - - return Validate(rep); -} - -// 32 entries is 32 * sizeof(pos_type) = 4 cache lines on x86 -static constexpr index_type kBinarySearchThreshold = 32; -static constexpr index_type kBinarySearchEndCount = 8; - -template <bool wrap> -CordRepRing::index_type CordRepRing::FindBinary(index_type head, - index_type tail, - size_t offset) const { - index_type count = tail + (wrap ? capacity_ : 0) - head; - do { - count = (count - 1) / 2; - assert(count < entries(head, tail_)); - index_type mid = wrap ? advance(head, count) : head + count; - index_type after_mid = wrap ? advance(mid) : mid + 1; - bool larger = (offset >= entry_end_offset(mid)); - head = larger ? after_mid : head; - tail = larger ? tail : mid; - assert(head != tail); - } while (ABSL_PREDICT_TRUE(count > kBinarySearchEndCount)); - return head; -} - -CordRepRing::Position CordRepRing::FindSlow(index_type head, - size_t offset) const { - index_type tail = tail_; - - // Binary search until we are good for linear search - // Optimize for branchless / non wrapping ops - if (tail > head) { - index_type count = tail - head; - if (count > kBinarySearchThreshold) { - head = FindBinary<false>(head, tail, offset); - } - } else { - index_type count = capacity_ + tail - head; - if (count > kBinarySearchThreshold) { - head = FindBinary<true>(head, tail, offset); - } - } - - pos_type pos = entry_begin_pos(head); - pos_type end_pos = entry_end_pos(head); - while (offset >= Distance(begin_pos_, end_pos)) { - head = advance(head); - pos = end_pos; - end_pos = entry_end_pos(head); - } - - return {head, offset - Distance(begin_pos_, pos)}; -} - -CordRepRing::Position CordRepRing::FindTailSlow(index_type head, - size_t offset) const { - index_type tail = tail_; - const size_t tail_offset = offset - 1; - - // Binary search until we are good for linear search - // Optimize for branchless / non wrapping ops - if (tail > head) { - index_type count = tail - head; - if (count > kBinarySearchThreshold) { - head = FindBinary<false>(head, tail, tail_offset); - } - } else { - index_type count = capacity_ + tail - head; - if (count > kBinarySearchThreshold) { - head = FindBinary<true>(head, tail, tail_offset); - } - } - - size_t end_offset = entry_end_offset(head); - while (tail_offset >= end_offset) { - head = advance(head); - end_offset = entry_end_offset(head); - } - - return {advance(head), end_offset - offset}; -} - -char CordRepRing::GetCharacter(size_t offset) const { - assert(offset < length); - - Position pos = Find(offset); - size_t data_offset = entry_data_offset(pos.index) + pos.offset; - return GetRepData(entry_child(pos.index))[data_offset]; -} - -CordRepRing* CordRepRing::SubRing(CordRepRing* rep, size_t offset, - size_t len, size_t extra) { - assert(offset <= rep->length); - assert(offset <= rep->length - len); - - if (len == 0) { - CordRep::Unref(rep); - return nullptr; - } - - // Find position of first byte - Position head = rep->Find(offset); - Position tail = rep->FindTail(head.index, offset + len); - const size_t new_entries = rep->entries(head.index, tail.index); - - if (rep->refcount.IsOne() && extra <= (rep->capacity() - new_entries)) { - // We adopt a privately owned rep and no extra entries needed. - if (head.index != rep->head_) UnrefEntries(rep, rep->head_, head.index); - if (tail.index != rep->tail_) UnrefEntries(rep, tail.index, rep->tail_); - rep->head_ = head.index; - rep->tail_ = tail.index; - } else { - // Copy subset to new rep - rep = Copy(rep, head.index, tail.index, extra); - head.index = rep->head_; - tail.index = rep->tail_; - } - - // Adjust begin_pos and length - rep->length = len; - rep->begin_pos_ += offset; - - // Adjust head and tail blocks - if (head.offset) { - rep->AddDataOffset(head.index, head.offset); - } - if (tail.offset) { - rep->SubLength(rep->retreat(tail.index), tail.offset); - } - - return Validate(rep); -} - -CordRepRing* CordRepRing::RemovePrefix(CordRepRing* rep, size_t len, - size_t extra) { - assert(len <= rep->length); - if (len == rep->length) { - CordRep::Unref(rep); - return nullptr; - } - - Position head = rep->Find(len); - if (rep->refcount.IsOne()) { - if (head.index != rep->head_) UnrefEntries(rep, rep->head_, head.index); - rep->head_ = head.index; - } else { - rep = Copy(rep, head.index, rep->tail_, extra); - head.index = rep->head_; - } - - // Adjust begin_pos and length - rep->length -= len; - rep->begin_pos_ += len; - - // Adjust head block - if (head.offset) { - rep->AddDataOffset(head.index, head.offset); - } - - return Validate(rep); -} - -CordRepRing* CordRepRing::RemoveSuffix(CordRepRing* rep, size_t len, - size_t extra) { - assert(len <= rep->length); - - if (len == rep->length) { - CordRep::Unref(rep); - return nullptr; - } - - Position tail = rep->FindTail(rep->length - len); - if (rep->refcount.IsOne()) { - // We adopt a privately owned rep, scrub. - if (tail.index != rep->tail_) UnrefEntries(rep, tail.index, rep->tail_); - rep->tail_ = tail.index; - } else { - // Copy subset to new rep - rep = Copy(rep, rep->head_, tail.index, extra); - tail.index = rep->tail_; - } - - // Adjust length - rep->length -= len; - - // Adjust tail block - if (tail.offset) { - rep->SubLength(rep->retreat(tail.index), tail.offset); - } - - return Validate(rep); -} - -} // namespace cord_internal -ABSL_NAMESPACE_END -} // namespace absl diff --git a/absl/strings/internal/cord_rep_ring.h b/absl/strings/internal/cord_rep_ring.h deleted file mode 100644 index 2000e21e..00000000 --- a/absl/strings/internal/cord_rep_ring.h +++ /dev/null @@ -1,607 +0,0 @@ -// Copyright 2020 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_CORD_REP_RING_H_ -#define ABSL_STRINGS_INTERNAL_CORD_REP_RING_H_ - -#include <cassert> -#include <cstddef> -#include <cstdint> -#include <iosfwd> -#include <limits> -#include <memory> - -#include "absl/container/internal/layout.h" -#include "absl/strings/internal/cord_internal.h" -#include "absl/strings/internal/cord_rep_flat.h" - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace cord_internal { - -// All operations modifying a ring buffer are implemented as static methods -// requiring a CordRepRing instance with a reference adopted by the method. -// -// The methods return the modified ring buffer, which may be equal to the input -// if the input was not shared, and having large enough capacity to accommodate -// any newly added node(s). Otherwise, a copy of the input rep with the new -// node(s) added is returned. -// -// Any modification on non shared ring buffers with enough capacity will then -// require minimum atomic operations. Caller should where possible provide -// reasonable `extra` hints for both anticipated extra `flat` byte space, as -// well as anticipated extra nodes required for complex operations. -// -// Example of code creating a ring buffer, adding some data to it, -// and discarding the buffer when done: -// -// void FunWithRings() { -// // Create ring with 3 flats -// CordRep* flat = CreateFlat("Hello"); -// CordRepRing* ring = CordRepRing::Create(flat, 2); -// ring = CordRepRing::Append(ring, CreateFlat(" ")); -// ring = CordRepRing::Append(ring, CreateFlat("world")); -// DoSomethingWithRing(ring); -// CordRep::Unref(ring); -// } -// -// Example of code Copying an existing ring buffer and modifying it: -// -// void MoreFunWithRings(CordRepRing* src) { -// CordRepRing* ring = CordRep::Ref(src)->ring(); -// ring = CordRepRing::Append(ring, CreateFlat("Hello")); -// ring = CordRepRing::Append(ring, CreateFlat(" ")); -// ring = CordRepRing::Append(ring, CreateFlat("world")); -// DoSomethingWithRing(ring); -// CordRep::Unref(ring); -// } -// -class CordRepRing : public CordRep { - public: - // `pos_type` represents a 'logical position'. A CordRepRing instance has a - // `begin_pos` (default 0), and each node inside the buffer will have an - // `end_pos` which is the `end_pos` of the previous node (or `begin_pos`) plus - // this node's length. The purpose is to allow for a binary search on this - // position, while allowing O(1) prepend and append operations. - using pos_type = size_t; - - // `index_type` is the type for the `head`, `tail` and `capacity` indexes. - // Ring buffers are limited to having no more than four billion entries. - using index_type = uint32_t; - - // `offset_type` is the type for the data offset inside a child rep's data. - using offset_type = uint32_t; - - // Position holds the node index and relative offset into the node for - // some physical offset in the contained data as returned by the Find() - // and FindTail() methods. - struct Position { - index_type index; - size_t offset; - }; - - // The maximum # of child nodes that can be hosted inside a CordRepRing. - static constexpr size_t kMaxCapacity = (std::numeric_limits<uint32_t>::max)(); - - // CordRepring can not be default constructed, moved, copied or assigned. - CordRepRing() = delete; - CordRepRing(const CordRepRing&) = delete; - CordRepRing& operator=(const CordRepRing&) = delete; - - // Returns true if this instance is valid, false if some or all of the - // invariants are broken. Intended for debug purposes only. - // `output` receives an explanation of the broken invariants. - bool IsValid(std::ostream& output) const; - - // Returns the size in bytes for a CordRepRing with `capacity' entries. - static constexpr size_t AllocSize(size_t capacity); - - // Returns the distance in bytes from `pos` to `end_pos`. - static constexpr size_t Distance(pos_type pos, pos_type end_pos); - - // Creates a new ring buffer from the provided `rep`. Adopts a reference - // on `rep`. The returned ring buffer has a capacity of at least `extra + 1` - static CordRepRing* Create(CordRep* child, size_t extra = 0); - - // `head`, `tail` and `capacity` indexes defining the ring buffer boundaries. - index_type head() const { return head_; } - index_type tail() const { return tail_; } - index_type capacity() const { return capacity_; } - - // Returns the number of entries in this instance. - index_type entries() const { return entries(head_, tail_); } - - // Returns the logical begin position of this instance. - pos_type begin_pos() const { return begin_pos_; } - - // Returns the number of entries for a given head-tail range. - // Requires `head` and `tail` values to be less than `capacity()`. - index_type entries(index_type head, index_type tail) const { - assert(head < capacity_ && tail < capacity_); - return tail - head + ((tail > head) ? 0 : capacity_); - } - - // Returns the logical end position of entry `index`. - pos_type const& entry_end_pos(index_type index) const { - assert(IsValidIndex(index)); - return Layout::Partial().Pointer<0>(data_)[index]; - } - - // Returns the child pointer of entry `index`. - CordRep* const& entry_child(index_type index) const { - assert(IsValidIndex(index)); - return Layout::Partial(capacity()).Pointer<1>(data_)[index]; - } - - // Returns the data offset of entry `index` - offset_type const& entry_data_offset(index_type index) const { - assert(IsValidIndex(index)); - return Layout::Partial(capacity(), capacity()).Pointer<2>(data_)[index]; - } - - // Appends the provided child node to the `rep` instance. - // Adopts a reference from `rep` and `child` which may not be null. - // If the provided child is a FLAT or EXTERNAL node, or a SUBSTRING node - // containing a FLAT or EXTERNAL node, then flat or external the node is added - // 'as is', with an offset added for the SUBSTRING case. - // If the provided child is a RING or CONCAT tree, or a SUBSTRING of a RING or - // CONCAT tree, then all child nodes not excluded by any start offset or - // length values are added recursively. - static CordRepRing* Append(CordRepRing* rep, CordRep* child); - - // Appends the provided string data to the `rep` instance. - // This function will attempt to utilize any remaining capacity in the last - // node of the input if that node is not shared (directly or indirectly), and - // of type FLAT. Remaining data will be added as one or more FLAT nodes. - // Any last node added to the ring buffer will be allocated with up to - // `extra` bytes of capacity for (anticipated) subsequent append actions. - static CordRepRing* Append(CordRepRing* rep, string_view data, - size_t extra = 0); - - // Prepends the provided child node to the `rep` instance. - // Adopts a reference from `rep` and `child` which may not be null. - // If the provided child is a FLAT or EXTERNAL node, or a SUBSTRING node - // containing a FLAT or EXTERNAL node, then flat or external the node is - // prepended 'as is', with an optional offset added for the SUBSTRING case. - // If the provided child is a RING or CONCAT tree, or a SUBSTRING of a RING - // or CONCAT tree, then all child nodes not excluded by any start offset or - // length values are added recursively. - static CordRepRing* Prepend(CordRepRing* rep, CordRep* child); - - // Prepends the provided string data to the `rep` instance. - // This function will attempt to utilize any remaining capacity in the first - // node of the input if that node is not shared (directly or indirectly), and - // of type FLAT. Remaining data will be added as one or more FLAT nodes. - // Any first node prepnded to the ring buffer will be allocated with up to - // `extra` bytes of capacity for (anticipated) subsequent prepend actions. - static CordRepRing* Prepend(CordRepRing* rep, string_view data, - size_t extra = 0); - - // Returns a span referencing potentially unused capacity in the last node. - // The returned span may be empty if no such capacity is available, or if the - // current instance is shared. Else, a span of size `n <= size` is returned. - // If non empty, the ring buffer is adjusted to the new length, with the newly - // added capacity left uninitialized. Callers should assign a value to the - // entire span before any other operations on this instance. - Span<char> GetAppendBuffer(size_t size); - - // Returns a span referencing potentially unused capacity in the first node. - // This function is identical to GetAppendBuffer except that it returns a span - // referencing up to `size` capacity directly before the existing data. - Span<char> GetPrependBuffer(size_t size); - - // Returns a cord ring buffer containing `len` bytes of data starting at - // `offset`. If the input is not shared, this function will remove all head - // and tail child nodes outside of the requested range, and adjust the new - // head and tail nodes as required. If the input is shared, this function - // returns a new instance sharing some or all of the nodes from the input. - static CordRepRing* SubRing(CordRepRing* r, size_t offset, size_t len, - size_t extra = 0); - - // Returns a cord ring buffer with the first `len` bytes removed. - // If the input is not shared, this function will remove all head child nodes - // fully inside the first `length` bytes, and adjust the new head as required. - // If the input is shared, this function returns a new instance sharing some - // or all of the nodes from the input. - static CordRepRing* RemoveSuffix(CordRepRing* r, size_t len, - size_t extra = 0); - - // Returns a cord ring buffer with the last `len` bytes removed. - // If the input is not shared, this function will remove all head child nodes - // fully inside the first `length` bytes, and adjust the new head as required. - // If the input is shared, this function returns a new instance sharing some - // or all of the nodes from the input. - static CordRepRing* RemovePrefix(CordRepRing* r, size_t len, - size_t extra = 0); - - // Returns the character at `offset`. Requires that `offset < length`. - char GetCharacter(size_t offset) const; - - // Returns true if this instance manages a single contiguous buffer, in which - // case the (optional) output parameter `fragment` is set. Otherwise, the - // function returns false, and `fragment` is left unchanged. - bool IsFlat(absl::string_view* fragment) const; - - // Returns true if the data starting at `offset` with length `len` is - // managed by this instance inside a single contiguous buffer, in which case - // the (optional) output parameter `fragment` is set to the contiguous memory - // starting at offset `offset` with length `length`. Otherwise, the function - // returns false, and `fragment` is left unchanged. - bool IsFlat(size_t offset, size_t len, absl::string_view* fragment) const; - - // Testing only: set capacity to requested capacity. - void SetCapacityForTesting(size_t capacity); - - // Returns the CordRep data pointer for the provided CordRep. - // Requires that the provided `rep` is either a FLAT or EXTERNAL CordRep. - static const char* GetLeafData(const CordRep* rep); - - // Returns the CordRep data pointer for the provided CordRep. - // Requires that `rep` is either a FLAT, EXTERNAL, or SUBSTRING CordRep. - static const char* GetRepData(const CordRep* rep); - - // Advances the provided position, wrapping around capacity as needed. - // Requires `index` < capacity() - inline index_type advance(index_type index) const; - - // Advances the provided position by 'n`, wrapping around capacity as needed. - // Requires `index` < capacity() and `n` <= capacity. - inline index_type advance(index_type index, index_type n) const; - - // Retreats the provided position, wrapping around 0 as needed. - // Requires `index` < capacity() - inline index_type retreat(index_type index) const; - - // Retreats the provided position by 'n', wrapping around 0 as needed. - // Requires `index` < capacity() - inline index_type retreat(index_type index, index_type n) const; - - // Returns the logical begin position of entry `index` - pos_type const& entry_begin_pos(index_type index) const { - return (index == head_) ? begin_pos_ : entry_end_pos(retreat(index)); - } - - // Returns the physical start offset of entry `index` - size_t entry_start_offset(index_type index) const { - return Distance(begin_pos_, entry_begin_pos(index)); - } - - // Returns the physical end offset of entry `index` - size_t entry_end_offset(index_type index) const { - return Distance(begin_pos_, entry_end_pos(index)); - } - - // Returns the data length for entry `index` - size_t entry_length(index_type index) const { - return Distance(entry_begin_pos(index), entry_end_pos(index)); - } - - // Returns the data for entry `index` - absl::string_view entry_data(index_type index) const; - - // Returns the position for `offset` as {index, prefix}. `index` holds the - // index of the entry at the specified offset and `prefix` holds the relative - // offset inside that entry. - // Requires `offset` < length. - // - // For example we can implement GetCharacter(offset) as: - // char GetCharacter(size_t offset) { - // Position pos = this->Find(offset); - // return this->entry_data(pos.pos)[pos.offset]; - // } - inline Position Find(size_t offset) const; - - // Find starting at `head` - inline Position Find(index_type head, size_t offset) const; - - // Returns the tail position for `offset` as {tail index, suffix}. - // `tail index` holds holds the index of the entry holding the offset directly - // before 'offset` advanced by one. 'suffix` holds the relative offset from - // that relative offset in the entry to the end of the entry. - // For example, FindTail(length) will return {tail(), 0}, FindTail(length - 5) - // will return {retreat(tail), 5)} provided the preceding entry contains at - // least 5 bytes of data. - // Requires offset >= 1 && offset <= length. - // - // This function is very useful in functions that need to clip the end of some - // ring buffer such as 'RemovePrefix'. - // For example, we could implement RemovePrefix for non shared instances as: - // void RemoveSuffix(size_t n) { - // Position pos = FindTail(length - n); - // UnrefEntries(pos.pos, this->tail_); - // this->tail_ = pos.pos; - // entry(retreat(pos.pos)).end_pos -= pos.offset; - // } - inline Position FindTail(size_t offset) const; - - // Find tail starting at `head` - inline Position FindTail(index_type head, size_t offset) const; - - // Invokes f(index_type index) for each entry inside the range [head, tail> - template <typename F> - void ForEach(index_type head, index_type tail, F&& f) const { - index_type n1 = (tail > head) ? tail : capacity_; - for (index_type i = head; i < n1; ++i) f(i); - if (tail <= head) { - for (index_type i = 0; i < tail; ++i) f(i); - } - } - - // Invokes f(index_type index) for each entry inside this instance. - template <typename F> - void ForEach(F&& f) const { - ForEach(head_, tail_, std::forward<F>(f)); - } - - // Dump this instance's data tp stream `s` in human readable format, excluding - // the actual data content itself. Intended for debug purposes only. - friend std::ostream& operator<<(std::ostream& s, const CordRepRing& rep); - - private: - enum class AddMode { kAppend, kPrepend }; - - using Layout = container_internal::Layout<pos_type, CordRep*, offset_type>; - - class Filler; - class Transaction; - class CreateTransaction; - - static constexpr size_t kLayoutAlignment = Layout::Partial().Alignment(); - - // Creates a new CordRepRing. - explicit CordRepRing(index_type capacity) : capacity_(capacity) {} - - // Returns true if `index` is a valid index into this instance. - bool IsValidIndex(index_type index) const; - - // Debug use only: validates the provided CordRepRing invariants. - // Verification of all CordRepRing methods can be enabled by defining - // EXTRA_CORD_RING_VALIDATION, i.e.: `--copts=-DEXTRA_CORD_RING_VALIDATION` - // Verification is VERY expensive, so only do it for debugging purposes. - static CordRepRing* Validate(CordRepRing* rep, const char* file = nullptr, - int line = 0); - - // Allocates a CordRepRing large enough to hold `capacity + extra' entries. - // The returned capacity may be larger if the allocated memory allows for it. - // The maximum capacity of a CordRepRing is capped at kMaxCapacity. - // Throws `std::length_error` if `capacity + extra' exceeds kMaxCapacity. - static CordRepRing* New(size_t capacity, size_t extra); - - // Deallocates (but does not destroy) the provided ring buffer. - static void Delete(CordRepRing* rep); - - // Destroys the provided ring buffer, decrementing the reference count of all - // contained child CordReps. The provided 1\`rep` should have a ref count of - // one (pre decrement destroy call observing `refcount.IsOne()`) or zero - // (post decrement destroy call observing `!refcount.Decrement()`). - static void Destroy(CordRepRing* rep); - - // Returns a mutable reference to the logical end position array. - pos_type* entry_end_pos() { - return Layout::Partial().Pointer<0>(data_); - } - - // Returns a mutable reference to the child pointer array. - CordRep** entry_child() { - return Layout::Partial(capacity()).Pointer<1>(data_); - } - - // Returns a mutable reference to the data offset array. - offset_type* entry_data_offset() { - return Layout::Partial(capacity(), capacity()).Pointer<2>(data_); - } - - // Find implementations for the non fast path 0 / length cases. - Position FindSlow(index_type head, size_t offset) const; - Position FindTailSlow(index_type head, size_t offset) const; - - // Finds the index of the first node that is inside a reasonable distance - // of the node at `offset` from which we can continue with a linear search. - template <bool wrap> - index_type FindBinary(index_type head, index_type tail, size_t offset) const; - - // Fills the current (initialized) instance from the provided source, copying - // entries [head, tail). Adds a reference to copied entries if `ref` is true. - template <bool ref> - void Fill(const CordRepRing* src, index_type head, index_type tail); - - // Create a copy of 'rep', copying all entries [head, tail), allocating room - // for `extra` entries. Adds a reference on all copied entries. - static CordRepRing* Copy(CordRepRing* rep, index_type head, index_type tail, - size_t extra = 0); - - // Returns a Mutable CordRepRing reference from `rep` with room for at least - // `extra` additional nodes. Adopts a reference count from `rep`. - // This function will return `rep` if, and only if: - // - rep.entries + extra <= rep.capacity - // - rep.refcount == 1 - // Otherwise, this function will create a new copy of `rep` with additional - // capacity to satisfy `extra` extra nodes, and unref the old `rep` instance. - // - // If a new CordRepRing can not be allocated, or the new capacity would exceed - // the maxmimum capacity, then the input is consumed only, and an exception is - // thrown. - static CordRepRing* Mutable(CordRepRing* rep, size_t extra); - - // Slow path for Append(CordRepRing* rep, CordRep* child). This function is - // exercised if the provided `child` in Append() is not a leaf node, i.e., a - // ring buffer or old (concat) cord tree. - static CordRepRing* AppendSlow(CordRepRing* rep, CordRep* child); - - // Appends the provided leaf node. Requires `child` to be FLAT or EXTERNAL. - static CordRepRing* AppendLeaf(CordRepRing* rep, CordRep* child, - size_t offset, size_t length); - - // Prepends the provided leaf node. Requires `child` to be FLAT or EXTERNAL. - static CordRepRing* PrependLeaf(CordRepRing* rep, CordRep* child, - size_t offset, size_t length); - - // Slow path for Prepend(CordRepRing* rep, CordRep* child). This function is - // exercised if the provided `child` in Prepend() is not a leaf node, i.e., a - // ring buffer or old (concat) cord tree. - static CordRepRing* PrependSlow(CordRepRing* rep, CordRep* child); - - // Slow path for Create(CordRep* child, size_t extra). This function is - // exercised if the provided `child` in Prepend() is not a leaf node, i.e., a - // ring buffer or old (concat) cord tree. - static CordRepRing* CreateSlow(CordRep* child, size_t extra); - - // Creates a new ring buffer from the provided `child` leaf node. Requires - // `child` to be FLAT or EXTERNAL. on `rep`. - // The returned ring buffer has a capacity of at least `1 + extra` - static CordRepRing* CreateFromLeaf(CordRep* child, size_t offset, - size_t length, size_t extra); - - // Appends or prepends (depending on AddMode) the ring buffer in `ring' to - // `rep` starting at `offset` with length `len`. - template <AddMode mode> - static CordRepRing* AddRing(CordRepRing* rep, CordRepRing* ring, - size_t offset, size_t len); - - // Increases the data offset for entry `index` by `n`. - void AddDataOffset(index_type index, size_t n); - - // Descreases the length for entry `index` by `n`. - void SubLength(index_type index, size_t n); - - index_type head_; - index_type tail_; - index_type capacity_; - pos_type begin_pos_; - - alignas(kLayoutAlignment) char data_[kLayoutAlignment]; - - friend struct CordRep; -}; - -constexpr size_t CordRepRing::AllocSize(size_t capacity) { - return sizeof(CordRepRing) - sizeof(data_) + - Layout(capacity, capacity, capacity).AllocSize(); -} - -inline constexpr size_t CordRepRing::Distance(pos_type pos, pos_type end_pos) { - return (end_pos - pos); -} - -inline const char* CordRepRing::GetLeafData(const CordRep* rep) { - return rep->tag != EXTERNAL ? rep->flat()->Data() : rep->external()->base; -} - -inline const char* CordRepRing::GetRepData(const CordRep* rep) { - if (rep->tag >= FLAT) return rep->flat()->Data(); - if (rep->tag == EXTERNAL) return rep->external()->base; - return GetLeafData(rep->substring()->child) + rep->substring()->start; -} - -inline CordRepRing::index_type CordRepRing::advance(index_type index) const { - assert(index < capacity_); - return ++index == capacity_ ? 0 : index; -} - -inline CordRepRing::index_type CordRepRing::advance(index_type index, - index_type n) const { - assert(index < capacity_ && n <= capacity_); - return (index += n) >= capacity_ ? index - capacity_ : index; -} - -inline CordRepRing::index_type CordRepRing::retreat(index_type index) const { - assert(index < capacity_); - return (index > 0 ? index : capacity_) - 1; -} - -inline CordRepRing::index_type CordRepRing::retreat(index_type index, - index_type n) const { - assert(index < capacity_ && n <= capacity_); - return index >= n ? index - n : capacity_ - n + index; -} - -inline absl::string_view CordRepRing::entry_data(index_type index) const { - size_t data_offset = entry_data_offset(index); - return {GetRepData(entry_child(index)) + data_offset, entry_length(index)}; -} - -inline bool CordRepRing::IsValidIndex(index_type index) const { - if (index >= capacity_) return false; - return (tail_ > head_) ? (index >= head_ && index < tail_) - : (index >= head_ || index < tail_); -} - -#ifndef EXTRA_CORD_RING_VALIDATION -inline CordRepRing* CordRepRing::Validate(CordRepRing* rep, - const char* /*file*/, int /*line*/) { - return rep; -} -#endif - -inline CordRepRing::Position CordRepRing::Find(size_t offset) const { - assert(offset < length); - return (offset == 0) ? Position{head_, 0} : FindSlow(head_, offset); -} - -inline CordRepRing::Position CordRepRing::Find(index_type head, - size_t offset) const { - assert(offset < length); - assert(IsValidIndex(head) && offset >= entry_start_offset(head)); - return (offset == 0) ? Position{head_, 0} : FindSlow(head, offset); -} - -inline CordRepRing::Position CordRepRing::FindTail(size_t offset) const { - assert(offset > 0 && offset <= length); - return (offset == length) ? Position{tail_, 0} : FindTailSlow(head_, offset); -} - -inline CordRepRing::Position CordRepRing::FindTail(index_type head, - size_t offset) const { - assert(offset > 0 && offset <= length); - assert(IsValidIndex(head) && offset >= entry_start_offset(head) + 1); - return (offset == length) ? Position{tail_, 0} : FindTailSlow(head, offset); -} - -// Now that CordRepRing is defined, we can define CordRep's helper casts: -inline CordRepRing* CordRep::ring() { - assert(IsRing()); - return static_cast<CordRepRing*>(this); -} - -inline const CordRepRing* CordRep::ring() const { - assert(IsRing()); - return static_cast<const CordRepRing*>(this); -} - -inline bool CordRepRing::IsFlat(absl::string_view* fragment) const { - if (entries() == 1) { - if (fragment) *fragment = entry_data(head()); - return true; - } - return false; -} - -inline bool CordRepRing::IsFlat(size_t offset, size_t len, - absl::string_view* fragment) const { - const Position pos = Find(offset); - const absl::string_view data = entry_data(pos.index); - if (data.length() >= len && data.length() - len >= pos.offset) { - if (fragment) *fragment = data.substr(pos.offset, len); - return true; - } - return false; -} - -std::ostream& operator<<(std::ostream& s, const CordRepRing& rep); - -} // namespace cord_internal -ABSL_NAMESPACE_END -} // namespace absl - -#endif // ABSL_STRINGS_INTERNAL_CORD_REP_RING_H_ diff --git a/absl/strings/internal/cord_rep_ring_reader.h b/absl/strings/internal/cord_rep_ring_reader.h deleted file mode 100644 index 7ceeaa00..00000000 --- a/absl/strings/internal/cord_rep_ring_reader.h +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright 2021 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_CORD_REP_RING_READER_H_ -#define ABSL_STRINGS_INTERNAL_CORD_REP_RING_READER_H_ - -#include <cassert> -#include <cstddef> -#include <cstdint> - -#include "absl/strings/internal/cord_internal.h" -#include "absl/strings/internal/cord_rep_ring.h" -#include "absl/strings/string_view.h" - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace cord_internal { - -// CordRepRingReader provides basic navigation over CordRepRing data. -class CordRepRingReader { - public: - // Returns true if this instance is not empty. - explicit operator bool() const { return ring_ != nullptr; } - - // Returns the ring buffer reference for this instance, or nullptr if empty. - CordRepRing* ring() const { return ring_; } - - // Returns the current node index inside the ring buffer for this instance. - // The returned value is undefined if this instance is empty. - CordRepRing::index_type index() const { return index_; } - - // Returns the current node inside the ring buffer for this instance. - // The returned value is undefined if this instance is empty. - CordRep* node() const { return ring_->entry_child(index_); } - - // Returns the length of the referenced ring buffer. - // Requires the current instance to be non empty. - size_t length() const { - assert(ring_); - return ring_->length; - } - - // Returns the end offset of the last navigated-to chunk, which represents the - // total bytes 'consumed' relative to the start of the ring. The returned - // value is never zero. For example, initializing a reader with a ring buffer - // with a first chunk of 19 bytes will return consumed() = 19. - // Requires the current instance to be non empty. - size_t consumed() const { - assert(ring_); - return ring_->entry_end_offset(index_); - } - - // Returns the number of bytes remaining beyond the last navigated-to chunk. - // Requires the current instance to be non empty. - size_t remaining() const { - assert(ring_); - return length() - consumed(); - } - - // Resets this instance to an empty value - void Reset() { ring_ = nullptr; } - - // Resets this instance to the start of `ring`. `ring` must not be null. - // Returns a reference into the first chunk of the provided ring. - absl::string_view Reset(CordRepRing* ring) { - assert(ring); - ring_ = ring; - index_ = ring_->head(); - return ring_->entry_data(index_); - } - - // Navigates to the next chunk inside the reference ring buffer. - // Returns a reference into the navigated-to chunk. - // Requires remaining() to be non zero. - absl::string_view Next() { - assert(remaining()); - index_ = ring_->advance(index_); - return ring_->entry_data(index_); - } - - // Navigates to the chunk at offset `offset`. - // Returns a reference into the navigated-to chunk, adjusted for the relative - // position of `offset` into that chunk. For example, calling Seek(13) on a - // ring buffer containing 2 chunks of 10 and 20 bytes respectively will return - // a string view into the second chunk starting at offset 3 with a size of 17. - // Requires `offset` to be less than `length()` - absl::string_view Seek(size_t offset) { - assert(offset < length()); - size_t current = ring_->entry_end_offset(index_); - CordRepRing::index_type hint = (offset >= current) ? index_ : ring_->head(); - const CordRepRing::Position head = ring_->Find(hint, offset); - index_ = head.index; - auto data = ring_->entry_data(head.index); - data.remove_prefix(head.offset); - return data; - } - - private: - CordRepRing* ring_ = nullptr; - CordRepRing::index_type index_; -}; - -} // namespace cord_internal -ABSL_NAMESPACE_END -} // namespace absl - -#endif // ABSL_STRINGS_INTERNAL_CORD_REP_RING_READER_H_ diff --git a/absl/strings/internal/cordz_functions_test.cc b/absl/strings/internal/cordz_functions_test.cc index 350623c1..b70a685e 100644 --- a/absl/strings/internal/cordz_functions_test.cc +++ b/absl/strings/internal/cordz_functions_test.cc @@ -38,7 +38,7 @@ TEST(CordzFunctionsTest, SampleRate) { } // Cordz is disabled when we don't have thread_local. All calls to -// should_profile will return false when cordz is diabled, so we might want to +// should_profile will return false when cordz is disabled, so we might want to // avoid those tests. #ifdef ABSL_INTERNAL_CORDZ_ENABLED diff --git a/absl/strings/internal/cordz_handle.cc b/absl/strings/internal/cordz_handle.cc index a73fefed..a7061dbe 100644 --- a/absl/strings/internal/cordz_handle.cc +++ b/absl/strings/internal/cordz_handle.cc @@ -16,34 +16,60 @@ #include <atomic> #include "absl/base/internal/raw_logging.h" // For ABSL_RAW_CHECK -#include "absl/base/internal/spinlock.h" +#include "absl/synchronization/mutex.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace cord_internal { -using ::absl::base_internal::SpinLockHolder; +namespace { -ABSL_CONST_INIT CordzHandle::Queue CordzHandle::global_queue_(absl::kConstInit); +struct Queue { + Queue() = default; + + absl::Mutex mutex; + std::atomic<CordzHandle*> dq_tail ABSL_GUARDED_BY(mutex){nullptr}; + + // Returns true if this delete queue is empty. This method does not acquire + // the lock, but does a 'load acquire' observation on the delete queue tail. + // It is used inside Delete() to check for the presence of a delete queue + // without holding the lock. The assumption is that the caller is in the + // state of 'being deleted', and can not be newly discovered by a concurrent + // 'being constructed' snapshot instance. Practically, this means that any + // such discovery (`find`, 'first' or 'next', etc) must have proper 'happens + // before / after' semantics and atomic fences. + bool IsEmpty() const ABSL_NO_THREAD_SAFETY_ANALYSIS { + return dq_tail.load(std::memory_order_acquire) == nullptr; + } +}; + +static Queue* GlobalQueue() { + static Queue* global_queue = new Queue; + return global_queue; +} + +} // namespace CordzHandle::CordzHandle(bool is_snapshot) : is_snapshot_(is_snapshot) { + Queue* global_queue = GlobalQueue(); if (is_snapshot) { - SpinLockHolder lock(&queue_->mutex); - CordzHandle* dq_tail = queue_->dq_tail.load(std::memory_order_acquire); + MutexLock lock(&global_queue->mutex); + CordzHandle* dq_tail = + global_queue->dq_tail.load(std::memory_order_acquire); if (dq_tail != nullptr) { dq_prev_ = dq_tail; dq_tail->dq_next_ = this; } - queue_->dq_tail.store(this, std::memory_order_release); + global_queue->dq_tail.store(this, std::memory_order_release); } } CordzHandle::~CordzHandle() { - ODRCheck(); + Queue* global_queue = GlobalQueue(); if (is_snapshot_) { std::vector<CordzHandle*> to_delete; { - SpinLockHolder lock(&queue_->mutex); + MutexLock lock(&global_queue->mutex); CordzHandle* next = dq_next_; if (dq_prev_ == nullptr) { // We were head of the queue, delete every CordzHandle until we reach @@ -59,7 +85,7 @@ CordzHandle::~CordzHandle() { if (next) { next->dq_prev_ = dq_prev_; } else { - queue_->dq_tail.store(dq_prev_, std::memory_order_release); + global_queue->dq_tail.store(dq_prev_, std::memory_order_release); } } for (CordzHandle* handle : to_delete) { @@ -69,16 +95,15 @@ CordzHandle::~CordzHandle() { } bool CordzHandle::SafeToDelete() const { - return is_snapshot_ || queue_->IsEmpty(); + return is_snapshot_ || GlobalQueue()->IsEmpty(); } void CordzHandle::Delete(CordzHandle* handle) { assert(handle); if (handle) { - handle->ODRCheck(); - Queue* const queue = handle->queue_; + Queue* const queue = GlobalQueue(); if (!handle->SafeToDelete()) { - SpinLockHolder lock(&queue->mutex); + MutexLock lock(&queue->mutex); CordzHandle* dq_tail = queue->dq_tail.load(std::memory_order_acquire); if (dq_tail != nullptr) { handle->dq_prev_ = dq_tail; @@ -93,8 +118,9 @@ void CordzHandle::Delete(CordzHandle* handle) { std::vector<const CordzHandle*> CordzHandle::DiagnosticsGetDeleteQueue() { std::vector<const CordzHandle*> handles; - SpinLockHolder lock(&global_queue_.mutex); - CordzHandle* dq_tail = global_queue_.dq_tail.load(std::memory_order_acquire); + Queue* global_queue = GlobalQueue(); + MutexLock lock(&global_queue->mutex); + CordzHandle* dq_tail = global_queue->dq_tail.load(std::memory_order_acquire); for (const CordzHandle* p = dq_tail; p; p = p->dq_prev_) { handles.push_back(p); } @@ -103,13 +129,13 @@ std::vector<const CordzHandle*> CordzHandle::DiagnosticsGetDeleteQueue() { bool CordzHandle::DiagnosticsHandleIsSafeToInspect( const CordzHandle* handle) const { - ODRCheck(); if (!is_snapshot_) return false; if (handle == nullptr) return true; if (handle->is_snapshot_) return false; bool snapshot_found = false; - SpinLockHolder lock(&queue_->mutex); - for (const CordzHandle* p = queue_->dq_tail; p; p = p->dq_prev_) { + Queue* global_queue = GlobalQueue(); + MutexLock lock(&global_queue->mutex); + for (const CordzHandle* p = global_queue->dq_tail; p; p = p->dq_prev_) { if (p == handle) return !snapshot_found; if (p == this) snapshot_found = true; } @@ -119,13 +145,13 @@ bool CordzHandle::DiagnosticsHandleIsSafeToInspect( std::vector<const CordzHandle*> CordzHandle::DiagnosticsGetSafeToInspectDeletedHandles() { - ODRCheck(); std::vector<const CordzHandle*> handles; if (!is_snapshot()) { return handles; } - SpinLockHolder lock(&queue_->mutex); + Queue* global_queue = GlobalQueue(); + MutexLock lock(&global_queue->mutex); for (const CordzHandle* p = dq_next_; p != nullptr; p = p->dq_next_) { if (!p->is_snapshot()) { handles.push_back(p); diff --git a/absl/strings/internal/cordz_handle.h b/absl/strings/internal/cordz_handle.h index 3c800b43..08e3f0d3 100644 --- a/absl/strings/internal/cordz_handle.h +++ b/absl/strings/internal/cordz_handle.h @@ -20,8 +20,6 @@ #include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" -#include "absl/base/internal/spinlock.h" -#include "absl/synchronization/mutex.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -34,7 +32,7 @@ namespace cord_internal { // has gained visibility into a CordzInfo object, that CordzInfo object will not // be deleted prematurely. This allows the profiler to inspect all CordzInfo // objects that are alive without needing to hold a global lock. -class CordzHandle { +class ABSL_DLL CordzHandle { public: CordzHandle() : CordzHandle(false) {} @@ -79,37 +77,6 @@ class CordzHandle { virtual ~CordzHandle(); private: - // Global queue data. CordzHandle stores a pointer to the global queue - // instance to harden against ODR violations. - struct Queue { - constexpr explicit Queue(absl::ConstInitType) - : mutex(absl::kConstInit, - absl::base_internal::SCHEDULE_COOPERATIVE_AND_KERNEL) {} - - absl::base_internal::SpinLock mutex; - std::atomic<CordzHandle*> dq_tail ABSL_GUARDED_BY(mutex){nullptr}; - - // Returns true if this delete queue is empty. This method does not acquire - // the lock, but does a 'load acquire' observation on the delete queue tail. - // It is used inside Delete() to check for the presence of a delete queue - // without holding the lock. The assumption is that the caller is in the - // state of 'being deleted', and can not be newly discovered by a concurrent - // 'being constructed' snapshot instance. Practically, this means that any - // such discovery (`find`, 'first' or 'next', etc) must have proper 'happens - // before / after' semantics and atomic fences. - bool IsEmpty() const ABSL_NO_THREAD_SAFETY_ANALYSIS { - return dq_tail.load(std::memory_order_acquire) == nullptr; - } - }; - - void ODRCheck() const { -#ifndef NDEBUG - ABSL_RAW_CHECK(queue_ == &global_queue_, "ODR violation in Cord"); -#endif - } - - ABSL_CONST_INIT static Queue global_queue_; - Queue* const queue_ = &global_queue_; const bool is_snapshot_; // dq_prev_ and dq_next_ require the global queue mutex to be held. diff --git a/absl/strings/internal/cordz_info.cc b/absl/strings/internal/cordz_info.cc index 530f33be..b24c3da7 100644 --- a/absl/strings/internal/cordz_info.cc +++ b/absl/strings/internal/cordz_info.cc @@ -21,19 +21,17 @@ #include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cord_rep_btree.h" #include "absl/strings/internal/cord_rep_crc.h" -#include "absl/strings/internal/cord_rep_ring.h" #include "absl/strings/internal/cordz_handle.h" #include "absl/strings/internal/cordz_statistics.h" #include "absl/strings/internal/cordz_update_tracker.h" #include "absl/synchronization/mutex.h" +#include "absl/time/clock.h" #include "absl/types/span.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace cord_internal { -using ::absl::base_internal::SpinLockHolder; - #ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL constexpr size_t CordzInfo::kMaxStackDepth; #endif @@ -53,7 +51,7 @@ namespace { // The top level node is treated specially: we assume the current thread // (typically called from the CordzHandler) to hold a reference purely to // perform a safe analysis, and not being part of the application. So we -// substract 1 from the reference count of the top node to compute the +// subtract 1 from the reference count of the top node to compute the // 'application fair share' excluding the reference of the current thread. // // An example of fair sharing, and why we multiply reference counts: @@ -78,6 +76,8 @@ class CordRepAnalyzer { // adds the results to `statistics`. Note that node counts and memory sizes // are not initialized, computed values are added to any existing values. void AnalyzeCordRep(const CordRep* rep) { + ABSL_ASSERT(rep != nullptr); + // Process all linear nodes. // As per the class comments, use refcout - 1 on the top level node, as the // top level node is assumed to be referenced only for analysis purposes. @@ -85,7 +85,7 @@ class CordRepAnalyzer { RepRef repref{rep, (refcount > 1) ? refcount - 1 : 1}; // Process the top level CRC node, if present. - if (repref.rep->tag == CRC) { + if (repref.tag() == CRC) { statistics_.node_count++; statistics_.node_counts.crc++; memory_usage_.Add(sizeof(CordRepCrc), repref.refcount); @@ -95,15 +95,14 @@ class CordRepAnalyzer { // Process all top level linear nodes (substrings and flats). repref = CountLinearReps(repref, memory_usage_); - if (repref.rep != nullptr) { - if (repref.rep->tag == RING) { - AnalyzeRing(repref); - } else if (repref.rep->tag == BTREE) { + switch (repref.tag()) { + case CordRepKind::BTREE: AnalyzeBtree(repref); - } else { - // We should have either a concat, btree, or ring node if not null. - assert(false); - } + break; + default: + // We should have a btree node if not null. + ABSL_ASSERT(repref.tag() == CordRepKind::UNUSED_0); + break; } // Adds values to output @@ -121,11 +120,19 @@ class CordRepAnalyzer { const CordRep* rep; size_t refcount; - // Returns a 'child' RepRef which contains the cumulative reference count of - // this instance multiplied by the child's reference count. + // Returns a 'child' RepRef which contains the cumulative reference count + // of this instance multiplied by the child's reference count. Returns a + // nullptr RepRef value with a refcount of 0 if `child` is nullptr. RepRef Child(const CordRep* child) const { + if (child == nullptr) return RepRef{nullptr, 0}; return RepRef{child, refcount * child->refcount.Get()}; } + + // Returns the tag of this rep, or UNUSED_0 if this instance is null + constexpr CordRepKind tag() const { + ABSL_ASSERT(rep == nullptr || rep->tag != CordRepKind::UNUSED_0); + return rep ? static_cast<CordRepKind>(rep->tag) : CordRepKind::UNUSED_0; + } }; // Memory usage values @@ -166,7 +173,7 @@ class CordRepAnalyzer { // buffers where we count children unrounded. RepRef CountLinearReps(RepRef rep, MemoryUsage& memory_usage) { // Consume all substrings - while (rep.rep->tag == SUBSTRING) { + while (rep.tag() == SUBSTRING) { statistics_.node_count++; statistics_.node_counts.substring++; memory_usage.Add(sizeof(CordRepSubstring), rep.refcount); @@ -174,7 +181,7 @@ class CordRepAnalyzer { } // Consume possible FLAT - if (rep.rep->tag >= FLAT) { + if (rep.tag() >= FLAT) { size_t size = rep.rep->flat()->AllocatedSize(); CountFlat(size); memory_usage.Add(size, rep.refcount); @@ -182,7 +189,7 @@ class CordRepAnalyzer { } // Consume possible external - if (rep.rep->tag == EXTERNAL) { + if (rep.tag() == EXTERNAL) { statistics_.node_count++; statistics_.node_counts.external++; size_t size = rep.rep->length + sizeof(CordRepExternalImpl<intptr_t>); @@ -193,17 +200,6 @@ class CordRepAnalyzer { return rep; } - // Analyzes the provided ring. - void AnalyzeRing(RepRef rep) { - statistics_.node_count++; - statistics_.node_counts.ring++; - const CordRepRing* ring = rep.rep->ring(); - memory_usage_.Add(CordRepRing::AllocSize(ring->capacity()), rep.refcount); - ring->ForEach([&](CordRepRing::index_type pos) { - CountLinearReps(rep.Child(ring->entry_child(pos)), memory_usage_); - }); - } - // Analyzes the provided btree. void AnalyzeBtree(RepRef rep) { statistics_.node_count++; diff --git a/absl/strings/internal/cordz_info_statistics_test.cc b/absl/strings/internal/cordz_info_statistics_test.cc index 53d2f2ea..d55773f2 100644 --- a/absl/strings/internal/cordz_info_statistics_test.cc +++ b/absl/strings/internal/cordz_info_statistics_test.cc @@ -25,7 +25,6 @@ #include "absl/strings/internal/cord_rep_btree.h" #include "absl/strings/internal/cord_rep_crc.h" #include "absl/strings/internal/cord_rep_flat.h" -#include "absl/strings/internal/cord_rep_ring.h" #include "absl/strings/internal/cordz_info.h" #include "absl/strings/internal/cordz_sample_token.h" #include "absl/strings/internal/cordz_statistics.h" @@ -123,11 +122,6 @@ size_t SizeOf(const CordRepExternal* rep) { return sizeof(CordRepExternalImpl<intptr_t>) + rep->length; } -template <> -size_t SizeOf(const CordRepRing* rep) { - return CordRepRing::AllocSize(rep->capacity()); -} - // Computes fair share memory used in a naive 'we dare to recurse' way. double FairShareImpl(CordRep* rep, size_t ref) { double self = 0.0, children = 0.0; @@ -144,11 +138,6 @@ double FairShareImpl(CordRep* rep, size_t ref) { for (CordRep*edge : rep->btree()->Edges()) { children += FairShareImpl(edge, ref); } - } else if (rep->tag == RING) { - self = SizeOf(rep->ring()); - rep->ring()->ForEach([&](CordRepRing::index_type i) { - self += FairShareImpl(rep->ring()->entry_child(i), 1); - }); } else { assert(false); } @@ -294,64 +283,6 @@ TEST(CordzInfoStatisticsTest, SharedSubstring) { EXPECT_THAT(SampleCord(substring), EqStatistics(expected)); } - -TEST(CordzInfoStatisticsTest, Ring) { - RefHelper ref; - auto* flat1 = Flat(240); - auto* flat2 = Flat(2000); - auto* flat3 = Flat(70); - auto* external = External(3000); - CordRepRing* ring = CordRepRing::Create(flat1); - ring = CordRepRing::Append(ring, flat2); - ring = CordRepRing::Append(ring, flat3); - ring = ref.NeedsUnref(CordRepRing::Append(ring, external)); - - CordzStatistics expected; - expected.size = ring->length; - expected.estimated_memory_usage = SizeOf(ring) + SizeOf(flat1) + - SizeOf(flat2) + SizeOf(flat3) + - SizeOf(external); - expected.estimated_fair_share_memory_usage = expected.estimated_memory_usage; - expected.node_count = 5; - expected.node_counts.flat = 3; - expected.node_counts.flat_128 = 1; - expected.node_counts.flat_256 = 1; - expected.node_counts.external = 1; - expected.node_counts.ring = 1; - - EXPECT_THAT(SampleCord(ring), EqStatistics(expected)); -} - -TEST(CordzInfoStatisticsTest, SharedSubstringRing) { - RefHelper ref; - auto* flat1 = ref.Ref(Flat(240)); - auto* flat2 = Flat(200); - auto* flat3 = Flat(70); - auto* external = ref.Ref(External(3000), 5); - CordRepRing* ring = CordRepRing::Create(flat1); - ring = CordRepRing::Append(ring, flat2); - ring = CordRepRing::Append(ring, flat3); - ring = ref.Ref(CordRepRing::Append(ring, external), 4); - auto* substring = ref.Ref(ref.NeedsUnref(Substring(ring))); - - - CordzStatistics expected; - expected.size = substring->length; - expected.estimated_memory_usage = SizeOf(ring) + SizeOf(flat1) + - SizeOf(flat2) + SizeOf(flat3) + - SizeOf(external) + SizeOf(substring); - expected.estimated_fair_share_memory_usage = FairShare(substring); - expected.node_count = 6; - expected.node_counts.flat = 3; - expected.node_counts.flat_128 = 1; - expected.node_counts.flat_256 = 2; - expected.node_counts.external = 1; - expected.node_counts.ring = 1; - expected.node_counts.substring = 1; - - EXPECT_THAT(SampleCord(substring), EqStatistics(expected)); -} - TEST(CordzInfoStatisticsTest, BtreeLeaf) { ASSERT_THAT(CordRepBtree::kMaxCapacity, Ge(3u)); RefHelper ref; @@ -452,8 +383,7 @@ TEST(CordzInfoStatisticsTest, BtreeNodeShared) { TEST(CordzInfoStatisticsTest, Crc) { RefHelper ref; auto* left = Flat(1000); - auto* crc = - ref.NeedsUnref(CordRepCrc::New(left, crc_internal::CrcCordState())); + auto* crc = ref.NeedsUnref(CordRepCrc::New(left, {})); CordzStatistics expected; expected.size = left->length; @@ -467,6 +397,20 @@ TEST(CordzInfoStatisticsTest, Crc) { EXPECT_THAT(SampleCord(crc), EqStatistics(expected)); } +TEST(CordzInfoStatisticsTest, EmptyCrc) { + RefHelper ref; + auto* crc = ref.NeedsUnref(CordRepCrc::New(nullptr, {})); + + CordzStatistics expected; + expected.size = 0; + expected.estimated_memory_usage = SizeOf(crc); + expected.estimated_fair_share_memory_usage = expected.estimated_memory_usage; + expected.node_count = 1; + expected.node_counts.crc = 1; + + EXPECT_THAT(SampleCord(crc), EqStatistics(expected)); +} + TEST(CordzInfoStatisticsTest, ThreadSafety) { Notification stop; static constexpr int kNumThreads = 8; @@ -497,6 +441,7 @@ TEST(CordzInfoStatisticsTest, ThreadSafety) { InlineData cords[2]; std::minstd_rand gen; std::uniform_int_distribution<int> coin_toss(0, 1); + std::uniform_int_distribution<int> dice_roll(1, 6); while (!stop.HasBeenNotified()) { for (InlineData& cord : cords) { @@ -514,13 +459,21 @@ TEST(CordzInfoStatisticsTest, ThreadSafety) { CordRep::Unref(cord.as_tree()); cord.set_inline_size(0); } else { - // Coin toss to 25% ring, 25% btree, and 50% flat. + // Coin toss to 50% btree, and 50% flat. CordRep* rep = Flat(256); if (coin_toss(gen) != 0) { - if (coin_toss(gen) != 0) { - rep = CordRepRing::Create(rep); + rep = CordRepBtree::Create(rep); + } + + // Maybe CRC this cord + if (dice_roll(gen) == 6) { + if (dice_roll(gen) == 6) { + // Empty CRC rep + CordRep::Unref(rep); + rep = CordRepCrc::New(nullptr, {}); } else { - rep = CordRepBtree::Create(rep); + // Regular CRC rep + rep = CordRepCrc::New(rep, {}); } } cord.make_tree(rep); diff --git a/absl/strings/internal/cordz_sample_token.h b/absl/strings/internal/cordz_sample_token.h index b58022c3..2a86bc3b 100644 --- a/absl/strings/internal/cordz_sample_token.h +++ b/absl/strings/internal/cordz_sample_token.h @@ -33,11 +33,11 @@ namespace cord_internal { // ST1 <- CH1 <- CH2 <- ST2 <- CH3 <- global_delete_queue_tail // // This list tracks that CH1 and CH2 were created after ST1, so the thread -// holding ST1 might have a referece to CH1, CH2, ST2, and CH3. However, ST2 was -// created later, so the thread holding the ST2 token cannot have a reference to -// ST1, CH1, or CH2. If ST1 is cleaned up first, that thread will delete ST1, -// CH1, and CH2. If instead ST2 is cleaned up first, that thread will only -// delete ST2. +// holding ST1 might have a reference to CH1, CH2, ST2, and CH3. However, ST2 +// was created later, so the thread holding the ST2 token cannot have a +// reference to ST1, CH1, or CH2. If ST1 is cleaned up first, that thread will +// delete ST1, CH1, and CH2. If instead ST2 is cleaned up first, that thread +// will only delete ST2. // // If ST1 is cleaned up first, the new list will be: // ST2 <- CH3 <- global_delete_queue_tail diff --git a/absl/strings/internal/damerau_levenshtein_distance_test.cc b/absl/strings/internal/damerau_levenshtein_distance_test.cc index a342b7db..49dd105b 100644 --- a/absl/strings/internal/damerau_levenshtein_distance_test.cc +++ b/absl/strings/internal/damerau_levenshtein_distance_test.cc @@ -54,7 +54,7 @@ TEST(Distance, TestDistances) { } TEST(Distance, TestCutoff) { - // Returing cutoff + 1 if the value is larger than cutoff or string longer + // Returning 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}); diff --git a/absl/strings/internal/escaping.cc b/absl/strings/internal/escaping.cc index 8bd0890d..56a4cbed 100644 --- a/absl/strings/internal/escaping.cc +++ b/absl/strings/internal/escaping.cc @@ -21,9 +21,17 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace strings_internal { +// The two strings below provide maps from normal 6-bit characters to their +// base64-escaped equivalent. +// For the inverse case, see kUn(WebSafe)Base64 in the external +// escaping.cc. ABSL_CONST_INIT const char kBase64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +ABSL_CONST_INIT const char kWebSafeBase64Chars[] = + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_"; + + size_t CalculateBase64EscapedLenInternal(size_t input_len, bool do_padding) { // Base64 encodes three bytes of input at a time. If the input is not // divisible by three, we pad as appropriate. @@ -62,6 +70,21 @@ size_t CalculateBase64EscapedLenInternal(size_t input_len, bool do_padding) { return len; } +// ---------------------------------------------------------------------- +// Take the input in groups of 4 characters and turn each +// character into a code 0 to 63 thus: +// A-Z map to 0 to 25 +// a-z map to 26 to 51 +// 0-9 map to 52 to 61 +// +(- for WebSafe) maps to 62 +// /(_ for WebSafe) maps to 63 +// There will be four numbers, all less than 64 which can be represented +// by a 6 digit binary number (aaaaaa, bbbbbb, cccccc, dddddd respectively). +// Arrange the 6 digit binary numbers into three bytes as such: +// aaaaaabb bbbbcccc ccdddddd +// Equals signs (one or two) are used at the end of the encoded block to +// indicate that the text was not an integer multiple of three bytes long. +// ---------------------------------------------------------------------- size_t Base64EscapeInternal(const unsigned char* src, size_t szsrc, char* dest, size_t szdest, const char* base64, bool do_padding) { diff --git a/absl/strings/internal/escaping.h b/absl/strings/internal/escaping.h index b04033ff..2186f778 100644 --- a/absl/strings/internal/escaping.h +++ b/absl/strings/internal/escaping.h @@ -24,6 +24,7 @@ ABSL_NAMESPACE_BEGIN namespace strings_internal { ABSL_CONST_INIT extern const char kBase64Chars[]; +ABSL_CONST_INIT extern const char kWebSafeBase64Chars[]; // Calculates the length of a Base64 encoding (RFC 4648) of a string of length // `input_len`, with or without padding per `do_padding`. Note that 'web-safe' diff --git a/absl/strings/internal/has_absl_stringify.h b/absl/strings/internal/has_absl_stringify.h index 55a08508..f82cfe26 100644 --- a/absl/strings/internal/has_absl_stringify.h +++ b/absl/strings/internal/has_absl_stringify.h @@ -1,4 +1,4 @@ -// Copyright 2022 The Abseil Authors +// Copyright 2024 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. @@ -14,38 +14,27 @@ #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" +#include "absl/strings/has_absl_stringify.h" + +#include "absl/base/config.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 {}; +// This exists to fix a circular dependency problem with the GoogleTest release. +// GoogleTest referenced this internal file and this internal trait. Since +// simultaneous releases are not possible since once release must reference +// another, we will temporarily add this back. +// https://github.com/google/googletest/blob/v1.14.x/googletest/include/gtest/gtest-printers.h#L119 +// +// This file can be deleted after the next Abseil and GoogleTest release. +// +// https://github.com/google/googletest/pull/4368#issuecomment-1717699895 +// https://github.com/google/googletest/pull/4368#issuecomment-1717699895 +using ::absl::HasAbslStringify; } // namespace strings_internal diff --git a/absl/strings/internal/memutil.cc b/absl/strings/internal/memutil.cc index 44996a75..0bbd8aa1 100644 --- a/absl/strings/internal/memutil.cc +++ b/absl/strings/internal/memutil.cc @@ -16,6 +16,8 @@ #include <cstdlib> +#include "absl/strings/ascii.h" + namespace absl { ABSL_NAMESPACE_BEGIN namespace strings_internal { @@ -25,91 +27,22 @@ int memcasecmp(const char* s1, const char* s2, size_t len) { const unsigned char* us2 = reinterpret_cast<const unsigned char*>(s2); for (size_t i = 0; i < len; i++) { - const int diff = - int{static_cast<unsigned char>(absl::ascii_tolower(us1[i]))} - - int{static_cast<unsigned char>(absl::ascii_tolower(us2[i]))}; - if (diff != 0) return diff; + unsigned char c1 = us1[i]; + unsigned char c2 = us2[i]; + // If bytes are the same, they will be the same when converted to lower. + // So we only need to convert if bytes are not equal. + // NOTE(b/308193381): We do not use `absl::ascii_tolower` here in order + // to avoid its lookup table and improve performance. + if (c1 != c2) { + c1 = c1 >= 'A' && c1 <= 'Z' ? c1 - 'A' + 'a' : c1; + c2 = c2 >= 'A' && c2 <= 'Z' ? c2 - 'A' + 'a' : c2; + const int diff = int{c1} - int{c2}; + if (diff != 0) return diff; + } } return 0; } -char* memdup(const char* s, size_t slen) { - void* copy; - if ((copy = malloc(slen)) == nullptr) return nullptr; - memcpy(copy, s, slen); - return reinterpret_cast<char*>(copy); -} - -char* memrchr(const char* s, int c, size_t slen) { - for (const char* e = s + slen - 1; e >= s; e--) { - if (*e == c) return const_cast<char*>(e); - } - return nullptr; -} - -size_t memspn(const char* s, size_t slen, const char* accept) { - const char* p = s; - const char* spanp; - char c, sc; - -cont: - c = *p++; - if (slen-- == 0) - return static_cast<size_t>(p - 1 - s); - for (spanp = accept; (sc = *spanp++) != '\0';) - if (sc == c) goto cont; - return static_cast<size_t>(p - 1 - s); -} - -size_t memcspn(const char* s, size_t slen, const char* reject) { - const char* p = s; - const char* spanp; - char c, sc; - - while (slen-- != 0) { - c = *p++; - for (spanp = reject; (sc = *spanp++) != '\0';) - if (sc == c) - return static_cast<size_t>(p - 1 - s); - } - return static_cast<size_t>(p - s); -} - -char* mempbrk(const char* s, size_t slen, const char* accept) { - const char* scanp; - int sc; - - for (; slen; ++s, --slen) { - for (scanp = accept; (sc = *scanp++) != '\0';) - if (sc == *s) return const_cast<char*>(s); - } - return nullptr; -} - -// This is significantly faster for case-sensitive matches with very -// few possible matches. See unit test for benchmarks. -const char* memmatch(const char* phaystack, size_t haylen, const char* pneedle, - size_t neelen) { - if (0 == neelen) { - return phaystack; // even if haylen is 0 - } - if (haylen < neelen) return nullptr; - - const char* match; - const char* hayend = phaystack + haylen - neelen + 1; - // A static cast is used here to work around the fact that memchr returns - // a void* on Posix-compliant systems and const void* on Windows. - while ( - (match = static_cast<const char*>(memchr( - phaystack, pneedle[0], static_cast<size_t>(hayend - phaystack))))) { - if (memcmp(match, pneedle, neelen) == 0) - return match; - else - phaystack = match + 1; - } - return nullptr; -} - } // namespace strings_internal ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/strings/internal/memutil.h b/absl/strings/internal/memutil.h index 9ad05358..b5911a01 100644 --- a/absl/strings/internal/memutil.h +++ b/absl/strings/internal/memutil.h @@ -14,51 +14,6 @@ // limitations under the License. // -// These routines provide mem versions of standard C string routines, -// such as strpbrk. They function exactly the same as the str versions, -// so if you wonder what they are, replace the word "mem" by -// "str" and check out the man page. I could return void*, as the -// strutil.h mem*() routines tend to do, but I return char* instead -// since this is by far the most common way these functions are called. -// -// The difference between the mem and str versions is the mem version -// takes a pointer and a length, rather than a '\0'-terminated string. -// The memcase* routines defined here assume the locale is "C" -// (they use absl::ascii_tolower instead of tolower). -// -// These routines are based on the BSD library. -// -// Here's a list of routines from string.h, and their mem analogues. -// Functions in lowercase are defined in string.h; those in UPPERCASE -// are defined here: -// -// strlen -- -// strcat strncat MEMCAT -// strcpy strncpy memcpy -// -- memccpy (very cool function, btw) -// -- memmove -// -- memset -// strcmp strncmp memcmp -// strcasecmp strncasecmp MEMCASECMP -// strchr memchr -// strcoll -- -// strxfrm -- -// strdup strndup MEMDUP -// strrchr MEMRCHR -// strspn MEMSPN -// strcspn MEMCSPN -// strpbrk MEMPBRK -// strstr MEMSTR MEMMEM -// (g)strcasestr MEMCASESTR MEMCASEMEM -// strtok -- -// strprefix MEMPREFIX (strprefix is from strutil.h) -// strcaseprefix MEMCASEPREFIX (strcaseprefix is from strutil.h) -// strsuffix MEMSUFFIX (strsuffix is from strutil.h) -// strcasesuffix MEMCASESUFFIX (strcasesuffix is from strutil.h) -// -- MEMIS -// -- MEMCASEIS -// strcount MEMCOUNT (strcount is from strutil.h) - #ifndef ABSL_STRINGS_INTERNAL_MEMUTIL_H_ #define ABSL_STRINGS_INTERNAL_MEMUTIL_H_ @@ -72,74 +27,11 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace strings_internal { -inline char* memcat(char* dest, size_t destlen, const char* src, - size_t srclen) { - return reinterpret_cast<char*>(memcpy(dest + destlen, src, srclen)); -} - +// Performs a byte-by-byte comparison of `len` bytes of the strings `s1` and +// `s2`, ignoring the case of the characters. It returns an integer less than, +// equal to, or greater than zero if `s1` is found, respectively, to be less +// than, to match, or be greater than `s2`. int memcasecmp(const char* s1, const char* s2, size_t len); -char* memdup(const char* s, size_t slen); -char* memrchr(const char* s, int c, size_t slen); -size_t memspn(const char* s, size_t slen, const char* accept); -size_t memcspn(const char* s, size_t slen, const char* reject); -char* mempbrk(const char* s, size_t slen, const char* accept); - -// This is for internal use only. Don't call this directly -template <bool case_sensitive> -const char* int_memmatch(const char* haystack, size_t haylen, - const char* needle, size_t neelen) { - if (0 == neelen) { - return haystack; // even if haylen is 0 - } - const char* hayend = haystack + haylen; - const char* needlestart = needle; - const char* needleend = needlestart + neelen; - - for (; haystack < hayend; ++haystack) { - char hay = case_sensitive - ? *haystack - : absl::ascii_tolower(static_cast<unsigned char>(*haystack)); - char nee = case_sensitive - ? *needle - : absl::ascii_tolower(static_cast<unsigned char>(*needle)); - if (hay == nee) { - if (++needle == needleend) { - return haystack + 1 - neelen; - } - } else if (needle != needlestart) { - // must back up haystack in case a prefix matched (find "aab" in "aaab") - haystack -= needle - needlestart; // for loop will advance one more - needle = needlestart; - } - } - return nullptr; -} - -// These are the guys you can call directly -inline const char* memstr(const char* phaystack, size_t haylen, - const char* pneedle) { - return int_memmatch<true>(phaystack, haylen, pneedle, strlen(pneedle)); -} - -inline const char* memcasestr(const char* phaystack, size_t haylen, - const char* pneedle) { - return int_memmatch<false>(phaystack, haylen, pneedle, strlen(pneedle)); -} - -inline const char* memmem(const char* phaystack, size_t haylen, - const char* pneedle, size_t needlelen) { - return int_memmatch<true>(phaystack, haylen, pneedle, needlelen); -} - -inline const char* memcasemem(const char* phaystack, size_t haylen, - const char* pneedle, size_t needlelen) { - return int_memmatch<false>(phaystack, haylen, pneedle, needlelen); -} - -// This is significantly faster for case-sensitive matches with very -// few possible matches. See unit test for benchmarks. -const char* memmatch(const char* phaystack, size_t haylen, const char* pneedle, - size_t neelen); } // namespace strings_internal ABSL_NAMESPACE_END diff --git a/absl/strings/internal/memutil_benchmark.cc b/absl/strings/internal/memutil_benchmark.cc index dc95c3e5..61e323a4 100644 --- a/absl/strings/internal/memutil_benchmark.cc +++ b/absl/strings/internal/memutil_benchmark.cc @@ -25,62 +25,6 @@ // - an easy search: 'b' // - a medium search: 'ab'. That means every letter is a possible match. // - a pathological search: 'aaaaaa.......aaaaab' (half as many a's as haytack) -// We benchmark case-sensitive and case-insensitive versions of -// three memmem implementations: -// - memmem() from memutil.h -// - search() from STL -// - memmatch(), a custom implementation using memchr and memcmp. -// Here are sample results: -// -// Run on (12 X 3800 MHz CPU s) -// CPU Caches: -// L1 Data 32K (x6) -// L1 Instruction 32K (x6) -// L2 Unified 256K (x6) -// L3 Unified 15360K (x1) -// ---------------------------------------------------------------- -// Benchmark Time CPU Iterations -// ---------------------------------------------------------------- -// BM_Memmem 3583 ns 3582 ns 196469 2.59966GB/s -// BM_MemmemMedium 13743 ns 13742 ns 50901 693.986MB/s -// BM_MemmemPathological 13695030 ns 13693977 ns 51 713.133kB/s -// BM_Memcasemem 3299 ns 3299 ns 212942 2.82309GB/s -// BM_MemcasememMedium 16407 ns 16406 ns 42170 581.309MB/s -// BM_MemcasememPathological 17267745 ns 17266030 ns 41 565.598kB/s -// BM_Search 1610 ns 1609 ns 431321 5.78672GB/s -// BM_SearchMedium 11111 ns 11110 ns 63001 858.414MB/s -// BM_SearchPathological 12117390 ns 12116397 ns 58 805.984kB/s -// BM_Searchcase 3081 ns 3081 ns 229949 3.02313GB/s -// BM_SearchcaseMedium 16003 ns 16001 ns 44170 595.998MB/s -// BM_SearchcasePathological 15823413 ns 15821909 ns 44 617.222kB/s -// BM_Memmatch 197 ns 197 ns 3584225 47.2951GB/s -// BM_MemmatchMedium 52333 ns 52329 ns 13280 182.244MB/s -// BM_MemmatchPathological 659799 ns 659727 ns 1058 14.4556MB/s -// BM_Memcasematch 5460 ns 5460 ns 127606 1.70586GB/s -// BM_MemcasematchMedium 32861 ns 32857 ns 21258 290.248MB/s -// BM_MemcasematchPathological 15154243 ns 15153089 ns 46 644.464kB/s -// BM_MemmemStartup 5 ns 5 ns 150821500 -// BM_SearchStartup 5 ns 5 ns 150644203 -// BM_MemmatchStartup 7 ns 7 ns 97068802 -// -// Conclusions: -// -// The following recommendations are based on the sample results above. However, -// we have found that the performance of STL search can vary significantly -// depending on compiler and standard library implementation. We recommend you -// run the benchmarks for yourself on relevant platforms. -// -// If you need case-insensitive, STL search is slightly better than memmem for -// all cases. -// -// Case-sensitive is more subtle: -// Custom memmatch is _very_ fast at scanning, so if you have very few possible -// matches in your haystack, that's the way to go. Performance drops -// significantly with more matches. -// -// STL search is slightly faster than memmem in the medium and pathological -// benchmarks. However, the performance of memmem is currently more dependable -// across platforms and build configurations. namespace { @@ -94,96 +38,10 @@ const char* MakeHaystack() { } const char* const kHaystack = MakeHaystack(); -void BM_Memmem(benchmark::State& state) { - for (auto _ : state) { - benchmark::DoNotOptimize( - absl::strings_internal::memmem(kHaystack, kHaystackSize, "b", 1)); - } - state.SetBytesProcessed(kHaystackSize64 * state.iterations()); -} -BENCHMARK(BM_Memmem); - -void BM_MemmemMedium(benchmark::State& state) { - for (auto _ : state) { - benchmark::DoNotOptimize( - absl::strings_internal::memmem(kHaystack, kHaystackSize, "ab", 2)); - } - state.SetBytesProcessed(kHaystackSize64 * state.iterations()); -} -BENCHMARK(BM_MemmemMedium); - -void BM_MemmemPathological(benchmark::State& state) { - for (auto _ : state) { - benchmark::DoNotOptimize(absl::strings_internal::memmem( - kHaystack, kHaystackSize, kHaystack + kHaystackSize / 2, - kHaystackSize - kHaystackSize / 2)); - } - state.SetBytesProcessed(kHaystackSize64 * state.iterations()); -} -BENCHMARK(BM_MemmemPathological); - -void BM_Memcasemem(benchmark::State& state) { - for (auto _ : state) { - benchmark::DoNotOptimize( - absl::strings_internal::memcasemem(kHaystack, kHaystackSize, "b", 1)); - } - state.SetBytesProcessed(kHaystackSize64 * state.iterations()); -} -BENCHMARK(BM_Memcasemem); - -void BM_MemcasememMedium(benchmark::State& state) { - for (auto _ : state) { - benchmark::DoNotOptimize( - absl::strings_internal::memcasemem(kHaystack, kHaystackSize, "ab", 2)); - } - state.SetBytesProcessed(kHaystackSize64 * state.iterations()); -} -BENCHMARK(BM_MemcasememMedium); - -void BM_MemcasememPathological(benchmark::State& state) { - for (auto _ : state) { - benchmark::DoNotOptimize(absl::strings_internal::memcasemem( - kHaystack, kHaystackSize, kHaystack + kHaystackSize / 2, - kHaystackSize - kHaystackSize / 2)); - } - state.SetBytesProcessed(kHaystackSize64 * state.iterations()); -} -BENCHMARK(BM_MemcasememPathological); - bool case_eq(const char a, const char b) { return absl::ascii_tolower(a) == absl::ascii_tolower(b); } -void BM_Search(benchmark::State& state) { - for (auto _ : state) { - benchmark::DoNotOptimize(std::search(kHaystack, kHaystack + kHaystackSize, - kHaystack + kHaystackSize - 1, - kHaystack + kHaystackSize)); - } - state.SetBytesProcessed(kHaystackSize64 * state.iterations()); -} -BENCHMARK(BM_Search); - -void BM_SearchMedium(benchmark::State& state) { - for (auto _ : state) { - benchmark::DoNotOptimize(std::search(kHaystack, kHaystack + kHaystackSize, - kHaystack + kHaystackSize - 2, - kHaystack + kHaystackSize)); - } - state.SetBytesProcessed(kHaystackSize64 * state.iterations()); -} -BENCHMARK(BM_SearchMedium); - -void BM_SearchPathological(benchmark::State& state) { - for (auto _ : state) { - benchmark::DoNotOptimize(std::search(kHaystack, kHaystack + kHaystackSize, - kHaystack + kHaystackSize / 2, - kHaystack + kHaystackSize)); - } - state.SetBytesProcessed(kHaystackSize64 * state.iterations()); -} -BENCHMARK(BM_SearchPathological); - void BM_Searchcase(benchmark::State& state) { for (auto _ : state) { benchmark::DoNotOptimize(std::search(kHaystack, kHaystack + kHaystackSize, @@ -241,34 +99,6 @@ const char* memcasematch(const char* phaystack, size_t haylen, return nullptr; } -void BM_Memmatch(benchmark::State& state) { - for (auto _ : state) { - benchmark::DoNotOptimize( - absl::strings_internal::memmatch(kHaystack, kHaystackSize, "b", 1)); - } - state.SetBytesProcessed(kHaystackSize64 * state.iterations()); -} -BENCHMARK(BM_Memmatch); - -void BM_MemmatchMedium(benchmark::State& state) { - for (auto _ : state) { - benchmark::DoNotOptimize( - absl::strings_internal::memmatch(kHaystack, kHaystackSize, "ab", 2)); - } - state.SetBytesProcessed(kHaystackSize64 * state.iterations()); -} -BENCHMARK(BM_MemmatchMedium); - -void BM_MemmatchPathological(benchmark::State& state) { - for (auto _ : state) { - benchmark::DoNotOptimize(absl::strings_internal::memmatch( - kHaystack, kHaystackSize, kHaystack + kHaystackSize / 2, - kHaystackSize - kHaystackSize / 2)); - } - state.SetBytesProcessed(kHaystackSize64 * state.iterations()); -} -BENCHMARK(BM_MemmatchPathological); - void BM_Memcasematch(benchmark::State& state) { for (auto _ : state) { benchmark::DoNotOptimize(memcasematch(kHaystack, kHaystackSize, "b", 1)); @@ -295,29 +125,4 @@ void BM_MemcasematchPathological(benchmark::State& state) { } BENCHMARK(BM_MemcasematchPathological); -void BM_MemmemStartup(benchmark::State& state) { - for (auto _ : state) { - benchmark::DoNotOptimize(absl::strings_internal::memmem( - kHaystack + kHaystackSize - 10, 10, kHaystack + kHaystackSize - 1, 1)); - } -} -BENCHMARK(BM_MemmemStartup); - -void BM_SearchStartup(benchmark::State& state) { - for (auto _ : state) { - benchmark::DoNotOptimize( - std::search(kHaystack + kHaystackSize - 10, kHaystack + kHaystackSize, - kHaystack + kHaystackSize - 1, kHaystack + kHaystackSize)); - } -} -BENCHMARK(BM_SearchStartup); - -void BM_MemmatchStartup(benchmark::State& state) { - for (auto _ : state) { - benchmark::DoNotOptimize(absl::strings_internal::memmatch( - kHaystack + kHaystackSize - 10, 10, kHaystack + kHaystackSize - 1, 1)); - } -} -BENCHMARK(BM_MemmatchStartup); - } // namespace diff --git a/absl/strings/internal/memutil_test.cc b/absl/strings/internal/memutil_test.cc index d8681ddf..277be2c4 100644 --- a/absl/strings/internal/memutil_test.cc +++ b/absl/strings/internal/memutil_test.cc @@ -19,42 +19,12 @@ #include <cstdlib> #include "gtest/gtest.h" -#include "absl/strings/ascii.h" namespace { -static char* memcasechr(const char* s, int c, size_t slen) { - c = absl::ascii_tolower(c); - for (; slen; ++s, --slen) { - if (absl::ascii_tolower(*s) == c) return const_cast<char*>(s); - } - return nullptr; -} - -static const char* memcasematch(const char* phaystack, size_t haylen, - const char* pneedle, size_t neelen) { - if (0 == neelen) { - return phaystack; // even if haylen is 0 - } - if (haylen < neelen) return nullptr; - - const char* match; - const char* hayend = phaystack + haylen - neelen + 1; - while ((match = static_cast<char*>( - memcasechr(phaystack, pneedle[0], hayend - phaystack)))) { - if (absl::strings_internal::memcasecmp(match, pneedle, neelen) == 0) - return match; - else - phaystack = match + 1; - } - return nullptr; -} - -TEST(MemUtilTest, AllTests) { +TEST(MemUtil, memcasecmp) { // check memutil functions - char a[1000]; - absl::strings_internal::memcat(a, 0, "hello", sizeof("hello") - 1); - absl::strings_internal::memcat(a, 5, " there", sizeof(" there") - 1); + const char a[] = "hello there"; EXPECT_EQ(absl::strings_internal::memcasecmp(a, "heLLO there", sizeof("hello there") - 1), @@ -66,114 +36,6 @@ TEST(MemUtilTest, AllTests) { sizeof("hello there") - 2), 0); EXPECT_EQ(absl::strings_internal::memcasecmp(a, "whatever", 0), 0); - - char* p = absl::strings_internal::memdup("hello", 5); - free(p); - - p = absl::strings_internal::memrchr("hello there", 'e', - sizeof("hello there") - 1); - EXPECT_TRUE(p && p[-1] == 'r'); - p = absl::strings_internal::memrchr("hello there", 'e', - sizeof("hello there") - 2); - EXPECT_TRUE(p && p[-1] == 'h'); - p = absl::strings_internal::memrchr("hello there", 'u', - sizeof("hello there") - 1); - EXPECT_TRUE(p == nullptr); - - int len = absl::strings_internal::memspn("hello there", - sizeof("hello there") - 1, "hole"); - EXPECT_EQ(len, sizeof("hello") - 1); - len = absl::strings_internal::memspn("hello there", sizeof("hello there") - 1, - "u"); - EXPECT_EQ(len, 0); - len = absl::strings_internal::memspn("hello there", sizeof("hello there") - 1, - ""); - EXPECT_EQ(len, 0); - len = absl::strings_internal::memspn("hello there", sizeof("hello there") - 1, - "trole h"); - EXPECT_EQ(len, sizeof("hello there") - 1); - len = absl::strings_internal::memspn("hello there!", - sizeof("hello there!") - 1, "trole h"); - EXPECT_EQ(len, sizeof("hello there") - 1); - len = absl::strings_internal::memspn("hello there!", - sizeof("hello there!") - 2, "trole h!"); - EXPECT_EQ(len, sizeof("hello there!") - 2); - - len = absl::strings_internal::memcspn("hello there", - sizeof("hello there") - 1, "leho"); - EXPECT_EQ(len, 0); - len = absl::strings_internal::memcspn("hello there", - sizeof("hello there") - 1, "u"); - EXPECT_EQ(len, sizeof("hello there") - 1); - len = absl::strings_internal::memcspn("hello there", - sizeof("hello there") - 1, ""); - EXPECT_EQ(len, sizeof("hello there") - 1); - len = absl::strings_internal::memcspn("hello there", - sizeof("hello there") - 1, " "); - EXPECT_EQ(len, 5); - - p = absl::strings_internal::mempbrk("hello there", sizeof("hello there") - 1, - "leho"); - EXPECT_TRUE(p && p[1] == 'e' && p[2] == 'l'); - p = absl::strings_internal::mempbrk("hello there", sizeof("hello there") - 1, - "nu"); - EXPECT_TRUE(p == nullptr); - p = absl::strings_internal::mempbrk("hello there!", - sizeof("hello there!") - 2, "!"); - EXPECT_TRUE(p == nullptr); - p = absl::strings_internal::mempbrk("hello there", sizeof("hello there") - 1, - " t "); - EXPECT_TRUE(p && p[-1] == 'o' && p[1] == 't'); - - { - const char kHaystack[] = "0123456789"; - EXPECT_EQ(absl::strings_internal::memmem(kHaystack, 0, "", 0), kHaystack); - EXPECT_EQ(absl::strings_internal::memmem(kHaystack, 10, "012", 3), - kHaystack); - EXPECT_EQ(absl::strings_internal::memmem(kHaystack, 10, "0xx", 1), - kHaystack); - EXPECT_EQ(absl::strings_internal::memmem(kHaystack, 10, "789", 3), - kHaystack + 7); - EXPECT_EQ(absl::strings_internal::memmem(kHaystack, 10, "9xx", 1), - kHaystack + 9); - EXPECT_TRUE(absl::strings_internal::memmem(kHaystack, 10, "9xx", 3) == - nullptr); - EXPECT_TRUE(absl::strings_internal::memmem(kHaystack, 10, "xxx", 1) == - nullptr); - } - { - const char kHaystack[] = "aBcDeFgHiJ"; - EXPECT_EQ(absl::strings_internal::memcasemem(kHaystack, 0, "", 0), - kHaystack); - EXPECT_EQ(absl::strings_internal::memcasemem(kHaystack, 10, "Abc", 3), - kHaystack); - EXPECT_EQ(absl::strings_internal::memcasemem(kHaystack, 10, "Axx", 1), - kHaystack); - EXPECT_EQ(absl::strings_internal::memcasemem(kHaystack, 10, "hIj", 3), - kHaystack + 7); - EXPECT_EQ(absl::strings_internal::memcasemem(kHaystack, 10, "jxx", 1), - kHaystack + 9); - EXPECT_TRUE(absl::strings_internal::memcasemem(kHaystack, 10, "jxx", 3) == - nullptr); - EXPECT_TRUE(absl::strings_internal::memcasemem(kHaystack, 10, "xxx", 1) == - nullptr); - } - { - const char kHaystack[] = "0123456789"; - EXPECT_EQ(absl::strings_internal::memmatch(kHaystack, 0, "", 0), kHaystack); - EXPECT_EQ(absl::strings_internal::memmatch(kHaystack, 10, "012", 3), - kHaystack); - EXPECT_EQ(absl::strings_internal::memmatch(kHaystack, 10, "0xx", 1), - kHaystack); - EXPECT_EQ(absl::strings_internal::memmatch(kHaystack, 10, "789", 3), - kHaystack + 7); - EXPECT_EQ(absl::strings_internal::memmatch(kHaystack, 10, "9xx", 1), - kHaystack + 9); - EXPECT_TRUE(absl::strings_internal::memmatch(kHaystack, 10, "9xx", 3) == - nullptr); - EXPECT_TRUE(absl::strings_internal::memmatch(kHaystack, 10, "xxx", 1) == - nullptr); - } } } // namespace diff --git a/absl/strings/internal/stl_type_traits.h b/absl/strings/internal/stl_type_traits.h index 6035ca45..e50468b0 100644 --- a/absl/strings/internal/stl_type_traits.h +++ b/absl/strings/internal/stl_type_traits.h @@ -13,7 +13,7 @@ // limitations under the License. // -// Thie file provides the IsStrictlyBaseOfAndConvertibleToSTLContainer type +// The file provides the IsStrictlyBaseOfAndConvertibleToSTLContainer type // trait metafunction to assist in working with the _GLIBCXX_DEBUG debug // wrappers of STL containers. // diff --git a/absl/strings/internal/str_format/arg.cc b/absl/strings/internal/str_format/arg.cc index 018dd052..eeb21081 100644 --- a/absl/strings/internal/str_format/arg.cc +++ b/absl/strings/internal/str_format/arg.cc @@ -18,15 +18,28 @@ // #include "absl/strings/internal/str_format/arg.h" +#include <algorithm> #include <cassert> -#include <cerrno> +#include <cstddef> +#include <cstdint> #include <cstdlib> +#include <cstring> +#include <cwchar> #include <string> #include <type_traits> -#include "absl/base/port.h" +#include "absl/base/config.h" +#include "absl/base/optimization.h" +#include "absl/container/fixed_array.h" +#include "absl/numeric/int128.h" +#include "absl/strings/internal/str_format/extension.h" #include "absl/strings/internal/str_format/float_conversion.h" #include "absl/strings/numbers.h" +#include "absl/strings/string_view.h" + +#if defined(ABSL_HAVE_STD_STRING_VIEW) +#include <string_view> +#endif namespace absl { ABSL_NAMESPACE_BEGIN @@ -106,7 +119,7 @@ class IntDigits { char *p = storage_ + sizeof(storage_); do { p -= 2; - numbers_internal::PutTwoDigits(static_cast<size_t>(v % 100), p); + numbers_internal::PutTwoDigits(static_cast<uint32_t>(v % 100), p); v /= 100; } while (v); if (p[0] == '0') { @@ -278,24 +291,6 @@ bool ConvertIntImplInnerSlow(const IntDigits &as_digits, return true; } -template <typename T, - typename std::enable_if<(std::is_integral<T>::value && - std::is_signed<T>::value) || - std::is_same<T, int128>::value, - int>::type = 0> -constexpr auto ConvertV(T) { - return FormatConversionCharInternal::d; -} - -template <typename T, - typename std::enable_if<(std::is_integral<T>::value && - std::is_unsigned<T>::value) || - std::is_same<T, uint128>::value, - int>::type = 0> -constexpr auto ConvertV(T) { - return FormatConversionCharInternal::u; -} - template <typename T> bool ConvertFloatArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) { if (conv.conversion_char() == FormatConversionCharInternal::v) { @@ -316,6 +311,83 @@ inline bool ConvertStringArg(string_view v, const FormatConversionSpecImpl conv, conv.has_left_flag()); } +struct ShiftState { + bool saw_high_surrogate = false; + uint8_t bits = 0; +}; + +// Converts `v` from UTF-16 or UTF-32 to UTF-8 and writes to `buf`. `buf` is +// assumed to have enough space for the output. `s` is used to carry state +// between successive calls with a UTF-16 surrogate pair. Returns the number of +// chars written, or `static_cast<size_t>(-1)` on failure. +// +// This is basically std::wcrtomb(), but always outputting UTF-8 instead of +// respecting the current locale. +inline size_t WideToUtf8(wchar_t wc, char *buf, ShiftState &s) { + const auto v = static_cast<uint32_t>(wc); + if (v < 0x80) { + *buf = static_cast<char>(v); + return 1; + } else if (v < 0x800) { + *buf++ = static_cast<char>(0xc0 | (v >> 6)); + *buf = static_cast<char>(0x80 | (v & 0x3f)); + return 2; + } else if (v < 0xd800 || (v - 0xe000) < 0x2000) { + *buf++ = static_cast<char>(0xe0 | (v >> 12)); + *buf++ = static_cast<char>(0x80 | ((v >> 6) & 0x3f)); + *buf = static_cast<char>(0x80 | (v & 0x3f)); + return 3; + } else if ((v - 0x10000) < 0x100000) { + *buf++ = static_cast<char>(0xf0 | (v >> 18)); + *buf++ = static_cast<char>(0x80 | ((v >> 12) & 0x3f)); + *buf++ = static_cast<char>(0x80 | ((v >> 6) & 0x3f)); + *buf = static_cast<char>(0x80 | (v & 0x3f)); + return 4; + } else if (v < 0xdc00) { + s.saw_high_surrogate = true; + s.bits = static_cast<uint8_t>(v & 0x3); + const uint8_t high_bits = ((v >> 6) & 0xf) + 1; + *buf++ = static_cast<char>(0xf0 | (high_bits >> 2)); + *buf = + static_cast<char>(0x80 | static_cast<uint8_t>((high_bits & 0x3) << 4) | + static_cast<uint8_t>((v >> 2) & 0xf)); + return 2; + } else if (v < 0xe000 && s.saw_high_surrogate) { + *buf++ = static_cast<char>(0x80 | static_cast<uint8_t>(s.bits << 4) | + static_cast<uint8_t>((v >> 6) & 0xf)); + *buf = static_cast<char>(0x80 | (v & 0x3f)); + s.saw_high_surrogate = false; + s.bits = 0; + return 2; + } else { + return static_cast<size_t>(-1); + } +} + +inline bool ConvertStringArg(const wchar_t *v, + size_t len, + const FormatConversionSpecImpl conv, + FormatSinkImpl *sink) { + FixedArray<char> mb(len * 4); + ShiftState s; + size_t chars_written = 0; + for (size_t i = 0; i < len; ++i) { + const size_t chars = WideToUtf8(v[i], &mb[chars_written], s); + if (chars == static_cast<size_t>(-1)) { return false; } + chars_written += chars; + } + return ConvertStringArg(string_view(mb.data(), chars_written), conv, sink); +} + +bool ConvertWCharTImpl(wchar_t v, const FormatConversionSpecImpl conv, + FormatSinkImpl *sink) { + char mb[4]; + ShiftState s; + const size_t chars_written = WideToUtf8(v, mb, s); + return chars_written != static_cast<size_t>(-1) && !s.saw_high_surrogate && + ConvertStringArg(string_view(mb, chars_written), conv, sink); +} + } // namespace bool ConvertBoolArg(bool v, FormatSinkImpl *sink) { @@ -332,17 +404,16 @@ bool ConvertIntArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) { using U = typename MakeUnsigned<T>::type; IntDigits as_digits; - if (conv.conversion_char() == FormatConversionCharInternal::v) { - conv.set_conversion_char(ConvertV(T{})); - } - // This odd casting is due to a bug in -Wswitch behavior in gcc49 which causes // it to complain about a switch/case type mismatch, even though both are - // FormatConverionChar. Likely this is because at this point + // FormatConversionChar. Likely this is because at this point // FormatConversionChar is declared, but not defined. switch (static_cast<uint8_t>(conv.conversion_char())) { case static_cast<uint8_t>(FormatConversionCharInternal::c): - return ConvertCharImpl(static_cast<char>(v), conv, sink); + return (std::is_same<T, wchar_t>::value || + (conv.length_mod() == LengthMod::l)) + ? ConvertWCharTImpl(static_cast<wchar_t>(v), conv, sink) + : ConvertCharImpl(static_cast<char>(v), conv, sink); case static_cast<uint8_t>(FormatConversionCharInternal::o): as_digits.PrintAsOct(static_cast<U>(v)); @@ -361,6 +432,7 @@ bool ConvertIntArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl *sink) { case static_cast<uint8_t>(FormatConversionCharInternal::d): case static_cast<uint8_t>(FormatConversionCharInternal::i): + case static_cast<uint8_t>(FormatConversionCharInternal::v): as_digits.PrintAsDec(v); break; @@ -393,6 +465,8 @@ template bool ConvertIntArg<signed char>(signed char v, template bool ConvertIntArg<unsigned char>(unsigned char v, FormatConversionSpecImpl conv, FormatSinkImpl *sink); +template bool ConvertIntArg<wchar_t>(wchar_t v, FormatConversionSpecImpl conv, + FormatSinkImpl *sink); template bool ConvertIntArg<short>(short v, // NOLINT FormatConversionSpecImpl conv, FormatSinkImpl *sink); @@ -424,16 +498,29 @@ StringConvertResult FormatConvertImpl(const std::string &v, return {ConvertStringArg(v, conv, sink)}; } +StringConvertResult FormatConvertImpl(const std::wstring &v, + const FormatConversionSpecImpl conv, + FormatSinkImpl *sink) { + return {ConvertStringArg(v.data(), v.size(), conv, sink)}; +} + StringConvertResult FormatConvertImpl(string_view v, const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return {ConvertStringArg(v, conv, sink)}; } -ArgConvertResult<FormatConversionCharSetUnion( - FormatConversionCharSetInternal::s, FormatConversionCharSetInternal::p)> -FormatConvertImpl(const char *v, const FormatConversionSpecImpl conv, - FormatSinkImpl *sink) { +#if defined(ABSL_HAVE_STD_STRING_VIEW) +StringConvertResult FormatConvertImpl(std::wstring_view v, + const FormatConversionSpecImpl conv, + FormatSinkImpl* sink) { + return {ConvertStringArg(v.data(), v.size(), conv, sink)}; +} +#endif + +StringPtrConvertResult FormatConvertImpl(const char* v, + const FormatConversionSpecImpl conv, + FormatSinkImpl* sink) { if (conv.conversion_char() == FormatConversionCharInternal::p) return {FormatConvertImpl(VoidPtr(v), conv, sink).value}; size_t len; @@ -448,6 +535,30 @@ FormatConvertImpl(const char *v, const FormatConversionSpecImpl conv, return {ConvertStringArg(string_view(v, len), conv, sink)}; } +StringPtrConvertResult FormatConvertImpl(const wchar_t* v, + const FormatConversionSpecImpl conv, + FormatSinkImpl* sink) { + if (conv.conversion_char() == FormatConversionCharInternal::p) { + return {FormatConvertImpl(VoidPtr(v), conv, sink).value}; + } + size_t len; + if (v == nullptr) { + len = 0; + } else if (conv.precision() < 0) { + len = std::wcslen(v); + } else { + // If precision is set, we look for the NUL-terminator on the valid range. + len = static_cast<size_t>(std::find(v, v + conv.precision(), L'\0') - v); + } + return {ConvertStringArg(v, len, conv, sink)}; +} + +StringPtrConvertResult FormatConvertImpl(std::nullptr_t, + const FormatConversionSpecImpl conv, + FormatSinkImpl* sink) { + return FormatConvertImpl(static_cast<const char*>(nullptr), conv, sink); +} + // ==================== Raw pointers ==================== ArgConvertResult<FormatConversionCharSetInternal::p> FormatConvertImpl( VoidPtr v, const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { @@ -482,18 +593,23 @@ CharConvertResult FormatConvertImpl(char v, const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { return {ConvertIntArg(v, conv, sink)}; } -CharConvertResult FormatConvertImpl(signed char v, +CharConvertResult FormatConvertImpl(wchar_t v, const FormatConversionSpecImpl conv, - FormatSinkImpl *sink) { - return {ConvertIntArg(v, conv, sink)}; -} -CharConvertResult FormatConvertImpl(unsigned char v, - const FormatConversionSpecImpl conv, - FormatSinkImpl *sink) { + FormatSinkImpl* sink) { return {ConvertIntArg(v, conv, sink)}; } // ==================== Ints ==================== +IntegralConvertResult FormatConvertImpl(signed char v, + const FormatConversionSpecImpl conv, + FormatSinkImpl *sink) { + return {ConvertIntArg(v, conv, sink)}; +} +IntegralConvertResult FormatConvertImpl(unsigned char v, + const FormatConversionSpecImpl conv, + FormatSinkImpl *sink) { + return {ConvertIntArg(v, conv, sink)}; +} IntegralConvertResult FormatConvertImpl(short v, // NOLINT const FormatConversionSpecImpl conv, FormatSinkImpl *sink) { diff --git a/absl/strings/internal/str_format/arg.h b/absl/strings/internal/str_format/arg.h index e4b16628..309161d5 100644 --- a/absl/strings/internal/str_format/arg.h +++ b/absl/strings/internal/str_format/arg.h @@ -19,8 +19,9 @@ #include <wchar.h> #include <algorithm> +#include <cstddef> +#include <cstdint> #include <cstdio> -#include <iomanip> #include <limits> #include <memory> #include <sstream> @@ -28,13 +29,18 @@ #include <type_traits> #include <utility> -#include "absl/base/port.h" +#include "absl/base/config.h" +#include "absl/base/optimization.h" #include "absl/meta/type_traits.h" #include "absl/numeric/int128.h" -#include "absl/strings/internal/has_absl_stringify.h" +#include "absl/strings/has_absl_stringify.h" #include "absl/strings/internal/str_format/extension.h" #include "absl/strings/string_view.h" +#if defined(ABSL_HAVE_STD_STRING_VIEW) +#include <string_view> +#endif + namespace absl { ABSL_NAMESPACE_BEGIN @@ -97,6 +103,9 @@ extern template bool ConvertIntArg<signed char>(signed char v, extern template bool ConvertIntArg<unsigned char>(unsigned char v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); +extern template bool ConvertIntArg<wchar_t>(wchar_t v, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); extern template bool ConvertIntArg<short>(short v, // NOLINT FormatConversionSpecImpl conv, FormatSinkImpl* sink); @@ -158,6 +167,7 @@ template <typename T> auto FormatConvertImpl(const T& v, FormatConversionSpecImpl, FormatSinkImpl* sink) -> std::enable_if_t<!std::is_enum<T>::value && + !std::is_same<T, absl::Cord>::value && std::is_void<decltype(AbslStringify( std::declval<FormatSink&>(), v))>::value, ArgConvertResult<FormatConversionCharSetInternal::v>> { @@ -202,30 +212,49 @@ constexpr FormatConversionCharSet ExtractCharSet(ArgConvertResult<C>) { return C; } -using StringConvertResult = ArgConvertResult<FormatConversionCharSetUnion( - FormatConversionCharSetInternal::s, FormatConversionCharSetInternal::v)>; ArgConvertResult<FormatConversionCharSetInternal::p> FormatConvertImpl( VoidPtr v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); // Strings. +using StringConvertResult = ArgConvertResult<FormatConversionCharSetUnion( + FormatConversionCharSetInternal::s, + FormatConversionCharSetInternal::v)>; StringConvertResult FormatConvertImpl(const std::string& v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); +StringConvertResult FormatConvertImpl(const std::wstring& v, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); StringConvertResult FormatConvertImpl(string_view v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); -#if defined(ABSL_HAVE_STD_STRING_VIEW) && !defined(ABSL_USES_STD_STRING_VIEW) +#if defined(ABSL_HAVE_STD_STRING_VIEW) +StringConvertResult FormatConvertImpl(std::wstring_view v, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +#if !defined(ABSL_USES_STD_STRING_VIEW) inline StringConvertResult FormatConvertImpl(std::string_view v, FormatConversionSpecImpl conv, FormatSinkImpl* sink) { return FormatConvertImpl(absl::string_view(v.data(), v.size()), conv, sink); } -#endif // ABSL_HAVE_STD_STRING_VIEW && !ABSL_USES_STD_STRING_VIEW - -ArgConvertResult<FormatConversionCharSetUnion( - FormatConversionCharSetInternal::s, FormatConversionCharSetInternal::p)> -FormatConvertImpl(const char* v, const FormatConversionSpecImpl conv, - FormatSinkImpl* sink); +#endif // !ABSL_USES_STD_STRING_VIEW +#endif // ABSL_HAVE_STD_STRING_VIEW + +using StringPtrConvertResult = ArgConvertResult<FormatConversionCharSetUnion( + FormatConversionCharSetInternal::s, + FormatConversionCharSetInternal::p)>; +StringPtrConvertResult FormatConvertImpl(const char* v, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +StringPtrConvertResult FormatConvertImpl(const wchar_t* v, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +// This overload is needed to disambiguate, since `nullptr` could match either +// of the other overloads equally well. +StringPtrConvertResult FormatConvertImpl(std::nullptr_t, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); template <class AbslCord, typename std::enable_if<std::is_same< AbslCord, absl::Cord>::value>::type* = nullptr> @@ -279,14 +308,17 @@ FloatingConvertResult FormatConvertImpl(long double v, // Chars. CharConvertResult FormatConvertImpl(char v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); -CharConvertResult FormatConvertImpl(signed char v, - FormatConversionSpecImpl conv, - FormatSinkImpl* sink); -CharConvertResult FormatConvertImpl(unsigned char v, +CharConvertResult FormatConvertImpl(wchar_t v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); // Ints. +IntegralConvertResult FormatConvertImpl(signed char v, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +IntegralConvertResult FormatConvertImpl(unsigned char v, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); IntegralConvertResult FormatConvertImpl(short v, // NOLINT FormatConversionSpecImpl conv, FormatSinkImpl* sink); @@ -333,7 +365,7 @@ IntegralConvertResult FormatConvertImpl(T v, FormatConversionSpecImpl conv, template <typename T> typename std::enable_if<std::is_enum<T>::value && !HasUserDefinedConvert<T>::value && - !strings_internal::HasAbslStringify<T>::value, + !HasAbslStringify<T>::value, IntegralConvertResult>::type FormatConvertImpl(T v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); @@ -440,28 +472,32 @@ class FormatArgImpl { // Anything with a user-defined Convert will get its own vtable. // For everything else: // - Decay char* and char arrays into `const char*` + // - Decay wchar_t* and wchar_t arrays into `const wchar_t*` // - Decay any other pointer to `const void*` - // - Decay all enums to their underlying type. + // - Decay all enums to the integral promotion of their underlying type. // - Decay function pointers to void*. template <typename T, typename = void> struct DecayType { static constexpr bool kHasUserDefined = str_format_internal::HasUserDefinedConvert<T>::value || - strings_internal::HasAbslStringify<T>::value; + HasAbslStringify<T>::value; using type = typename std::conditional< !kHasUserDefined && std::is_convertible<T, const char*>::value, const char*, - typename std::conditional<!kHasUserDefined && - std::is_convertible<T, VoidPtr>::value, - VoidPtr, const T&>::type>::type; + typename std::conditional< + !kHasUserDefined && std::is_convertible<T, const wchar_t*>::value, + const wchar_t*, + typename std::conditional< + !kHasUserDefined && std::is_convertible<T, VoidPtr>::value, + VoidPtr, + const T&>::type>::type>::type; }; template <typename T> - struct DecayType<T, - typename std::enable_if< - !str_format_internal::HasUserDefinedConvert<T>::value && - !strings_internal::HasAbslStringify<T>::value && - std::is_enum<T>::value>::type> { - using type = typename std::underlying_type<T>::type; + struct DecayType< + T, typename std::enable_if< + !str_format_internal::HasUserDefinedConvert<T>::value && + !HasAbslStringify<T>::value && std::is_enum<T>::value>::type> { + using type = decltype(+typename std::underlying_type<T>::type()); }; public: @@ -585,7 +621,7 @@ class FormatArgImpl { E template bool FormatArgImpl::Dispatch<T>(Data, FormatConversionSpecImpl, \ void*) -#define ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(...) \ +#define ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_NO_WSTRING_VIEW_(...) \ ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(str_format_internal::VoidPtr, \ __VA_ARGS__); \ ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(bool, __VA_ARGS__); \ @@ -611,7 +647,19 @@ class FormatArgImpl { ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(long double, __VA_ARGS__); \ ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(const char*, __VA_ARGS__); \ ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(std::string, __VA_ARGS__); \ - ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(string_view, __VA_ARGS__) + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(string_view, __VA_ARGS__); \ + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(const wchar_t*, __VA_ARGS__); \ + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(std::wstring, __VA_ARGS__) + +#if defined(ABSL_HAVE_STD_STRING_VIEW) +#define ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(...) \ + ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_NO_WSTRING_VIEW_( \ + __VA_ARGS__); \ + ABSL_INTERNAL_FORMAT_DISPATCH_INSTANTIATE_(std::wstring_view, __VA_ARGS__) +#else +#define ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(...) \ + ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_NO_WSTRING_VIEW_(__VA_ARGS__) +#endif ABSL_INTERNAL_FORMAT_DISPATCH_OVERLOADS_EXPAND_(extern); diff --git a/absl/strings/internal/str_format/arg_test.cc b/absl/strings/internal/str_format/arg_test.cc index 1261937c..f663d7c5 100644 --- a/absl/strings/internal/str_format/arg_test.cc +++ b/absl/strings/internal/str_format/arg_test.cc @@ -14,9 +14,10 @@ #include "absl/strings/internal/str_format/arg.h" -#include <ostream> +#include <limits> #include <string> #include "gtest/gtest.h" +#include "absl/base/config.h" #include "absl/strings/str_format.h" namespace absl { @@ -93,6 +94,21 @@ TEST_F(FormatArgImplTest, CharArraysDecayToCharPtr) { FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(kMyArray))); } +extern const wchar_t kMyWCharTArray[]; + +TEST_F(FormatArgImplTest, WCharTArraysDecayToWCharTPtr) { + const wchar_t* a = L""; + EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)), + FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(L""))); + EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)), + FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(L"A"))); + EXPECT_EQ(FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)), + FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(L"ABC"))); + EXPECT_EQ( + FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(a)), + FormatArgImplFriend::GetVTablePtrForTest(FormatArgImpl(kMyWCharTArray))); +} + TEST_F(FormatArgImplTest, OtherPtrDecayToVoidPtr) { auto expected = FormatArgImplFriend::GetVTablePtrForTest( FormatArgImpl(static_cast<void *>(nullptr))); @@ -124,6 +140,22 @@ TEST_F(FormatArgImplTest, WorksWithCharArraysOfUnknownSize) { } const char kMyArray[] = "ABCDE"; +TEST_F(FormatArgImplTest, WorksWithWCharTArraysOfUnknownSize) { + std::string s; + FormatSinkImpl sink(&s); + FormatConversionSpecImpl conv; + FormatConversionSpecImplFriend::SetConversionChar( + FormatConversionCharInternal::s, &conv); + FormatConversionSpecImplFriend::SetFlags(Flags(), &conv); + FormatConversionSpecImplFriend::SetWidth(-1, &conv); + FormatConversionSpecImplFriend::SetPrecision(-1, &conv); + EXPECT_TRUE( + FormatArgImplFriend::Convert(FormatArgImpl(kMyWCharTArray), conv, &sink)); + sink.Flush(); + EXPECT_EQ("ABCDE", s); +} +const wchar_t kMyWCharTArray[] = L"ABCDE"; + } // namespace } // namespace str_format_internal ABSL_NAMESPACE_END diff --git a/absl/strings/internal/str_format/bind.cc b/absl/strings/internal/str_format/bind.cc index 77a42223..87e23b56 100644 --- a/absl/strings/internal/str_format/bind.cc +++ b/absl/strings/internal/str_format/bind.cc @@ -14,10 +14,24 @@ #include "absl/strings/internal/str_format/bind.h" +#include <algorithm> +#include <cassert> #include <cerrno> +#include <cstddef> +#include <cstdio> +#include <ios> #include <limits> +#include <ostream> #include <sstream> #include <string> +#include "absl/base/config.h" +#include "absl/base/optimization.h" +#include "absl/strings/internal/str_format/arg.h" +#include "absl/strings/internal/str_format/constexpr_parser.h" +#include "absl/strings/internal/str_format/extension.h" +#include "absl/strings/internal/str_format/output.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -90,6 +104,8 @@ inline bool ArgContext::Bind(const UnboundConversion* unbound, } else { FormatConversionSpecImplFriend::SetFlags(unbound->flags, bound); } + + FormatConversionSpecImplFriend::SetLengthMod(unbound->length_mod, bound); } else { FormatConversionSpecImplFriend::SetFlags(unbound->flags, bound); FormatConversionSpecImplFriend::SetWidth(-1, bound); @@ -215,7 +231,7 @@ std::string& AppendPack(std::string* out, const UntypedFormatSpecImpl format, return *out; } -std::string FormatPack(const UntypedFormatSpecImpl format, +std::string FormatPack(UntypedFormatSpecImpl format, absl::Span<const FormatArgImpl> args) { std::string out; if (ABSL_PREDICT_FALSE(!FormatUntyped(&out, format, args))) { diff --git a/absl/strings/internal/str_format/bind.h b/absl/strings/internal/str_format/bind.h index b73c5028..120bc355 100644 --- a/absl/strings/internal/str_format/bind.h +++ b/absl/strings/internal/str_format/bind.h @@ -15,15 +15,19 @@ #ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_BIND_H_ #define ABSL_STRINGS_INTERNAL_STR_FORMAT_BIND_H_ -#include <array> +#include <cassert> #include <cstdio> -#include <sstream> +#include <ostream> #include <string> -#include "absl/base/port.h" +#include "absl/base/config.h" +#include "absl/container/inlined_vector.h" #include "absl/strings/internal/str_format/arg.h" #include "absl/strings/internal/str_format/checker.h" +#include "absl/strings/internal/str_format/constexpr_parser.h" +#include "absl/strings/internal/str_format/extension.h" #include "absl/strings/internal/str_format/parser.h" +#include "absl/strings/string_view.h" #include "absl/types/span.h" #include "absl/utility/utility.h" @@ -177,17 +181,7 @@ class Streamable { public: Streamable(const UntypedFormatSpecImpl& format, absl::Span<const FormatArgImpl> args) - : format_(format) { - if (args.size() <= ABSL_ARRAYSIZE(few_args_)) { - for (size_t i = 0; i < args.size(); ++i) { - few_args_[i] = args[i]; - } - args_ = absl::MakeSpan(few_args_, args.size()); - } else { - many_args_.assign(args.begin(), args.end()); - args_ = many_args_; - } - } + : format_(format), args_(args.begin(), args.end()) {} std::ostream& Print(std::ostream& os) const; @@ -197,12 +191,7 @@ class Streamable { private: const UntypedFormatSpecImpl& format_; - absl::Span<const FormatArgImpl> args_; - // if args_.size() is 4 or less: - FormatArgImpl few_args_[4] = {FormatArgImpl(0), FormatArgImpl(0), - FormatArgImpl(0), FormatArgImpl(0)}; - // if args_.size() is more than 4: - std::vector<FormatArgImpl> many_args_; + absl::InlinedVector<FormatArgImpl, 4> args_; }; // for testing @@ -211,14 +200,13 @@ std::string Summarize(UntypedFormatSpecImpl format, bool BindWithPack(const UnboundConversion* props, absl::Span<const FormatArgImpl> pack, BoundConversion* bound); -bool FormatUntyped(FormatRawSinkImpl raw_sink, - UntypedFormatSpecImpl format, +bool FormatUntyped(FormatRawSinkImpl raw_sink, UntypedFormatSpecImpl format, absl::Span<const FormatArgImpl> args); std::string& AppendPack(std::string* out, UntypedFormatSpecImpl format, absl::Span<const FormatArgImpl> args); -std::string FormatPack(const UntypedFormatSpecImpl format, +std::string FormatPack(UntypedFormatSpecImpl format, absl::Span<const FormatArgImpl> args); int FprintF(std::FILE* output, UntypedFormatSpecImpl format, @@ -231,7 +219,7 @@ int SnprintF(char* output, size_t size, UntypedFormatSpecImpl format, template <typename T> class StreamedWrapper { public: - explicit StreamedWrapper(const T& v) : v_(v) { } + explicit StreamedWrapper(const T& v) : v_(v) {} private: template <typename S> diff --git a/absl/strings/internal/str_format/constexpr_parser.h b/absl/strings/internal/str_format/constexpr_parser.h index 3dc1776b..8f593870 100644 --- a/absl/strings/internal/str_format/constexpr_parser.h +++ b/absl/strings/internal/str_format/constexpr_parser.h @@ -17,17 +17,18 @@ #include <cassert> #include <cstdint> +#include <cstdio> #include <limits> +#include "absl/base/config.h" #include "absl/base/const_init.h" +#include "absl/base/optimization.h" #include "absl/strings/internal/str_format/extension.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace str_format_internal { -enum class LengthMod : std::uint8_t { h, hh, l, ll, L, j, z, t, q, none }; - // The analyzed properties of a single specified conversion. struct UnboundConversion { // This is a user defined default constructor on purpose to skip the @@ -306,7 +307,6 @@ constexpr const char* ConsumeConversion(const char* pos, const char* const end, if (ABSL_PREDICT_FALSE(!tag.is_length())) return nullptr; // It is a length modifier. - using str_format_internal::LengthMod; LengthMod length_mod = tag.as_length(); ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); if (c == 'h' && length_mod == LengthMod::h) { @@ -322,7 +322,13 @@ constexpr const char* ConsumeConversion(const char* pos, const char* const end, if (ABSL_PREDICT_FALSE(c == 'v')) return nullptr; if (ABSL_PREDICT_FALSE(!tag.is_conv())) return nullptr; + + // `wchar_t` args are marked non-basic so `Bind()` will copy the length mod. + if (conv->length_mod == LengthMod::l && c == 'c') { + conv->flags = conv->flags | Flags::kNonBasic; + } } +#undef ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR assert(CheckFastPathSetting(*conv)); (void)(&CheckFastPathSetting); diff --git a/absl/strings/internal/str_format/convert_test.cc b/absl/strings/internal/str_format/convert_test.cc index 300612b7..7f222778 100644 --- a/absl/strings/internal/str_format/convert_test.cc +++ b/absl/strings/internal/str_format/convert_test.cc @@ -12,23 +12,43 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include <errno.h> +#include <assert.h> +#include <locale.h> #include <stdarg.h> #include <stdio.h> -#include <cctype> +#include <algorithm> +#include <climits> #include <cmath> +#include <cstdlib> +#include <cstring> +#include <cwctype> #include <limits> +#include <set> +#include <sstream> #include <string> #include <thread> // NOLINT +#include <type_traits> +#include <vector> #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/attributes.h" #include "absl/base/internal/raw_logging.h" +#include "absl/log/log.h" +#include "absl/numeric/int128.h" +#include "absl/strings/ascii.h" +#include "absl/strings/internal/str_format/arg.h" #include "absl/strings/internal/str_format/bind.h" #include "absl/strings/match.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" #include "absl/types/optional.h" +#include "absl/types/span.h" + +#if defined(ABSL_HAVE_STD_STRING_VIEW) +#include <string_view> +#endif namespace absl { ABSL_NAMESPACE_BEGIN @@ -47,36 +67,103 @@ size_t ArraySize(T (&)[N]) { return N; } -std::string LengthModFor(float) { return ""; } -std::string LengthModFor(double) { return ""; } -std::string LengthModFor(long double) { return "L"; } -std::string LengthModFor(char) { return "hh"; } -std::string LengthModFor(signed char) { return "hh"; } -std::string LengthModFor(unsigned char) { return "hh"; } -std::string LengthModFor(short) { return "h"; } // NOLINT -std::string LengthModFor(unsigned short) { return "h"; } // NOLINT -std::string LengthModFor(int) { return ""; } -std::string LengthModFor(unsigned) { return ""; } -std::string LengthModFor(long) { return "l"; } // NOLINT -std::string LengthModFor(unsigned long) { return "l"; } // NOLINT -std::string LengthModFor(long long) { return "ll"; } // NOLINT -std::string LengthModFor(unsigned long long) { return "ll"; } // NOLINT +template <typename T> +struct AlwaysFalse : std::false_type {}; + +template <typename T> +std::string LengthModFor() { + static_assert(AlwaysFalse<T>::value, "Unsupported type"); + return ""; +} +template <> +std::string LengthModFor<char>() { + return "hh"; +} +template <> +std::string LengthModFor<signed char>() { + return "hh"; +} +template <> +std::string LengthModFor<unsigned char>() { + return "hh"; +} +template <> +std::string LengthModFor<short>() { // NOLINT + return "h"; +} +template <> +std::string LengthModFor<unsigned short>() { // NOLINT + return "h"; +} +template <> +std::string LengthModFor<int>() { + return ""; +} +template <> +std::string LengthModFor<unsigned>() { + return ""; +} +template <> +std::string LengthModFor<long>() { // NOLINT + return "l"; +} +template <> +std::string LengthModFor<unsigned long>() { // NOLINT + return "l"; +} +template <> +std::string LengthModFor<long long>() { // NOLINT + return "ll"; +} +template <> +std::string LengthModFor<unsigned long long>() { // NOLINT + return "ll"; +} + +// An integral type of the same rank and signedness as `wchar_t`, that isn't +// `wchar_t`. +using IntegralTypeForWCharT = + std::conditional_t<std::is_signed<wchar_t>::value, + // Some STLs are broken and return `wchar_t` from + // `std::make_[un]signed_t<wchar_t>` when the signedness + // matches. Work around by round-tripping through the + // opposite signedness. + std::make_signed_t<std::make_unsigned_t<wchar_t>>, + std::make_unsigned_t<std::make_signed_t<wchar_t>>>; + +// Given an integral type `T`, returns a type of the same rank and signedness +// that is guaranteed to not be `wchar_t`. +template <typename T> +using MatchingIntegralType = std::conditional_t<std::is_same<T, wchar_t>::value, + IntegralTypeForWCharT, T>; std::string EscCharImpl(int v) { - if (std::isprint(static_cast<unsigned char>(v))) { - return std::string(1, static_cast<char>(v)); - } char buf[64]; - int n = snprintf(buf, sizeof(buf), "\\%#.2x", - static_cast<unsigned>(v & 0xff)); - assert(n > 0 && n < sizeof(buf)); - return std::string(buf, n); + int n = absl::ascii_isprint(static_cast<unsigned char>(v)) + ? snprintf(buf, sizeof(buf), "'%c'", v) + : snprintf(buf, sizeof(buf), "'\\x%.*x'", CHAR_BIT / 4, + static_cast<unsigned>( + static_cast<std::make_unsigned_t<char>>(v))); + assert(n > 0 && static_cast<size_t>(n) < sizeof(buf)); + return std::string(buf, static_cast<size_t>(n)); } std::string Esc(char v) { return EscCharImpl(v); } std::string Esc(signed char v) { return EscCharImpl(v); } std::string Esc(unsigned char v) { return EscCharImpl(v); } +std::string Esc(wchar_t v) { + char buf[64]; + int n = std::iswprint(static_cast<wint_t>(v)) + ? snprintf(buf, sizeof(buf), "L'%lc'", static_cast<wint_t>(v)) + : snprintf(buf, sizeof(buf), "L'\\x%.*llx'", + static_cast<int>(sizeof(wchar_t) * CHAR_BIT / 4), + static_cast<unsigned long long>( + static_cast<std::make_unsigned_t<wchar_t>>(v))); + assert(n > 0 && static_cast<size_t>(n) < sizeof(buf)); + return std::string(buf, static_cast<size_t>(n)); +} + template <typename T> std::string Esc(const T &v) { std::ostringstream oss; @@ -99,7 +186,7 @@ void StrAppendV(std::string *dst, const char *format, va_list ap) { if (result < kSpaceLength) { if (result >= 0) { // Normal case -- everything fit. - dst->append(space, result); + dst->append(space, static_cast<size_t>(result)); return; } if (result < 0) { @@ -110,7 +197,7 @@ void StrAppendV(std::string *dst, const char *format, va_list ap) { // Increase the buffer size to the size requested by vsnprintf, // plus one for the closing \0. - int length = result + 1; + size_t length = static_cast<size_t>(result) + 1; char *buf = new char[length]; // Restore the va_list before we use it again @@ -118,9 +205,9 @@ void StrAppendV(std::string *dst, const char *format, va_list ap) { result = vsnprintf(buf, length, format, backup_ap); va_end(backup_ap); - if (result >= 0 && result < length) { + if (result >= 0 && static_cast<size_t>(result) < length) { // It fit - dst->append(buf, result); + dst->append(buf, static_cast<size_t>(result)); } delete[] buf; } @@ -229,11 +316,15 @@ void TestStringConvert(const T& str) { TEST_F(FormatConvertTest, BasicString) { TestStringConvert("hello"); // As char array. + TestStringConvert(L"hello"); TestStringConvert(static_cast<const char*>("hello")); + TestStringConvert(static_cast<const wchar_t*>(L"hello")); TestStringConvert(std::string("hello")); + TestStringConvert(std::wstring(L"hello")); TestStringConvert(string_view("hello")); #if defined(ABSL_HAVE_STD_STRING_VIEW) TestStringConvert(std::string_view("hello")); + TestStringConvert(std::wstring_view(L"hello")); #endif // ABSL_HAVE_STD_STRING_VIEW } @@ -241,6 +332,10 @@ TEST_F(FormatConvertTest, NullString) { const char* p = nullptr; UntypedFormatSpecImpl format("%s"); EXPECT_EQ("", FormatPack(format, {FormatArgImpl(p)})); + + const wchar_t* wp = nullptr; + UntypedFormatSpecImpl wformat("%ls"); + EXPECT_EQ("", FormatPack(wformat, {FormatArgImpl(wp)})); } TEST_F(FormatConvertTest, StringPrecision) { @@ -250,10 +345,19 @@ TEST_F(FormatConvertTest, StringPrecision) { UntypedFormatSpecImpl format("%.1s"); EXPECT_EQ("a", FormatPack(format, {FormatArgImpl(p)})); + wchar_t wc = L'a'; + const wchar_t* wp = &wc; + UntypedFormatSpecImpl wformat("%.1ls"); + EXPECT_EQ("a", FormatPack(wformat, {FormatArgImpl(wp)})); + // We cap at the NUL-terminator. p = "ABC"; UntypedFormatSpecImpl format2("%.10s"); EXPECT_EQ("ABC", FormatPack(format2, {FormatArgImpl(p)})); + + wp = L"ABC"; + UntypedFormatSpecImpl wformat2("%.10ls"); + EXPECT_EQ("ABC", FormatPack(wformat2, {FormatArgImpl(wp)})); } // Pointer formatting is implementation defined. This checks that the argument @@ -264,7 +368,7 @@ MATCHER_P(MatchesPointerString, ptr, "") { } void* parsed = nullptr; if (sscanf(arg.c_str(), "%p", &parsed) != 1) { - ABSL_RAW_LOG(FATAL, "Could not parse %s", arg.c_str()); + LOG(FATAL) << "Could not parse " << arg; } return ptr == parsed; } @@ -276,16 +380,25 @@ TEST_F(FormatConvertTest, Pointer) { char *mcp = &c; const char *cp = "hi"; const char *cnil = nullptr; + wchar_t wc = L'h'; + wchar_t *mwcp = &wc; + const wchar_t *wcp = L"hi"; + const wchar_t *wcnil = nullptr; const int *inil = nullptr; using VoidF = void (*)(); VoidF fp = [] {}, fnil = nullptr; volatile char vc; volatile char *vcp = &vc; volatile char *vcnil = nullptr; + volatile wchar_t vwc; + volatile wchar_t *vwcp = &vwc; + volatile wchar_t *vwcnil = nullptr; const FormatArgImpl args_array[] = { - FormatArgImpl(xp), FormatArgImpl(cp), FormatArgImpl(inil), - FormatArgImpl(cnil), FormatArgImpl(mcp), FormatArgImpl(fp), - FormatArgImpl(fnil), FormatArgImpl(vcp), FormatArgImpl(vcnil), + FormatArgImpl(xp), FormatArgImpl(cp), FormatArgImpl(wcp), + FormatArgImpl(inil), FormatArgImpl(cnil), FormatArgImpl(wcnil), + FormatArgImpl(mcp), FormatArgImpl(mwcp), FormatArgImpl(fp), + FormatArgImpl(fnil), FormatArgImpl(vcp), FormatArgImpl(vwcp), + FormatArgImpl(vcnil), FormatArgImpl(vwcnil), }; auto args = absl::MakeConstSpan(args_array); @@ -311,30 +424,49 @@ TEST_F(FormatConvertTest, Pointer) { EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%-30.20p"), args), MatchesPointerString(&x)); + // const int* + EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%1$p"), args), + MatchesPointerString(xp)); // const char* EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%2$p"), args), MatchesPointerString(cp)); - // null const int* + // const wchar_t* EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%3$p"), args), + MatchesPointerString(wcp)); + // null const int* + EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%4$p"), args), MatchesPointerString(nullptr)); // null const char* - EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%4$p"), args), + EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%5$p"), args), + MatchesPointerString(nullptr)); + // null const wchar_t* + EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%6$p"), args), MatchesPointerString(nullptr)); // nonconst char* - EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%5$p"), args), + EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%7$p"), args), MatchesPointerString(mcp)); - - // function pointers - EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%6$p"), args), - MatchesPointerString(reinterpret_cast<const void*>(fp))); + // nonconst wchar_t* + EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%8$p"), args), + MatchesPointerString(mwcp)); + // function pointer + EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%9$p"), args), + MatchesPointerString(reinterpret_cast<const void *>(fp))); + // null function pointer + EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%10$p"), args), + MatchesPointerString(nullptr)); + // volatile char* EXPECT_THAT( - FormatPack(UntypedFormatSpecImpl("%8$p"), args), + FormatPack(UntypedFormatSpecImpl("%11$p"), args), MatchesPointerString(reinterpret_cast<volatile const void *>(vcp))); - - // null function pointers - EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%7$p"), args), + // volatile wchar_t* + EXPECT_THAT( + FormatPack(UntypedFormatSpecImpl("%12$p"), args), + MatchesPointerString(reinterpret_cast<volatile const void *>(vwcp))); + // null volatile char* + EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%13$p"), args), MatchesPointerString(nullptr)); - EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%9$p"), args), + // null volatile wchar_t* + EXPECT_THAT(FormatPack(UntypedFormatSpecImpl("%14$p"), args), MatchesPointerString(nullptr)); } @@ -434,12 +566,15 @@ TYPED_TEST_P(TypedFormatConvertTest, AllIntsWithFlags) { // as printf can't do that conversion properly. For those // cases, we do expect agreement with printf with a "%u" // and the unsigned equivalent of 'val'. - UnsignedT uval = val; - old_fmt += LengthModFor(uval); + UnsignedT uval = + static_cast<std::remove_volatile_t<UnsignedT>>(val); + old_fmt += LengthModFor< + MatchingIntegralType<std::remove_cv_t<decltype(uval)>>>(); old_fmt += "u"; old_result = StrPrint(old_fmt.c_str(), uval); } else { - old_fmt += LengthModFor(val); + old_fmt += LengthModFor< + MatchingIntegralType<std::remove_cv_t<decltype(val)>>>(); old_fmt += conv_char; old_result = StrPrint(old_fmt.c_str(), val); } @@ -457,6 +592,47 @@ TYPED_TEST_P(TypedFormatConvertTest, AllIntsWithFlags) { } } +template <typename T> +absl::optional<std::string> StrPrintChar(T c) { + return StrPrint("%c", static_cast<int>(c)); +} +template <> +absl::optional<std::string> StrPrintChar(wchar_t c) { + // musl libc has a bug where ("%lc", 0) writes no characters, and Android + // doesn't support forcing UTF-8 via setlocale(). Hardcode the expected + // answers for ASCII inputs to maximize test coverage on these platforms. + if (static_cast<std::make_unsigned_t<wchar_t>>(c) < 0x80) { + return std::string(1, static_cast<char>(c)); + } + + // Force a UTF-8 locale to match the expected `StrFormat()` behavior. + // It's important to copy the string returned by `old_locale` here, because + // its contents are not guaranteed to be valid after the next `setlocale()` + // call. + std::string old_locale = setlocale(LC_CTYPE, nullptr); + if (!setlocale(LC_CTYPE, "en_US.UTF-8")) { + return absl::nullopt; + } + const std::string output = StrPrint("%lc", static_cast<wint_t>(c)); + setlocale(LC_CTYPE, old_locale.c_str()); + return output; +} + +template <typename T> +typename std::remove_volatile<T>::type GetMaxForConversion() { + return static_cast<typename std::remove_volatile<T>::type>( + std::numeric_limits<int>::max()); +} + +template <> +wchar_t GetMaxForConversion<wchar_t>() { + // Don't return values that aren't legal Unicode. For wchar_t conversions in a + // UTF-8 locale, conversion behavior for such values is unspecified, and we + // don't care about matching it. + return (sizeof(wchar_t) * CHAR_BIT <= 16) ? wchar_t{0xffff} + : static_cast<wchar_t>(0x10ffff); +} + TYPED_TEST_P(TypedFormatConvertTest, Char) { // Pass a bunch of values of type TypeParam to both FormatPack and libc's // vsnprintf("%c", ...) (wrapped in StrPrint) to make sure we get the same @@ -473,28 +649,50 @@ TYPED_TEST_P(TypedFormatConvertTest, Char) { // std::numeric_limits::max(), too, but vsnprintf("%c", ...) can't handle // anything larger than an int. Add in the most extreme values we can without // exceeding that range. + // Special case: Formatting a wchar_t should behave like vsnprintf("%lc"). + // Technically vsnprintf can accept a wint_t in this case, but since we must + // pass a wchar_t to FormatPack, the largest type we can use here is wchar_t. + using ArgType = + std::conditional_t<std::is_same<T, wchar_t>::value, wchar_t, int>; static const T kMin = - static_cast<remove_volatile_t>(std::numeric_limits<int>::min()); - static const T kMax = - static_cast<remove_volatile_t>(std::numeric_limits<int>::max()); - vals.insert(vals.end(), {kMin + 1, kMin, kMax - 1, kMax}); + static_cast<remove_volatile_t>(std::numeric_limits<ArgType>::min()); + static const T kMax = GetMaxForConversion<T>(); + vals.insert(vals.end(), {static_cast<remove_volatile_t>(kMin + 1), kMin, + static_cast<remove_volatile_t>(kMax - 1), kMax}); + static const auto kMaxWCharT = + static_cast<remove_volatile_t>(GetMaxForConversion<wchar_t>()); for (const T c : vals) { + SCOPED_TRACE(Esc(c)); const FormatArgImpl args[] = {FormatArgImpl(c)}; UntypedFormatSpecImpl format("%c"); - EXPECT_EQ(StrPrint("%c", static_cast<int>(c)), - FormatPack(format, absl::MakeSpan(args))); + absl::optional<std::string> result = StrPrintChar(c); + if (result.has_value()) { + EXPECT_EQ(result.value(), FormatPack(format, absl::MakeSpan(args))); + } + + // Also test that if the format specifier is "%lc", the argument is treated + // as if it's a `wchar_t`. + const T wc = + std::max(remove_volatile_t{0}, + std::min(static_cast<remove_volatile_t>(c), kMaxWCharT)); + SCOPED_TRACE(Esc(wc)); + const FormatArgImpl wide_args[] = {FormatArgImpl(wc)}; + UntypedFormatSpecImpl wide_format("%lc"); + result = StrPrintChar(static_cast<wchar_t>(wc)); + if (result.has_value()) { + EXPECT_EQ(result.value(), + FormatPack(wide_format, absl::MakeSpan(wide_args))); + } } } REGISTER_TYPED_TEST_SUITE_P(TypedFormatConvertTest, AllIntsWithFlags, Char); -typedef ::testing::Types< - int, unsigned, volatile int, - short, unsigned short, - long, unsigned long, - long long, unsigned long long, - signed char, unsigned char, char> +typedef ::testing::Types<int, unsigned, volatile int, short, // NOLINT + unsigned short, long, unsigned long, // NOLINT + long long, unsigned long long, // NOLINT + signed char, unsigned char, char, wchar_t> AllIntTypes; INSTANTIATE_TYPED_TEST_SUITE_P(TypedFormatConvertTestWithAllIntTypes, TypedFormatConvertTest, AllIntTypes); @@ -509,6 +707,22 @@ TEST_F(FormatConvertTest, VectorBool) { FormatArgImpl(cv[0]), FormatArgImpl(cv[1])}))); } +TEST_F(FormatConvertTest, UnicodeWideString) { + // StrFormat() should be able to convert wide strings containing Unicode + // characters (to UTF-8). + const FormatArgImpl args[] = {FormatArgImpl(L"\u47e3 \U00011112")}; + // `u8""` forces UTF-8 encoding; MSVC will default to e.g. CP1252 (and warn) + // without it. However, the resulting character type differs between pre-C++20 + // (`char`) and C++20 (`char8_t`). So deduce the right character type for all + // C++ versions, init it with UTF-8, then `memcpy()` to get the result as a + // `char*`. + using ConstChar8T = std::remove_reference_t<decltype(*u8"a")>; + ConstChar8T kOutputUtf8[] = u8"\u47e3 \U00011112"; + char output[sizeof kOutputUtf8]; + std::memcpy(output, kOutputUtf8, sizeof kOutputUtf8); + EXPECT_EQ(output, + FormatPack(UntypedFormatSpecImpl("%ls"), absl::MakeSpan(args))); +} TEST_F(FormatConvertTest, Int128) { absl::int128 positive = static_cast<absl::int128>(0x1234567890abcdef) * 1979; @@ -683,7 +897,11 @@ TEST_F(FormatConvertTest, Float) { } // Remove duplicates to speed up the logic below. - std::sort(floats.begin(), floats.end()); + std::sort(floats.begin(), floats.end(), [](const float a, const float b) { + if (std::isnan(a)) return false; + if (std::isnan(b)) return true; + return a < b; + }); floats.erase(std::unique(floats.begin(), floats.end()), floats.end()); TestWithMultipleFormatsHelper(floats, {}); @@ -757,7 +975,11 @@ TEST_F(FormatConvertTest, Double) { } // Remove duplicates to speed up the logic below. - std::sort(doubles.begin(), doubles.end()); + std::sort(doubles.begin(), doubles.end(), [](const double a, const double b) { + if (std::isnan(a)) return false; + if (std::isnan(b)) return true; + return a < b; + }); doubles.erase(std::unique(doubles.begin(), doubles.end()), doubles.end()); TestWithMultipleFormatsHelper(doubles, skip_verify); @@ -1058,7 +1280,7 @@ TEST_F(FormatConvertTest, LongDoubleRoundA) { // We don't actually store the results. This is just to exercise the rest of the // machinery. struct NullSink { - friend void AbslFormatFlush(NullSink *sink, string_view str) {} + friend void AbslFormatFlush(NullSink *, string_view) {} }; template <typename... T> @@ -1235,15 +1457,22 @@ TEST_F(FormatConvertTest, ExpectedFailures) { // Sanity check to make sure that we are testing what we think we're testing on // e.g. the x86_64+glibc platform. TEST_F(FormatConvertTest, GlibcHasCorrectTraits) { -#if !defined(__GLIBC__) || !defined(__x86_64__) - return; +#if defined(__GLIBC__) && defined(__x86_64__) + constexpr bool kIsSupportedGlibc = true; +#else + constexpr bool kIsSupportedGlibc = false; #endif + + if (!kIsSupportedGlibc) { + GTEST_SKIP() << "Test does not support this platform"; + } + const NativePrintfTraits &native_traits = VerifyNativeImplementation(); // If one of the following tests break then it is either because the above PP // macro guards failed to exclude a new platform (likely) or because something - // has changed in the implemention of glibc sprintf float formatting behavior. - // If the latter, then the code that computes these flags needs to be - // revisited and/or possibly the StrFormat implementation. + // has changed in the implementation of glibc sprintf float formatting + // behavior. If the latter, then the code that computes these flags needs to + // be revisited and/or possibly the StrFormat implementation. EXPECT_TRUE(native_traits.hex_float_has_glibc_rounding); EXPECT_TRUE(native_traits.hex_float_prefers_denormal_repr); EXPECT_TRUE( diff --git a/absl/strings/internal/str_format/extension.h b/absl/strings/internal/str_format/extension.h index 603bd49d..173284c6 100644 --- a/absl/strings/internal/str_format/extension.h +++ b/absl/strings/internal/str_format/extension.h @@ -16,16 +16,14 @@ #ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_EXTENSION_H_ #define ABSL_STRINGS_INTERNAL_STR_FORMAT_EXTENSION_H_ -#include <limits.h> #include <cstddef> #include <cstdint> #include <cstring> #include <ostream> +#include <string> #include "absl/base/config.h" -#include "absl/base/port.h" -#include "absl/meta/type_traits.h" #include "absl/strings/internal/str_format/output.h" #include "absl/strings/string_view.h" @@ -34,6 +32,7 @@ ABSL_NAMESPACE_BEGIN enum class FormatConversionChar : uint8_t; enum class FormatConversionCharSet : uint64_t; +enum class LengthMod : std::uint8_t { h, hh, l, ll, L, j, z, t, q, none }; namespace str_format_internal { @@ -139,7 +138,8 @@ enum class Flags : uint8_t { kAlt = 1 << 3, kZero = 1 << 4, // This is not a real flag. It just exists to turn off kBasic when no other - // flags are set. This is for when width/precision are specified. + // flags are set. This is for when width/precision are specified, or a length + // modifier affects the behavior ("%lc"). kNonBasic = 1 << 5, }; @@ -273,7 +273,7 @@ struct FormatConversionSpecImplFriend; class FormatConversionSpecImpl { public: - // Width and precison are not specified, no flags are set. + // Width and precision are not specified, no flags are set. bool is_basic() const { return flags_ == Flags::kBasic; } bool has_left_flag() const { return FlagsContains(flags_, Flags::kLeft); } bool has_show_pos_flag() const { @@ -285,6 +285,8 @@ class FormatConversionSpecImpl { bool has_alt_flag() const { return FlagsContains(flags_, Flags::kAlt); } bool has_zero_flag() const { return FlagsContains(flags_, Flags::kZero); } + LengthMod length_mod() const { return length_mod_; } + FormatConversionChar conversion_char() const { // Keep this field first in the struct . It generates better code when // accessing it when ConversionSpec is passed by value in registers. @@ -310,6 +312,7 @@ class FormatConversionSpecImpl { friend struct str_format_internal::FormatConversionSpecImplFriend; FormatConversionChar conv_ = FormatConversionCharInternal::kNone; Flags flags_; + LengthMod length_mod_ = LengthMod::none; int width_; int precision_; }; @@ -318,6 +321,9 @@ struct FormatConversionSpecImplFriend final { static void SetFlags(Flags f, FormatConversionSpecImpl* conv) { conv->flags_ = f; } + static void SetLengthMod(LengthMod l, FormatConversionSpecImpl* conv) { + conv->length_mod_ = l; + } static void SetConversionChar(FormatConversionChar c, FormatConversionSpecImpl* conv) { conv->conv_ = c; diff --git a/absl/strings/internal/str_format/float_conversion.cc b/absl/strings/internal/str_format/float_conversion.cc index 8e497852..8edf520d 100644 --- a/absl/strings/internal/str_format/float_conversion.cc +++ b/absl/strings/internal/str_format/float_conversion.cc @@ -711,12 +711,12 @@ bool IncrementNibble(size_t nibble_index, Int* n) { constexpr size_t kShift = sizeof(Int) * 8 - 1; constexpr size_t kNumNibbles = sizeof(Int) * 8 / 4; Int before = *n >> kShift; - // Here we essentially want to take the number 1 and move it into the requsted - // nibble, then add it to *n to effectively increment the nibble. However, - // ASan will complain if we try to shift the 1 beyond the limits of the Int, - // i.e., if the nibble_index is out of range. So therefore we check for this - // and if we are out of range we just add 0 which leaves *n unchanged, which - // seems like the reasonable thing to do in that case. + // Here we essentially want to take the number 1 and move it into the + // requested nibble, then add it to *n to effectively increment the nibble. + // However, ASan will complain if we try to shift the 1 beyond the limits of + // the Int, i.e., if the nibble_index is out of range. So therefore we check + // for this and if we are out of range we just add 0 which leaves *n + // unchanged, which seems like the reasonable thing to do in that case. *n += ((nibble_index >= kNumNibbles) ? 0 : (Int{1} << static_cast<int>(nibble_index * 4))); @@ -937,7 +937,7 @@ void FormatA(const HexFloatTypeParams float_traits, Int mantissa, int exp, // =============== Exponent ================== constexpr size_t kBufSizeForExpDecRepr = - numbers_internal::kFastToBufferSize // requred for FastIntToBuffer + numbers_internal::kFastToBufferSize // required for FastIntToBuffer + 1 // 'p' or 'P' + 1; // '+' or '-' char exp_buffer[kBufSizeForExpDecRepr]; @@ -1015,7 +1015,7 @@ struct Buffer { --end; } - char &back() { + char &back() const { assert(begin < end); return end[-1]; } @@ -1102,7 +1102,7 @@ void PrintExponent(int exp, char e, Buffer *out) { template <typename Float, typename Int> constexpr bool CanFitMantissa() { return -#if defined(__clang__) && !defined(__SSE3__) +#if defined(__clang__) && (__clang_major__ < 9) && !defined(__SSE3__) // Workaround for clang bug: https://bugs.llvm.org/show_bug.cgi?id=38289 // Casting from long double to uint64_t is miscompiled and drops bits. (!std::is_same<Float, long double>::value || diff --git a/absl/strings/internal/str_format/parser.h b/absl/strings/internal/str_format/parser.h index 35b6d49c..b1d6d5fd 100644 --- a/absl/strings/internal/str_format/parser.h +++ b/absl/strings/internal/str_format/parser.h @@ -15,22 +15,23 @@ #ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_PARSER_H_ #define ABSL_STRINGS_INTERNAL_STR_FORMAT_PARSER_H_ -#include <limits.h> #include <stddef.h> #include <stdlib.h> #include <cassert> -#include <cstdint> +#include <cstring> #include <initializer_list> -#include <iosfwd> -#include <iterator> #include <memory> #include <string> +#include <utility> #include <vector> +#include "absl/base/config.h" +#include "absl/base/optimization.h" #include "absl/strings/internal/str_format/checker.h" #include "absl/strings/internal/str_format/constexpr_parser.h" #include "absl/strings/internal/str_format/extension.h" +#include "absl/strings/string_view.h" namespace absl { ABSL_NAMESPACE_BEGIN diff --git a/absl/strings/internal/str_format/parser_test.cc b/absl/strings/internal/str_format/parser_test.cc index 021f6a87..e2225c60 100644 --- a/absl/strings/internal/str_format/parser_test.cc +++ b/absl/strings/internal/str_format/parser_test.cc @@ -15,10 +15,18 @@ #include "absl/strings/internal/str_format/parser.h" #include <string.h> +#include <algorithm> +#include <initializer_list> +#include <string> +#include <utility> #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/base/config.h" #include "absl/base/macros.h" +#include "absl/strings/internal/str_format/constexpr_parser.h" +#include "absl/strings/internal/str_format/extension.h" +#include "absl/strings/string_view.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -303,7 +311,7 @@ TEST_F(ConsumeUnboundConversionTest, BasicFlag) { } // Flag is off - for (const char* fmt : {"3d", ".llx", "-G", "1$#X"}) { + for (const char* fmt : {"3d", ".llx", "-G", "1$#X", "lc"}) { SCOPED_TRACE(fmt); EXPECT_TRUE(Run(fmt)); EXPECT_NE(o.flags, Flags::kBasic); diff --git a/absl/strings/internal/str_split_internal.h b/absl/strings/internal/str_split_internal.h index 35edf3aa..081ad85a 100644 --- a/absl/strings/internal/str_split_internal.h +++ b/absl/strings/internal/str_split_internal.h @@ -235,6 +235,24 @@ struct SplitterIsConvertibleTo HasMappedType<C>::value> { }; +template <typename StringType, typename Container, typename = void> +struct ShouldUseLifetimeBound : std::false_type {}; + +template <typename StringType, typename Container> +struct ShouldUseLifetimeBound< + StringType, Container, + std::enable_if_t< + std::is_same<StringType, std::string>::value && + std::is_same<typename Container::value_type, absl::string_view>::value>> + : std::true_type {}; + +template <typename StringType, typename First, typename Second> +using ShouldUseLifetimeBoundForPair = std::integral_constant< + bool, std::is_same<StringType, std::string>::value && + (std::is_same<First, absl::string_view>::value || + std::is_same<Second, absl::string_view>::value)>; + + // This class implements the range that is returned by absl::StrSplit(). This // class has templated conversion operators that allow it to be implicitly // converted to a variety of types that the caller may have specified on the @@ -281,10 +299,24 @@ class Splitter { // An implicit conversion operator that is restricted to only those containers // that the splitter is convertible to. - template <typename Container, - typename = typename std::enable_if< - SplitterIsConvertibleTo<Container>::value>::type> - operator Container() const { // NOLINT(runtime/explicit) + template < + typename Container, + std::enable_if_t<ShouldUseLifetimeBound<StringType, Container>::value && + SplitterIsConvertibleTo<Container>::value, + std::nullptr_t> = nullptr> + // NOLINTNEXTLINE(google-explicit-constructor) + operator Container() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return ConvertToContainer<Container, typename Container::value_type, + HasMappedType<Container>::value>()(*this); + } + + template < + typename Container, + std::enable_if_t<!ShouldUseLifetimeBound<StringType, Container>::value && + SplitterIsConvertibleTo<Container>::value, + std::nullptr_t> = nullptr> + // NOLINTNEXTLINE(google-explicit-constructor) + operator Container() const { return ConvertToContainer<Container, typename Container::value_type, HasMappedType<Container>::value>()(*this); } @@ -293,8 +325,27 @@ class Splitter { // strings returned by the begin() iterator. Either/both of .first and .second // will be constructed with empty strings if the iterator doesn't have a // corresponding value. + template <typename First, typename Second, + std::enable_if_t< + ShouldUseLifetimeBoundForPair<StringType, First, Second>::value, + std::nullptr_t> = nullptr> + // NOLINTNEXTLINE(google-explicit-constructor) + operator std::pair<First, Second>() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return ConvertToPair<First, Second>(); + } + + template <typename First, typename Second, + std::enable_if_t<!ShouldUseLifetimeBoundForPair<StringType, First, + Second>::value, + std::nullptr_t> = nullptr> + // NOLINTNEXTLINE(google-explicit-constructor) + operator std::pair<First, Second>() const { + return ConvertToPair<First, Second>(); + } + + private: template <typename First, typename Second> - operator std::pair<First, Second>() const { // NOLINT(runtime/explicit) + std::pair<First, Second> ConvertToPair() const { absl::string_view first, second; auto it = begin(); if (it != end()) { @@ -306,7 +357,6 @@ class Splitter { return {First(first), Second(second)}; } - private: // ConvertToContainer is a functor converting a Splitter to the requested // Container of ValueType. It is specialized below to optimize splitting to // certain combinations of Container and ValueType. diff --git a/absl/strings/match.cc b/absl/strings/match.cc index 2d672509..72ae6a43 100644 --- a/absl/strings/match.cc +++ b/absl/strings/match.cc @@ -14,7 +14,16 @@ #include "absl/strings/match.h" +#include <algorithm> +#include <cstdint> + +#include "absl/base/config.h" +#include "absl/base/internal/endian.h" +#include "absl/base/optimization.h" +#include "absl/numeric/bits.h" +#include "absl/strings/ascii.h" #include "absl/strings/internal/memutil.h" +#include "absl/strings/string_view.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -27,6 +36,27 @@ bool EqualsIgnoreCase(absl::string_view piece1, // memcasecmp uses absl::ascii_tolower(). } +bool StrContainsIgnoreCase(absl::string_view haystack, + absl::string_view needle) noexcept { + while (haystack.size() >= needle.size()) { + if (StartsWithIgnoreCase(haystack, needle)) return true; + haystack.remove_prefix(1); + } + return false; +} + +bool StrContainsIgnoreCase(absl::string_view haystack, + char needle) noexcept { + char upper_needle = absl::ascii_toupper(static_cast<unsigned char>(needle)); + char lower_needle = absl::ascii_tolower(static_cast<unsigned char>(needle)); + if (upper_needle == lower_needle) { + return StrContains(haystack, needle); + } else { + const char both_cstr[3] = {lower_needle, upper_needle, '\0'}; + return haystack.find_first_of(both_cstr) != absl::string_view::npos; + } +} + bool StartsWithIgnoreCase(absl::string_view text, absl::string_view prefix) noexcept { return (text.size() >= prefix.size()) && @@ -39,5 +69,65 @@ bool EndsWithIgnoreCase(absl::string_view text, EqualsIgnoreCase(text.substr(text.size() - suffix.size()), suffix); } +absl::string_view FindLongestCommonPrefix(absl::string_view a, + absl::string_view b) { + const absl::string_view::size_type limit = std::min(a.size(), b.size()); + const char* const pa = a.data(); + const char* const pb = b.data(); + absl::string_view::size_type count = (unsigned) 0; + + if (ABSL_PREDICT_FALSE(limit < 8)) { + while (ABSL_PREDICT_TRUE(count + 2 <= limit)) { + uint16_t xor_bytes = absl::little_endian::Load16(pa + count) ^ + absl::little_endian::Load16(pb + count); + if (ABSL_PREDICT_FALSE(xor_bytes != 0)) { + if (ABSL_PREDICT_TRUE((xor_bytes & 0xff) == 0)) ++count; + return absl::string_view(pa, count); + } + count += 2; + } + if (ABSL_PREDICT_TRUE(count != limit)) { + if (ABSL_PREDICT_TRUE(pa[count] == pb[count])) ++count; + } + return absl::string_view(pa, count); + } + + do { + uint64_t xor_bytes = absl::little_endian::Load64(pa + count) ^ + absl::little_endian::Load64(pb + count); + if (ABSL_PREDICT_FALSE(xor_bytes != 0)) { + count += static_cast<uint64_t>(absl::countr_zero(xor_bytes) >> 3); + return absl::string_view(pa, count); + } + count += 8; + } while (ABSL_PREDICT_TRUE(count + 8 < limit)); + + count = limit - 8; + uint64_t xor_bytes = absl::little_endian::Load64(pa + count) ^ + absl::little_endian::Load64(pb + count); + if (ABSL_PREDICT_TRUE(xor_bytes != 0)) { + count += static_cast<uint64_t>(absl::countr_zero(xor_bytes) >> 3); + return absl::string_view(pa, count); + } + return absl::string_view(pa, limit); +} + +absl::string_view FindLongestCommonSuffix(absl::string_view a, + absl::string_view b) { + const absl::string_view::size_type limit = std::min(a.size(), b.size()); + if (limit == 0) return absl::string_view(); + + const char* pa = a.data() + a.size() - 1; + const char* pb = b.data() + b.size() - 1; + absl::string_view::size_type count = (unsigned) 0; + while (count < limit && *pa == *pb) { + --pa; + --pb; + ++count; + } + + return absl::string_view(++pa, count); +} + ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/strings/match.h b/absl/strings/match.h index 038cbb3f..1eeafbbf 100644 --- a/absl/strings/match.h +++ b/absl/strings/match.h @@ -72,6 +72,15 @@ inline bool EndsWith(absl::string_view text, memcmp(text.data() + (text.size() - suffix.size()), suffix.data(), suffix.size()) == 0); } +// StrContainsIgnoreCase() +// +// Returns whether a given ASCII string `haystack` contains the ASCII substring +// `needle`, ignoring case in the comparison. +bool StrContainsIgnoreCase(absl::string_view haystack, + absl::string_view needle) noexcept; + +bool StrContainsIgnoreCase(absl::string_view haystack, + char needle) noexcept; // EqualsIgnoreCase() // @@ -94,6 +103,16 @@ bool StartsWithIgnoreCase(absl::string_view text, bool EndsWithIgnoreCase(absl::string_view text, absl::string_view suffix) noexcept; +// Yields the longest prefix in common between both input strings. +// Pointer-wise, the returned result is a subset of input "a". +absl::string_view FindLongestCommonPrefix(absl::string_view a, + absl::string_view b); + +// Yields the longest suffix in common between both input strings. +// Pointer-wise, the returned result is a subset of input "a". +absl::string_view FindLongestCommonSuffix(absl::string_view a, + absl::string_view b); + ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/strings/match_test.cc b/absl/strings/match_test.cc index 5841bc1b..6218ce4e 100644 --- a/absl/strings/match_test.cc +++ b/absl/strings/match_test.cc @@ -14,7 +14,10 @@ #include "absl/strings/match.h" +#include <string> + #include "gtest/gtest.h" +#include "absl/strings/string_view.h" namespace { @@ -124,4 +127,165 @@ TEST(MatchTest, EndsWithIgnoreCase) { EXPECT_FALSE(absl::EndsWithIgnoreCase("", "fo")); } +TEST(MatchTest, ContainsIgnoreCase) { + EXPECT_TRUE(absl::StrContainsIgnoreCase("foo", "foo")); + EXPECT_TRUE(absl::StrContainsIgnoreCase("FOO", "Foo")); + EXPECT_TRUE(absl::StrContainsIgnoreCase("--FOO", "Foo")); + EXPECT_TRUE(absl::StrContainsIgnoreCase("FOO--", "Foo")); + EXPECT_FALSE(absl::StrContainsIgnoreCase("BAR", "Foo")); + EXPECT_FALSE(absl::StrContainsIgnoreCase("BAR", "Foo")); + EXPECT_TRUE(absl::StrContainsIgnoreCase("123456", "123456")); + EXPECT_TRUE(absl::StrContainsIgnoreCase("123456", "234")); + EXPECT_TRUE(absl::StrContainsIgnoreCase("", "")); + EXPECT_TRUE(absl::StrContainsIgnoreCase("abc", "")); + EXPECT_FALSE(absl::StrContainsIgnoreCase("", "a")); +} + +TEST(MatchTest, ContainsCharIgnoreCase) { + absl::string_view a("AaBCdefg!"); + absl::string_view b("AaBCd!"); + EXPECT_TRUE(absl::StrContainsIgnoreCase(a, 'a')); + EXPECT_TRUE(absl::StrContainsIgnoreCase(a, 'A')); + EXPECT_TRUE(absl::StrContainsIgnoreCase(a, 'b')); + EXPECT_TRUE(absl::StrContainsIgnoreCase(a, 'B')); + EXPECT_TRUE(absl::StrContainsIgnoreCase(a, 'e')); + EXPECT_TRUE(absl::StrContainsIgnoreCase(a, 'E')); + EXPECT_FALSE(absl::StrContainsIgnoreCase(a, 'h')); + EXPECT_FALSE(absl::StrContainsIgnoreCase(a, 'H')); + EXPECT_TRUE(absl::StrContainsIgnoreCase(a, '!')); + EXPECT_FALSE(absl::StrContainsIgnoreCase(a, '?')); + + EXPECT_TRUE(absl::StrContainsIgnoreCase(b, 'a')); + EXPECT_TRUE(absl::StrContainsIgnoreCase(b, 'A')); + EXPECT_TRUE(absl::StrContainsIgnoreCase(b, 'b')); + EXPECT_TRUE(absl::StrContainsIgnoreCase(b, 'B')); + EXPECT_FALSE(absl::StrContainsIgnoreCase(b, 'e')); + EXPECT_FALSE(absl::StrContainsIgnoreCase(b, 'E')); + EXPECT_FALSE(absl::StrContainsIgnoreCase(b, 'h')); + EXPECT_FALSE(absl::StrContainsIgnoreCase(b, 'H')); + EXPECT_TRUE(absl::StrContainsIgnoreCase(b, '!')); + EXPECT_FALSE(absl::StrContainsIgnoreCase(b, '?')); + + EXPECT_FALSE(absl::StrContainsIgnoreCase("", 'a')); + EXPECT_FALSE(absl::StrContainsIgnoreCase("", 'A')); + EXPECT_FALSE(absl::StrContainsIgnoreCase("", '0')); +} + +TEST(MatchTest, FindLongestCommonPrefix) { + EXPECT_EQ(absl::FindLongestCommonPrefix("", ""), ""); + EXPECT_EQ(absl::FindLongestCommonPrefix("", "abc"), ""); + EXPECT_EQ(absl::FindLongestCommonPrefix("abc", ""), ""); + EXPECT_EQ(absl::FindLongestCommonPrefix("ab", "abc"), "ab"); + EXPECT_EQ(absl::FindLongestCommonPrefix("abc", "ab"), "ab"); + EXPECT_EQ(absl::FindLongestCommonPrefix("abc", "abd"), "ab"); + EXPECT_EQ(absl::FindLongestCommonPrefix("abc", "abcd"), "abc"); + EXPECT_EQ(absl::FindLongestCommonPrefix("abcd", "abcd"), "abcd"); + EXPECT_EQ(absl::FindLongestCommonPrefix("abcd", "efgh"), ""); + + // "abcde" v. "abc" but in the middle of other data + EXPECT_EQ(absl::FindLongestCommonPrefix( + absl::string_view("1234 abcdef").substr(5, 5), + absl::string_view("5678 abcdef").substr(5, 3)), + "abc"); +} + +// Since the little-endian implementation involves a bit of if-else and various +// return paths, the following tests aims to provide full test coverage of the +// implementation. +TEST(MatchTest, FindLongestCommonPrefixLoad16Mismatch) { + const std::string x1 = "abcdefgh"; + const std::string x2 = "abcde_"; + EXPECT_EQ(absl::FindLongestCommonPrefix(x1, x2), "abcde"); + EXPECT_EQ(absl::FindLongestCommonPrefix(x2, x1), "abcde"); +} + +TEST(MatchTest, FindLongestCommonPrefixLoad16MatchesNoLast) { + const std::string x1 = "abcdef"; + const std::string x2 = "abcdef"; + EXPECT_EQ(absl::FindLongestCommonPrefix(x1, x2), "abcdef"); + EXPECT_EQ(absl::FindLongestCommonPrefix(x2, x1), "abcdef"); +} + +TEST(MatchTest, FindLongestCommonPrefixLoad16MatchesLastCharMismatches) { + const std::string x1 = "abcdefg"; + const std::string x2 = "abcdef_h"; + EXPECT_EQ(absl::FindLongestCommonPrefix(x1, x2), "abcdef"); + EXPECT_EQ(absl::FindLongestCommonPrefix(x2, x1), "abcdef"); +} + +TEST(MatchTest, FindLongestCommonPrefixLoad16MatchesLastMatches) { + const std::string x1 = "abcde"; + const std::string x2 = "abcdefgh"; + EXPECT_EQ(absl::FindLongestCommonPrefix(x1, x2), "abcde"); + EXPECT_EQ(absl::FindLongestCommonPrefix(x2, x1), "abcde"); +} + +TEST(MatchTest, FindLongestCommonPrefixSize8Load64Mismatches) { + const std::string x1 = "abcdefghijk"; + const std::string x2 = "abcde_g_"; + EXPECT_EQ(absl::FindLongestCommonPrefix(x1, x2), "abcde"); + EXPECT_EQ(absl::FindLongestCommonPrefix(x2, x1), "abcde"); +} + +TEST(MatchTest, FindLongestCommonPrefixSize8Load64Matches) { + const std::string x1 = "abcdefgh"; + const std::string x2 = "abcdefgh"; + EXPECT_EQ(absl::FindLongestCommonPrefix(x1, x2), "abcdefgh"); + EXPECT_EQ(absl::FindLongestCommonPrefix(x2, x1), "abcdefgh"); +} + +TEST(MatchTest, FindLongestCommonPrefixSize15Load64Mismatches) { + const std::string x1 = "012345670123456"; + const std::string x2 = "0123456701_34_6"; + EXPECT_EQ(absl::FindLongestCommonPrefix(x1, x2), "0123456701"); + EXPECT_EQ(absl::FindLongestCommonPrefix(x2, x1), "0123456701"); +} + +TEST(MatchTest, FindLongestCommonPrefixSize15Load64Matches) { + const std::string x1 = "012345670123456"; + const std::string x2 = "0123456701234567"; + EXPECT_EQ(absl::FindLongestCommonPrefix(x1, x2), "012345670123456"); + EXPECT_EQ(absl::FindLongestCommonPrefix(x2, x1), "012345670123456"); +} + +TEST(MatchTest, FindLongestCommonPrefixSizeFirstByteOfLast8BytesMismatch) { + const std::string x1 = "012345670123456701234567"; + const std::string x2 = "0123456701234567_1234567"; + EXPECT_EQ(absl::FindLongestCommonPrefix(x1, x2), "0123456701234567"); + EXPECT_EQ(absl::FindLongestCommonPrefix(x2, x1), "0123456701234567"); +} + +TEST(MatchTest, FindLongestCommonPrefixLargeLastCharMismatches) { + const std::string x1(300, 'x'); + std::string x2 = x1; + x2.back() = '#'; + EXPECT_EQ(absl::FindLongestCommonPrefix(x1, x2), std::string(299, 'x')); + EXPECT_EQ(absl::FindLongestCommonPrefix(x2, x1), std::string(299, 'x')); +} + +TEST(MatchTest, FindLongestCommonPrefixLargeFullMatch) { + const std::string x1(300, 'x'); + const std::string x2 = x1; + EXPECT_EQ(absl::FindLongestCommonPrefix(x1, x2), std::string(300, 'x')); + EXPECT_EQ(absl::FindLongestCommonPrefix(x2, x1), std::string(300, 'x')); +} + +TEST(MatchTest, FindLongestCommonSuffix) { + EXPECT_EQ(absl::FindLongestCommonSuffix("", ""), ""); + EXPECT_EQ(absl::FindLongestCommonSuffix("", "abc"), ""); + EXPECT_EQ(absl::FindLongestCommonSuffix("abc", ""), ""); + EXPECT_EQ(absl::FindLongestCommonSuffix("bc", "abc"), "bc"); + EXPECT_EQ(absl::FindLongestCommonSuffix("abc", "bc"), "bc"); + EXPECT_EQ(absl::FindLongestCommonSuffix("abc", "dbc"), "bc"); + EXPECT_EQ(absl::FindLongestCommonSuffix("bcd", "abcd"), "bcd"); + EXPECT_EQ(absl::FindLongestCommonSuffix("abcd", "abcd"), "abcd"); + EXPECT_EQ(absl::FindLongestCommonSuffix("abcd", "efgh"), ""); + + // "abcde" v. "cde" but in the middle of other data + EXPECT_EQ(absl::FindLongestCommonSuffix( + absl::string_view("1234 abcdef").substr(5, 5), + absl::string_view("5678 abcdef").substr(7, 3)), + "cde"); +} + } // namespace diff --git a/absl/strings/numbers.cc b/absl/strings/numbers.cc index 2987158e..882c3a8b 100644 --- a/absl/strings/numbers.cc +++ b/absl/strings/numbers.cc @@ -20,30 +20,36 @@ #include <algorithm> #include <cassert> #include <cfloat> // for DBL_DIG and FLT_DIG +#include <climits> #include <cmath> // for HUGE_VAL +#include <cstddef> #include <cstdint> #include <cstdio> #include <cstdlib> #include <cstring> #include <iterator> #include <limits> -#include <memory> +#include <system_error> // NOLINT(build/c++11) +#include <type_traits> #include <utility> #include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/internal/endian.h" #include "absl/base/internal/raw_logging.h" +#include "absl/base/nullability.h" +#include "absl/base/optimization.h" #include "absl/numeric/bits.h" +#include "absl/numeric/int128.h" #include "absl/strings/ascii.h" #include "absl/strings/charconv.h" -#include "absl/strings/escaping.h" -#include "absl/strings/internal/memutil.h" #include "absl/strings/match.h" -#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" namespace absl { ABSL_NAMESPACE_BEGIN -bool SimpleAtof(absl::string_view str, float* out) { +bool SimpleAtof(absl::string_view str, absl::Nonnull<float*> out) { *out = 0.0; str = StripAsciiWhitespace(str); // std::from_chars doesn't accept an initial +, but SimpleAtof does, so if one @@ -74,7 +80,7 @@ bool SimpleAtof(absl::string_view str, float* out) { return true; } -bool SimpleAtod(absl::string_view str, double* out) { +bool SimpleAtod(absl::string_view str, absl::Nonnull<double*> out) { *out = 0.0; str = StripAsciiWhitespace(str); // std::from_chars doesn't accept an initial +, but SimpleAtod does, so if one @@ -105,7 +111,7 @@ bool SimpleAtod(absl::string_view str, double* out) { return true; } -bool SimpleAtob(absl::string_view str, bool* out) { +bool SimpleAtob(absl::string_view str, absl::Nonnull<bool*> out) { ABSL_RAW_CHECK(out != nullptr, "Output pointer must not be nullptr."); if (EqualsIgnoreCase(str, "true") || EqualsIgnoreCase(str, "t") || EqualsIgnoreCase(str, "yes") || EqualsIgnoreCase(str, "y") || @@ -136,144 +142,424 @@ bool SimpleAtob(absl::string_view str, bool* out) { namespace { -// Used to optimize printing a decimal number's final digit. -const char one_ASCII_final_digits[10][2] { - {'0', 0}, {'1', 0}, {'2', 0}, {'3', 0}, {'4', 0}, - {'5', 0}, {'6', 0}, {'7', 0}, {'8', 0}, {'9', 0}, -}; +// Various routines to encode integers to strings. -} // namespace +// We split data encodings into a group of 2 digits, 4 digits, 8 digits as +// it's easier to combine powers of two into scalar arithmetic. + +// Previous implementation used a lookup table of 200 bytes for every 2 bytes +// and it was memory bound, any L1 cache miss would result in a much slower +// result. When benchmarking with a cache eviction rate of several percent, +// this implementation proved to be better. + +// These constants represent '00', '0000' and '00000000' as ascii strings in +// integers. We can add these numbers if we encode to bytes from 0 to 9. as +// 'i' = '0' + i for 0 <= i <= 9. +constexpr uint32_t kTwoZeroBytes = 0x0101 * '0'; +constexpr uint64_t kFourZeroBytes = 0x01010101 * '0'; +constexpr uint64_t kEightZeroBytes = 0x0101010101010101ull * '0'; + +template <typename T> +constexpr T Pow(T base, uint32_t n) { + // Exponentiation by squaring + return static_cast<T>((n > 1 ? Pow(base * base, n >> 1) : static_cast<T>(1)) * + ((n & 1) ? base : static_cast<T>(1))); +} + +// Given n, calculates C where the following holds for all 0 <= x < Pow(100, n): +// x / Pow(10, n) == x * C / Pow(2, n * 10) +// In other words, it allows us to divide by a power of 10 via a single +// multiplication and bit shifts, assuming the input will be smaller than the +// square of that power of 10. +template <typename T> +constexpr T ComputePowerOf100DivisionCoefficient(uint32_t n) { + if (n > 4) { + // This doesn't work for large powers of 100, due to overflow + abort(); + } + T denom = 16 - 1; + T num = (denom + 1) - 10; + T gcd = 3; // Greatest common divisor of numerator and denominator + denom = Pow(denom / gcd, n); + num = Pow(num / gcd, 9 * n); + T quotient = num / denom; + if (num % denom >= denom / 2) { + // Round up, since the remainder is more than half the denominator + ++quotient; + } + return quotient; +} + +// * kDivisionBy10Mul / kDivisionBy10Div is a division by 10 for values from 0 +// to 99. It's also a division of a structure [k takes 2 bytes][m takes 2 +// bytes], then * kDivisionBy10Mul / kDivisionBy10Div will be [k / 10][m / 10]. +// It allows parallel division. +constexpr uint64_t kDivisionBy10Mul = + ComputePowerOf100DivisionCoefficient<uint64_t>(1); +static_assert(kDivisionBy10Mul == 103, + "division coefficient for 10 is incorrect"); +constexpr uint64_t kDivisionBy10Div = 1 << 10; + +// * kDivisionBy100Mul / kDivisionBy100Div is a division by 100 for values from +// 0 to 9999. +constexpr uint64_t kDivisionBy100Mul = + ComputePowerOf100DivisionCoefficient<uint64_t>(2); +static_assert(kDivisionBy100Mul == 10486, + "division coefficient for 100 is incorrect"); +constexpr uint64_t kDivisionBy100Div = 1 << 20; + +static_assert(ComputePowerOf100DivisionCoefficient<uint64_t>(3) == 1073742, + "division coefficient for 1000 is incorrect"); + +// Same as `PrepareEightDigits`, but produces 2 digits for integers < 100. +inline uint32_t PrepareTwoDigitsImpl(uint32_t i, bool reversed) { + assert(i < 100); + uint32_t div10 = (i * kDivisionBy10Mul) / kDivisionBy10Div; + uint32_t mod10 = i - 10u * div10; + return (div10 << (reversed ? 8 : 0)) + (mod10 << (reversed ? 0 : 8)); +} +inline uint32_t PrepareTwoDigits(uint32_t i) { + return PrepareTwoDigitsImpl(i, false); +} + +// Same as `PrepareEightDigits`, but produces 4 digits for integers < 10000. +inline uint32_t PrepareFourDigitsImpl(uint32_t n, bool reversed) { + // We split lower 2 digits and upper 2 digits of n into 2 byte consecutive + // blocks. 123 -> [\0\1][\0\23]. We divide by 10 both blocks + // (it's 1 division + zeroing upper bits), and compute modulo 10 as well "in + // parallel". Then we combine both results to have both ASCII digits, + // strip trailing zeros, add ASCII '0000' and return. + uint32_t div100 = (n * kDivisionBy100Mul) / kDivisionBy100Div; + uint32_t mod100 = n - 100ull * div100; + uint32_t hundreds = + (mod100 << (reversed ? 0 : 16)) + (div100 << (reversed ? 16 : 0)); + uint32_t tens = (hundreds * kDivisionBy10Mul) / kDivisionBy10Div; + tens &= (0xFull << 16) | 0xFull; + tens = (tens << (reversed ? 8 : 0)) + + static_cast<uint32_t>((hundreds - 10ull * tens) << (reversed ? 0 : 8)); + return tens; +} +inline uint32_t PrepareFourDigits(uint32_t n) { + return PrepareFourDigitsImpl(n, false); +} +inline uint32_t PrepareFourDigitsReversed(uint32_t n) { + return PrepareFourDigitsImpl(n, true); +} + +// Helper function to produce an ASCII representation of `i`. +// +// Function returns an 8-byte integer which when summed with `kEightZeroBytes`, +// can be treated as a printable buffer with ascii representation of `i`, +// possibly with leading zeros. +// +// Example: +// +// uint64_t buffer = PrepareEightDigits(102030) + kEightZeroBytes; +// char* ascii = reinterpret_cast<char*>(&buffer); +// // Note two leading zeros: +// EXPECT_EQ(absl::string_view(ascii, 8), "00102030"); +// +// If `Reversed` is set to true, the result becomes reversed to "03020100". +// +// Pre-condition: `i` must be less than 100000000. +inline uint64_t PrepareEightDigitsImpl(uint32_t i, bool reversed) { + ABSL_ASSUME(i < 10000'0000); + // Prepare 2 blocks of 4 digits "in parallel". + uint32_t hi = i / 10000; + uint32_t lo = i % 10000; + uint64_t merged = (uint64_t{hi} << (reversed ? 32 : 0)) | + (uint64_t{lo} << (reversed ? 0 : 32)); + uint64_t div100 = ((merged * kDivisionBy100Mul) / kDivisionBy100Div) & + ((0x7Full << 32) | 0x7Full); + uint64_t mod100 = merged - 100ull * div100; + uint64_t hundreds = + (mod100 << (reversed ? 0 : 16)) + (div100 << (reversed ? 16 : 0)); + uint64_t tens = (hundreds * kDivisionBy10Mul) / kDivisionBy10Div; + tens &= (0xFull << 48) | (0xFull << 32) | (0xFull << 16) | 0xFull; + tens = (tens << (reversed ? 8 : 0)) + + ((hundreds - 10ull * tens) << (reversed ? 0 : 8)); + return tens; +} +inline uint64_t PrepareEightDigits(uint32_t i) { + return PrepareEightDigitsImpl(i, false); +} +inline uint64_t PrepareEightDigitsReversed(uint32_t i) { + return PrepareEightDigitsImpl(i, true); +} + +template <typename T, typename BackwardIt> +class FastUIntToStringConverter { + static_assert( + std::is_same<T, decltype(+std::declval<T>())>::value, + "to avoid code bloat, only instantiate this for int and larger types"); + static_assert(std::is_unsigned<T>::value, + "this class is only for unsigned types"); + + public: + // Outputs the given number backward (like with std::copy_backward), + // starting from the end of the string. + // The number of digits in the number must have been already measured and + // passed *exactly*, otherwise the behavior is undefined. + // (This is an optimization, as calculating the number of digits again would + // slow down the hot path.) + // Returns an iterator to the start of the suffix that was appended. + static BackwardIt FastIntToBufferBackward(T v, BackwardIt end) { + // THIS IS A HOT FUNCTION with a very deliberate structure to exploit branch + // prediction and shorten the critical path for smaller numbers. + // Do not move around the if/else blocks or attempt to simplify it + // without benchmarking any changes. + + if (v < 10) { + goto AT_LEAST_1 /* NOTE: mandatory for the 0 case */; + } + if (v < 1000) { + goto AT_LEAST_10; + } + if (v < 10000000) { + goto AT_LEAST_1000; + } + + if (v >= 100000000 / 10) { + if (v >= 10000000000000000 / 10) { + DoFastIntToBufferBackward<8>(v, end); + } + DoFastIntToBufferBackward<8>(v, end); + } + + if (v >= 10000 / 10) { + AT_LEAST_1000: + DoFastIntToBufferBackward<4>(v, end); + } + + if (v >= 100 / 10) { + AT_LEAST_10: + DoFastIntToBufferBackward<2>(v, end); + } + + if (v >= 10 / 10) { + AT_LEAST_1: + end = DoFastIntToBufferBackward(v, end, std::integral_constant<int, 1>()); + } + return end; + } + + private: + // Only assume pointers are contiguous for now. String and vector iterators + // could be special-cased as well, but there's no need for them here. + // With C++20 we can probably switch to std::contiguous_iterator_tag. + static constexpr bool kIsContiguousIterator = + std::is_pointer<BackwardIt>::value; + + template <int Exponent> + static void DoFastIntToBufferBackward(T& v, BackwardIt& end) { + constexpr T kModulus = Pow<T>(10, Exponent); + T remainder = static_cast<T>(v % kModulus); + v = static_cast<T>(v / kModulus); + end = DoFastIntToBufferBackward(remainder, end, + std::integral_constant<int, Exponent>()); + } -char* numbers_internal::FastIntToBuffer(uint32_t i, char* buffer) { - uint32_t digits; - // The idea of this implementation is to trim the number of divides to as few - // as possible, and also reducing memory stores and branches, by going in - // steps of two digits at a time rather than one whenever possible. - // The huge-number case is first, in the hopes that the compiler will output - // that case in one branch-free block of code, and only output conditional - // branches into it from below. - if (i >= 1000000000) { // >= 1,000,000,000 - digits = i / 100000000; // 100,000,000 - i -= digits * 100000000; - PutTwoDigits(digits, buffer); - buffer += 2; - lt100_000_000: - digits = i / 1000000; // 1,000,000 - i -= digits * 1000000; - PutTwoDigits(digits, buffer); - buffer += 2; - lt1_000_000: - digits = i / 10000; // 10,000 - i -= digits * 10000; - PutTwoDigits(digits, buffer); - buffer += 2; - lt10_000: - digits = i / 100; - i -= digits * 100; - PutTwoDigits(digits, buffer); - buffer += 2; - lt100: - digits = i; - PutTwoDigits(digits, buffer); - buffer += 2; - *buffer = 0; - return buffer; + static BackwardIt DoFastIntToBufferBackward(const T&, BackwardIt end, + std::integral_constant<int, 0>) { + return end; } - if (i < 100) { - digits = i; - if (i >= 10) goto lt100; - memcpy(buffer, one_ASCII_final_digits[i], 2); - return buffer + 1; + static BackwardIt DoFastIntToBufferBackward(T v, BackwardIt end, + std::integral_constant<int, 1>) { + *--end = static_cast<char>('0' + v); + return DoFastIntToBufferBackward(v, end, std::integral_constant<int, 0>()); } - if (i < 10000) { // 10,000 - if (i >= 1000) goto lt10_000; - digits = i / 100; - i -= digits * 100; - *buffer++ = '0' + static_cast<char>(digits); - goto lt100; + + static BackwardIt DoFastIntToBufferBackward(T v, BackwardIt end, + std::integral_constant<int, 4>) { + if (kIsContiguousIterator) { + const uint32_t digits = + PrepareFourDigits(static_cast<uint32_t>(v)) + kFourZeroBytes; + end -= sizeof(digits); + little_endian::Store32(&*end, digits); + } else { + uint32_t digits = + PrepareFourDigitsReversed(static_cast<uint32_t>(v)) + kFourZeroBytes; + for (size_t i = 0; i < sizeof(digits); ++i) { + *--end = static_cast<char>(digits); + digits >>= CHAR_BIT; + } + } + return end; } - if (i < 1000000) { // 1,000,000 - if (i >= 100000) goto lt1_000_000; - digits = i / 10000; // 10,000 - i -= digits * 10000; - *buffer++ = '0' + static_cast<char>(digits); - goto lt10_000; + + static BackwardIt DoFastIntToBufferBackward(T v, BackwardIt end, + std::integral_constant<int, 8>) { + if (kIsContiguousIterator) { + const uint64_t digits = + PrepareEightDigits(static_cast<uint32_t>(v)) + kEightZeroBytes; + end -= sizeof(digits); + little_endian::Store64(&*end, digits); + } else { + uint64_t digits = PrepareEightDigitsReversed(static_cast<uint32_t>(v)) + + kEightZeroBytes; + for (size_t i = 0; i < sizeof(digits); ++i) { + *--end = static_cast<char>(digits); + digits >>= CHAR_BIT; + } + } + return end; } - if (i < 100000000) { // 100,000,000 - if (i >= 10000000) goto lt100_000_000; - digits = i / 1000000; // 1,000,000 - i -= digits * 1000000; - *buffer++ = '0' + static_cast<char>(digits); - goto lt1_000_000; + + template <int Digits> + static BackwardIt DoFastIntToBufferBackward( + T v, BackwardIt end, std::integral_constant<int, Digits>) { + constexpr int kLogModulus = Digits - Digits / 2; + constexpr T kModulus = Pow(static_cast<T>(10), kLogModulus); + bool is_safe_to_use_division_trick = Digits <= 8; + T quotient, remainder; + if (is_safe_to_use_division_trick) { + constexpr uint64_t kCoefficient = + ComputePowerOf100DivisionCoefficient<uint64_t>(kLogModulus); + quotient = (v * kCoefficient) >> (10 * kLogModulus); + remainder = v - quotient * kModulus; + } else { + quotient = v / kModulus; + remainder = v % kModulus; + } + end = DoFastIntToBufferBackward(remainder, end, + std::integral_constant<int, kLogModulus>()); + return DoFastIntToBufferBackward( + quotient, end, std::integral_constant<int, Digits - kLogModulus>()); } - // we already know that i < 1,000,000,000 - digits = i / 100000000; // 100,000,000 - i -= digits * 100000000; - *buffer++ = '0' + static_cast<char>(digits); - goto lt100_000_000; +}; + +// Returns an iterator to the start of the suffix that was appended +template <typename T, typename BackwardIt> +std::enable_if_t<std::is_unsigned<T>::value, BackwardIt> +DoFastIntToBufferBackward(T v, BackwardIt end, uint32_t digits) { + using PromotedT = std::decay_t<decltype(+v)>; + using Converter = FastUIntToStringConverter<PromotedT, BackwardIt>; + (void)digits; + return Converter().FastIntToBufferBackward(v, end); } -char* numbers_internal::FastIntToBuffer(int32_t i, char* buffer) { - uint32_t u = static_cast<uint32_t>(i); - if (i < 0) { - *buffer++ = '-'; - // We need to do the negation in modular (i.e., "unsigned") - // arithmetic; MSVC++ apprently warns for plain "-u", so - // we write the equivalent expression "0 - u" instead. - u = 0 - u; +template <typename T, typename BackwardIt> +std::enable_if_t<std::is_signed<T>::value, BackwardIt> +DoFastIntToBufferBackward(T v, BackwardIt end, uint32_t digits) { + if (absl::numbers_internal::IsNegative(v)) { + // Store the minus sign *before* we produce the number itself, not after. + // This gets us a tail call. + end[-static_cast<ptrdiff_t>(digits) - 1] = '-'; } - return numbers_internal::FastIntToBuffer(u, buffer); + return DoFastIntToBufferBackward( + absl::numbers_internal::UnsignedAbsoluteValue(v), end, digits); } -char* numbers_internal::FastIntToBuffer(uint64_t i, char* buffer) { - uint32_t u32 = static_cast<uint32_t>(i); - if (u32 == i) return numbers_internal::FastIntToBuffer(u32, buffer); +template <class T> +std::enable_if_t<std::is_integral<T>::value, int> +GetNumDigitsOrNegativeIfNegativeImpl(T v) { + const auto /* either bool or std::false_type */ is_negative = + absl::numbers_internal::IsNegative(v); + const int digits = static_cast<int>(absl::numbers_internal::Base10Digits( + absl::numbers_internal::UnsignedAbsoluteValue(v))); + return is_negative ? ~digits : digits; +} - // Here we know i has at least 10 decimal digits. - uint64_t top_1to11 = i / 1000000000; - u32 = static_cast<uint32_t>(i - top_1to11 * 1000000000); - uint32_t top_1to11_32 = static_cast<uint32_t>(top_1to11); +} // namespace - if (top_1to11_32 == top_1to11) { - buffer = numbers_internal::FastIntToBuffer(top_1to11_32, buffer); - } else { - // top_1to11 has more than 32 bits too; print it in two steps. - uint32_t top_8to9 = static_cast<uint32_t>(top_1to11 / 100); - uint32_t mid_2 = static_cast<uint32_t>(top_1to11 - top_8to9 * 100); - buffer = numbers_internal::FastIntToBuffer(top_8to9, buffer); - PutTwoDigits(mid_2, buffer); - buffer += 2; - } +void numbers_internal::PutTwoDigits(uint32_t i, absl::Nonnull<char*> buf) { + little_endian::Store16( + buf, static_cast<uint16_t>(PrepareTwoDigits(i) + kTwoZeroBytes)); +} - // We have only 9 digits now, again the maximum uint32_t can handle fully. - uint32_t digits = u32 / 10000000; // 10,000,000 - u32 -= digits * 10000000; - PutTwoDigits(digits, buffer); - buffer += 2; - digits = u32 / 100000; // 100,000 - u32 -= digits * 100000; - PutTwoDigits(digits, buffer); - buffer += 2; - digits = u32 / 1000; // 1,000 - u32 -= digits * 1000; - PutTwoDigits(digits, buffer); - buffer += 2; - digits = u32 / 10; - u32 -= digits * 10; - PutTwoDigits(digits, buffer); - buffer += 2; - memcpy(buffer, one_ASCII_final_digits[u32], 2); - return buffer + 1; +absl::Nonnull<char*> numbers_internal::FastIntToBuffer( + uint32_t i, absl::Nonnull<char*> buffer) { + const uint32_t digits = absl::numbers_internal::Base10Digits(i); + buffer += digits; + *buffer = '\0'; // We're going backward, so store this first + FastIntToBufferBackward(i, buffer, digits); + return buffer; } -char* numbers_internal::FastIntToBuffer(int64_t i, char* buffer) { - uint64_t u = static_cast<uint64_t>(i); - if (i < 0) { - *buffer++ = '-'; - u = 0 - u; - } - return numbers_internal::FastIntToBuffer(u, buffer); +absl::Nonnull<char*> numbers_internal::FastIntToBuffer( + int32_t i, absl::Nonnull<char*> buffer) { + buffer += static_cast<int>(i < 0); + uint32_t digits = absl::numbers_internal::Base10Digits( + absl::numbers_internal::UnsignedAbsoluteValue(i)); + buffer += digits; + *buffer = '\0'; // We're going backward, so store this first + FastIntToBufferBackward(i, buffer, digits); + return buffer; +} + +absl::Nonnull<char*> numbers_internal::FastIntToBuffer( + uint64_t i, absl::Nonnull<char*> buffer) { + uint32_t digits = absl::numbers_internal::Base10Digits(i); + buffer += digits; + *buffer = '\0'; // We're going backward, so store this first + FastIntToBufferBackward(i, buffer, digits); + return buffer; +} + +absl::Nonnull<char*> numbers_internal::FastIntToBuffer( + int64_t i, absl::Nonnull<char*> buffer) { + buffer += static_cast<int>(i < 0); + uint32_t digits = absl::numbers_internal::Base10Digits( + absl::numbers_internal::UnsignedAbsoluteValue(i)); + buffer += digits; + *buffer = '\0'; // We're going backward, so store this first + FastIntToBufferBackward(i, buffer, digits); + return buffer; +} + +absl::Nonnull<char*> numbers_internal::FastIntToBufferBackward( + uint32_t i, absl::Nonnull<char*> buffer_end, uint32_t exact_digit_count) { + return DoFastIntToBufferBackward(i, buffer_end, exact_digit_count); +} + +absl::Nonnull<char*> numbers_internal::FastIntToBufferBackward( + int32_t i, absl::Nonnull<char*> buffer_end, uint32_t exact_digit_count) { + return DoFastIntToBufferBackward(i, buffer_end, exact_digit_count); +} + +absl::Nonnull<char*> numbers_internal::FastIntToBufferBackward( + uint64_t i, absl::Nonnull<char*> buffer_end, uint32_t exact_digit_count) { + return DoFastIntToBufferBackward(i, buffer_end, exact_digit_count); +} + +absl::Nonnull<char*> numbers_internal::FastIntToBufferBackward( + int64_t i, absl::Nonnull<char*> buffer_end, uint32_t exact_digit_count) { + return DoFastIntToBufferBackward(i, buffer_end, exact_digit_count); +} + +int numbers_internal::GetNumDigitsOrNegativeIfNegative(signed char v) { + return GetNumDigitsOrNegativeIfNegativeImpl(v); +} +int numbers_internal::GetNumDigitsOrNegativeIfNegative(unsigned char v) { + return GetNumDigitsOrNegativeIfNegativeImpl(v); +} +int numbers_internal::GetNumDigitsOrNegativeIfNegative(short v) { // NOLINT + return GetNumDigitsOrNegativeIfNegativeImpl(v); +} +int numbers_internal::GetNumDigitsOrNegativeIfNegative( + unsigned short v) { // NOLINT + return GetNumDigitsOrNegativeIfNegativeImpl(v); +} +int numbers_internal::GetNumDigitsOrNegativeIfNegative(int v) { + return GetNumDigitsOrNegativeIfNegativeImpl(v); +} +int numbers_internal::GetNumDigitsOrNegativeIfNegative(unsigned int v) { + return GetNumDigitsOrNegativeIfNegativeImpl(v); +} +int numbers_internal::GetNumDigitsOrNegativeIfNegative(long v) { // NOLINT + return GetNumDigitsOrNegativeIfNegativeImpl(v); +} +int numbers_internal::GetNumDigitsOrNegativeIfNegative( + unsigned long v) { // NOLINT + return GetNumDigitsOrNegativeIfNegativeImpl(v); +} +int numbers_internal::GetNumDigitsOrNegativeIfNegative(long long v) { // NOLINT + return GetNumDigitsOrNegativeIfNegativeImpl(v); +} +int numbers_internal::GetNumDigitsOrNegativeIfNegative( + unsigned long long v) { // NOLINT + return GetNumDigitsOrNegativeIfNegativeImpl(v); } // Given a 128-bit number expressed as a pair of uint64_t, high half first, @@ -483,7 +769,8 @@ static ExpDigits SplitToSix(const double value) { // Helper function for fast formatting of floating-point. // The result is the same as "%g", a.k.a. "%.6g". -size_t numbers_internal::SixDigitsToBuffer(double d, char* const buffer) { +size_t numbers_internal::SixDigitsToBuffer(double d, + absl::Nonnull<char*> const buffer) { static_assert(std::numeric_limits<float>::is_iec559, "IEEE-754/IEC-559 support only"); @@ -630,9 +917,10 @@ static const int8_t kAsciiToInt[256] = { 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36, 36}; // Parse the sign and optional hex or oct prefix in text. -inline bool safe_parse_sign_and_base(absl::string_view* text /*inout*/, - int* base_ptr /*inout*/, - bool* negative_ptr /*output*/) { +inline bool safe_parse_sign_and_base( + absl::Nonnull<absl::string_view*> text /*inout*/, + absl::Nonnull<int*> base_ptr /*inout*/, + absl::Nonnull<bool*> negative_ptr /*output*/) { if (text->data() == nullptr) { return false; } @@ -917,7 +1205,7 @@ ABSL_CONST_INIT const IntType LookupTables<IntType>::kVminOverBase[] = template <typename IntType> inline bool safe_parse_positive_int(absl::string_view text, int base, - IntType* value_p) { + absl::Nonnull<IntType*> value_p) { IntType value = 0; const IntType vmax = std::numeric_limits<IntType>::max(); assert(vmax > 0); @@ -954,7 +1242,7 @@ inline bool safe_parse_positive_int(absl::string_view text, int base, template <typename IntType> inline bool safe_parse_negative_int(absl::string_view text, int base, - IntType* value_p) { + absl::Nonnull<IntType*> value_p) { IntType value = 0; const IntType vmin = std::numeric_limits<IntType>::min(); assert(vmin < 0); @@ -998,8 +1286,8 @@ inline bool safe_parse_negative_int(absl::string_view text, int base, // Input format based on POSIX.1-2008 strtol // http://pubs.opengroup.org/onlinepubs/9699919799/functions/strtol.html template <typename IntType> -inline bool safe_int_internal(absl::string_view text, IntType* value_p, - int base) { +inline bool safe_int_internal(absl::string_view text, + absl::Nonnull<IntType*> value_p, int base) { *value_p = 0; bool negative; if (!safe_parse_sign_and_base(&text, &base, &negative)) { @@ -1013,8 +1301,8 @@ inline bool safe_int_internal(absl::string_view text, IntType* value_p, } template <typename IntType> -inline bool safe_uint_internal(absl::string_view text, IntType* value_p, - int base) { +inline bool safe_uint_internal(absl::string_view text, + absl::Nonnull<IntType*> value_p, int base) { *value_p = 0; bool negative; if (!safe_parse_sign_and_base(&text, &base, &negative) || negative) { @@ -1048,46 +1336,33 @@ ABSL_CONST_INIT ABSL_DLL const char kHexTable[513] = "e0e1e2e3e4e5e6e7e8e9eaebecedeeef" "f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff"; -ABSL_CONST_INIT ABSL_DLL const char two_ASCII_digits[100][2] = { - {'0', '0'}, {'0', '1'}, {'0', '2'}, {'0', '3'}, {'0', '4'}, {'0', '5'}, - {'0', '6'}, {'0', '7'}, {'0', '8'}, {'0', '9'}, {'1', '0'}, {'1', '1'}, - {'1', '2'}, {'1', '3'}, {'1', '4'}, {'1', '5'}, {'1', '6'}, {'1', '7'}, - {'1', '8'}, {'1', '9'}, {'2', '0'}, {'2', '1'}, {'2', '2'}, {'2', '3'}, - {'2', '4'}, {'2', '5'}, {'2', '6'}, {'2', '7'}, {'2', '8'}, {'2', '9'}, - {'3', '0'}, {'3', '1'}, {'3', '2'}, {'3', '3'}, {'3', '4'}, {'3', '5'}, - {'3', '6'}, {'3', '7'}, {'3', '8'}, {'3', '9'}, {'4', '0'}, {'4', '1'}, - {'4', '2'}, {'4', '3'}, {'4', '4'}, {'4', '5'}, {'4', '6'}, {'4', '7'}, - {'4', '8'}, {'4', '9'}, {'5', '0'}, {'5', '1'}, {'5', '2'}, {'5', '3'}, - {'5', '4'}, {'5', '5'}, {'5', '6'}, {'5', '7'}, {'5', '8'}, {'5', '9'}, - {'6', '0'}, {'6', '1'}, {'6', '2'}, {'6', '3'}, {'6', '4'}, {'6', '5'}, - {'6', '6'}, {'6', '7'}, {'6', '8'}, {'6', '9'}, {'7', '0'}, {'7', '1'}, - {'7', '2'}, {'7', '3'}, {'7', '4'}, {'7', '5'}, {'7', '6'}, {'7', '7'}, - {'7', '8'}, {'7', '9'}, {'8', '0'}, {'8', '1'}, {'8', '2'}, {'8', '3'}, - {'8', '4'}, {'8', '5'}, {'8', '6'}, {'8', '7'}, {'8', '8'}, {'8', '9'}, - {'9', '0'}, {'9', '1'}, {'9', '2'}, {'9', '3'}, {'9', '4'}, {'9', '5'}, - {'9', '6'}, {'9', '7'}, {'9', '8'}, {'9', '9'}}; - -bool safe_strto32_base(absl::string_view text, int32_t* value, int base) { +bool safe_strto32_base(absl::string_view text, absl::Nonnull<int32_t*> value, + int base) { return safe_int_internal<int32_t>(text, value, base); } -bool safe_strto64_base(absl::string_view text, int64_t* value, int base) { +bool safe_strto64_base(absl::string_view text, absl::Nonnull<int64_t*> value, + int base) { return safe_int_internal<int64_t>(text, value, base); } -bool safe_strto128_base(absl::string_view text, int128* value, int base) { +bool safe_strto128_base(absl::string_view text, absl::Nonnull<int128*> value, + int base) { return safe_int_internal<absl::int128>(text, value, base); } -bool safe_strtou32_base(absl::string_view text, uint32_t* value, int base) { +bool safe_strtou32_base(absl::string_view text, absl::Nonnull<uint32_t*> value, + int base) { return safe_uint_internal<uint32_t>(text, value, base); } -bool safe_strtou64_base(absl::string_view text, uint64_t* value, int base) { +bool safe_strtou64_base(absl::string_view text, absl::Nonnull<uint64_t*> value, + int base) { return safe_uint_internal<uint64_t>(text, value, base); } -bool safe_strtou128_base(absl::string_view text, uint128* value, int base) { +bool safe_strtou128_base(absl::string_view text, absl::Nonnull<uint128*> value, + int base) { return safe_uint_internal<absl::uint128>(text, value, base); } diff --git a/absl/strings/numbers.h b/absl/strings/numbers.h index 86c84ed3..ad4e66b6 100644 --- a/absl/strings/numbers.h +++ b/absl/strings/numbers.h @@ -32,6 +32,7 @@ #endif #include <cstddef> +#include <cstdint> #include <cstdlib> #include <cstring> #include <ctime> @@ -39,9 +40,12 @@ #include <string> #include <type_traits> +#include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/internal/endian.h" #include "absl/base/macros.h" +#include "absl/base/nullability.h" +#include "absl/base/optimization.h" #include "absl/base/port.h" #include "absl/numeric/bits.h" #include "absl/numeric/int128.h" @@ -59,7 +63,8 @@ ABSL_NAMESPACE_BEGIN // encountered, this function returns `false`, leaving `out` in an unspecified // state. template <typename int_type> -ABSL_MUST_USE_RESULT bool SimpleAtoi(absl::string_view str, int_type* out); +ABSL_MUST_USE_RESULT bool SimpleAtoi(absl::string_view str, + absl::Nonnull<int_type*> out); // SimpleAtof() // @@ -70,7 +75,8 @@ ABSL_MUST_USE_RESULT bool SimpleAtoi(absl::string_view str, int_type* out); // allowed formats for `str`, except SimpleAtof() is locale-independent and will // always use the "C" locale. If any errors are encountered, this function // returns `false`, leaving `out` in an unspecified state. -ABSL_MUST_USE_RESULT bool SimpleAtof(absl::string_view str, float* out); +ABSL_MUST_USE_RESULT bool SimpleAtof(absl::string_view str, + absl::Nonnull<float*> out); // SimpleAtod() // @@ -81,7 +87,8 @@ ABSL_MUST_USE_RESULT bool SimpleAtof(absl::string_view str, float* out); // allowed formats for `str`, except SimpleAtod is locale-independent and will // always use the "C" locale. If any errors are encountered, this function // returns `false`, leaving `out` in an unspecified state. -ABSL_MUST_USE_RESULT bool SimpleAtod(absl::string_view str, double* out); +ABSL_MUST_USE_RESULT bool SimpleAtod(absl::string_view str, + absl::Nonnull<double*> out); // SimpleAtob() // @@ -91,7 +98,8 @@ ABSL_MUST_USE_RESULT bool SimpleAtod(absl::string_view str, double* out); // are interpreted as boolean `false`: "false", "f", "no", "n", "0". If any // errors are encountered, this function returns `false`, leaving `out` in an // unspecified state. -ABSL_MUST_USE_RESULT bool SimpleAtob(absl::string_view str, bool* out); +ABSL_MUST_USE_RESULT bool SimpleAtob(absl::string_view str, + absl::Nonnull<bool*> out); // SimpleHexAtoi() // @@ -104,13 +112,14 @@ ABSL_MUST_USE_RESULT bool SimpleAtob(absl::string_view str, bool* out); // by this function. If any errors are encountered, this function returns // `false`, leaving `out` in an unspecified state. template <typename int_type> -ABSL_MUST_USE_RESULT bool SimpleHexAtoi(absl::string_view str, int_type* out); +ABSL_MUST_USE_RESULT bool SimpleHexAtoi(absl::string_view str, + absl::Nonnull<int_type*> out); // Overloads of SimpleHexAtoi() for 128 bit integers. -ABSL_MUST_USE_RESULT inline bool SimpleHexAtoi(absl::string_view str, - absl::int128* out); -ABSL_MUST_USE_RESULT inline bool SimpleHexAtoi(absl::string_view str, - absl::uint128* out); +ABSL_MUST_USE_RESULT inline bool SimpleHexAtoi( + absl::string_view str, absl::Nonnull<absl::int128*> out); +ABSL_MUST_USE_RESULT inline bool SimpleHexAtoi( + absl::string_view str, absl::Nonnull<absl::uint128*> out); ABSL_NAMESPACE_END } // namespace absl @@ -125,8 +134,6 @@ namespace numbers_internal { ABSL_DLL extern const char kHexChar[17]; // 0123456789abcdef ABSL_DLL extern const char kHexTable[513]; // 000102030405060708090a0b0c0d0e0f1011... -ABSL_DLL extern const char - two_ASCII_digits[100][2]; // 00, 01, 02, 03... // Writes a two-character representation of 'i' to 'buf'. 'i' must be in the // range 0 <= i < 100, and buf must have space for two characters. Example: @@ -134,45 +141,136 @@ ABSL_DLL extern const char // PutTwoDigits(42, buf); // // buf[0] == '4' // // buf[1] == '2' -inline void PutTwoDigits(size_t i, char* buf) { - assert(i < 100); - memcpy(buf, two_ASCII_digits[i], 2); -} +void PutTwoDigits(uint32_t i, absl::Nonnull<char*> buf); // safe_strto?() functions for implementing SimpleAtoi() -bool safe_strto32_base(absl::string_view text, int32_t* value, int base); -bool safe_strto64_base(absl::string_view text, int64_t* value, int base); -bool safe_strto128_base(absl::string_view text, absl::int128* value, - int base); -bool safe_strtou32_base(absl::string_view text, uint32_t* value, int base); -bool safe_strtou64_base(absl::string_view text, uint64_t* value, int base); -bool safe_strtou128_base(absl::string_view text, absl::uint128* value, - int base); +bool safe_strto32_base(absl::string_view text, absl::Nonnull<int32_t*> value, + int base); +bool safe_strto64_base(absl::string_view text, absl::Nonnull<int64_t*> value, + int base); +bool safe_strto128_base(absl::string_view text, + absl::Nonnull<absl::int128*> value, int base); +bool safe_strtou32_base(absl::string_view text, absl::Nonnull<uint32_t*> value, + int base); +bool safe_strtou64_base(absl::string_view text, absl::Nonnull<uint64_t*> value, + int base); +bool safe_strtou128_base(absl::string_view text, + absl::Nonnull<absl::uint128*> value, int base); static const int kFastToBufferSize = 32; static const int kSixDigitsToBufferSize = 16; +template <class T> +std::enable_if_t<!std::is_unsigned<T>::value, bool> IsNegative(const T& v) { + return v < T(); +} + +template <class T> +std::enable_if_t<std::is_unsigned<T>::value, std::false_type> IsNegative( + const T&) { + // The integer is unsigned, so return a compile-time constant. + // This can help the optimizer avoid having to prove bool to be false later. + return std::false_type(); +} + +template <class T> +std::enable_if_t<std::is_unsigned<std::decay_t<T>>::value, T&&> +UnsignedAbsoluteValue(T&& v ABSL_ATTRIBUTE_LIFETIME_BOUND) { + // The value is unsigned; just return the original. + return std::forward<T>(v); +} + +template <class T> +ABSL_ATTRIBUTE_CONST_FUNCTION + std::enable_if_t<!std::is_unsigned<T>::value, std::make_unsigned_t<T>> + UnsignedAbsoluteValue(T v) { + using U = std::make_unsigned_t<T>; + return IsNegative(v) ? U() - static_cast<U>(v) : static_cast<U>(v); +} + +// Returns the number of base-10 digits in the given number. +// Note that this strictly counts digits. It does not count the sign. +// The `initial_digits` parameter is the starting point, which is normally equal +// to 1 because the number of digits in 0 is 1 (a special case). +// However, callers may e.g. wish to change it to 2 to account for the sign. +template <typename T> +std::enable_if_t<std::is_unsigned<T>::value, uint32_t> Base10Digits( + T v, const uint32_t initial_digits = 1) { + uint32_t r = initial_digits; + // If code size becomes an issue, the 'if' stage can be removed for a minor + // performance loss. + for (;;) { + if (ABSL_PREDICT_TRUE(v < 10 * 10)) { + r += (v >= 10); + break; + } + if (ABSL_PREDICT_TRUE(v < 1000 * 10)) { + r += (v >= 1000) + 2; + break; + } + if (ABSL_PREDICT_TRUE(v < 100000 * 10)) { + r += (v >= 100000) + 4; + break; + } + r += 6; + v = static_cast<T>(v / 1000000); + } + return r; +} + +template <typename T> +std::enable_if_t<std::is_signed<T>::value, uint32_t> Base10Digits( + T v, uint32_t r = 1) { + // Branchlessly add 1 to account for a minus sign. + r += static_cast<uint32_t>(IsNegative(v)); + return Base10Digits(UnsignedAbsoluteValue(v), r); +} + +// These functions return the number of base-10 digits, but multiplied by -1 if +// the input itself is negative. This is handy and efficient for later usage, +// since the bitwise complement of the result becomes equal to the number of +// characters required. +ABSL_ATTRIBUTE_CONST_FUNCTION int GetNumDigitsOrNegativeIfNegative( + signed char v); +ABSL_ATTRIBUTE_CONST_FUNCTION int GetNumDigitsOrNegativeIfNegative( + unsigned char v); +ABSL_ATTRIBUTE_CONST_FUNCTION int GetNumDigitsOrNegativeIfNegative( + short v); // NOLINT +ABSL_ATTRIBUTE_CONST_FUNCTION int GetNumDigitsOrNegativeIfNegative( + unsigned short v); // NOLINT +ABSL_ATTRIBUTE_CONST_FUNCTION int GetNumDigitsOrNegativeIfNegative(int v); +ABSL_ATTRIBUTE_CONST_FUNCTION int GetNumDigitsOrNegativeIfNegative( + unsigned int v); +ABSL_ATTRIBUTE_CONST_FUNCTION int GetNumDigitsOrNegativeIfNegative( + long v); // NOLINT +ABSL_ATTRIBUTE_CONST_FUNCTION int GetNumDigitsOrNegativeIfNegative( + unsigned long v); // NOLINT +ABSL_ATTRIBUTE_CONST_FUNCTION int GetNumDigitsOrNegativeIfNegative( + long long v); // NOLINT +ABSL_ATTRIBUTE_CONST_FUNCTION int GetNumDigitsOrNegativeIfNegative( + unsigned long long v); // NOLINT + // Helper function for fast formatting of floating-point values. // The result is the same as printf's "%g", a.k.a. "%.6g"; that is, six // significant digits are returned, trailing zeros are removed, and numbers // outside the range 0.0001-999999 are output using scientific notation // (1.23456e+06). This routine is heavily optimized. // Required buffer size is `kSixDigitsToBufferSize`. -size_t SixDigitsToBuffer(double d, char* buffer); +size_t SixDigitsToBuffer(double d, absl::Nonnull<char*> buffer); -// These functions are intended for speed. All functions take an output buffer +// All of these functions take an output buffer // as an argument and return a pointer to the last byte they wrote, which is the // terminating '\0'. At most `kFastToBufferSize` bytes are written. -char* FastIntToBuffer(int32_t, char*); -char* FastIntToBuffer(uint32_t, char*); -char* FastIntToBuffer(int64_t, char*); -char* FastIntToBuffer(uint64_t, char*); +absl::Nonnull<char*> FastIntToBuffer(int32_t i, absl::Nonnull<char*> buffer); +absl::Nonnull<char*> FastIntToBuffer(uint32_t i, absl::Nonnull<char*> buffer); +absl::Nonnull<char*> FastIntToBuffer(int64_t i, absl::Nonnull<char*> buffer); +absl::Nonnull<char*> FastIntToBuffer(uint64_t i, absl::Nonnull<char*> buffer); // For enums and integer types that are not an exact match for the types above, // use templates to call the appropriate one of the four overloads above. template <typename int_type> -char* FastIntToBuffer(int_type i, char* buffer) { +absl::Nonnull<char*> FastIntToBuffer(int_type i, absl::Nonnull<char*> buffer) { static_assert(sizeof(i) <= 64 / 8, "FastIntToBuffer works only with 64-bit-or-less integers."); // TODO(jorg): This signed-ness check is used because it works correctly @@ -196,10 +294,63 @@ char* FastIntToBuffer(int_type i, char* buffer) { } } +// These functions do NOT add any null-terminator. +// They return a pointer to the beginning of the written string. +// The digit counts provided must *exactly* match the number of base-10 digits +// in the number, or the behavior is undefined. +// (i.e. do NOT count the minus sign, or over- or under-count the digits.) +absl::Nonnull<char*> FastIntToBufferBackward(int32_t i, + absl::Nonnull<char*> buffer_end, + uint32_t exact_digit_count); +absl::Nonnull<char*> FastIntToBufferBackward(uint32_t i, + absl::Nonnull<char*> buffer_end, + uint32_t exact_digit_count); +absl::Nonnull<char*> FastIntToBufferBackward(int64_t i, + absl::Nonnull<char*> buffer_end, + uint32_t exact_digit_count); +absl::Nonnull<char*> FastIntToBufferBackward(uint64_t i, + absl::Nonnull<char*> buffer_end, + uint32_t exact_digit_count); + +// For enums and integer types that are not an exact match for the types above, +// use templates to call the appropriate one of the four overloads above. +template <typename int_type> +absl::Nonnull<char*> FastIntToBufferBackward(int_type i, + absl::Nonnull<char*> buffer_end, + uint32_t exact_digit_count) { + static_assert( + sizeof(i) <= 64 / 8, + "FastIntToBufferBackward works only with 64-bit-or-less integers."); + // This signed-ness check is used because it works correctly + // with enums, and it also serves to check that int_type is not a pointer. + // If one day something like std::is_signed<enum E> works, switch to it. + // These conditions are constexpr bools to suppress MSVC warning C4127. + constexpr bool kIsSigned = static_cast<int_type>(1) - 2 < 0; + constexpr bool kUse64Bit = sizeof(i) > 32 / 8; + if (kIsSigned) { + if (kUse64Bit) { + return FastIntToBufferBackward(static_cast<int64_t>(i), buffer_end, + exact_digit_count); + } else { + return FastIntToBufferBackward(static_cast<int32_t>(i), buffer_end, + exact_digit_count); + } + } else { + if (kUse64Bit) { + return FastIntToBufferBackward(static_cast<uint64_t>(i), buffer_end, + exact_digit_count); + } else { + return FastIntToBufferBackward(static_cast<uint32_t>(i), buffer_end, + exact_digit_count); + } + } +} + // Implementation of SimpleAtoi, generalized to support arbitrary base (used // with base different from 10 elsewhere in Abseil implementation). template <typename int_type> -ABSL_MUST_USE_RESULT bool safe_strtoi_base(absl::string_view s, int_type* out, +ABSL_MUST_USE_RESULT bool safe_strtoi_base(absl::string_view s, + absl::Nonnull<int_type*> out, int base) { static_assert(sizeof(*out) == 4 || sizeof(*out) == 8, "SimpleAtoi works only with 32-bit or 64-bit integers."); @@ -242,7 +393,7 @@ ABSL_MUST_USE_RESULT bool safe_strtoi_base(absl::string_view s, int_type* out, // without the terminating null character. Thus `out` must be of length >= 16. // Returns the number of non-pad digits of the output (it can never be zero // since 0 has one digit). -inline size_t FastHexToBufferZeroPad16(uint64_t val, char* out) { +inline size_t FastHexToBufferZeroPad16(uint64_t val, absl::Nonnull<char*> out) { #ifdef ABSL_INTERNAL_HAVE_SSSE3 uint64_t be = absl::big_endian::FromHost64(val); const auto kNibbleMask = _mm_set1_epi8(0xf); @@ -268,32 +419,34 @@ inline size_t FastHexToBufferZeroPad16(uint64_t val, char* out) { } // namespace numbers_internal template <typename int_type> -ABSL_MUST_USE_RESULT bool SimpleAtoi(absl::string_view str, int_type* out) { +ABSL_MUST_USE_RESULT bool SimpleAtoi(absl::string_view str, + absl::Nonnull<int_type*> out) { return numbers_internal::safe_strtoi_base(str, out, 10); } ABSL_MUST_USE_RESULT inline bool SimpleAtoi(absl::string_view str, - absl::int128* out) { + absl::Nonnull<absl::int128*> out) { return numbers_internal::safe_strto128_base(str, out, 10); } ABSL_MUST_USE_RESULT inline bool SimpleAtoi(absl::string_view str, - absl::uint128* out) { + absl::Nonnull<absl::uint128*> out) { return numbers_internal::safe_strtou128_base(str, out, 10); } template <typename int_type> -ABSL_MUST_USE_RESULT bool SimpleHexAtoi(absl::string_view str, int_type* out) { +ABSL_MUST_USE_RESULT bool SimpleHexAtoi(absl::string_view str, + absl::Nonnull<int_type*> out) { return numbers_internal::safe_strtoi_base(str, out, 16); } -ABSL_MUST_USE_RESULT inline bool SimpleHexAtoi(absl::string_view str, - absl::int128* out) { +ABSL_MUST_USE_RESULT inline bool SimpleHexAtoi( + absl::string_view str, absl::Nonnull<absl::int128*> out) { return numbers_internal::safe_strto128_base(str, out, 16); } -ABSL_MUST_USE_RESULT inline bool SimpleHexAtoi(absl::string_view str, - absl::uint128* out) { +ABSL_MUST_USE_RESULT inline bool SimpleHexAtoi( + absl::string_view str, absl::Nonnull<absl::uint128*> out) { return numbers_internal::safe_strtou128_base(str, out, 16); } diff --git a/absl/strings/numbers_benchmark.cc b/absl/strings/numbers_benchmark.cc index 6e79b3e8..e7cb60a4 100644 --- a/absl/strings/numbers_benchmark.cc +++ b/absl/strings/numbers_benchmark.cc @@ -13,6 +13,7 @@ // limitations under the License. #include <cstdint> +#include <limits> #include <random> #include <string> #include <type_traits> @@ -23,6 +24,7 @@ #include "absl/random/distributions.h" #include "absl/random/random.h" #include "absl/strings/numbers.h" +#include "absl/strings/string_view.h" namespace { diff --git a/absl/strings/numbers_test.cc b/absl/strings/numbers_test.cc index b3c098d1..1ceff70f 100644 --- a/absl/strings/numbers_test.cc +++ b/absl/strings/numbers_test.cc @@ -28,6 +28,7 @@ #include <cstdio> #include <cstdlib> #include <cstring> +#include <ios> #include <limits> #include <numeric> #include <random> @@ -37,13 +38,15 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "absl/base/internal/raw_logging.h" +#include "absl/log/log.h" +#include "absl/numeric/int128.h" #include "absl/random/distributions.h" #include "absl/random/random.h" #include "absl/strings/internal/numbers_test_common.h" #include "absl/strings/internal/ostringstream.h" #include "absl/strings/internal/pow10_helper.h" #include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" namespace { @@ -60,6 +63,7 @@ using absl::strings_internal::strtouint32_test_cases; using absl::strings_internal::strtouint64_test_cases; using testing::Eq; using testing::MatchesRegex; +using testing::Pointee; // Number of floats to test with. // 5,000,000 is a reasonable default for a test that only takes a few seconds. @@ -227,10 +231,15 @@ TEST(Numbers, TestFastPrints) { CheckInt32(INT_MIN); CheckInt32(INT_MAX); CheckInt64(LONG_MIN); + CheckInt64(uint64_t{10000000}); + CheckInt64(uint64_t{100000000}); CheckInt64(uint64_t{1000000000}); CheckInt64(uint64_t{9999999999}); CheckInt64(uint64_t{100000000000000}); CheckInt64(uint64_t{999999999999999}); + CheckInt64(uint64_t{1000000000000000}); + CheckInt64(uint64_t{10000000000000000}); + CheckInt64(uint64_t{100000000000000000}); CheckInt64(uint64_t{1000000000000000000}); CheckInt64(uint64_t{1199999999999999999}); CheckInt64(int64_t{-700000000000000000}); @@ -242,6 +251,8 @@ TEST(Numbers, TestFastPrints) { CheckUInt64(uint64_t{999999999999999}); CheckUInt64(uint64_t{1000000000000000000}); CheckUInt64(uint64_t{1199999999999999999}); + CheckUInt64(uint64_t{10000000000000000000u}); + CheckUInt64(uint64_t{10200300040000500006u}); CheckUInt64(std::numeric_limits<uint64_t>::max()); for (int i = 0; i < 10000; i++) { @@ -1337,11 +1348,9 @@ TEST_F(SimpleDtoaTest, ExhaustiveDoubleToSixDigits) { if (strcmp(sixdigitsbuf, snprintfbuf) != 0) { mismatches.push_back(d); if (mismatches.size() < 10) { - ABSL_RAW_LOG(ERROR, "%s", - absl::StrCat("Six-digit failure with double. ", "d=", d, - "=", d, " sixdigits=", sixdigitsbuf, - " printf(%g)=", snprintfbuf) - .c_str()); + LOG(ERROR) << "Six-digit failure with double. d=" << d + << " sixdigits=" << sixdigitsbuf + << " printf(%g)=" << snprintfbuf; } } }; @@ -1389,12 +1398,10 @@ TEST_F(SimpleDtoaTest, ExhaustiveDoubleToSixDigits) { if (kFloatNumCases >= 1e9) { // The exhaustive test takes a very long time, so log progress. char buf[kSixDigitsToBufferSize]; - ABSL_RAW_LOG( - INFO, "%s", - absl::StrCat("Exp ", exponent, " powten=", powten, "(", powten, - ") (", - std::string(buf, SixDigitsToBuffer(powten, buf)), ")") - .c_str()); + LOG(INFO) << "Exp " << exponent << " powten=" << powten << "(" << powten + << ") (" + << absl::string_view(buf, SixDigitsToBuffer(powten, buf)) + << ")"; } for (int digits : digit_testcases) { if (exponent == 308 && digits >= 179769) break; // don't overflow! @@ -1419,20 +1426,17 @@ TEST_F(SimpleDtoaTest, ExhaustiveDoubleToSixDigits) { double before = nextafter(d, 0.0); double after = nextafter(d, 1.7976931348623157e308); char b1[32], b2[kSixDigitsToBufferSize]; - ABSL_RAW_LOG( - ERROR, "%s", - absl::StrCat( - "Mismatch #", i, " d=", d, " (", ToNineDigits(d), ")", - " sixdigits='", sixdigitsbuf, "'", " snprintf='", snprintfbuf, - "'", " Before.=", PerfectDtoa(before), " ", - (SixDigitsToBuffer(before, b2), b2), - " vs snprintf=", (snprintf(b1, sizeof(b1), "%g", before), b1), - " Perfect=", PerfectDtoa(d), " ", (SixDigitsToBuffer(d, b2), b2), - " vs snprintf=", (snprintf(b1, sizeof(b1), "%g", d), b1), - " After.=.", PerfectDtoa(after), " ", - (SixDigitsToBuffer(after, b2), b2), - " vs snprintf=", (snprintf(b1, sizeof(b1), "%g", after), b1)) - .c_str()); + LOG(ERROR) << "Mismatch #" << i << " d=" << d << " (" << ToNineDigits(d) + << ") sixdigits='" << sixdigitsbuf << "' snprintf='" + << snprintfbuf << "' Before.=" << PerfectDtoa(before) << " " + << (SixDigitsToBuffer(before, b2), b2) << " vs snprintf=" + << (snprintf(b1, sizeof(b1), "%g", before), b1) + << " Perfect=" << PerfectDtoa(d) << " " + << (SixDigitsToBuffer(d, b2), b2) + << " vs snprintf=" << (snprintf(b1, sizeof(b1), "%g", d), b1) + << " After.=." << PerfectDtoa(after) << " " + << (SixDigitsToBuffer(after, b2), b2) << " vs snprintf=" + << (snprintf(b1, sizeof(b1), "%g", after), b1); } } } @@ -1719,4 +1723,25 @@ TEST(FastHexToBufferZeroPad16, Smoke) { } } +template <typename Int> +void ExpectWritesNull() { + { + char buf[absl::numbers_internal::kFastToBufferSize]; + Int x = std::numeric_limits<Int>::min(); + EXPECT_THAT(absl::numbers_internal::FastIntToBuffer(x, buf), Pointee('\0')); + } + { + char buf[absl::numbers_internal::kFastToBufferSize]; + Int x = std::numeric_limits<Int>::max(); + EXPECT_THAT(absl::numbers_internal::FastIntToBuffer(x, buf), Pointee('\0')); + } +} + +TEST(FastIntToBuffer, WritesNull) { + ExpectWritesNull<int32_t>(); + ExpectWritesNull<uint32_t>(); + ExpectWritesNull<int64_t>(); + ExpectWritesNull<uint32_t>(); +} + } // namespace diff --git a/absl/strings/str_cat.cc b/absl/strings/str_cat.cc index 114a2ff2..098ab183 100644 --- a/absl/strings/str_cat.cc +++ b/absl/strings/str_cat.cc @@ -16,13 +16,15 @@ #include <assert.h> -#include <algorithm> #include <cstddef> #include <cstdint> #include <cstring> +#include <initializer_list> #include <string> +#include <type_traits> -#include "absl/strings/ascii.h" +#include "absl/base/config.h" +#include "absl/base/nullability.h" #include "absl/strings/internal/resize_uninitialized.h" #include "absl/strings/numbers.h" #include "absl/strings/string_view.h" @@ -30,54 +32,6 @@ namespace absl { ABSL_NAMESPACE_BEGIN -AlphaNum::AlphaNum(Hex hex) { - static_assert(numbers_internal::kFastToBufferSize >= 32, - "This function only works when output buffer >= 32 bytes long"); - char* const end = &digits_[numbers_internal::kFastToBufferSize]; - auto real_width = - absl::numbers_internal::FastHexToBufferZeroPad16(hex.value, end - 16); - if (real_width >= hex.width) { - piece_ = absl::string_view(end - real_width, real_width); - } else { - // Pad first 16 chars because FastHexToBufferZeroPad16 pads only to 16 and - // max pad width can be up to 20. - std::memset(end - 32, hex.fill, 16); - // Patch up everything else up to the real_width. - std::memset(end - real_width - 16, hex.fill, 16); - piece_ = absl::string_view(end - hex.width, hex.width); - } -} - -AlphaNum::AlphaNum(Dec dec) { - assert(dec.width <= numbers_internal::kFastToBufferSize); - char* const end = &digits_[numbers_internal::kFastToBufferSize]; - char* const minfill = end - dec.width; - char* writer = end; - uint64_t value = dec.value; - bool neg = dec.neg; - while (value > 9) { - *--writer = '0' + (value % 10); - value /= 10; - } - *--writer = '0' + static_cast<char>(value); - if (neg) *--writer = '-'; - - ptrdiff_t fillers = writer - minfill; - if (fillers > 0) { - // Tricky: if the fill character is ' ', then it's <fill><+/-><digits> - // But...: if the fill character is '0', then it's <+/-><fill><digits> - bool add_sign_again = false; - if (neg && dec.fill == '0') { // If filling with '0', - ++writer; // ignore the sign we just added - add_sign_again = true; // and re-add the sign later. - } - writer -= fillers; - std::fill_n(writer, fillers, dec.fill); - if (add_sign_again) *--writer = '-'; - } - - piece_ = absl::string_view(writer, static_cast<size_t>(end - writer)); -} // ---------------------------------------------------------------------- // StrCat() @@ -86,9 +40,10 @@ AlphaNum::AlphaNum(Dec dec) { // of a mix of raw C strings, string_views, strings, and integer values. // ---------------------------------------------------------------------- +namespace { // Append is merely a version of memcpy that returns the address of the byte // after the area just overwritten. -static char* Append(char* out, const AlphaNum& x) { +absl::Nonnull<char*> Append(absl::Nonnull<char*> out, const AlphaNum& x) { // memcpy is allowed to overwrite arbitrary memory, so doing this after the // call would force an extra fetch of x.size(). char* after = out + x.size(); @@ -98,6 +53,8 @@ static char* Append(char* out, const AlphaNum& x) { return after; } +} // namespace + std::string StrCat(const AlphaNum& a, const AlphaNum& b) { std::string result; absl::strings_internal::STLStringResizeUninitialized(&result, @@ -141,6 +98,130 @@ std::string StrCat(const AlphaNum& a, const AlphaNum& b, const AlphaNum& c, namespace strings_internal { // Do not call directly - these are not part of the public API. +void STLStringAppendUninitializedAmortized(std::string* dest, + size_t to_append) { + strings_internal::AppendUninitializedTraits<std::string>::Append(dest, + to_append); +} + +template <typename Integer> +std::enable_if_t<std::is_integral<Integer>::value, std::string> IntegerToString( + Integer i) { + std::string str; + const auto /* either bool or std::false_type */ is_negative = + absl::numbers_internal::IsNegative(i); + const uint32_t digits = absl::numbers_internal::Base10Digits( + absl::numbers_internal::UnsignedAbsoluteValue(i)); + absl::strings_internal::STLStringResizeUninitialized( + &str, digits + static_cast<uint32_t>(is_negative)); + absl::numbers_internal::FastIntToBufferBackward(i, &str[str.size()], digits); + return str; +} + +template <> +std::string IntegerToString(long i) { // NOLINT + if (sizeof(i) <= sizeof(int)) { + return IntegerToString(static_cast<int>(i)); + } else { + return IntegerToString(static_cast<long long>(i)); // NOLINT + } +} + +template <> +std::string IntegerToString(unsigned long i) { // NOLINT + if (sizeof(i) <= sizeof(unsigned int)) { + return IntegerToString(static_cast<unsigned int>(i)); + } else { + return IntegerToString(static_cast<unsigned long long>(i)); // NOLINT + } +} + +template <typename Float> +std::enable_if_t<std::is_floating_point<Float>::value, std::string> +FloatToString(Float f) { + std::string result; + strings_internal::STLStringResizeUninitialized( + &result, numbers_internal::kSixDigitsToBufferSize); + char* start = &result[0]; + result.erase(numbers_internal::SixDigitsToBuffer(f, start)); + return result; +} + +std::string SingleArgStrCat(int x) { return IntegerToString(x); } +std::string SingleArgStrCat(unsigned int x) { return IntegerToString(x); } +// NOLINTNEXTLINE +std::string SingleArgStrCat(long x) { return IntegerToString(x); } +// NOLINTNEXTLINE +std::string SingleArgStrCat(unsigned long x) { return IntegerToString(x); } +// NOLINTNEXTLINE +std::string SingleArgStrCat(long long x) { return IntegerToString(x); } +// NOLINTNEXTLINE +std::string SingleArgStrCat(unsigned long long x) { return IntegerToString(x); } +std::string SingleArgStrCat(float x) { return FloatToString(x); } +std::string SingleArgStrCat(double x) { return FloatToString(x); } + +template <class Integer> +std::enable_if_t<std::is_integral<Integer>::value, void> AppendIntegerToString( + std::string& str, Integer i) { + const auto /* either bool or std::false_type */ is_negative = + absl::numbers_internal::IsNegative(i); + const uint32_t digits = absl::numbers_internal::Base10Digits( + absl::numbers_internal::UnsignedAbsoluteValue(i)); + absl::strings_internal::STLStringAppendUninitializedAmortized( + &str, digits + static_cast<uint32_t>(is_negative)); + absl::numbers_internal::FastIntToBufferBackward(i, &str[str.size()], digits); +} + +template <> +void AppendIntegerToString(std::string& str, long i) { // NOLINT + if (sizeof(i) <= sizeof(int)) { + return AppendIntegerToString(str, static_cast<int>(i)); + } else { + return AppendIntegerToString(str, static_cast<long long>(i)); // NOLINT + } +} + +template <> +void AppendIntegerToString(std::string& str, + unsigned long i) { // NOLINT + if (sizeof(i) <= sizeof(unsigned int)) { + return AppendIntegerToString(str, static_cast<unsigned int>(i)); + } else { + return AppendIntegerToString(str, + static_cast<unsigned long long>(i)); // NOLINT + } +} + +// `SingleArgStrAppend` overloads are defined here for the same reasons as with +// `SingleArgStrCat` above. +void SingleArgStrAppend(std::string& str, int x) { + return AppendIntegerToString(str, x); +} + +void SingleArgStrAppend(std::string& str, unsigned int x) { + return AppendIntegerToString(str, x); +} + +// NOLINTNEXTLINE +void SingleArgStrAppend(std::string& str, long x) { + return AppendIntegerToString(str, x); +} + +// NOLINTNEXTLINE +void SingleArgStrAppend(std::string& str, unsigned long x) { + return AppendIntegerToString(str, x); +} + +// NOLINTNEXTLINE +void SingleArgStrAppend(std::string& str, long long x) { + return AppendIntegerToString(str, x); +} + +// NOLINTNEXTLINE +void SingleArgStrAppend(std::string& str, unsigned long long x) { + return AppendIntegerToString(str, x); +} + std::string CatPieces(std::initializer_list<absl::string_view> pieces) { std::string result; size_t total_size = 0; @@ -169,15 +250,15 @@ std::string CatPieces(std::initializer_list<absl::string_view> pieces) { assert(((src).size() == 0) || \ (uintptr_t((src).data() - (dest).data()) > uintptr_t((dest).size()))) -void AppendPieces(std::string* dest, +void AppendPieces(absl::Nonnull<std::string*> dest, std::initializer_list<absl::string_view> pieces) { size_t old_size = dest->size(); - size_t total_size = old_size; + size_t to_append = 0; for (absl::string_view piece : pieces) { ASSERT_NO_OVERLAP(*dest, piece); - total_size += piece.size(); + to_append += piece.size(); } - strings_internal::STLStringResizeUninitializedAmortized(dest, total_size); + strings_internal::STLStringAppendUninitializedAmortized(dest, to_append); char* const begin = &(*dest)[0]; char* out = begin + old_size; @@ -193,17 +274,23 @@ void AppendPieces(std::string* dest, } // namespace strings_internal -void StrAppend(std::string* dest, const AlphaNum& a) { +void StrAppend(absl::Nonnull<std::string*> dest, const AlphaNum& a) { ASSERT_NO_OVERLAP(*dest, a); - dest->append(a.data(), a.size()); + std::string::size_type old_size = dest->size(); + strings_internal::STLStringAppendUninitializedAmortized(dest, a.size()); + char* const begin = &(*dest)[0]; + char* out = begin + old_size; + out = Append(out, a); + assert(out == begin + dest->size()); } -void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b) { +void StrAppend(absl::Nonnull<std::string*> dest, const AlphaNum& a, + const AlphaNum& b) { ASSERT_NO_OVERLAP(*dest, a); ASSERT_NO_OVERLAP(*dest, b); std::string::size_type old_size = dest->size(); - strings_internal::STLStringResizeUninitializedAmortized( - dest, old_size + a.size() + b.size()); + strings_internal::STLStringAppendUninitializedAmortized(dest, + a.size() + b.size()); char* const begin = &(*dest)[0]; char* out = begin + old_size; out = Append(out, a); @@ -211,14 +298,14 @@ void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b) { assert(out == begin + dest->size()); } -void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b, - const AlphaNum& c) { +void StrAppend(absl::Nonnull<std::string*> dest, const AlphaNum& a, + const AlphaNum& b, const AlphaNum& c) { ASSERT_NO_OVERLAP(*dest, a); ASSERT_NO_OVERLAP(*dest, b); ASSERT_NO_OVERLAP(*dest, c); std::string::size_type old_size = dest->size(); - strings_internal::STLStringResizeUninitializedAmortized( - dest, old_size + a.size() + b.size() + c.size()); + strings_internal::STLStringAppendUninitializedAmortized( + dest, a.size() + b.size() + c.size()); char* const begin = &(*dest)[0]; char* out = begin + old_size; out = Append(out, a); @@ -227,15 +314,15 @@ void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b, assert(out == begin + dest->size()); } -void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b, - const AlphaNum& c, const AlphaNum& d) { +void StrAppend(absl::Nonnull<std::string*> dest, const AlphaNum& a, + const AlphaNum& b, const AlphaNum& c, const AlphaNum& d) { ASSERT_NO_OVERLAP(*dest, a); ASSERT_NO_OVERLAP(*dest, b); ASSERT_NO_OVERLAP(*dest, c); ASSERT_NO_OVERLAP(*dest, d); std::string::size_type old_size = dest->size(); - strings_internal::STLStringResizeUninitializedAmortized( - dest, old_size + a.size() + b.size() + c.size() + d.size()); + strings_internal::STLStringAppendUninitializedAmortized( + dest, a.size() + b.size() + c.size() + d.size()); char* const begin = &(*dest)[0]; char* out = begin + old_size; out = Append(out, a); diff --git a/absl/strings/str_cat.h b/absl/strings/str_cat.h index 730b4d8c..ea2c4dca 100644 --- a/absl/strings/str_cat.h +++ b/absl/strings/str_cat.h @@ -87,15 +87,23 @@ #ifndef ABSL_STRINGS_STR_CAT_H_ #define ABSL_STRINGS_STR_CAT_H_ +#include <algorithm> #include <array> +#include <cassert> +#include <cstddef> #include <cstdint> +#include <cstring> #include <string> #include <type_traits> #include <utility> #include <vector> +#include "absl/base/attributes.h" +#include "absl/base/nullability.h" #include "absl/base/port.h" -#include "absl/strings/internal/has_absl_stringify.h" +#include "absl/meta/type_traits.h" +#include "absl/strings/has_absl_stringify.h" +#include "absl/strings/internal/resize_uninitialized.h" #include "absl/strings/internal/stringify_sink.h" #include "absl/strings/numbers.h" #include "absl/strings/string_view.h" @@ -198,9 +206,30 @@ struct Hex { !std::is_pointer<Int>::value>::type* = nullptr) : Hex(spec, static_cast<uint64_t>(v)) {} template <typename Pointee> - explicit Hex(Pointee* v, PadSpec spec = absl::kNoPad) + explicit Hex(absl::Nullable<Pointee*> v, PadSpec spec = absl::kNoPad) : Hex(spec, reinterpret_cast<uintptr_t>(v)) {} + template <typename S> + friend void AbslStringify(S& sink, Hex hex) { + static_assert( + numbers_internal::kFastToBufferSize >= 32, + "This function only works when output buffer >= 32 bytes long"); + char buffer[numbers_internal::kFastToBufferSize]; + char* const end = &buffer[numbers_internal::kFastToBufferSize]; + auto real_width = + absl::numbers_internal::FastHexToBufferZeroPad16(hex.value, end - 16); + if (real_width >= hex.width) { + sink.Append(absl::string_view(end - real_width, real_width)); + } else { + // Pad first 16 chars because FastHexToBufferZeroPad16 pads only to 16 and + // max pad width can be up to 20. + std::memset(end - 32, hex.fill, 16); + // Patch up everything else up to the real_width. + std::memset(end - real_width - 16, hex.fill, 16); + sink.Append(absl::string_view(end - hex.width, hex.width)); + } + } + private: Hex(PadSpec spec, uint64_t v) : value(v), @@ -229,12 +258,43 @@ struct Dec { typename std::enable_if<(sizeof(Int) <= 8)>::type* = nullptr) : value(v >= 0 ? static_cast<uint64_t>(v) : uint64_t{0} - static_cast<uint64_t>(v)), - width(spec == absl::kNoPad - ? 1 - : spec >= absl::kSpacePad2 ? spec - absl::kSpacePad2 + 2 - : spec - absl::kZeroPad2 + 2), + width(spec == absl::kNoPad ? 1 + : spec >= absl::kSpacePad2 ? spec - absl::kSpacePad2 + 2 + : spec - absl::kZeroPad2 + 2), fill(spec >= absl::kSpacePad2 ? ' ' : '0'), neg(v < 0) {} + + template <typename S> + friend void AbslStringify(S& sink, Dec dec) { + assert(dec.width <= numbers_internal::kFastToBufferSize); + char buffer[numbers_internal::kFastToBufferSize]; + char* const end = &buffer[numbers_internal::kFastToBufferSize]; + char* const minfill = end - dec.width; + char* writer = end; + uint64_t val = dec.value; + while (val > 9) { + *--writer = '0' + (val % 10); + val /= 10; + } + *--writer = '0' + static_cast<char>(val); + if (dec.neg) *--writer = '-'; + + ptrdiff_t fillers = writer - minfill; + if (fillers > 0) { + // Tricky: if the fill character is ' ', then it's <fill><+/-><digits> + // But...: if the fill character is '0', then it's <+/-><fill><digits> + bool add_sign_again = false; + if (dec.neg && dec.fill == '0') { // If filling with '0', + ++writer; // ignore the sign we just added + add_sign_again = true; // and re-add the sign later. + } + writer -= fillers; + std::fill_n(writer, fillers, dec.fill); + if (add_sign_again) *--writer = '-'; + } + + sink.Append(absl::string_view(writer, static_cast<size_t>(end - writer))); + } }; // ----------------------------------------------------------------------------- @@ -282,28 +342,30 @@ class AlphaNum { AlphaNum(double f) // NOLINT(runtime/explicit) : piece_(digits_, numbers_internal::SixDigitsToBuffer(f, digits_)) {} - AlphaNum(Hex hex); // NOLINT(runtime/explicit) - AlphaNum(Dec dec); // NOLINT(runtime/explicit) - template <size_t size> AlphaNum( // NOLINT(runtime/explicit) - const strings_internal::AlphaNumBuffer<size>& buf) + const strings_internal::AlphaNumBuffer<size>& buf + ABSL_ATTRIBUTE_LIFETIME_BOUND) : piece_(&buf.data[0], buf.size) {} - AlphaNum(const char* c_str) // NOLINT(runtime/explicit) - : piece_(NullSafeStringView(c_str)) {} // NOLINT(runtime/explicit) - AlphaNum(absl::string_view pc) : piece_(pc) {} // NOLINT(runtime/explicit) + AlphaNum(absl::Nullable<const char*> c_str // NOLINT(runtime/explicit) + ABSL_ATTRIBUTE_LIFETIME_BOUND) + : piece_(NullSafeStringView(c_str)) {} + AlphaNum(absl::string_view pc // NOLINT(runtime/explicit) + ABSL_ATTRIBUTE_LIFETIME_BOUND) + : piece_(pc) {} template <typename T, typename = typename std::enable_if< - strings_internal::HasAbslStringify<T>::value>::type> - AlphaNum( // NOLINT(runtime/explicit) - const T& v, // NOLINT(runtime/explicit) - strings_internal::StringifySink&& sink = {}) // NOLINT(runtime/explicit) + HasAbslStringify<T>::value>::type> + AlphaNum( // NOLINT(runtime/explicit) + const T& v ABSL_ATTRIBUTE_LIFETIME_BOUND, + strings_internal::StringifySink&& sink ABSL_ATTRIBUTE_LIFETIME_BOUND = {}) : piece_(strings_internal::ExtractStringification(sink, v)) {} template <typename Allocator> AlphaNum( // NOLINT(runtime/explicit) - const std::basic_string<char, std::char_traits<char>, Allocator>& str) + const std::basic_string<char, std::char_traits<char>, Allocator>& str + ABSL_ATTRIBUTE_LIFETIME_BOUND) : piece_(str) {} // Use string literals ":" instead of character literals ':'. @@ -313,17 +375,27 @@ class AlphaNum { AlphaNum& operator=(const AlphaNum&) = delete; absl::string_view::size_type size() const { return piece_.size(); } - const char* data() const { return piece_.data(); } + absl::Nullable<const char*> data() const { return piece_.data(); } absl::string_view Piece() const { return piece_; } - // Normal enums are already handled by the integer formatters. - // This overload matches only scoped enums. + // Match unscoped enums. Use integral promotion so that a `char`-backed + // enum becomes a wider integral type AlphaNum will accept. template <typename T, typename = typename std::enable_if< - std::is_enum<T>{} && !std::is_convertible<T, int>{} && - !strings_internal::HasAbslStringify<T>::value>::type> + std::is_enum<T>{} && std::is_convertible<T, int>{} && + !HasAbslStringify<T>::value>::type> + AlphaNum(T e) // NOLINT(runtime/explicit) + : AlphaNum(+e) {} + + // This overload matches scoped enums. We must explicitly cast to the + // underlying type, but use integral promotion for the same reason as above. + template <typename T, + typename std::enable_if<std::is_enum<T>{} && + !std::is_convertible<T, int>{} && + !HasAbslStringify<T>::value, + char*>::type = nullptr> AlphaNum(T e) // NOLINT(runtime/explicit) - : AlphaNum(static_cast<typename std::underlying_type<T>::type>(e)) {} + : AlphaNum(+static_cast<typename std::underlying_type<T>::type>(e)) {} // vector<bool>::reference and const_reference require special help to // convert to `AlphaNum` because it requires two user defined conversions. @@ -373,13 +445,48 @@ namespace strings_internal { // Do not call directly - this is not part of the public API. std::string CatPieces(std::initializer_list<absl::string_view> pieces); -void AppendPieces(std::string* dest, +void AppendPieces(absl::Nonnull<std::string*> dest, std::initializer_list<absl::string_view> pieces); +void STLStringAppendUninitializedAmortized(std::string* dest, size_t to_append); + +// `SingleArgStrCat` overloads take built-in `int`, `long` and `long long` types +// (signed / unsigned) to avoid ambiguity on the call side. If we used int32_t +// and int64_t, then at least one of the three (`int` / `long` / `long long`) +// would have been ambiguous when passed to `SingleArgStrCat`. +std::string SingleArgStrCat(int x); +std::string SingleArgStrCat(unsigned int x); +std::string SingleArgStrCat(long x); // NOLINT +std::string SingleArgStrCat(unsigned long x); // NOLINT +std::string SingleArgStrCat(long long x); // NOLINT +std::string SingleArgStrCat(unsigned long long x); // NOLINT +std::string SingleArgStrCat(float x); +std::string SingleArgStrCat(double x); + +// `SingleArgStrAppend` overloads are defined here for the same reasons as with +// `SingleArgStrCat` above. +void SingleArgStrAppend(std::string& str, int x); +void SingleArgStrAppend(std::string& str, unsigned int x); +void SingleArgStrAppend(std::string& str, long x); // NOLINT +void SingleArgStrAppend(std::string& str, unsigned long x); // NOLINT +void SingleArgStrAppend(std::string& str, long long x); // NOLINT +void SingleArgStrAppend(std::string& str, unsigned long long x); // NOLINT + +template <typename T, + typename = std::enable_if_t<std::is_arithmetic<T>::value && + !std::is_same<T, char>::value && + !std::is_same<T, bool>::value>> +using EnableIfFastCase = T; + } // namespace strings_internal ABSL_MUST_USE_RESULT inline std::string StrCat() { return std::string(); } +template <typename T> +ABSL_MUST_USE_RESULT inline std::string StrCat( + strings_internal::EnableIfFastCase<T> a) { + return strings_internal::SingleArgStrCat(a); +} ABSL_MUST_USE_RESULT inline std::string StrCat(const AlphaNum& a) { return std::string(a.data(), a.size()); } @@ -427,24 +534,87 @@ ABSL_MUST_USE_RESULT inline std::string StrCat( // absl::string_view p = s; // StrAppend(&s, p); -inline void StrAppend(std::string*) {} -void StrAppend(std::string* dest, const AlphaNum& a); -void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b); -void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b, - const AlphaNum& c); -void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b, - const AlphaNum& c, const AlphaNum& d); +inline void StrAppend(absl::Nonnull<std::string*>) {} +void StrAppend(absl::Nonnull<std::string*> dest, const AlphaNum& a); +void StrAppend(absl::Nonnull<std::string*> dest, const AlphaNum& a, + const AlphaNum& b); +void StrAppend(absl::Nonnull<std::string*> dest, const AlphaNum& a, + const AlphaNum& b, const AlphaNum& c); +void StrAppend(absl::Nonnull<std::string*> dest, const AlphaNum& a, + const AlphaNum& b, const AlphaNum& c, const AlphaNum& d); // Support 5 or more arguments template <typename... AV> -inline void StrAppend(std::string* dest, const AlphaNum& a, const AlphaNum& b, - const AlphaNum& c, const AlphaNum& d, const AlphaNum& e, - const AV&... args) { +inline void StrAppend(absl::Nonnull<std::string*> dest, const AlphaNum& a, + const AlphaNum& b, const AlphaNum& c, const AlphaNum& d, + const AlphaNum& e, const AV&... args) { strings_internal::AppendPieces( dest, {a.Piece(), b.Piece(), c.Piece(), d.Piece(), e.Piece(), static_cast<const AlphaNum&>(args).Piece()...}); } +template <class String, class T> +std::enable_if_t< + std::is_integral<absl::strings_internal::EnableIfFastCase<T>>::value, void> +StrAppend(absl::Nonnull<String*> result, T i) { + return absl::strings_internal::SingleArgStrAppend(*result, i); +} + +// This overload is only selected if all the parameters are numbers that can be +// handled quickly. +// Later we can look into how we can extend this to more general argument +// mixtures without bloating codegen too much, or copying unnecessarily. +template <typename String, typename... T> +std::enable_if_t< + (sizeof...(T) > 1), + std::common_type_t<std::conditional_t< + true, void, absl::strings_internal::EnableIfFastCase<T>>...>> +StrAppend(absl::Nonnull<String*> str, T... args) { + // Do not add unnecessary variables, logic, or even "free" lambdas here. + // They can add overhead for the compiler and/or at run time. + // Furthermore, assume this function will be inlined. + // This function is carefully tailored to be able to be largely optimized away + // so that it becomes near-equivalent to the caller handling each argument + // individually while minimizing register pressure, so that the compiler + // can inline it with minimal overhead. + + // First, calculate the total length, so we can perform just a single resize. + // Save all the lengths for later. + size_t total_length = 0; + const ptrdiff_t lengths[] = { + absl::numbers_internal::GetNumDigitsOrNegativeIfNegative(args)...}; + for (const ptrdiff_t possibly_negative_length : lengths) { + // Lengths are negative for negative numbers. Keep them for later use, but + // take their absolute values for calculating total lengths; + total_length += possibly_negative_length < 0 + ? static_cast<size_t>(-possibly_negative_length) + : static_cast<size_t>(possibly_negative_length); + } + + // Now reserve space for all the arguments. + const size_t old_size = str->size(); + absl::strings_internal::STLStringAppendUninitializedAmortized(str, + total_length); + + // Finally, output each argument one-by-one, from left to right. + size_t i = 0; // The current argument we're processing + ptrdiff_t n; // The length of the current argument + typename String::pointer pos = &(*str)[old_size]; + using SomeTrivialEmptyType = std::false_type; + // Ugly code due to the lack of C++14 fold expression makes us. + const SomeTrivialEmptyType dummy1; + for (const SomeTrivialEmptyType& dummy2 : + {(/* Comma expressions are poor man's C++17 fold expression for C++14 */ + (void)(n = lengths[i]), + (void)(n < 0 ? (void)(*pos++ = '-'), (n = ~n) : 0), + (void)absl::numbers_internal::FastIntToBufferBackward( + absl::numbers_internal::UnsignedAbsoluteValue(std::move(args)), + pos += n, static_cast<uint32_t>(n)), + (void)++i, dummy1)...}) { + (void)dummy2; // Remove & migrate to fold expressions in C++17 + } +} + // Helper function for the future StrCat default floating-point format, %.6g // This is fast. inline strings_internal::AlphaNumBuffer< diff --git a/absl/strings/str_cat_benchmark.cc b/absl/strings/str_cat_benchmark.cc index 02c4dbe6..0a851e7b 100644 --- a/absl/strings/str_cat_benchmark.cc +++ b/absl/strings/str_cat_benchmark.cc @@ -12,12 +12,20 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "absl/strings/str_cat.h" - +#include <array> #include <cstdint> +#include <cstdio> +#include <cstdlib> +#include <cstring> #include <string> +#include <tuple> +#include <utility> #include "benchmark/benchmark.h" +#include "absl/random/log_uniform_int_distribution.h" +#include "absl/random/random.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" #include "absl/strings/substitute.h" namespace { @@ -137,39 +145,65 @@ void BM_DoubleToString_By_SixDigits(benchmark::State& state) { } BENCHMARK(BM_DoubleToString_By_SixDigits); -template <typename... Chunks> -void BM_StrAppendImpl(benchmark::State& state, size_t total_bytes, - Chunks... chunks) { +template <typename Table, size_t... Index> +void BM_StrAppendImpl(benchmark::State& state, Table table, size_t total_bytes, + std::index_sequence<Index...>) { for (auto s : state) { + const size_t table_size = table.size(); + size_t i = 0; std::string result; while (result.size() < total_bytes) { - absl::StrAppend(&result, chunks...); + absl::StrAppend(&result, std::get<Index>(table[i])...); benchmark::DoNotOptimize(result); + ++i; + i -= i >= table_size ? table_size : 0; } } } -void BM_StrAppend(benchmark::State& state) { - const int total_bytes = state.range(0); +template <typename Array> +void BM_StrAppend(benchmark::State& state, Array&& table) { + const size_t total_bytes = state.range(0); const int chunks_at_a_time = state.range(1); - const absl::string_view kChunk = "0123456789"; switch (chunks_at_a_time) { case 1: - return BM_StrAppendImpl(state, total_bytes, kChunk); + return BM_StrAppendImpl(state, std::forward<Array>(table), total_bytes, + std::make_index_sequence<1>()); case 2: - return BM_StrAppendImpl(state, total_bytes, kChunk, kChunk); + return BM_StrAppendImpl(state, std::forward<Array>(table), total_bytes, + std::make_index_sequence<2>()); case 4: - return BM_StrAppendImpl(state, total_bytes, kChunk, kChunk, kChunk, - kChunk); + return BM_StrAppendImpl(state, std::forward<Array>(table), total_bytes, + std::make_index_sequence<4>()); case 8: - return BM_StrAppendImpl(state, total_bytes, kChunk, kChunk, kChunk, - kChunk, kChunk, kChunk, kChunk, kChunk); + return BM_StrAppendImpl(state, std::forward<Array>(table), total_bytes, + std::make_index_sequence<8>()); default: std::abort(); } } +void BM_StrAppendStr(benchmark::State& state) { + using T = absl::string_view; + using Row = std::tuple<T, T, T, T, T, T, T, T>; + constexpr absl::string_view kChunk = "0123456789"; + Row row = {kChunk, kChunk, kChunk, kChunk, kChunk, kChunk, kChunk, kChunk}; + return BM_StrAppend(state, std::array<Row, 1>({row})); +} + +template <typename T> +void BM_StrAppendInt(benchmark::State& state) { + absl::BitGen rng; + absl::log_uniform_int_distribution<T> dist; + std::array<std::tuple<T, T, T, T, T, T, T, T>, (1 << 7)> table; + for (size_t i = 0; i < table.size(); ++i) { + table[i] = {dist(rng), dist(rng), dist(rng), dist(rng), + dist(rng), dist(rng), dist(rng), dist(rng)}; + } + return BM_StrAppend(state, table); +} + template <typename B> void StrAppendConfig(B* benchmark) { for (int bytes : {10, 100, 1000, 10000}) { @@ -182,6 +216,50 @@ void StrAppendConfig(B* benchmark) { } } -BENCHMARK(BM_StrAppend)->Apply(StrAppendConfig); +BENCHMARK(BM_StrAppendStr)->Apply(StrAppendConfig); +BENCHMARK(BM_StrAppendInt<int64_t>)->Apply(StrAppendConfig); +BENCHMARK(BM_StrAppendInt<uint64_t>)->Apply(StrAppendConfig); +BENCHMARK(BM_StrAppendInt<int32_t>)->Apply(StrAppendConfig); +BENCHMARK(BM_StrAppendInt<uint32_t>)->Apply(StrAppendConfig); + +template <typename... Chunks> +void BM_StrCatImpl(benchmark::State& state, + Chunks... chunks) { + for (auto s : state) { + std::string result = absl::StrCat(chunks...); + benchmark::DoNotOptimize(result); + } +} + +void BM_StrCat(benchmark::State& state) { + const int chunks_at_a_time = state.range(0); + const absl::string_view kChunk = "0123456789"; + + switch (chunks_at_a_time) { + case 1: + return BM_StrCatImpl(state, kChunk); + case 2: + return BM_StrCatImpl(state, kChunk, kChunk); + case 3: + return BM_StrCatImpl(state, kChunk, kChunk, kChunk); + case 4: + return BM_StrCatImpl(state, kChunk, kChunk, kChunk, kChunk); + default: + std::abort(); + } +} + +BENCHMARK(BM_StrCat)->Arg(1)->Arg(2)->Arg(3)->Arg(4); + +void BM_StrCat_int(benchmark::State& state) { + int i = 0; + for (auto s : state) { + std::string result = absl::StrCat(i); + benchmark::DoNotOptimize(result); + i = IncrementAlternatingSign(i); + } +} + +BENCHMARK(BM_StrCat_int); } // namespace diff --git a/absl/strings/str_cat_test.cc b/absl/strings/str_cat_test.cc index 2d74245e..b30a86fe 100644 --- a/absl/strings/str_cat_test.cc +++ b/absl/strings/str_cat_test.cc @@ -16,13 +16,16 @@ #include "absl/strings/str_cat.h" +#include <cstddef> #include <cstdint> +#include <cstdlib> +#include <limits> #include <string> #include <vector> #include "gtest/gtest.h" #include "absl/strings/str_format.h" -#include "absl/strings/substitute.h" +#include "absl/strings/string_view.h" #ifdef __ANDROID__ // Android assert messages only go to system log, so death tests cannot inspect @@ -36,6 +39,24 @@ namespace { +template <typename Integer> +void VerifyInteger(Integer value) { + const std::string expected = std::to_string(value); + + EXPECT_EQ(absl::StrCat(value), expected); + + const char* short_prefix = "x"; + const char* long_prefix = "2;k.msabxiuow2[09i;o3k21-93-9=29]"; + + std::string short_str = short_prefix; + absl::StrAppend(&short_str, value); + EXPECT_EQ(short_str, short_prefix + expected); + + std::string long_str = long_prefix; + absl::StrAppend(&long_str, value); + EXPECT_EQ(long_str, long_prefix + expected); +} + // Test absl::StrCat of ints and longs of various sizes and signdedness. TEST(StrCat, Ints) { const short s = -1; // NOLINT(runtime/int) @@ -65,6 +86,34 @@ TEST(StrCat, Ints) { EXPECT_EQ(answer, "-9-12"); answer = absl::StrCat(uintptr, 0); EXPECT_EQ(answer, "130"); + + for (const uint32_t base : {2u, 10u}) { + for (const int extra_shift : {0, 12}) { + for (uint64_t i = 0; i < (1 << 8); ++i) { + uint64_t j = i; + while (true) { + uint64_t v = j ^ (extra_shift != 0 ? (j << extra_shift) * base : 0); + VerifyInteger(static_cast<bool>(v)); + VerifyInteger(static_cast<wchar_t>(v)); + VerifyInteger(static_cast<signed char>(v)); + VerifyInteger(static_cast<unsigned char>(v)); + VerifyInteger(static_cast<short>(v)); // NOLINT + VerifyInteger(static_cast<unsigned short>(v)); // NOLINT + VerifyInteger(static_cast<int>(v)); // NOLINT + VerifyInteger(static_cast<unsigned int>(v)); // NOLINT + VerifyInteger(static_cast<long>(v)); // NOLINT + VerifyInteger(static_cast<unsigned long>(v)); // NOLINT + VerifyInteger(static_cast<long long>(v)); // NOLINT + VerifyInteger(static_cast<unsigned long long>(v)); // NOLINT + const uint64_t next = j == 0 ? 1 : j * base; + if (next <= j) { + break; + } + j = next; + } + } + } + } } TEST(StrCat, Enums) { @@ -662,4 +711,20 @@ TEST(StrCat, AbslStringifyWithEnum) { EXPECT_EQ(absl::StrCat(e), "Choices"); } +template <typename Integer> +void CheckSingleArgumentIntegerLimits() { + Integer max = std::numeric_limits<Integer>::max(); + Integer min = std::numeric_limits<Integer>::min(); + + EXPECT_EQ(absl::StrCat(max), std::to_string(max)); + EXPECT_EQ(absl::StrCat(min), std::to_string(min)); +} + +TEST(StrCat, SingleArgumentLimits) { + CheckSingleArgumentIntegerLimits<int32_t>(); + CheckSingleArgumentIntegerLimits<uint32_t>(); + CheckSingleArgumentIntegerLimits<int64_t>(); + CheckSingleArgumentIntegerLimits<uint64_t>(); +} + } // namespace diff --git a/absl/strings/str_format.h b/absl/strings/str_format.h index 3536b70e..66b6af58 100644 --- a/absl/strings/str_format.h +++ b/absl/strings/str_format.h @@ -36,10 +36,12 @@ // * `absl::StreamFormat()` to more efficiently write a format string to a // stream, such as`std::cout`. // * `absl::PrintF()`, `absl::FPrintF()` and `absl::SNPrintF()` as -// replacements for `std::printf()`, `std::fprintf()` and `std::snprintf()`. +// drop-in replacements for `std::printf()`, `std::fprintf()` and +// `std::snprintf()`. // -// Note: a version of `std::sprintf()` is not supported as it is -// generally unsafe due to buffer overflows. +// Note: An `absl::SPrintF()` drop-in replacement is not supported as it +// is generally unsafe due to buffer overflows. Use `absl::StrFormat` which +// returns the string as output instead of expecting a pre-allocated buffer. // // Additionally, you can provide a format string (and its associated arguments) // using one of the following abstractions: @@ -70,14 +72,21 @@ #ifndef ABSL_STRINGS_STR_FORMAT_H_ #define ABSL_STRINGS_STR_FORMAT_H_ +#include <cstdint> #include <cstdio> #include <string> +#include <type_traits> +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/nullability.h" #include "absl/strings/internal/str_format/arg.h" // IWYU pragma: export #include "absl/strings/internal/str_format/bind.h" // IWYU pragma: export #include "absl/strings/internal/str_format/checker.h" // IWYU pragma: export #include "absl/strings/internal/str_format/extension.h" // IWYU pragma: export #include "absl/strings/internal/str_format/parser.h" // IWYU pragma: export +#include "absl/strings/string_view.h" +#include "absl/types/span.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -102,7 +111,8 @@ class UntypedFormatSpec { explicit UntypedFormatSpec(string_view s) : spec_(s) {} protected: - explicit UntypedFormatSpec(const str_format_internal::ParsedFormatBase* pc) + explicit UntypedFormatSpec( + absl::Nonnull<const str_format_internal::ParsedFormatBase*> pc) : spec_(pc) {} private: @@ -142,7 +152,7 @@ str_format_internal::StreamedWrapper<T> FormatStreamed(const T& v) { // EXPECT_EQ(8, n); class FormatCountCapture { public: - explicit FormatCountCapture(int* p) : p_(p) {} + explicit FormatCountCapture(absl::Nonnull<int*> p) : p_(p) {} private: // FormatCountCaptureHelper is used to define FormatConvertImpl() for this @@ -151,8 +161,8 @@ class FormatCountCapture { // Unused() is here because of the false positive from -Wunused-private-field // p_ is used in the templated function of the friend FormatCountCaptureHelper // class. - int* Unused() { return p_; } - int* p_; + absl::Nonnull<int*> Unused() { return p_; } + absl::Nonnull<int*> p_; }; // FormatSpec @@ -248,22 +258,23 @@ class FormatCountCapture { // `v` uses `d` for signed integer values, `u` for unsigned integer values, `g` // for floating point values, and formats boolean values as "true"/"false" // (instead of 1 or 0 for booleans formatted using d). `const char*` is not -// supported; please use `std:string` and `string_view`. `char` is also not +// supported; please use `std::string` and `string_view`. `char` is also not // supported due to ambiguity of the type. This specifier does not support // modifiers. // // The `FormatSpec` intrinsically supports all of these fundamental C++ types: // -// * Characters: `char`, `signed char`, `unsigned char` +// * Characters: `char`, `signed char`, `unsigned char`, `wchar_t` // * Integers: `int`, `short`, `unsigned short`, `unsigned`, `long`, // `unsigned long`, `long long`, `unsigned long long` +// * Enums: printed as their underlying integral value // * Floating-point: `float`, `double`, `long double` // // However, in the `str_format` library, a format conversion specifies a broader // C++ conceptual category instead of an exact type. For example, `%s` binds to -// any string-like argument, so `std::string`, `absl::string_view`, and -// `const char*` are all accepted. Likewise, `%d` accepts any integer-like -// argument, etc. +// any string-like argument, so `std::string`, `std::wstring`, +// `absl::string_view`, `const char*`, and `const wchar_t*` are all accepted. +// Likewise, `%d` accepts any integer-like argument, etc. template <typename... Args> using FormatSpec = str_format_internal::FormatSpecTemplate< @@ -284,8 +295,8 @@ using FormatSpec = str_format_internal::FormatSpecTemplate< // Example: // // // Verified at compile time. -// absl::ParsedFormat<'s', 'd'> formatString("Welcome to %s, Number %d!"); -// absl::StrFormat(formatString, "TheVillage", 6); +// absl::ParsedFormat<'s', 'd'> format_string("Welcome to %s, Number %d!"); +// absl::StrFormat(format_string, "TheVillage", 6); // // // Verified at runtime. // auto format_runtime = absl::ParsedFormat<'d'>::New(format_string); @@ -366,7 +377,7 @@ ABSL_MUST_USE_RESULT std::string StrFormat(const FormatSpec<Args...>& format, // std::string orig("For example PI is approximately "); // std::cout << StrAppendFormat(&orig, "%12.6f", 3.14); template <typename... Args> -std::string& StrAppendFormat(std::string* dst, +std::string& StrAppendFormat(absl::Nonnull<std::string*> dst, const FormatSpec<Args...>& format, const Args&... args) { return str_format_internal::AppendPack( @@ -378,7 +389,7 @@ std::string& StrAppendFormat(std::string* dst, // // Writes to an output stream given a format string and zero or more arguments, // generally in a manner that is more efficient than streaming the result of -// `absl:: StrFormat()`. The returned object must be streamed before the full +// `absl::StrFormat()`. The returned object must be streamed before the full // expression ends. // // Example: @@ -426,7 +437,7 @@ int PrintF(const FormatSpec<Args...>& format, const Args&... args) { // Outputs: "The capital of Mongolia is Ulaanbaatar" // template <typename... Args> -int FPrintF(std::FILE* output, const FormatSpec<Args...>& format, +int FPrintF(absl::Nonnull<std::FILE*> output, const FormatSpec<Args...>& format, const Args&... args) { return str_format_internal::FprintF( output, str_format_internal::UntypedFormatSpecImpl::Extract(format), @@ -455,8 +466,8 @@ int FPrintF(std::FILE* output, const FormatSpec<Args...>& format, // Post-condition: output == "The capital of Mongolia is Ulaanbaatar" // template <typename... Args> -int SNPrintF(char* output, std::size_t size, const FormatSpec<Args...>& format, - const Args&... args) { +int SNPrintF(absl::Nonnull<char*> output, std::size_t size, + const FormatSpec<Args...>& format, const Args&... args) { return str_format_internal::SnprintF( output, size, str_format_internal::UntypedFormatSpecImpl::Extract(format), {str_format_internal::FormatArgImpl(args)...}); @@ -489,7 +500,7 @@ class FormatRawSink { template <typename T, typename = typename std::enable_if<std::is_constructible< str_format_internal::FormatRawSinkImpl, T*>::value>::type> - FormatRawSink(T* raw) // NOLINT + FormatRawSink(absl::Nonnull<T*> raw) // NOLINT : sink_(raw) {} private: @@ -846,14 +857,16 @@ class FormatSink { } // Support `absl::Format(&sink, format, args...)`. - friend void AbslFormatFlush(FormatSink* sink, absl::string_view v) { + friend void AbslFormatFlush(absl::Nonnull<FormatSink*> sink, + absl::string_view v) { sink->Append(v); } private: friend str_format_internal::FormatSinkImpl; - explicit FormatSink(str_format_internal::FormatSinkImpl* s) : sink_(s) {} - str_format_internal::FormatSinkImpl* sink_; + explicit FormatSink(absl::Nonnull<str_format_internal::FormatSinkImpl*> s) + : sink_(s) {} + absl::Nonnull<str_format_internal::FormatSinkImpl*> sink_; }; // FormatConvertResult diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc index 5198fb33..3c52be1e 100644 --- a/absl/strings/str_format_test.cc +++ b/absl/strings/str_format_test.cc @@ -14,16 +14,22 @@ #include "absl/strings/str_format.h" +#include <cerrno> #include <cstdarg> #include <cstdint> #include <cstdio> +#include <ostream> +#include <sstream> #include <string> +#include <type_traits> -#include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/base/macros.h" #include "absl/strings/cord.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" +#include "absl/types/span.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -628,6 +634,10 @@ TEST(StrFormat, BehavesAsDocumented) { const int& something = *reinterpret_cast<const int*>(ptr_value); EXPECT_EQ(StrFormat("%p", &something), StrFormat("0x%x", ptr_value)); + // The output of formatting a null pointer is not documented as being a + // specific thing, but the attempt should at least compile. + (void)StrFormat("%p", nullptr); + // Output widths are supported, with optional flags. EXPECT_EQ(StrFormat("%3d", 1), " 1"); EXPECT_EQ(StrFormat("%3d", 123456), "123456"); @@ -638,6 +648,8 @@ TEST(StrFormat, BehavesAsDocumented) { EXPECT_EQ(StrFormat("%#o", 10), "012"); EXPECT_EQ(StrFormat("%#x", 15), "0xf"); EXPECT_EQ(StrFormat("%04d", 8), "0008"); + EXPECT_EQ(StrFormat("%#04x", 0), "0000"); + EXPECT_EQ(StrFormat("%#04x", 1), "0x01"); // Posix positional substitution. EXPECT_EQ(absl::StrFormat("%2$s, %3$s, %1$s!", "vici", "veni", "vidi"), "veni, vidi, vici!"); diff --git a/absl/strings/str_join.h b/absl/strings/str_join.h index ee5ae7ef..6a92c0f5 100644 --- a/absl/strings/str_join.h +++ b/absl/strings/str_join.h @@ -80,7 +80,7 @@ ABSL_NAMESPACE_BEGIN // absl::StrJoin(v, ", ", [](std::string* out, absl::Duration dur) { // absl::StrAppend(out, absl::FormatDuration(dur)); // }); -// EXPECT_EQ("1s, 10ms", s); +// EXPECT_EQ(s, "1s, 10ms"); // // The following standard formatters are provided within this file: // @@ -164,21 +164,21 @@ DereferenceFormatter() { // // of `absl::string_view` or even `const char*`. // std::vector<std::string> v = {"foo", "bar", "baz"}; // std::string s = absl::StrJoin(v, "-"); -// EXPECT_EQ("foo-bar-baz", s); +// EXPECT_EQ(s, "foo-bar-baz"); // // Example 2: // // Joins the values in the given `std::initializer_list<>` specified using // // brace initialization. This pattern also works with an initializer_list // // of ints or `absl::string_view` -- any `AlphaNum`-compatible type. // std::string s = absl::StrJoin({"foo", "bar", "baz"}, "-"); -// EXPECT_EQ("foo-bar-baz", s); +// EXPECT_EQs, "foo-bar-baz"); // // Example 3: // // Joins a collection of ints. This pattern also works with floats, // // doubles, int64s -- any `StrCat()`-compatible type. // std::vector<int> v = {1, 2, 3, -4}; // std::string s = absl::StrJoin(v, "-"); -// EXPECT_EQ("1-2-3--4", s); +// EXPECT_EQ(s, "1-2-3--4"); // // Example 4: // // Joins a collection of pointer-to-int. By default, pointers are @@ -189,7 +189,7 @@ DereferenceFormatter() { // int x = 1, y = 2, z = 3; // std::vector<int*> v = {&x, &y, &z}; // std::string s = absl::StrJoin(v, "-"); -// EXPECT_EQ("1-2-3", s); +// EXPECT_EQ(s, "1-2-3"); // // Example 5: // // Dereferencing of `std::unique_ptr<>` is also supported: @@ -198,42 +198,42 @@ DereferenceFormatter() { // v.emplace_back(new int(2)); // v.emplace_back(new int(3)); // std::string s = absl::StrJoin(v, "-"); -// EXPECT_EQ("1-2-3", s); +// EXPECT_EQ(s, "1-2-3"); // // Example 6: // // Joins a `std::map`, with each key-value pair separated by an equals // // sign. This pattern would also work with, say, a // // `std::vector<std::pair<>>`. // std::map<std::string, int> m = { -// std::make_pair("a", 1), -// std::make_pair("b", 2), -// std::make_pair("c", 3)}; +// {"a", 1}, +// {"b", 2}, +// {"c", 3}}; // std::string s = absl::StrJoin(m, ",", absl::PairFormatter("=")); -// EXPECT_EQ("a=1,b=2,c=3", s); +// EXPECT_EQ(s, "a=1,b=2,c=3"); // // Example 7: // // These examples show how `absl::StrJoin()` handles a few common edge // // cases: // std::vector<std::string> v_empty; -// EXPECT_EQ("", absl::StrJoin(v_empty, "-")); +// EXPECT_EQ(absl::StrJoin(v_empty, "-"), ""); // // std::vector<std::string> v_one_item = {"foo"}; -// EXPECT_EQ("foo", absl::StrJoin(v_one_item, "-")); +// EXPECT_EQ(absl::StrJoin(v_one_item, "-"), "foo"); // // std::vector<std::string> v_empty_string = {""}; -// EXPECT_EQ("", absl::StrJoin(v_empty_string, "-")); +// EXPECT_EQ(absl::StrJoin(v_empty_string, "-"), ""); // // std::vector<std::string> v_one_item_empty_string = {"a", ""}; -// EXPECT_EQ("a-", absl::StrJoin(v_one_item_empty_string, "-")); +// EXPECT_EQ(absl::StrJoin(v_one_item_empty_string, "-"), "a-"); // // std::vector<std::string> v_two_empty_string = {"", ""}; -// EXPECT_EQ("-", absl::StrJoin(v_two_empty_string, "-")); +// EXPECT_EQ(absl::StrJoin(v_two_empty_string, "-"), "-"); // // Example 8: // // Joins a `std::tuple<T...>` of heterogeneous types, converting each to // // a std::string using the `absl::AlphaNum` class. // std::string s = absl::StrJoin(std::make_tuple(123, "abc", 0.456), "-"); -// EXPECT_EQ("123-abc-0.456", s); +// EXPECT_EQ(s, "123-abc-0.456"); template <typename Iterator, typename Formatter> std::string StrJoin(Iterator start, Iterator end, absl::string_view sep, diff --git a/absl/strings/str_join_test.cc b/absl/strings/str_join_test.cc index c986e863..449f95be 100644 --- a/absl/strings/str_join_test.cc +++ b/absl/strings/str_join_test.cc @@ -25,8 +25,9 @@ #include <map> #include <memory> #include <ostream> +#include <string> #include <tuple> -#include <type_traits> +#include <utility> #include <vector> #include "gtest/gtest.h" diff --git a/absl/strings/str_replace.cc b/absl/strings/str_replace.cc index 2bd5fa98..a7ab52fe 100644 --- a/absl/strings/str_replace.cc +++ b/absl/strings/str_replace.cc @@ -14,7 +14,16 @@ #include "absl/strings/str_replace.h" +#include <cstddef> +#include <initializer_list> +#include <string> +#include <utility> +#include <vector> + +#include "absl/base/config.h" +#include "absl/base/nullability.h" #include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -28,8 +37,8 @@ using FixedMapping = // occurred. int ApplySubstitutions( absl::string_view s, - std::vector<strings_internal::ViableSubstitution>* subs_ptr, - std::string* result_ptr) { + absl::Nonnull<std::vector<strings_internal::ViableSubstitution>*> subs_ptr, + absl::Nonnull<std::string*> result_ptr) { auto& subs = *subs_ptr; int substitutions = 0; size_t pos = 0; @@ -74,7 +83,7 @@ std::string StrReplaceAll(absl::string_view s, } int StrReplaceAll(strings_internal::FixedMapping replacements, - std::string* target) { + absl::Nonnull<std::string*> target) { return StrReplaceAll<strings_internal::FixedMapping>(replacements, target); } diff --git a/absl/strings/str_replace.h b/absl/strings/str_replace.h index 273c7077..e77ced3e 100644 --- a/absl/strings/str_replace.h +++ b/absl/strings/str_replace.h @@ -43,6 +43,7 @@ #include <vector> #include "absl/base/attributes.h" +#include "absl/base/nullability.h" #include "absl/strings/string_view.h" namespace absl { @@ -113,7 +114,7 @@ std::string StrReplaceAll(absl::string_view s, int StrReplaceAll( std::initializer_list<std::pair<absl::string_view, absl::string_view>> replacements, - std::string* target); + absl::Nonnull<std::string*> target); // Overload of `StrReplaceAll()` to replace patterns within a given output // string *in place* with replacements provided within a container of key/value @@ -128,7 +129,8 @@ int StrReplaceAll( // EXPECT_EQ(count, 2); // EXPECT_EQ("if (ptr < &foo)", s); template <typename StrToStrMapping> -int StrReplaceAll(const StrToStrMapping& replacements, std::string* target); +int StrReplaceAll(const StrToStrMapping& replacements, + absl::Nonnull<std::string*> target); // Implementation details only, past this point. namespace strings_internal { @@ -185,8 +187,8 @@ std::vector<ViableSubstitution> FindSubstitutions( } int ApplySubstitutions(absl::string_view s, - std::vector<ViableSubstitution>* subs_ptr, - std::string* result_ptr); + absl::Nonnull<std::vector<ViableSubstitution>*> subs_ptr, + absl::Nonnull<std::string*> result_ptr); } // namespace strings_internal @@ -201,7 +203,8 @@ std::string StrReplaceAll(absl::string_view s, } template <typename StrToStrMapping> -int StrReplaceAll(const StrToStrMapping& replacements, std::string* target) { +int StrReplaceAll(const StrToStrMapping& replacements, + absl::Nonnull<std::string*> target) { auto subs = strings_internal::FindSubstitutions(*target, replacements); if (subs.empty()) return 0; diff --git a/absl/strings/str_replace_test.cc b/absl/strings/str_replace_test.cc index 9d8c7f75..04b23af6 100644 --- a/absl/strings/str_replace_test.cc +++ b/absl/strings/str_replace_test.cc @@ -16,11 +16,15 @@ #include <list> #include <map> +#include <string> #include <tuple> +#include <utility> +#include <vector> #include "gtest/gtest.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_split.h" +#include "absl/strings/string_view.h" TEST(StrReplaceAll, OneReplacement) { std::string s; @@ -175,7 +179,7 @@ TEST(StrReplaceAll, ReplacementsInPlaceInMap) { } struct Cont { - Cont() {} + Cont() = default; explicit Cont(absl::string_view src) : data(src) {} absl::string_view data; diff --git a/absl/strings/str_split.cc b/absl/strings/str_split.cc index e08c26b6..abe486b9 100644 --- a/absl/strings/str_split.cc +++ b/absl/strings/str_split.cc @@ -15,16 +15,13 @@ #include "absl/strings/str_split.h" #include <algorithm> -#include <cassert> -#include <cstdint> +#include <cstddef> #include <cstdlib> #include <cstring> -#include <iterator> -#include <limits> -#include <memory> +#include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" -#include "absl/strings/ascii.h" +#include "absl/strings/string_view.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -60,19 +57,23 @@ absl::string_view GenericFind(absl::string_view text, // Finds using absl::string_view::find(), therefore the length of the found // delimiter is delimiter.length(). struct LiteralPolicy { - size_t Find(absl::string_view text, absl::string_view delimiter, size_t pos) { + static size_t Find(absl::string_view text, absl::string_view delimiter, + size_t pos) { return text.find(delimiter, pos); } - size_t Length(absl::string_view delimiter) { return delimiter.length(); } + static size_t Length(absl::string_view delimiter) { + return delimiter.length(); + } }; // Finds using absl::string_view::find_first_of(), therefore the length of the // found delimiter is 1. struct AnyOfPolicy { - size_t Find(absl::string_view text, absl::string_view delimiter, size_t pos) { + static size_t Find(absl::string_view text, absl::string_view delimiter, + size_t pos) { return text.find_first_of(delimiter, pos); } - size_t Length(absl::string_view /* delimiter */) { return 1; } + static size_t Length(absl::string_view /* delimiter */) { return 1; } }; } // namespace @@ -95,6 +96,11 @@ absl::string_view ByString::Find(absl::string_view text, size_t pos) const { return GenericFind(text, delimiter_, pos, LiteralPolicy()); } +absl::string_view ByAsciiWhitespace::Find(absl::string_view text, + size_t pos) const { + return GenericFind(text, " \t\v\f\r\n", pos, AnyOfPolicy()); +} + // // ByChar // @@ -123,8 +129,7 @@ ByLength::ByLength(ptrdiff_t length) : length_(length) { ABSL_RAW_CHECK(length > 0, ""); } -absl::string_view ByLength::Find(absl::string_view text, - size_t pos) const { +absl::string_view ByLength::Find(absl::string_view text, size_t pos) const { pos = std::min(pos, text.size()); // truncate `pos` absl::string_view substr = text.substr(pos); // If the string is shorter than the chunk size we say we diff --git a/absl/strings/str_split.h b/absl/strings/str_split.h index 7bbb68a3..87540278 100644 --- a/absl/strings/str_split.h +++ b/absl/strings/str_split.h @@ -130,6 +130,24 @@ class ByString { const std::string delimiter_; }; +// ByAsciiWhitespace +// +// A sub-string delimiter that splits by ASCII whitespace +// (space, tab, vertical tab, formfeed, linefeed, or carriage return). +// Note: you probably want to use absl::SkipEmpty() as well! +// +// This class is equivalent to ByAnyChar with ASCII whitespace chars. +// +// Example: +// +// std::vector<std::string> v = absl::StrSplit( +// "a b\tc\n d \n", absl::ByAsciiWhitespace(), absl::SkipEmpty()); +// // v[0] == "a", v[1] == "b", v[2] == "c", v[3] == "d" +class ByAsciiWhitespace { + public: + absl::string_view Find(absl::string_view text, size_t pos) const; +}; + // ByChar // // A single character delimiter. `ByChar` is functionally equivalent to a diff --git a/absl/strings/str_split_benchmark.cc b/absl/strings/str_split_benchmark.cc index f38dfcfe..003a66b5 100644 --- a/absl/strings/str_split_benchmark.cc +++ b/absl/strings/str_split_benchmark.cc @@ -14,6 +14,7 @@ #include "absl/strings/str_split.h" +#include <cstddef> #include <iterator> #include <string> #include <unordered_map> diff --git a/absl/strings/str_split_test.cc b/absl/strings/str_split_test.cc index 04a64a42..df6c460f 100644 --- a/absl/strings/str_split_test.cc +++ b/absl/strings/str_split_test.cc @@ -14,30 +14,33 @@ #include "absl/strings/str_split.h" +#include <cstddef> +#include <cstdint> #include <deque> #include <initializer_list> #include <list> #include <map> #include <memory> +#include <set> #include <string> -#include <type_traits> #include <unordered_map> #include <unordered_set> +#include <utility> #include <vector> #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "absl/base/dynamic_annotations.h" #include "absl/base/macros.h" #include "absl/container/btree_map.h" #include "absl/container/btree_set.h" #include "absl/container/flat_hash_map.h" #include "absl/container/node_hash_map.h" -#include "absl/strings/numbers.h" +#include "absl/strings/string_view.h" namespace { using ::testing::ElementsAre; +using ::testing::IsEmpty; using ::testing::Pair; using ::testing::UnorderedElementsAre; @@ -922,6 +925,45 @@ TEST(Delimiter, ByAnyChar) { } // +// Tests for ByAsciiWhitespace +// +TEST(Split, ByAsciiWhitespace) { + using absl::ByAsciiWhitespace; + using absl::SkipEmpty; + std::vector<absl::string_view> results; + + results = absl::StrSplit("aaaa\n", ByAsciiWhitespace()); + EXPECT_THAT(results, ElementsAre("aaaa", "")); + + results = absl::StrSplit("aaaa\n", ByAsciiWhitespace(), SkipEmpty()); + EXPECT_THAT(results, ElementsAre("aaaa")); + + results = absl::StrSplit(" ", ByAsciiWhitespace()); + EXPECT_THAT(results, ElementsAre("", "")); + + results = absl::StrSplit(" ", ByAsciiWhitespace(), SkipEmpty()); + EXPECT_THAT(results, IsEmpty()); + + results = absl::StrSplit("a", ByAsciiWhitespace()); + EXPECT_THAT(results, ElementsAre("a")); + + results = absl::StrSplit("", ByAsciiWhitespace()); + EXPECT_THAT(results, ElementsAre("")); + + results = absl::StrSplit("", ByAsciiWhitespace(), SkipEmpty()); + EXPECT_THAT(results, IsEmpty()); + + results = absl::StrSplit("a b\tc\n d\n", ByAsciiWhitespace()); + EXPECT_THAT(results, ElementsAre("a", "b", "c", "", "", "d", "")); + + results = absl::StrSplit("a b\tc\n d \n", ByAsciiWhitespace(), SkipEmpty()); + EXPECT_THAT(results, ElementsAre("a", "b", "c", "d")); + + results = absl::StrSplit("a\t\n\v\f\r b", ByAsciiWhitespace(), SkipEmpty()); + EXPECT_THAT(results, ElementsAre("a", "b")); +} + +// // Tests for ByLength // diff --git a/absl/strings/string_view.cc b/absl/strings/string_view.cc index e2261625..97025c32 100644 --- a/absl/strings/string_view.cc +++ b/absl/strings/string_view.cc @@ -21,12 +21,39 @@ #include <cstring> #include <ostream> -#include "absl/strings/internal/memutil.h" +#include "absl/base/nullability.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace { + +// This is significantly faster for case-sensitive matches with very +// few possible matches. +absl::Nullable<const char*> memmatch(absl::Nullable<const char*> phaystack, + size_t haylen, + absl::Nullable<const char*> pneedle, + size_t neelen) { + if (0 == neelen) { + return phaystack; // even if haylen is 0 + } + if (haylen < neelen) return nullptr; + + const char* match; + const char* hayend = phaystack + haylen - neelen + 1; + // A static cast is used here as memchr returns a const void *, and pointer + // arithmetic is not allowed on pointers to void. + while ( + (match = static_cast<const char*>(memchr( + phaystack, pneedle[0], static_cast<size_t>(hayend - phaystack))))) { + if (memcmp(match, pneedle, neelen) == 0) + return match; + else + phaystack = match + 1; + } + return nullptr; +} + void WritePadding(std::ostream& o, size_t pad) { char fill_buf[32]; memset(fill_buf, o.fill(), sizeof(fill_buf)); @@ -84,8 +111,7 @@ string_view::size_type string_view::find(string_view s, if (empty() && pos == 0 && s.empty()) return 0; return npos; } - const char* result = - strings_internal::memmatch(ptr_ + pos, length_ - pos, s.ptr_, s.length_); + const char* result = memmatch(ptr_ + pos, length_ - pos, s.ptr_, s.length_); return result ? static_cast<size_type>(result - ptr_) : npos; } @@ -207,7 +233,6 @@ string_view::size_type string_view::find_last_not_of( return npos; } - #ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL constexpr string_view::size_type string_view::npos; constexpr string_view::size_type string_view::kMaxSize; @@ -216,4 +241,22 @@ constexpr string_view::size_type string_view::kMaxSize; ABSL_NAMESPACE_END } // namespace absl +#else + +// https://github.com/abseil/abseil-cpp/issues/1465 +// CMake builds on Apple platforms error when libraries are empty. +// Our CMake configuration can avoid this error on header-only libraries, +// but since this library is conditionally empty, including a single +// variable is an easy workaround. +#ifdef __APPLE__ +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace strings_internal { +extern const char kAvoidEmptyStringViewLibraryWarning; +const char kAvoidEmptyStringViewLibraryWarning = 0; +} // namespace strings_internal +ABSL_NAMESPACE_END +} // namespace absl +#endif // __APPLE__ + #endif // ABSL_USES_STD_STRING_VIEW diff --git a/absl/strings/string_view.h b/absl/strings/string_view.h index eae11b2a..04ca0a38 100644 --- a/absl/strings/string_view.h +++ b/absl/strings/string_view.h @@ -37,6 +37,7 @@ #include <string> #include "absl/base/attributes.h" +#include "absl/base/nullability.h" #include "absl/base/config.h" #include "absl/base/internal/throw_delegate.h" #include "absl/base/macros.h" @@ -162,11 +163,11 @@ class string_view { public: using traits_type = std::char_traits<char>; using value_type = char; - using pointer = char*; - using const_pointer = const char*; + using pointer = absl::Nullable<char*>; + using const_pointer = absl::Nullable<const char*>; using reference = char&; using const_reference = const char&; - using const_iterator = const char*; + using const_iterator = absl::Nullable<const char*>; using iterator = const_iterator; using const_reverse_iterator = std::reverse_iterator<const_iterator>; using reverse_iterator = const_reverse_iterator; @@ -194,11 +195,12 @@ class string_view { // accepting possibly null strings, use `absl::NullSafeStringView(str)` // instead (see below). // The length check is skipped since it is unnecessary and causes code bloat. - constexpr string_view(const char* str) // NOLINT(runtime/explicit) + constexpr string_view( // NOLINT(runtime/explicit) + absl::Nonnull<const char*> str) : ptr_(str), length_(str ? StrlenInternal(str) : 0) {} // Implicit constructor of a `string_view` from a `const char*` and length. - constexpr string_view(const char* data, size_type len) + constexpr string_view(absl::Nullable<const char*> data, size_type len) : ptr_(data), length_(CheckLengthInternal(len)) {} // NOTE: Harmlessly omitted to work around gdb bug. @@ -427,18 +429,21 @@ class string_view { // Overload of `string_view::compare()` for comparing a `string_view` and a // a different C-style string `s`. - constexpr int compare(const char* s) const { return compare(string_view(s)); } + constexpr int compare(absl::Nonnull<const char*> s) const { + return compare(string_view(s)); + } // Overload of `string_view::compare()` for comparing a substring of the // `string_view` and a different string C-style string `s`. - constexpr int compare(size_type pos1, size_type count1, const char* s) const { + constexpr int compare(size_type pos1, size_type count1, + absl::Nonnull<const char*> s) const { return substr(pos1, count1).compare(string_view(s)); } // Overload of `string_view::compare()` for comparing a substring of the // `string_view` and a substring of a different C-style string `s`. - constexpr int compare(size_type pos1, size_type count1, const char* s, - size_type count2) const { + constexpr int compare(size_type pos1, size_type count1, + absl::Nonnull<const char*> s, size_type count2) const { return substr(pos1, count1).compare(string_view(s, count2)); } @@ -457,13 +462,14 @@ class string_view { // Overload of `string_view::find()` for finding a substring of a different // C-style string `s` within the `string_view`. - size_type find(const char* s, size_type pos, size_type count) const { + size_type find(absl::Nonnull<const char*> s, size_type pos, + size_type count) const { return find(string_view(s, count), pos); } // Overload of `string_view::find()` for finding a different C-style string // `s` within the `string_view`. - size_type find(const char* s, size_type pos = 0) const { + size_type find(absl::Nonnull<const char *> s, size_type pos = 0) const { return find(string_view(s), pos); } @@ -480,13 +486,14 @@ class string_view { // Overload of `string_view::rfind()` for finding a substring of a different // C-style string `s` within the `string_view`. - size_type rfind(const char* s, size_type pos, size_type count) const { + size_type rfind(absl::Nonnull<const char*> s, size_type pos, + size_type count) const { return rfind(string_view(s, count), pos); } // Overload of `string_view::rfind()` for finding a different C-style string // `s` within the `string_view`. - size_type rfind(const char* s, size_type pos = npos) const { + size_type rfind(absl::Nonnull<const char*> s, size_type pos = npos) const { return rfind(string_view(s), pos); } @@ -505,14 +512,15 @@ class string_view { // Overload of `string_view::find_first_of()` for finding a substring of a // different C-style string `s` within the `string_view`. - size_type find_first_of(const char* s, size_type pos, - size_type count) const { + size_type find_first_of(absl::Nonnull<const char*> s, size_type pos, + size_type count) const { return find_first_of(string_view(s, count), pos); } // Overload of `string_view::find_first_of()` for finding a different C-style // string `s` within the `string_view`. - size_type find_first_of(const char* s, size_type pos = 0) const { + size_type find_first_of(absl::Nonnull<const char*> s, + size_type pos = 0) const { return find_first_of(string_view(s), pos); } @@ -531,13 +539,15 @@ class string_view { // Overload of `string_view::find_last_of()` for finding a substring of a // different C-style string `s` within the `string_view`. - size_type find_last_of(const char* s, size_type pos, size_type count) const { + size_type find_last_of(absl::Nonnull<const char*> s, size_type pos, + size_type count) const { return find_last_of(string_view(s, count), pos); } // Overload of `string_view::find_last_of()` for finding a different C-style // string `s` within the `string_view`. - size_type find_last_of(const char* s, size_type pos = npos) const { + size_type find_last_of(absl::Nonnull<const char*> s, + size_type pos = npos) const { return find_last_of(string_view(s), pos); } @@ -554,14 +564,15 @@ class string_view { // Overload of `string_view::find_first_not_of()` for finding a substring of a // different C-style string `s` within the `string_view`. - size_type find_first_not_of(const char* s, size_type pos, + size_type find_first_not_of(absl::Nonnull<const char*> s, size_type pos, size_type count) const { return find_first_not_of(string_view(s, count), pos); } // Overload of `string_view::find_first_not_of()` for finding a different // C-style string `s` within the `string_view`. - size_type find_first_not_of(const char* s, size_type pos = 0) const { + size_type find_first_not_of(absl::Nonnull<const char*> s, + size_type pos = 0) const { return find_first_not_of(string_view(s), pos); } @@ -579,22 +590,76 @@ class string_view { // Overload of `string_view::find_last_not_of()` for finding a substring of a // different C-style string `s` within the `string_view`. - size_type find_last_not_of(const char* s, size_type pos, + size_type find_last_not_of(absl::Nonnull<const char*> s, size_type pos, size_type count) const { return find_last_not_of(string_view(s, count), pos); } // Overload of `string_view::find_last_not_of()` for finding a different // C-style string `s` within the `string_view`. - size_type find_last_not_of(const char* s, size_type pos = npos) const { + size_type find_last_not_of(absl::Nonnull<const char*> s, + size_type pos = npos) const { return find_last_not_of(string_view(s), pos); } +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L + // string_view::starts_with() + // + // Returns true if the `string_view` starts with the prefix `s`. + // + // This method only exists when targeting at least C++20. + // If support for C++ prior to C++20 is required, use `absl::StartsWith()` + // from `//absl/strings/match.h` for compatibility. + constexpr bool starts_with(string_view s) const noexcept { + return s.empty() || + (size() >= s.size() && + ABSL_INTERNAL_STRING_VIEW_MEMCMP(data(), s.data(), s.size()) == 0); + } + + // Overload of `string_view::starts_with()` that returns true if `c` is the + // first character of the `string_view`. + constexpr bool starts_with(char c) const noexcept { + return !empty() && front() == c; + } + + // Overload of `string_view::starts_with()` that returns true if the + // `string_view` starts with the C-style prefix `s`. + constexpr bool starts_with(const char* s) const { + return starts_with(string_view(s)); + } + + // string_view::ends_with() + // + // Returns true if the `string_view` ends with the suffix `s`. + // + // This method only exists when targeting at least C++20. + // If support for C++ prior to C++20 is required, use `absl::EndsWith()` + // from `//absl/strings/match.h` for compatibility. + constexpr bool ends_with(string_view s) const noexcept { + return s.empty() || (size() >= s.size() && ABSL_INTERNAL_STRING_VIEW_MEMCMP( + data() + (size() - s.size()), + s.data(), s.size()) == 0); + } + + // Overload of `string_view::ends_with()` that returns true if `c` is the + // last character of the `string_view`. + constexpr bool ends_with(char c) const noexcept { + return !empty() && back() == c; + } + + // Overload of `string_view::ends_with()` that returns true if the + // `string_view` ends with the C-style suffix `s`. + constexpr bool ends_with(const char* s) const { + return ends_with(string_view(s)); + } +#endif // ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L + private: // The constructor from std::string delegates to this constructor. // See the comment on that constructor for the rationale. struct SkipCheckLengthTag {}; - string_view(const char* data, size_type len, SkipCheckLengthTag) noexcept + string_view(absl::Nullable<const char*> data, size_type len, + SkipCheckLengthTag) noexcept : ptr_(data), length_(len) {} static constexpr size_type kMaxSize = @@ -604,7 +669,7 @@ class string_view { return ABSL_HARDENING_ASSERT(len <= kMaxSize), len; } - static constexpr size_type StrlenInternal(const char* str) { + static constexpr size_type StrlenInternal(absl::Nonnull<const char*> str) { #if defined(_MSC_VER) && _MSC_VER >= 1910 && !defined(__clang__) // MSVC 2017+ can evaluate this at compile-time. const char* begin = str; @@ -633,7 +698,7 @@ class string_view { : (compare_result < 0 ? -1 : 1); } - const char* ptr_; + absl::Nullable<const char*> ptr_; size_type length_; }; @@ -694,7 +759,7 @@ inline string_view ClippedSubstr(string_view s, size_t pos, // Creates an `absl::string_view` from a pointer `p` even if it's null-valued. // This function should be used where an `absl::string_view` can be created from // a possibly-null pointer. -constexpr string_view NullSafeStringView(const char* p) { +constexpr string_view NullSafeStringView(absl::Nullable<const char*> p) { return p ? string_view(p) : string_view(); } diff --git a/absl/strings/string_view_benchmark.cc b/absl/strings/string_view_benchmark.cc index 0d74e23e..98f747ca 100644 --- a/absl/strings/string_view_benchmark.cc +++ b/absl/strings/string_view_benchmark.cc @@ -15,6 +15,7 @@ #include "absl/strings/string_view.h" #include <algorithm> +#include <cstddef> #include <cstdint> #include <map> #include <random> diff --git a/absl/strings/string_view_test.cc b/absl/strings/string_view_test.cc index 990c211a..5b1eb01a 100644 --- a/absl/strings/string_view_test.cc +++ b/absl/strings/string_view_test.cc @@ -15,20 +15,23 @@ #include "absl/strings/string_view.h" #include <stdlib.h> + +#include <cstddef> +#include <cstdlib> +#include <cstring> #include <iomanip> +#include <ios> #include <iterator> #include <limits> #include <map> +#include <memory> #include <sstream> -#include <stdexcept> #include <string> #include <type_traits> #include <utility> #include "gtest/gtest.h" #include "absl/base/config.h" -#include "absl/base/dynamic_annotations.h" -#include "absl/base/options.h" #if defined(ABSL_HAVE_STD_STRING_VIEW) || defined(__ANDROID__) // We don't control the death messaging when using std::string_view. @@ -948,6 +951,76 @@ TEST(StringViewTest, At) { #endif } +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L +TEST(StringViewTest, StartsWith) { + const absl::string_view a("foobar"); + const absl::string_view b("123\0abc", 7); + const absl::string_view e; + EXPECT_TRUE(a.starts_with(a)); + EXPECT_TRUE(a.starts_with("foo")); + EXPECT_TRUE(a.starts_with('f')); + EXPECT_TRUE(a.starts_with(e)); + EXPECT_TRUE(b.starts_with(b)); + EXPECT_TRUE(b.starts_with('1')); + EXPECT_TRUE(b.starts_with(e)); + EXPECT_TRUE(e.starts_with("")); + EXPECT_FALSE(a.starts_with(b)); + EXPECT_FALSE(b.starts_with(a)); + EXPECT_FALSE(e.starts_with(a)); + EXPECT_FALSE(a.starts_with('r')); + EXPECT_FALSE(a.starts_with('\0')); + EXPECT_FALSE(e.starts_with('r')); + EXPECT_FALSE(e.starts_with('\0')); + + // Test that constexpr compiles. + constexpr absl::string_view kFooBar("foobar"); + constexpr absl::string_view kFoo("foo"); + constexpr absl::string_view kBar("bar"); + constexpr bool k1 = kFooBar.starts_with(kFoo); + EXPECT_TRUE(k1); + constexpr bool k2 = kFooBar.starts_with(kBar); + EXPECT_FALSE(k2); + constexpr bool k3 = kFooBar.starts_with('f'); + EXPECT_TRUE(k3); + constexpr bool k4 = kFooBar.starts_with("fo"); + EXPECT_TRUE(k4); +} + +TEST(StringViewTest, EndsWith) { + const absl::string_view a("foobar"); + const absl::string_view b("123\0abc", 7); + const absl::string_view e; + EXPECT_TRUE(a.ends_with(a)); + EXPECT_TRUE(a.ends_with('r')); + EXPECT_TRUE(a.ends_with("bar")); + EXPECT_TRUE(a.ends_with(e)); + EXPECT_TRUE(b.ends_with(b)); + EXPECT_TRUE(b.ends_with('c')); + EXPECT_TRUE(b.ends_with(e)); + EXPECT_TRUE(e.ends_with("")); + EXPECT_FALSE(a.ends_with(b)); + EXPECT_FALSE(b.ends_with(a)); + EXPECT_FALSE(e.ends_with(a)); + EXPECT_FALSE(a.ends_with('f')); + EXPECT_FALSE(a.ends_with('\0')); + EXPECT_FALSE(e.ends_with('r')); + EXPECT_FALSE(e.ends_with('\0')); + + // Test that constexpr compiles. + constexpr absl::string_view kFooBar("foobar"); + constexpr absl::string_view kFoo("foo"); + constexpr absl::string_view kBar("bar"); + constexpr bool k1 = kFooBar.ends_with(kFoo); + EXPECT_FALSE(k1); + constexpr bool k2 = kFooBar.ends_with(kBar); + EXPECT_TRUE(k2); + constexpr bool k3 = kFooBar.ends_with('r'); + EXPECT_TRUE(k3); + constexpr bool k4 = kFooBar.ends_with("ar"); + EXPECT_TRUE(k4); +} +#endif // ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L + struct MyCharAlloc : std::allocator<char> {}; TEST(StringViewTest, ExplicitConversionOperator) { diff --git a/absl/strings/strip.h b/absl/strings/strip.h index 341e66fc..e3cda5ba 100644 --- a/absl/strings/strip.h +++ b/absl/strings/strip.h @@ -25,6 +25,7 @@ #include <string> #include "absl/base/macros.h" +#include "absl/base/nullability.h" #include "absl/strings/ascii.h" #include "absl/strings/match.h" #include "absl/strings/string_view.h" @@ -43,7 +44,8 @@ ABSL_NAMESPACE_BEGIN // absl::string_view input("abc"); // EXPECT_TRUE(absl::ConsumePrefix(&input, "a")); // EXPECT_EQ(input, "bc"); -inline bool ConsumePrefix(absl::string_view* str, absl::string_view expected) { +inline bool ConsumePrefix(absl::Nonnull<absl::string_view*> str, + absl::string_view expected) { if (!absl::StartsWith(*str, expected)) return false; str->remove_prefix(expected.size()); return true; @@ -59,7 +61,8 @@ inline bool ConsumePrefix(absl::string_view* str, absl::string_view expected) { // absl::string_view input("abcdef"); // EXPECT_TRUE(absl::ConsumeSuffix(&input, "def")); // EXPECT_EQ(input, "abc"); -inline bool ConsumeSuffix(absl::string_view* str, absl::string_view expected) { +inline bool ConsumeSuffix(absl::Nonnull<absl::string_view*> str, + absl::string_view expected) { if (!absl::EndsWith(*str, expected)) return false; str->remove_suffix(expected.size()); return true; diff --git a/absl/strings/substitute.cc b/absl/strings/substitute.cc index 33a39305..dd32c75f 100644 --- a/absl/strings/substitute.cc +++ b/absl/strings/substitute.cc @@ -15,20 +15,28 @@ #include "absl/strings/substitute.h" #include <algorithm> +#include <cassert> +#include <cstddef> +#include <cstdint> +#include <string> +#include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" +#include "absl/base/nullability.h" #include "absl/strings/ascii.h" #include "absl/strings/escaping.h" #include "absl/strings/internal/resize_uninitialized.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace substitute_internal { -void SubstituteAndAppendArray(std::string* output, absl::string_view format, - const absl::string_view* args_array, - size_t num_args) { +void SubstituteAndAppendArray( + absl::Nonnull<std::string*> output, absl::string_view format, + absl::Nullable<const absl::string_view*> args_array, size_t num_args) { // Determine total size needed. size_t size = 0; for (size_t i = 0; i < format.size(); i++) { @@ -97,7 +105,7 @@ void SubstituteAndAppendArray(std::string* output, absl::string_view format, assert(target == output->data() + output->size()); } -Arg::Arg(const void* value) { +Arg::Arg(absl::Nullable<const void*> value) { static_assert(sizeof(scratch_) >= sizeof(value) * 2 + 2, "fix sizeof(scratch_)"); if (value == nullptr) { diff --git a/absl/strings/substitute.h b/absl/strings/substitute.h index d6a5a690..6c7cba4b 100644 --- a/absl/strings/substitute.h +++ b/absl/strings/substitute.h @@ -78,6 +78,7 @@ #include <vector> #include "absl/base/macros.h" +#include "absl/base/nullability.h" #include "absl/base/port.h" #include "absl/strings/ascii.h" #include "absl/strings/escaping.h" @@ -105,7 +106,7 @@ class Arg { // Overloads for string-y things // // Explicitly overload `const char*` so the compiler doesn't cast to `bool`. - Arg(const char* value) // NOLINT(google-explicit-constructor) + Arg(absl::Nullable<const char*> value) // NOLINT(google-explicit-constructor) : piece_(absl::NullSafeStringView(value)) {} template <typename Allocator> Arg( // NOLINT @@ -176,7 +177,7 @@ class Arg { : piece_(value ? "true" : "false") {} template <typename T, typename = typename std::enable_if< - strings_internal::HasAbslStringify<T>::value>::type> + HasAbslStringify<T>::value>::type> Arg( // NOLINT(google-explicit-constructor) const T& v, strings_internal::StringifySink&& sink = {}) : piece_(strings_internal::ExtractStringification(sink, v)) {} @@ -197,14 +198,15 @@ 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(google-explicit-constructor) + Arg( // NOLINT(google-explicit-constructor) + absl::Nullable<const void*> value); // Normal enums are already handled by the integer formatters. // This overload matches only scoped enums. template <typename T, typename = typename std::enable_if< std::is_enum<T>{} && !std::is_convertible<T, int>{} && - !strings_internal::HasAbslStringify<T>::value>::type> + !HasAbslStringify<T>::value>::type> Arg(T value) // NOLINT(google-explicit-constructor) : Arg(static_cast<typename std::underlying_type<T>::type>(value)) {} @@ -220,12 +222,12 @@ class Arg { // Internal helper function. Don't call this from outside this implementation. // This interface may change without notice. -void SubstituteAndAppendArray(std::string* output, absl::string_view format, - const absl::string_view* args_array, - size_t num_args); +void SubstituteAndAppendArray( + absl::Nonnull<std::string*> output, absl::string_view format, + absl::Nullable<const absl::string_view*> args_array, size_t num_args); #if defined(ABSL_BAD_CALL_IF) -constexpr int CalculateOneBit(const char* format) { +constexpr int CalculateOneBit(absl::Nonnull<const char*> format) { // Returns: // * 2^N for '$N' when N is in [0-9] // * 0 for correct '$' escaping: '$$'. @@ -234,11 +236,11 @@ constexpr int CalculateOneBit(const char* format) { : (1 << (*format - '0')); } -constexpr const char* SkipNumber(const char* format) { +constexpr const char* SkipNumber(absl::Nonnull<const char*> format) { return !*format ? format : (format + 1); } -constexpr int PlaceholderBitmask(const char* format) { +constexpr int PlaceholderBitmask(absl::Nonnull<const char*> format) { return !*format ? 0 : *format != '$' ? PlaceholderBitmask(format + 1) @@ -271,18 +273,21 @@ constexpr int PlaceholderBitmask(const char* format) { // absl::SubstituteAndAppend(boilerplate, format, args...); // } // -inline void SubstituteAndAppend(std::string* output, absl::string_view format) { +inline void SubstituteAndAppend(absl::Nonnull<std::string*> output, + absl::string_view format) { substitute_internal::SubstituteAndAppendArray(output, format, nullptr, 0); } -inline void SubstituteAndAppend(std::string* output, absl::string_view format, +inline void SubstituteAndAppend(absl::Nonnull<std::string*> output, + absl::string_view format, const substitute_internal::Arg& a0) { const absl::string_view args[] = {a0.piece()}; substitute_internal::SubstituteAndAppendArray(output, format, args, ABSL_ARRAYSIZE(args)); } -inline void SubstituteAndAppend(std::string* output, absl::string_view format, +inline void SubstituteAndAppend(absl::Nonnull<std::string*> output, + absl::string_view format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a1) { const absl::string_view args[] = {a0.piece(), a1.piece()}; @@ -290,7 +295,8 @@ inline void SubstituteAndAppend(std::string* output, absl::string_view format, ABSL_ARRAYSIZE(args)); } -inline void SubstituteAndAppend(std::string* output, absl::string_view format, +inline void SubstituteAndAppend(absl::Nonnull<std::string*> output, + absl::string_view format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, const substitute_internal::Arg& a2) { @@ -299,7 +305,8 @@ inline void SubstituteAndAppend(std::string* output, absl::string_view format, ABSL_ARRAYSIZE(args)); } -inline void SubstituteAndAppend(std::string* output, absl::string_view format, +inline void SubstituteAndAppend(absl::Nonnull<std::string*> output, + absl::string_view format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, const substitute_internal::Arg& a2, @@ -310,7 +317,8 @@ inline void SubstituteAndAppend(std::string* output, absl::string_view format, ABSL_ARRAYSIZE(args)); } -inline void SubstituteAndAppend(std::string* output, absl::string_view format, +inline void SubstituteAndAppend(absl::Nonnull<std::string*> output, + absl::string_view format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, const substitute_internal::Arg& a2, @@ -322,27 +330,23 @@ inline void SubstituteAndAppend(std::string* output, absl::string_view format, ABSL_ARRAYSIZE(args)); } -inline void SubstituteAndAppend(std::string* output, absl::string_view format, - const substitute_internal::Arg& a0, - const substitute_internal::Arg& a1, - const substitute_internal::Arg& a2, - const substitute_internal::Arg& a3, - const substitute_internal::Arg& a4, - const substitute_internal::Arg& a5) { +inline void SubstituteAndAppend( + absl::Nonnull<std::string*> output, absl::string_view format, + const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, + const substitute_internal::Arg& a2, const substitute_internal::Arg& a3, + const substitute_internal::Arg& a4, const substitute_internal::Arg& a5) { const absl::string_view args[] = {a0.piece(), a1.piece(), a2.piece(), a3.piece(), a4.piece(), a5.piece()}; substitute_internal::SubstituteAndAppendArray(output, format, args, ABSL_ARRAYSIZE(args)); } -inline void SubstituteAndAppend(std::string* output, absl::string_view format, - const substitute_internal::Arg& a0, - const substitute_internal::Arg& a1, - const substitute_internal::Arg& a2, - const substitute_internal::Arg& a3, - const substitute_internal::Arg& a4, - const substitute_internal::Arg& a5, - const substitute_internal::Arg& a6) { +inline void SubstituteAndAppend( + absl::Nonnull<std::string*> output, absl::string_view format, + const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, + const substitute_internal::Arg& a2, const substitute_internal::Arg& a3, + const substitute_internal::Arg& a4, const substitute_internal::Arg& a5, + const substitute_internal::Arg& a6) { const absl::string_view args[] = {a0.piece(), a1.piece(), a2.piece(), a3.piece(), a4.piece(), a5.piece(), a6.piece()}; @@ -351,7 +355,7 @@ inline void SubstituteAndAppend(std::string* output, absl::string_view format, } inline void SubstituteAndAppend( - std::string* output, absl::string_view format, + absl::Nonnull<std::string*> output, absl::string_view format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, const substitute_internal::Arg& a2, const substitute_internal::Arg& a3, const substitute_internal::Arg& a4, const substitute_internal::Arg& a5, @@ -364,7 +368,7 @@ inline void SubstituteAndAppend( } inline void SubstituteAndAppend( - std::string* output, absl::string_view format, + absl::Nonnull<std::string*> output, absl::string_view format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, const substitute_internal::Arg& a2, const substitute_internal::Arg& a3, const substitute_internal::Arg& a4, const substitute_internal::Arg& a5, @@ -378,7 +382,7 @@ inline void SubstituteAndAppend( } inline void SubstituteAndAppend( - std::string* output, absl::string_view format, + absl::Nonnull<std::string*> output, absl::string_view format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, const substitute_internal::Arg& a2, const substitute_internal::Arg& a3, const substitute_internal::Arg& a4, const substitute_internal::Arg& a5, @@ -394,14 +398,16 @@ inline void SubstituteAndAppend( #if defined(ABSL_BAD_CALL_IF) // This body of functions catches cases where the number of placeholders // doesn't match the number of data arguments. -void SubstituteAndAppend(std::string* output, const char* format) +void SubstituteAndAppend(absl::Nonnull<std::string*> output, + absl::Nonnull<const char*> format) ABSL_BAD_CALL_IF( substitute_internal::PlaceholderBitmask(format) != 0, "There were no substitution arguments " "but this format string either has a $[0-9] in it or contains " "an unescaped $ character (use $$ instead)"); -void SubstituteAndAppend(std::string* output, const char* format, +void SubstituteAndAppend(absl::Nonnull<std::string*> output, + absl::Nonnull<const char*> format, const substitute_internal::Arg& a0) ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 1, "There was 1 substitution argument given, but " @@ -409,7 +415,8 @@ void SubstituteAndAppend(std::string* output, const char* format, "one of $1-$9, or contains an unescaped $ character (use " "$$ instead)"); -void SubstituteAndAppend(std::string* output, const char* format, +void SubstituteAndAppend(absl::Nonnull<std::string*> output, + absl::Nonnull<const char*> format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a1) ABSL_BAD_CALL_IF( @@ -418,7 +425,8 @@ void SubstituteAndAppend(std::string* output, const char* format, "missing its $0/$1, contains one of $2-$9, or contains an " "unescaped $ character (use $$ instead)"); -void SubstituteAndAppend(std::string* output, const char* format, +void SubstituteAndAppend(absl::Nonnull<std::string*> output, + absl::Nonnull<const char*> format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, const substitute_internal::Arg& a2) @@ -428,7 +436,8 @@ void SubstituteAndAppend(std::string* output, const char* format, "this format string is missing its $0/$1/$2, contains one of " "$3-$9, or contains an unescaped $ character (use $$ instead)"); -void SubstituteAndAppend(std::string* output, const char* format, +void SubstituteAndAppend(absl::Nonnull<std::string*> output, + absl::Nonnull<const char*> format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, const substitute_internal::Arg& a2, @@ -439,7 +448,8 @@ void SubstituteAndAppend(std::string* output, const char* format, "this format string is missing its $0-$3, contains one of " "$4-$9, or contains an unescaped $ character (use $$ instead)"); -void SubstituteAndAppend(std::string* output, const char* format, +void SubstituteAndAppend(absl::Nonnull<std::string*> output, + absl::Nonnull<const char*> format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, const substitute_internal::Arg& a2, @@ -451,13 +461,11 @@ void SubstituteAndAppend(std::string* output, const char* format, "this format string is missing its $0-$4, contains one of " "$5-$9, or contains an unescaped $ character (use $$ instead)"); -void SubstituteAndAppend(std::string* output, const char* format, - const substitute_internal::Arg& a0, - const substitute_internal::Arg& a1, - const substitute_internal::Arg& a2, - const substitute_internal::Arg& a3, - const substitute_internal::Arg& a4, - const substitute_internal::Arg& a5) +void SubstituteAndAppend( + absl::Nonnull<std::string*> output, absl::Nonnull<const char*> format, + const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, + const substitute_internal::Arg& a2, const substitute_internal::Arg& a3, + const substitute_internal::Arg& a4, const substitute_internal::Arg& a5) ABSL_BAD_CALL_IF( substitute_internal::PlaceholderBitmask(format) != 63, "There were 6 substitution arguments given, but " @@ -465,10 +473,11 @@ void SubstituteAndAppend(std::string* output, const char* format, "$6-$9, or contains an unescaped $ character (use $$ instead)"); void SubstituteAndAppend( - std::string* output, const char* format, const substitute_internal::Arg& a0, - const substitute_internal::Arg& a1, const substitute_internal::Arg& a2, - const substitute_internal::Arg& a3, const substitute_internal::Arg& a4, - const substitute_internal::Arg& a5, const substitute_internal::Arg& a6) + absl::Nonnull<std::string*> output, absl::Nonnull<const char*> format, + const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, + const substitute_internal::Arg& a2, const substitute_internal::Arg& a3, + const substitute_internal::Arg& a4, const substitute_internal::Arg& a5, + const substitute_internal::Arg& a6) ABSL_BAD_CALL_IF( substitute_internal::PlaceholderBitmask(format) != 127, "There were 7 substitution arguments given, but " @@ -476,11 +485,11 @@ void SubstituteAndAppend( "$7-$9, or contains an unescaped $ character (use $$ instead)"); void SubstituteAndAppend( - std::string* output, const char* format, const substitute_internal::Arg& a0, - const substitute_internal::Arg& a1, const substitute_internal::Arg& a2, - const substitute_internal::Arg& a3, const substitute_internal::Arg& a4, - const substitute_internal::Arg& a5, const substitute_internal::Arg& a6, - const substitute_internal::Arg& a7) + absl::Nonnull<std::string*> output, absl::Nonnull<const char*> format, + const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, + const substitute_internal::Arg& a2, const substitute_internal::Arg& a3, + const substitute_internal::Arg& a4, const substitute_internal::Arg& a5, + const substitute_internal::Arg& a6, const substitute_internal::Arg& a7) ABSL_BAD_CALL_IF( substitute_internal::PlaceholderBitmask(format) != 255, "There were 8 substitution arguments given, but " @@ -488,11 +497,12 @@ void SubstituteAndAppend( "$8-$9, or contains an unescaped $ character (use $$ instead)"); void SubstituteAndAppend( - std::string* output, const char* format, const substitute_internal::Arg& a0, - const substitute_internal::Arg& a1, const substitute_internal::Arg& a2, - const substitute_internal::Arg& a3, const substitute_internal::Arg& a4, - const substitute_internal::Arg& a5, const substitute_internal::Arg& a6, - const substitute_internal::Arg& a7, const substitute_internal::Arg& a8) + absl::Nonnull<std::string*> output, absl::Nonnull<const char*> format, + const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, + const substitute_internal::Arg& a2, const substitute_internal::Arg& a3, + const substitute_internal::Arg& a4, const substitute_internal::Arg& a5, + const substitute_internal::Arg& a6, const substitute_internal::Arg& a7, + const substitute_internal::Arg& a8) ABSL_BAD_CALL_IF( substitute_internal::PlaceholderBitmask(format) != 511, "There were 9 substitution arguments given, but " @@ -500,12 +510,12 @@ void SubstituteAndAppend( "contains an unescaped $ character (use $$ instead)"); void SubstituteAndAppend( - std::string* output, const char* format, const substitute_internal::Arg& a0, - const substitute_internal::Arg& a1, const substitute_internal::Arg& a2, - const substitute_internal::Arg& a3, const substitute_internal::Arg& a4, - const substitute_internal::Arg& a5, const substitute_internal::Arg& a6, - const substitute_internal::Arg& a7, const substitute_internal::Arg& a8, - const substitute_internal::Arg& a9) + absl::Nonnull<std::string*> output, absl::Nonnull<const char*> format, + const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, + const substitute_internal::Arg& a2, const substitute_internal::Arg& a3, + const substitute_internal::Arg& a4, const substitute_internal::Arg& a5, + const substitute_internal::Arg& a6, const substitute_internal::Arg& a7, + const substitute_internal::Arg& a8, const substitute_internal::Arg& a9) ABSL_BAD_CALL_IF( substitute_internal::PlaceholderBitmask(format) != 1023, "There were 10 substitution arguments given, but this " @@ -633,20 +643,22 @@ ABSL_MUST_USE_RESULT inline std::string Substitute( #if defined(ABSL_BAD_CALL_IF) // This body of functions catches cases where the number of placeholders // doesn't match the number of data arguments. -std::string Substitute(const char* format) +std::string Substitute(absl::Nonnull<const char*> format) ABSL_BAD_CALL_IF(substitute_internal::PlaceholderBitmask(format) != 0, "There were no substitution arguments " "but this format string either has a $[0-9] in it or " "contains an unescaped $ character (use $$ instead)"); -std::string Substitute(const char* format, const substitute_internal::Arg& a0) +std::string Substitute(absl::Nonnull<const char*> format, + const substitute_internal::Arg& a0) ABSL_BAD_CALL_IF( substitute_internal::PlaceholderBitmask(format) != 1, "There was 1 substitution argument given, but " "this format string is missing its $0, contains one of $1-$9, " "or contains an unescaped $ character (use $$ instead)"); -std::string Substitute(const char* format, const substitute_internal::Arg& a0, +std::string Substitute(absl::Nonnull<const char*> format, + const substitute_internal::Arg& a0, const substitute_internal::Arg& a1) ABSL_BAD_CALL_IF( substitute_internal::PlaceholderBitmask(format) != 3, @@ -654,7 +666,8 @@ std::string Substitute(const char* format, const substitute_internal::Arg& a0, "this format string is missing its $0/$1, contains one of " "$2-$9, or contains an unescaped $ character (use $$ instead)"); -std::string Substitute(const char* format, const substitute_internal::Arg& a0, +std::string Substitute(absl::Nonnull<const char*> format, + const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, const substitute_internal::Arg& a2) ABSL_BAD_CALL_IF( @@ -663,7 +676,8 @@ std::string Substitute(const char* format, const substitute_internal::Arg& a0, "this format string is missing its $0/$1/$2, contains one of " "$3-$9, or contains an unescaped $ character (use $$ instead)"); -std::string Substitute(const char* format, const substitute_internal::Arg& a0, +std::string Substitute(absl::Nonnull<const char*> format, + const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, const substitute_internal::Arg& a2, const substitute_internal::Arg& a3) @@ -673,7 +687,8 @@ std::string Substitute(const char* format, const substitute_internal::Arg& a0, "this format string is missing its $0-$3, contains one of " "$4-$9, or contains an unescaped $ character (use $$ instead)"); -std::string Substitute(const char* format, const substitute_internal::Arg& a0, +std::string Substitute(absl::Nonnull<const char*> format, + const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, const substitute_internal::Arg& a2, const substitute_internal::Arg& a3, @@ -684,7 +699,8 @@ std::string Substitute(const char* format, const substitute_internal::Arg& a0, "this format string is missing its $0-$4, contains one of " "$5-$9, or contains an unescaped $ character (use $$ instead)"); -std::string Substitute(const char* format, const substitute_internal::Arg& a0, +std::string Substitute(absl::Nonnull<const char*> format, + const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, const substitute_internal::Arg& a2, const substitute_internal::Arg& a3, @@ -696,27 +712,23 @@ std::string Substitute(const char* format, const substitute_internal::Arg& a0, "this format string is missing its $0-$5, contains one of " "$6-$9, or contains an unescaped $ character (use $$ instead)"); -std::string Substitute(const char* format, const substitute_internal::Arg& a0, - const substitute_internal::Arg& a1, - const substitute_internal::Arg& a2, - const substitute_internal::Arg& a3, - const substitute_internal::Arg& a4, - const substitute_internal::Arg& a5, - const substitute_internal::Arg& a6) +std::string Substitute( + absl::Nonnull<const char*> format, const substitute_internal::Arg& a0, + const substitute_internal::Arg& a1, const substitute_internal::Arg& a2, + const substitute_internal::Arg& a3, const substitute_internal::Arg& a4, + const substitute_internal::Arg& a5, const substitute_internal::Arg& a6) ABSL_BAD_CALL_IF( substitute_internal::PlaceholderBitmask(format) != 127, "There were 7 substitution arguments given, but " "this format string is missing its $0-$6, contains one of " "$7-$9, or contains an unescaped $ character (use $$ instead)"); -std::string Substitute(const char* format, const substitute_internal::Arg& a0, - const substitute_internal::Arg& a1, - const substitute_internal::Arg& a2, - const substitute_internal::Arg& a3, - const substitute_internal::Arg& a4, - const substitute_internal::Arg& a5, - const substitute_internal::Arg& a6, - const substitute_internal::Arg& a7) +std::string Substitute( + absl::Nonnull<const char*> format, const substitute_internal::Arg& a0, + const substitute_internal::Arg& a1, const substitute_internal::Arg& a2, + const substitute_internal::Arg& a3, const substitute_internal::Arg& a4, + const substitute_internal::Arg& a5, const substitute_internal::Arg& a6, + const substitute_internal::Arg& a7) ABSL_BAD_CALL_IF( substitute_internal::PlaceholderBitmask(format) != 255, "There were 8 substitution arguments given, but " @@ -724,7 +736,7 @@ std::string Substitute(const char* format, const substitute_internal::Arg& a0, "$8-$9, or contains an unescaped $ character (use $$ instead)"); std::string Substitute( - const char* format, const substitute_internal::Arg& a0, + absl::Nonnull<const char*> format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, const substitute_internal::Arg& a2, const substitute_internal::Arg& a3, const substitute_internal::Arg& a4, const substitute_internal::Arg& a5, const substitute_internal::Arg& a6, @@ -736,7 +748,7 @@ std::string Substitute( "contains an unescaped $ character (use $$ instead)"); std::string Substitute( - const char* format, const substitute_internal::Arg& a0, + absl::Nonnull<const char*> format, const substitute_internal::Arg& a0, const substitute_internal::Arg& a1, const substitute_internal::Arg& a2, const substitute_internal::Arg& a3, const substitute_internal::Arg& a4, const substitute_internal::Arg& a5, const substitute_internal::Arg& a6, diff --git a/absl/strings/substitute_test.cc b/absl/strings/substitute_test.cc index ecf78d6b..70f9119b 100644 --- a/absl/strings/substitute_test.cc +++ b/absl/strings/substitute_test.cc @@ -15,10 +15,13 @@ #include "absl/strings/substitute.h" #include <cstdint> +#include <cstring> +#include <string> #include <vector> #include "gtest/gtest.h" #include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" namespace { diff --git a/absl/synchronization/BUILD.bazel b/absl/synchronization/BUILD.bazel index ccaee796..de06ebdd 100644 --- a/absl/synchronization/BUILD.bazel +++ b/absl/synchronization/BUILD.bazel @@ -21,7 +21,14 @@ load( "ABSL_TEST_COPTS", ) -package(default_visibility = ["//visibility:public"]) +package( + default_visibility = ["//visibility:private"], + features = [ + "header_modules", + "layering_check", + "parse_headers", + ], +) licenses(["notice"]) @@ -38,9 +45,6 @@ cc_library( "//conditions:default": [], }), linkopts = ABSL_DEFAULT_LINKOPTS, - visibility = [ - "//absl:__subpackages__", - ], deps = [ "//absl/base", "//absl/base:base_internal", @@ -53,27 +57,50 @@ cc_library( cc_library( name = "kernel_timeout_internal", + srcs = ["internal/kernel_timeout.cc"], hdrs = ["internal/kernel_timeout.h"], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, visibility = [ - "//absl/synchronization:__pkg__", ], deps = [ + "//absl/base", + "//absl/base:config", "//absl/base:core_headers", "//absl/base:raw_logging_internal", "//absl/time", ], ) +cc_test( + name = "kernel_timeout_internal_test", + srcs = ["internal/kernel_timeout_test.cc"], + copts = ABSL_TEST_COPTS, + flaky = 1, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":kernel_timeout_internal", + "//absl/base:config", + "//absl/random", + "//absl/time", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) + cc_library( name = "synchronization", srcs = [ "barrier.cc", "blocking_counter.cc", "internal/create_thread_identity.cc", + "internal/futex_waiter.cc", "internal/per_thread_sem.cc", - "internal/waiter.cc", + "internal/pthread_waiter.cc", + "internal/sem_waiter.cc", + "internal/stdcpp_waiter.cc", + "internal/waiter_base.cc", + "internal/win32_waiter.cc", "mutex.cc", "notification.cc", ], @@ -82,8 +109,14 @@ cc_library( "blocking_counter.h", "internal/create_thread_identity.h", "internal/futex.h", + "internal/futex_waiter.h", "internal/per_thread_sem.h", + "internal/pthread_waiter.h", + "internal/sem_waiter.h", + "internal/stdcpp_waiter.h", "internal/waiter.h", + "internal/waiter_base.h", + "internal/win32_waiter.h", "mutex.h", "notification.h", ], @@ -94,11 +127,12 @@ cc_library( "//absl:wasm": [], "//conditions:default": ["-pthread"], }) + ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:public"], deps = [ ":graphcycles_internal", ":kernel_timeout_internal", - "//absl/base:atomic_hook", "//absl/base", + "//absl/base:atomic_hook", "//absl/base:base_internal", "//absl/base:config", "//absl/base:core_headers", @@ -120,11 +154,12 @@ cc_test( copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, tags = [ - "no_test_wasm", + "no_test_wasm", # b/122473323 ], deps = [ ":synchronization", "//absl/time", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -136,11 +171,12 @@ cc_test( copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, tags = [ - "no_test_wasm", + "no_test_wasm", # b/122473323 ], deps = [ ":synchronization", "//absl/time", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -152,10 +188,10 @@ cc_binary( copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, tags = ["benchmark"], - visibility = ["//visibility:private"], deps = [ ":synchronization", ":thread_pool", + "//absl/base:no_destructor", "@com_github_google_benchmark//:benchmark_main", ], ) @@ -169,7 +205,9 @@ cc_test( deps = [ ":graphcycles_internal", "//absl/base:core_headers", - "//absl/base:raw_logging_internal", + "//absl/log", + "//absl/log:check", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -186,6 +224,7 @@ cc_test( ":graphcycles_internal", "//absl/base:raw_logging_internal", "@com_github_google_benchmark//:benchmark_main", + "@com_google_googletest//:gtest", ], ) @@ -209,6 +248,7 @@ cc_test( size = "large", srcs = ["mutex_test.cc"], copts = ABSL_TEST_COPTS, + flaky = 1, linkopts = ABSL_DEFAULT_LINKOPTS, shard_count = 25, deps = [ @@ -217,9 +257,11 @@ cc_test( "//absl/base", "//absl/base:config", "//absl/base:core_headers", - "//absl/base:raw_logging_internal", + "//absl/log", + "//absl/log:check", "//absl/memory", "//absl/time", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -232,6 +274,7 @@ cc_test( deps = [ ":synchronization", "//absl/base:config", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -243,13 +286,13 @@ cc_library( copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, visibility = [ - "//absl/synchronization:__pkg__", ], deps = [ ":synchronization", ":thread_pool", "//absl/base", "//absl/base:config", + "//absl/base:no_destructor", "@com_github_google_benchmark//:benchmark_main", ], alwayslink = 1, @@ -260,7 +303,6 @@ cc_binary( testonly = 1, copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, - visibility = ["//visibility:private"], deps = [ ":mutex_benchmark_common", ], @@ -271,11 +313,13 @@ cc_test( size = "small", srcs = ["notification_test.cc"], copts = ABSL_TEST_COPTS, + flaky = 1, linkopts = ABSL_DEFAULT_LINKOPTS, tags = ["no_test_lexan"], deps = [ ":synchronization", "//absl/time", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -286,6 +330,8 @@ cc_library( srcs = ["internal/per_thread_sem_test.cc"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + ], deps = [ ":synchronization", "//absl/base", @@ -315,6 +361,24 @@ cc_test( ) cc_test( + name = "waiter_test", + srcs = ["internal/waiter_test.cc"], + copts = ABSL_TEST_COPTS, + flaky = 1, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":kernel_timeout_internal", + ":synchronization", + ":thread_pool", + "//absl/base:config", + "//absl/random", + "//absl/time", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( name = "lifetime_test", srcs = [ "lifetime_test.cc", @@ -328,6 +392,6 @@ cc_test( deps = [ ":synchronization", "//absl/base:core_headers", - "//absl/base:raw_logging_internal", + "//absl/log:check", ], ) diff --git a/absl/synchronization/CMakeLists.txt b/absl/synchronization/CMakeLists.txt index f64653bb..a0f64e5c 100644 --- a/absl/synchronization/CMakeLists.txt +++ b/absl/synchronization/CMakeLists.txt @@ -39,14 +39,33 @@ absl_cc_library( kernel_timeout_internal HDRS "internal/kernel_timeout.h" + SRCS + "internal/kernel_timeout.cc" COPTS ${ABSL_DEFAULT_COPTS} DEPS + absl::base + absl::config absl::core_headers absl::raw_logging_internal absl::time ) +absl_cc_test( + NAME + kernel_timeout_internal_test + SRCS + "internal/kernel_timeout_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::kernel_timeout_internal + absl::config + absl::random_random + absl::time + GTest::gmock_main +) + absl_cc_library( NAME synchronization @@ -55,16 +74,27 @@ absl_cc_library( "blocking_counter.h" "internal/create_thread_identity.h" "internal/futex.h" + "internal/futex_waiter.h" "internal/per_thread_sem.h" + "internal/pthread_waiter.h" + "internal/sem_waiter.h" + "internal/stdcpp_waiter.h" "internal/waiter.h" + "internal/waiter_base.h" + "internal/win32_waiter.h" "mutex.h" "notification.h" SRCS "barrier.cc" "blocking_counter.cc" "internal/create_thread_identity.cc" + "internal/futex_waiter.cc" "internal/per_thread_sem.cc" - "internal/waiter.cc" + "internal/pthread_waiter.cc" + "internal/sem_waiter.cc" + "internal/stdcpp_waiter.cc" + "internal/waiter_base.cc" + "internal/win32_waiter.cc" "notification.cc" "mutex.cc" COPTS @@ -121,9 +151,10 @@ absl_cc_test( COPTS ${ABSL_TEST_COPTS} DEPS - absl::graphcycles_internal + absl::check absl::core_headers - absl::raw_logging_internal + absl::graphcycles_internal + absl::log GTest::gmock_main ) @@ -153,10 +184,11 @@ absl_cc_test( absl::synchronization absl::thread_pool absl::base + absl::check absl::config absl::core_headers + absl::log absl::memory - absl::raw_logging_internal absl::time GTest::gmock_main ) @@ -223,6 +255,23 @@ absl_cc_test( absl_cc_test( NAME + waiter_test + SRCS + "internal/waiter_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::config + absl::kernel_timeout_internal + absl::random_random + absl::synchronization + absl::thread_pool + absl::time + GTest::gmock_main +) + +absl_cc_test( + NAME lifetime_test SRCS "lifetime_test.cc" @@ -231,5 +280,5 @@ absl_cc_test( DEPS absl::synchronization absl::core_headers - absl::raw_logging_internal + absl::check ) diff --git a/absl/synchronization/blocking_counter_benchmark.cc b/absl/synchronization/blocking_counter_benchmark.cc index b504d1a5..ea2bf9f9 100644 --- a/absl/synchronization/blocking_counter_benchmark.cc +++ b/absl/synchronization/blocking_counter_benchmark.cc @@ -14,6 +14,7 @@ #include <limits> +#include "absl/base/no_destructor.h" #include "absl/synchronization/blocking_counter.h" #include "absl/synchronization/internal/thread_pool.h" #include "benchmark/benchmark.h" @@ -39,8 +40,8 @@ BENCHMARK(BM_BlockingCounter_SingleThread) ->Arg(256); void BM_BlockingCounter_DecrementCount(benchmark::State& state) { - static absl::BlockingCounter* counter = - new absl::BlockingCounter{std::numeric_limits<int>::max()}; + static absl::NoDestructor<absl::BlockingCounter> counter( + std::numeric_limits<int>::max()); for (auto _ : state) { counter->DecrementCount(); } diff --git a/absl/synchronization/internal/create_thread_identity.cc b/absl/synchronization/internal/create_thread_identity.cc index 44e6129b..eacaa28d 100644 --- a/absl/synchronization/internal/create_thread_identity.cc +++ b/absl/synchronization/internal/create_thread_identity.cc @@ -13,10 +13,12 @@ // limitations under the License. #include <stdint.h> + #include <new> // This file is a no-op if the required LowLevelAlloc support is missing. #include "absl/base/internal/low_level_alloc.h" +#include "absl/synchronization/internal/waiter.h" #ifndef ABSL_LOW_LEVEL_ALLOC_MISSING #include <string.h> @@ -71,6 +73,9 @@ static intptr_t RoundUp(intptr_t addr, intptr_t align) { void OneTimeInitThreadIdentity(base_internal::ThreadIdentity* identity) { PerThreadSem::Init(identity); + identity->ticker.store(0, std::memory_order_relaxed); + identity->wait_start.store(0, std::memory_order_relaxed); + identity->is_idle.store(false, std::memory_order_relaxed); } static void ResetThreadIdentityBetweenReuse( diff --git a/absl/synchronization/internal/futex.h b/absl/synchronization/internal/futex.h index cb97da09..573c01b7 100644 --- a/absl/synchronization/internal/futex.h +++ b/absl/synchronization/internal/futex.h @@ -16,9 +16,7 @@ #include "absl/base/config.h" -#ifdef _WIN32 -#include <windows.h> -#else +#ifndef _WIN32 #include <sys/time.h> #include <unistd.h> #endif @@ -34,6 +32,7 @@ #include <atomic> #include <cstdint> +#include <limits> #include "absl/base/optimization.h" #include "absl/synchronization/internal/kernel_timeout.h" @@ -81,51 +80,64 @@ namespace synchronization_internal { #if defined(SYS_futex_time64) && !defined(SYS_futex) #define SYS_futex SYS_futex_time64 +using FutexTimespec = struct timespec; +#else +// Some libc implementations have switched to an unconditional 64-bit `time_t` +// definition. This means that `struct timespec` may not match the layout +// expected by the kernel ABI on 32-bit platforms. So we define the +// FutexTimespec that matches the kernel timespec definition. It should be safe +// to use this struct for 64-bit userspace builds too, since it will use another +// SYS_futex kernel call with 64-bit tv_sec inside timespec. +struct FutexTimespec { + long tv_sec; // NOLINT + long tv_nsec; // NOLINT +}; #endif class FutexImpl { public: - static int WaitUntil(std::atomic<int32_t> *v, int32_t val, - KernelTimeout t) { - long err = 0; // NOLINT(runtime/int) - if (t.has_timeout()) { - // https://locklessinc.com/articles/futex_cheat_sheet/ - // Unlike FUTEX_WAIT, FUTEX_WAIT_BITSET uses absolute time. - struct timespec abs_timeout = t.MakeAbsTimespec(); - // Atomically check that the futex value is still 0, and if it - // is, sleep until abs_timeout or until woken by FUTEX_WAKE. - err = syscall( - SYS_futex, reinterpret_cast<int32_t *>(v), - FUTEX_WAIT_BITSET | FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME, val, - &abs_timeout, nullptr, FUTEX_BITSET_MATCH_ANY); - } else { - // Atomically check that the futex value is still 0, and if it - // is, sleep until woken by FUTEX_WAKE. - err = syscall(SYS_futex, reinterpret_cast<int32_t *>(v), - FUTEX_WAIT | FUTEX_PRIVATE_FLAG, val, nullptr); - } - if (ABSL_PREDICT_FALSE(err != 0)) { + // Atomically check that `*v == val`, and if it is, then sleep until the until + // woken by `Wake()`. + static int Wait(std::atomic<int32_t>* v, int32_t val) { + return WaitAbsoluteTimeout(v, val, nullptr); + } + + // Atomically check that `*v == val`, and if it is, then sleep until + // CLOCK_REALTIME reaches `*abs_timeout`, or until woken by `Wake()`. + static int WaitAbsoluteTimeout(std::atomic<int32_t>* v, int32_t val, + const struct timespec* abs_timeout) { + FutexTimespec ts; + // https://locklessinc.com/articles/futex_cheat_sheet/ + // Unlike FUTEX_WAIT, FUTEX_WAIT_BITSET uses absolute time. + auto err = syscall( + SYS_futex, reinterpret_cast<int32_t*>(v), + FUTEX_WAIT_BITSET | FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME, val, + ToFutexTimespec(abs_timeout, &ts), nullptr, FUTEX_BITSET_MATCH_ANY); + if (err != 0) { return -errno; } return 0; } - static int WaitBitsetAbsoluteTimeout(std::atomic<int32_t> *v, int32_t val, - int32_t bits, - const struct timespec *abstime) { - // NOLINTNEXTLINE(runtime/int) - long err = syscall(SYS_futex, reinterpret_cast<int32_t*>(v), - FUTEX_WAIT_BITSET | FUTEX_PRIVATE_FLAG, val, abstime, - nullptr, bits); - if (ABSL_PREDICT_FALSE(err != 0)) { + // Atomically check that `*v == val`, and if it is, then sleep until + // `*rel_timeout` has elapsed, or until woken by `Wake()`. + static int WaitRelativeTimeout(std::atomic<int32_t>* v, int32_t val, + const struct timespec* rel_timeout) { + FutexTimespec ts; + // Atomically check that the futex value is still 0, and if it + // is, sleep until abs_timeout or until woken by FUTEX_WAKE. + auto err = + syscall(SYS_futex, reinterpret_cast<int32_t*>(v), FUTEX_PRIVATE_FLAG, + val, ToFutexTimespec(rel_timeout, &ts)); + if (err != 0) { return -errno; } return 0; } - static int Wake(std::atomic<int32_t> *v, int32_t count) { - // NOLINTNEXTLINE(runtime/int) - long err = syscall(SYS_futex, reinterpret_cast<int32_t*>(v), + // Wakes at most `count` waiters that have entered the sleep state on `v`. + static int Wake(std::atomic<int32_t>* v, int32_t count) { + auto err = syscall(SYS_futex, reinterpret_cast<int32_t*>(v), FUTEX_WAKE | FUTEX_PRIVATE_FLAG, count); if (ABSL_PREDICT_FALSE(err < 0)) { return -errno; @@ -133,16 +145,24 @@ class FutexImpl { return 0; } - // FUTEX_WAKE_BITSET - static int WakeBitset(std::atomic<int32_t> *v, int32_t count, int32_t bits) { - // NOLINTNEXTLINE(runtime/int) - long err = syscall(SYS_futex, reinterpret_cast<int32_t*>(v), - FUTEX_WAKE_BITSET | FUTEX_PRIVATE_FLAG, count, nullptr, - nullptr, bits); - if (ABSL_PREDICT_FALSE(err < 0)) { - return -errno; + private: + static FutexTimespec* ToFutexTimespec(const struct timespec* userspace_ts, + FutexTimespec* futex_ts) { + if (userspace_ts == nullptr) { + return nullptr; } - return 0; + + using FutexSeconds = decltype(futex_ts->tv_sec); + using FutexNanoseconds = decltype(futex_ts->tv_nsec); + + constexpr auto kMaxSeconds{(std::numeric_limits<FutexSeconds>::max)()}; + if (userspace_ts->tv_sec > kMaxSeconds) { + futex_ts->tv_sec = kMaxSeconds; + } else { + futex_ts->tv_sec = static_cast<FutexSeconds>(userspace_ts->tv_sec); + } + futex_ts->tv_nsec = static_cast<FutexNanoseconds>(userspace_ts->tv_nsec); + return futex_ts; } }; diff --git a/absl/synchronization/internal/futex_waiter.cc b/absl/synchronization/internal/futex_waiter.cc new file mode 100644 index 00000000..87eb3b23 --- /dev/null +++ b/absl/synchronization/internal/futex_waiter.cc @@ -0,0 +1,111 @@ +// Copyright 2023 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/synchronization/internal/futex_waiter.h" + +#ifdef ABSL_INTERNAL_HAVE_FUTEX_WAITER + +#include <atomic> +#include <cstdint> +#include <cerrno> + +#include "absl/base/config.h" +#include "absl/base/internal/raw_logging.h" +#include "absl/base/internal/thread_identity.h" +#include "absl/base/optimization.h" +#include "absl/synchronization/internal/kernel_timeout.h" +#include "absl/synchronization/internal/futex.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace synchronization_internal { + +#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL +constexpr char FutexWaiter::kName[]; +#endif + +int FutexWaiter::WaitUntil(std::atomic<int32_t>* v, int32_t val, + KernelTimeout t) { +#ifdef CLOCK_MONOTONIC + constexpr bool kHasClockMonotonic = true; +#else + constexpr bool kHasClockMonotonic = false; +#endif + + // We can't call Futex::WaitUntil() here because the prodkernel implementation + // does not know about KernelTimeout::SupportsSteadyClock(). + if (!t.has_timeout()) { + return Futex::Wait(v, val); + } else if (kHasClockMonotonic && KernelTimeout::SupportsSteadyClock() && + t.is_relative_timeout()) { + auto rel_timespec = t.MakeRelativeTimespec(); + return Futex::WaitRelativeTimeout(v, val, &rel_timespec); + } else { + auto abs_timespec = t.MakeAbsTimespec(); + return Futex::WaitAbsoluteTimeout(v, val, &abs_timespec); + } +} + +bool FutexWaiter::Wait(KernelTimeout t) { + // Loop until we can atomically decrement futex from a positive + // value, waiting on a futex while we believe it is zero. + // Note that, since the thread ticker is just reset, we don't need to check + // whether the thread is idle on the very first pass of the loop. + bool first_pass = true; + while (true) { + int32_t x = futex_.load(std::memory_order_relaxed); + while (x != 0) { + if (!futex_.compare_exchange_weak(x, x - 1, + std::memory_order_acquire, + std::memory_order_relaxed)) { + continue; // Raced with someone, retry. + } + return true; // Consumed a wakeup, we are done. + } + + if (!first_pass) MaybeBecomeIdle(); + const int err = WaitUntil(&futex_, 0, t); + if (err != 0) { + if (err == -EINTR || err == -EWOULDBLOCK) { + // Do nothing, the loop will retry. + } else if (err == -ETIMEDOUT) { + return false; + } else { + ABSL_RAW_LOG(FATAL, "Futex operation failed with error %d\n", err); + } + } + first_pass = false; + } +} + +void FutexWaiter::Post() { + if (futex_.fetch_add(1, std::memory_order_release) == 0) { + // We incremented from 0, need to wake a potential waiter. + Poke(); + } +} + +void FutexWaiter::Poke() { + // Wake one thread waiting on the futex. + const int err = Futex::Wake(&futex_, 1); + if (ABSL_PREDICT_FALSE(err < 0)) { + ABSL_RAW_LOG(FATAL, "Futex operation failed with error %d\n", err); + } +} + +} // namespace synchronization_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_INTERNAL_HAVE_FUTEX_WAITER diff --git a/absl/synchronization/internal/futex_waiter.h b/absl/synchronization/internal/futex_waiter.h new file mode 100644 index 00000000..11dfa93b --- /dev/null +++ b/absl/synchronization/internal/futex_waiter.h @@ -0,0 +1,63 @@ +// Copyright 2023 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_SYNCHRONIZATION_INTERNAL_FUTEX_WAITER_H_ +#define ABSL_SYNCHRONIZATION_INTERNAL_FUTEX_WAITER_H_ + +#include <atomic> +#include <cstdint> + +#include "absl/base/config.h" +#include "absl/synchronization/internal/kernel_timeout.h" +#include "absl/synchronization/internal/futex.h" +#include "absl/synchronization/internal/waiter_base.h" + +#ifdef ABSL_INTERNAL_HAVE_FUTEX + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace synchronization_internal { + +#define ABSL_INTERNAL_HAVE_FUTEX_WAITER 1 + +class FutexWaiter : public WaiterCrtp<FutexWaiter> { + public: + FutexWaiter() : futex_(0) {} + + bool Wait(KernelTimeout t); + void Post(); + void Poke(); + + static constexpr char kName[] = "FutexWaiter"; + + private: + // Atomically check that `*v == val`, and if it is, then sleep until the + // timeout `t` has been reached, or until woken by `Wake()`. + static int WaitUntil(std::atomic<int32_t>* v, int32_t val, + KernelTimeout t); + + // Futexes are defined by specification to be 32-bits. + // Thus std::atomic<int32_t> must be just an int32_t with lockfree methods. + std::atomic<int32_t> futex_; + static_assert(sizeof(int32_t) == sizeof(futex_), "Wrong size for futex"); +}; + +} // namespace synchronization_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_INTERNAL_HAVE_FUTEX + +#endif // ABSL_SYNCHRONIZATION_INTERNAL_FUTEX_WAITER_H_ diff --git a/absl/synchronization/internal/graphcycles.cc b/absl/synchronization/internal/graphcycles.cc index feec4581..39b18482 100644 --- a/absl/synchronization/internal/graphcycles.cc +++ b/absl/synchronization/internal/graphcycles.cc @@ -37,6 +37,7 @@ #include <algorithm> #include <array> +#include <cinttypes> #include <limits> #include "absl/base/internal/hide_ptr.h" #include "absl/base/internal/raw_logging.h" @@ -114,7 +115,7 @@ class Vec { if (src->ptr_ == src->space_) { // Need to actually copy resize(src->size_); - std::copy(src->ptr_, src->ptr_ + src->size_, ptr_); + std::copy_n(src->ptr_, src->size_, ptr_); src->size_ = 0; } else { Discard(); @@ -148,7 +149,7 @@ class Vec { size_t request = static_cast<size_t>(capacity_) * sizeof(T); T* copy = static_cast<T*>( base_internal::LowLevelAlloc::AllocWithArena(request, arena)); - std::copy(ptr_, ptr_ + size_, copy); + std::copy_n(ptr_, size_, copy); Discard(); ptr_ = copy; } @@ -386,19 +387,22 @@ bool GraphCycles::CheckInvariants() const { Node* nx = r->nodes_[x]; void* ptr = base_internal::UnhidePtr<void>(nx->masked_ptr); if (ptr != nullptr && static_cast<uint32_t>(r->ptrmap_.Find(ptr)) != x) { - ABSL_RAW_LOG(FATAL, "Did not find live node in hash table %u %p", x, ptr); + ABSL_RAW_LOG(FATAL, "Did not find live node in hash table %" PRIu32 " %p", + x, ptr); } if (nx->visited) { - ABSL_RAW_LOG(FATAL, "Did not clear visited marker on node %u", x); + ABSL_RAW_LOG(FATAL, "Did not clear visited marker on node %" PRIu32, x); } if (!ranks.insert(nx->rank)) { - ABSL_RAW_LOG(FATAL, "Duplicate occurrence of rank %d", nx->rank); + ABSL_RAW_LOG(FATAL, "Duplicate occurrence of rank %" PRId32, nx->rank); } HASH_FOR_EACH(y, nx->out) { Node* ny = r->nodes_[static_cast<uint32_t>(y)]; if (nx->rank >= ny->rank) { - ABSL_RAW_LOG(FATAL, "Edge %u->%d has bad rank assignment %d->%d", x, y, - nx->rank, ny->rank); + ABSL_RAW_LOG(FATAL, + "Edge %" PRIu32 " ->%" PRId32 + " has bad rank assignment %" PRId32 "->%" PRId32, + x, y, nx->rank, ny->rank); } } } diff --git a/absl/synchronization/internal/graphcycles_test.cc b/absl/synchronization/internal/graphcycles_test.cc index 74eaffe7..3c6ef798 100644 --- a/absl/synchronization/internal/graphcycles_test.cc +++ b/absl/synchronization/internal/graphcycles_test.cc @@ -21,8 +21,9 @@ #include <vector> #include "gtest/gtest.h" -#include "absl/base/internal/raw_logging.h" #include "absl/base/macros.h" +#include "absl/log/check.h" +#include "absl/log/log.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -65,51 +66,51 @@ static bool IsReachable(Edges *edges, int from, int to, } static void PrintEdges(Edges *edges) { - ABSL_RAW_LOG(INFO, "EDGES (%zu)", edges->size()); + LOG(INFO) << "EDGES (" << edges->size() << ")"; for (const auto &edge : *edges) { int a = edge.from; int b = edge.to; - ABSL_RAW_LOG(INFO, "%d %d", a, b); + LOG(INFO) << a << " " << b; } - ABSL_RAW_LOG(INFO, "---"); + LOG(INFO) << "---"; } static void PrintGCEdges(Nodes *nodes, const IdMap &id, GraphCycles *gc) { - ABSL_RAW_LOG(INFO, "GC EDGES"); + LOG(INFO) << "GC EDGES"; for (int a : *nodes) { for (int b : *nodes) { if (gc->HasEdge(Get(id, a), Get(id, b))) { - ABSL_RAW_LOG(INFO, "%d %d", a, b); + LOG(INFO) << a << " " << b; } } } - ABSL_RAW_LOG(INFO, "---"); + LOG(INFO) << "---"; } static void PrintTransitiveClosure(Nodes *nodes, Edges *edges) { - ABSL_RAW_LOG(INFO, "Transitive closure"); + LOG(INFO) << "Transitive closure"; for (int a : *nodes) { for (int b : *nodes) { std::unordered_set<int> seen; if (IsReachable(edges, a, b, &seen)) { - ABSL_RAW_LOG(INFO, "%d %d", a, b); + LOG(INFO) << a << " " << b; } } } - ABSL_RAW_LOG(INFO, "---"); + LOG(INFO) << "---"; } static void PrintGCTransitiveClosure(Nodes *nodes, const IdMap &id, GraphCycles *gc) { - ABSL_RAW_LOG(INFO, "GC Transitive closure"); + LOG(INFO) << "GC Transitive closure"; for (int a : *nodes) { for (int b : *nodes) { if (gc->IsReachable(Get(id, a), Get(id, b))) { - ABSL_RAW_LOG(INFO, "%d %d", a, b); + LOG(INFO) << a << " " << b; } } } - ABSL_RAW_LOG(INFO, "---"); + LOG(INFO) << "---"; } static void CheckTransitiveClosure(Nodes *nodes, Edges *edges, const IdMap &id, @@ -125,9 +126,8 @@ static void CheckTransitiveClosure(Nodes *nodes, Edges *edges, const IdMap &id, PrintGCEdges(nodes, id, gc); PrintTransitiveClosure(nodes, edges); PrintGCTransitiveClosure(nodes, id, gc); - ABSL_RAW_LOG(FATAL, "gc_reachable %s reachable %s a %d b %d", - gc_reachable ? "true" : "false", - reachable ? "true" : "false", a, b); + LOG(FATAL) << "gc_reachable " << gc_reachable << " reachable " + << reachable << " a " << a << " b " << b; } } } @@ -142,7 +142,7 @@ static void CheckEdges(Nodes *nodes, Edges *edges, const IdMap &id, if (!gc->HasEdge(Get(id, a), Get(id, b))) { PrintEdges(edges); PrintGCEdges(nodes, id, gc); - ABSL_RAW_LOG(FATAL, "!gc->HasEdge(%d, %d)", a, b); + LOG(FATAL) << "!gc->HasEdge(" << a << ", " << b << ")"; } } for (const auto &a : *nodes) { @@ -155,13 +155,12 @@ static void CheckEdges(Nodes *nodes, Edges *edges, const IdMap &id, if (count != edges->size()) { PrintEdges(edges); PrintGCEdges(nodes, id, gc); - ABSL_RAW_LOG(FATAL, "edges->size() %zu count %d", edges->size(), count); + LOG(FATAL) << "edges->size() " << edges->size() << " count " << count; } } static void CheckInvariants(const GraphCycles &gc) { - if (ABSL_PREDICT_FALSE(!gc.CheckInvariants())) - ABSL_RAW_LOG(FATAL, "CheckInvariants"); + CHECK(gc.CheckInvariants()) << "CheckInvariants"; } // Returns the index of a randomly chosen node in *nodes. @@ -309,7 +308,7 @@ TEST(GraphCycles, RandomizedTest) { break; default: - ABSL_RAW_LOG(FATAL, "op %d", op); + LOG(FATAL) << "op " << op; } // Very rarely, test graph expansion by adding then removing many nodes. diff --git a/absl/synchronization/internal/kernel_timeout.cc b/absl/synchronization/internal/kernel_timeout.cc new file mode 100644 index 00000000..48ea6287 --- /dev/null +++ b/absl/synchronization/internal/kernel_timeout.cc @@ -0,0 +1,225 @@ +// Copyright 2023 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/synchronization/internal/kernel_timeout.h" + +#ifndef _WIN32 +#include <sys/types.h> +#endif + +#include <algorithm> +#include <chrono> // NOLINT(build/c++11) +#include <cstdint> +#include <cstdlib> +#include <cstring> +#include <ctime> +#include <limits> + +#include "absl/base/attributes.h" +#include "absl/base/call_once.h" +#include "absl/base/config.h" +#include "absl/time/time.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace synchronization_internal { + +#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL +constexpr uint64_t KernelTimeout::kNoTimeout; +constexpr int64_t KernelTimeout::kMaxNanos; +#endif + +int64_t KernelTimeout::SteadyClockNow() { + if (!SupportsSteadyClock()) { + return absl::GetCurrentTimeNanos(); + } + return std::chrono::duration_cast<std::chrono::nanoseconds>( + std::chrono::steady_clock::now().time_since_epoch()) + .count(); +} + +KernelTimeout::KernelTimeout(absl::Time t) { + // `absl::InfiniteFuture()` is a common "no timeout" value and cheaper to + // compare than convert. + if (t == absl::InfiniteFuture()) { + rep_ = kNoTimeout; + return; + } + + int64_t unix_nanos = absl::ToUnixNanos(t); + + // A timeout that lands before the unix epoch is converted to 0. + // In theory implementations should expire these timeouts immediately. + if (unix_nanos < 0) { + unix_nanos = 0; + } + + // Values greater than or equal to kMaxNanos are converted to infinite. + if (unix_nanos >= kMaxNanos) { + rep_ = kNoTimeout; + return; + } + + rep_ = static_cast<uint64_t>(unix_nanos) << 1; +} + +KernelTimeout::KernelTimeout(absl::Duration d) { + // `absl::InfiniteDuration()` is a common "no timeout" value and cheaper to + // compare than convert. + if (d == absl::InfiniteDuration()) { + rep_ = kNoTimeout; + return; + } + + int64_t nanos = absl::ToInt64Nanoseconds(d); + + // Negative durations are normalized to 0. + // In theory implementations should expire these timeouts immediately. + if (nanos < 0) { + nanos = 0; + } + + int64_t now = SteadyClockNow(); + if (nanos > kMaxNanos - now) { + // Durations that would be greater than kMaxNanos are converted to infinite. + rep_ = kNoTimeout; + return; + } + + nanos += now; + rep_ = (static_cast<uint64_t>(nanos) << 1) | uint64_t{1}; +} + +int64_t KernelTimeout::MakeAbsNanos() const { + if (!has_timeout()) { + return kMaxNanos; + } + + int64_t nanos = RawAbsNanos(); + + if (is_relative_timeout()) { + // We need to change epochs, because the relative timeout might be + // represented by an absolute timestamp from another clock. + nanos = std::max<int64_t>(nanos - SteadyClockNow(), 0); + int64_t now = absl::GetCurrentTimeNanos(); + if (nanos > kMaxNanos - now) { + // Overflow. + nanos = kMaxNanos; + } else { + nanos += now; + } + } else if (nanos == 0) { + // Some callers have assumed that 0 means no timeout, so instead we return a + // time of 1 nanosecond after the epoch. + nanos = 1; + } + + return nanos; +} + +int64_t KernelTimeout::InNanosecondsFromNow() const { + if (!has_timeout()) { + return kMaxNanos; + } + + int64_t nanos = RawAbsNanos(); + if (is_absolute_timeout()) { + return std::max<int64_t>(nanos - absl::GetCurrentTimeNanos(), 0); + } + return std::max<int64_t>(nanos - SteadyClockNow(), 0); +} + +struct timespec KernelTimeout::MakeAbsTimespec() const { + return absl::ToTimespec(absl::Nanoseconds(MakeAbsNanos())); +} + +struct timespec KernelTimeout::MakeRelativeTimespec() const { + return absl::ToTimespec(absl::Nanoseconds(InNanosecondsFromNow())); +} + +#ifndef _WIN32 +struct timespec KernelTimeout::MakeClockAbsoluteTimespec(clockid_t c) const { + if (!has_timeout()) { + return absl::ToTimespec(absl::Nanoseconds(kMaxNanos)); + } + + int64_t nanos = RawAbsNanos(); + if (is_absolute_timeout()) { + nanos -= absl::GetCurrentTimeNanos(); + } else { + nanos -= SteadyClockNow(); + } + + struct timespec now; + ABSL_RAW_CHECK(clock_gettime(c, &now) == 0, "clock_gettime() failed"); + absl::Duration from_clock_epoch = + absl::DurationFromTimespec(now) + absl::Nanoseconds(nanos); + if (from_clock_epoch <= absl::ZeroDuration()) { + // Some callers have assumed that 0 means no timeout, so instead we return a + // time of 1 nanosecond after the epoch. For safety we also do not return + // negative values. + return absl::ToTimespec(absl::Nanoseconds(1)); + } + return absl::ToTimespec(from_clock_epoch); +} +#endif + +KernelTimeout::DWord KernelTimeout::InMillisecondsFromNow() const { + constexpr DWord kInfinite = std::numeric_limits<DWord>::max(); + + if (!has_timeout()) { + return kInfinite; + } + + constexpr uint64_t kNanosInMillis = uint64_t{1'000'000}; + constexpr uint64_t kMaxValueNanos = + std::numeric_limits<int64_t>::max() - kNanosInMillis + 1; + + uint64_t ns_from_now = static_cast<uint64_t>(InNanosecondsFromNow()); + if (ns_from_now >= kMaxValueNanos) { + // Rounding up would overflow. + return kInfinite; + } + // Convert to milliseconds, always rounding up. + uint64_t ms_from_now = (ns_from_now + kNanosInMillis - 1) / kNanosInMillis; + if (ms_from_now > kInfinite) { + return kInfinite; + } + return static_cast<DWord>(ms_from_now); +} + +std::chrono::time_point<std::chrono::system_clock> +KernelTimeout::ToChronoTimePoint() const { + if (!has_timeout()) { + return std::chrono::time_point<std::chrono::system_clock>::max(); + } + + // The cast to std::microseconds is because (on some platforms) the + // std::ratio used by std::chrono::steady_clock doesn't convert to + // std::nanoseconds, so it doesn't compile. + auto micros = std::chrono::duration_cast<std::chrono::microseconds>( + std::chrono::nanoseconds(MakeAbsNanos())); + return std::chrono::system_clock::from_time_t(0) + micros; +} + +std::chrono::nanoseconds KernelTimeout::ToChronoDuration() const { + if (!has_timeout()) { + return std::chrono::nanoseconds::max(); + } + return std::chrono::nanoseconds(InNanosecondsFromNow()); +} + +} // namespace synchronization_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/synchronization/internal/kernel_timeout.h b/absl/synchronization/internal/kernel_timeout.h index f5c2c0ef..06404a75 100644 --- a/absl/synchronization/internal/kernel_timeout.h +++ b/absl/synchronization/internal/kernel_timeout.h @@ -11,26 +11,21 @@ // 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. -// - -// An optional absolute timeout, with nanosecond granularity, -// compatible with absl::Time. Suitable for in-register -// parameter-passing (e.g. syscalls.) -// Constructible from a absl::Time (for a timeout to be respected) or {} -// (for "no timeout".) -// This is a private low-level API for use by a handful of low-level -// components. Higher-level components should build APIs based on -// absl::Time and absl::Duration. #ifndef ABSL_SYNCHRONIZATION_INTERNAL_KERNEL_TIMEOUT_H_ #define ABSL_SYNCHRONIZATION_INTERNAL_KERNEL_TIMEOUT_H_ -#include <time.h> +#ifndef _WIN32 +#include <sys/types.h> +#endif #include <algorithm> +#include <chrono> // NOLINT(build/c++11) #include <cstdint> +#include <ctime> #include <limits> +#include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" #include "absl/time/clock.h" #include "absl/time/time.h" @@ -39,58 +34,73 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace synchronization_internal { -class Waiter; - +// An optional timeout, with nanosecond granularity. +// +// This is a private low-level API for use by a handful of low-level +// components. Higher-level components should build APIs based on +// absl::Time and absl::Duration. class KernelTimeout { public: - // A timeout that should expire at <t>. Any value, in the full - // InfinitePast() to InfiniteFuture() range, is valid here and will be - // respected. - explicit KernelTimeout(absl::Time t) : ns_(MakeNs(t)) {} - // No timeout. - KernelTimeout() : ns_(0) {} + // Construct an absolute timeout that should expire at `t`. + explicit KernelTimeout(absl::Time t); - // A more explicit factory for those who prefer it. Equivalent to {}. - static KernelTimeout Never() { return {}; } + // Construct a relative timeout that should expire after `d`. + explicit KernelTimeout(absl::Duration d); - // We explicitly do not support other custom formats: timespec, int64_t nanos. - // Unify on this and absl::Time, please. + // Infinite timeout. + constexpr KernelTimeout() : rep_(kNoTimeout) {} - bool has_timeout() const { return ns_ != 0; } + // A more explicit factory for those who prefer it. + // Equivalent to `KernelTimeout()`. + static constexpr KernelTimeout Never() { return KernelTimeout(); } - // Convert to parameter for sem_timedwait/futex/similar. Only for approved - // users. Do not call if !has_timeout. + // Returns true if there is a timeout that will eventually expire. + // Returns false if the timeout is infinite. + bool has_timeout() const { return rep_ != kNoTimeout; } + + // If `has_timeout()` is true, returns true if the timeout was provided as an + // `absl::Time`. The return value is undefined if `has_timeout()` is false + // because all indefinite timeouts are equivalent. + bool is_absolute_timeout() const { return (rep_ & 1) == 0; } + + // If `has_timeout()` is true, returns true if the timeout was provided as an + // `absl::Duration`. The return value is undefined if `has_timeout()` is false + // because all indefinite timeouts are equivalent. + bool is_relative_timeout() const { return (rep_ & 1) == 1; } + + // Convert to `struct timespec` for interfaces that expect an absolute + // timeout. If !has_timeout() or is_relative_timeout(), attempts to convert to + // a reasonable absolute timeout, but callers should to test has_timeout() and + // is_relative_timeout() and prefer to use a more appropriate interface. struct timespec MakeAbsTimespec() const; - // Convert to unix epoch nanos. Do not call if !has_timeout. + // Convert to `struct timespec` for interfaces that expect a relative + // timeout. If !has_timeout() or is_absolute_timeout(), attempts to convert to + // a reasonable relative timeout, but callers should to test has_timeout() and + // is_absolute_timeout() and prefer to use a more appropriate interface. Since + // the return value is a relative duration, it should be recomputed by calling + // this method in the case of a spurious wakeup. + struct timespec MakeRelativeTimespec() const; + +#ifndef _WIN32 + // Convert to `struct timespec` for interfaces that expect an absolute timeout + // on a specific clock `c`. This is similar to `MakeAbsTimespec()`, but + // callers usually want to use this method with `CLOCK_MONOTONIC` when + // relative timeouts are requested, and when the appropriate interface expects + // an absolute timeout relative to a specific clock (for example, + // pthread_cond_clockwait() or sem_clockwait()). If !has_timeout(), attempts + // to convert to a reasonable absolute timeout, but callers should to test + // has_timeout() prefer to use a more appropriate interface. + struct timespec MakeClockAbsoluteTimespec(clockid_t c) const; +#endif + + // Convert to unix epoch nanos for interfaces that expect an absolute timeout + // in nanoseconds. If !has_timeout() or is_relative_timeout(), attempts to + // convert to a reasonable absolute timeout, but callers should to test + // has_timeout() and is_relative_timeout() and prefer to use a more + // appropriate interface. int64_t MakeAbsNanos() const; - private: - // internal rep, not user visible: ns after unix epoch. - // zero = no timeout. - // Negative we treat as an unlikely (and certainly expired!) but valid - // timeout. - int64_t ns_; - - static int64_t MakeNs(absl::Time t) { - // optimization--InfiniteFuture is common "no timeout" value - // and cheaper to compare than convert. - if (t == absl::InfiniteFuture()) return 0; - int64_t x = ToUnixNanos(t); - - // A timeout that lands exactly on the epoch (x=0) needs to be respected, - // so we alter it unnoticably to 1. Negative timeouts are in - // theory supported, but handled poorly by the kernel (long - // delays) so push them forward too; since all such times have - // already passed, it's indistinguishable. - if (x <= 0) x = 1; - // A time larger than what can be represented to the kernel is treated - // as no timeout. - if (x == (std::numeric_limits<int64_t>::max)()) x = 0; - return x; - } - -#ifdef _WIN32 // Converts to milliseconds from now, or INFINITE when // !has_timeout(). For use by SleepConditionVariableSRW on // Windows. Callers should recognize that the return value is a @@ -100,68 +110,66 @@ class KernelTimeout { // so we define our own DWORD and INFINITE instead of getting them from // <intsafe.h> and <WinBase.h>. typedef unsigned long DWord; // NOLINT - DWord InMillisecondsFromNow() const { - constexpr DWord kInfinite = (std::numeric_limits<DWord>::max)(); - if (!has_timeout()) { - return kInfinite; - } - // The use of absl::Now() to convert from absolute time to - // relative time means that absl::Now() cannot use anything that - // depends on KernelTimeout (for example, Mutex) on Windows. - int64_t now = ToUnixNanos(absl::Now()); - if (ns_ >= now) { - // Round up so that Now() + ms_from_now >= ns_. - constexpr uint64_t max_nanos = - (std::numeric_limits<int64_t>::max)() - 999999u; - uint64_t ms_from_now = - ((std::min)(max_nanos, static_cast<uint64_t>(ns_ - now)) + 999999u) / - 1000000u; - if (ms_from_now > kInfinite) { - return kInfinite; - } - return static_cast<DWord>(ms_from_now); - } - return 0; - } - - friend class Waiter; -#endif -}; + DWord InMillisecondsFromNow() const; + + // Convert to std::chrono::time_point for interfaces that expect an absolute + // timeout, like std::condition_variable::wait_until(). If !has_timeout() or + // is_relative_timeout(), attempts to convert to a reasonable absolute + // timeout, but callers should test has_timeout() and is_relative_timeout() + // and prefer to use a more appropriate interface. + std::chrono::time_point<std::chrono::system_clock> ToChronoTimePoint() const; + + // Convert to std::chrono::time_point for interfaces that expect a relative + // timeout, like std::condition_variable::wait_for(). If !has_timeout() or + // is_absolute_timeout(), attempts to convert to a reasonable relative + // timeout, but callers should test has_timeout() and is_absolute_timeout() + // and prefer to use a more appropriate interface. Since the return value is a + // relative duration, it should be recomputed by calling this method in the + // case of a spurious wakeup. + std::chrono::nanoseconds ToChronoDuration() const; + + // Returns true if steady (aka monotonic) clocks are supported by the system. + // This method exists because go/btm requires synchronized clocks, and + // thus requires we use the system (aka walltime) clock. + static constexpr bool SupportsSteadyClock() { return true; } -inline struct timespec KernelTimeout::MakeAbsTimespec() const { - int64_t n = ns_; - static const int64_t kNanosPerSecond = 1000 * 1000 * 1000; - if (n == 0) { - ABSL_RAW_LOG( - ERROR, "Tried to create a timespec from a non-timeout; never do this."); - // But we'll try to continue sanely. no-timeout ~= saturated timeout. - n = (std::numeric_limits<int64_t>::max)(); - } - - // Kernel APIs validate timespecs as being at or after the epoch, - // despite the kernel time type being signed. However, no one can - // tell the difference between a timeout at or before the epoch (since - // all such timeouts have expired!) - if (n < 0) n = 0; - - struct timespec abstime; - int64_t seconds = (std::min)(n / kNanosPerSecond, - int64_t{(std::numeric_limits<time_t>::max)()}); - abstime.tv_sec = static_cast<time_t>(seconds); - abstime.tv_nsec = static_cast<decltype(abstime.tv_nsec)>(n % kNanosPerSecond); - return abstime; -} - -inline int64_t KernelTimeout::MakeAbsNanos() const { - if (ns_ == 0) { - ABSL_RAW_LOG( - ERROR, "Tried to create a timeout from a non-timeout; never do this."); - // But we'll try to continue sanely. no-timeout ~= saturated timeout. - return (std::numeric_limits<int64_t>::max)(); - } - - return ns_; -} + private: + // Returns the current time, expressed as a count of nanoseconds since the + // epoch used by an arbitrary clock. The implementation tries to use a steady + // (monotonic) clock if one is available. + static int64_t SteadyClockNow(); + + // Internal representation. + // - If the value is kNoTimeout, then the timeout is infinite, and + // has_timeout() will return true. + // - If the low bit is 0, then the high 63 bits is the number of nanoseconds + // after the unix epoch. + // - If the low bit is 1, then the high 63 bits is the number of nanoseconds + // after the epoch used by SteadyClockNow(). + // + // In all cases the time is stored as an absolute time, the only difference is + // the clock epoch. The use of absolute times is important since in the case + // of a relative timeout with a spurious wakeup, the program would have to + // restart the wait, and thus needs a way of recomputing the remaining time. + uint64_t rep_; + + // Returns the number of nanoseconds stored in the internal representation. + // When combined with the clock epoch indicated by the low bit (which is + // accessed through is_absolute_timeout() and is_relative_timeout()), the + // return value is used to compute when the timeout should occur. + int64_t RawAbsNanos() const { return static_cast<int64_t>(rep_ >> 1); } + + // Converts to nanoseconds from now. Since the return value is a relative + // duration, it should be recomputed by calling this method in the case of a + // spurious wakeup. + int64_t InNanosecondsFromNow() const; + + // A value that represents no timeout (or an infinite timeout). + static constexpr uint64_t kNoTimeout = (std::numeric_limits<uint64_t>::max)(); + + // The maximum value that can be stored in the high 63 bits. + static constexpr int64_t kMaxNanos = (std::numeric_limits<int64_t>::max)(); +}; } // namespace synchronization_internal ABSL_NAMESPACE_END diff --git a/absl/synchronization/internal/kernel_timeout_test.cc b/absl/synchronization/internal/kernel_timeout_test.cc new file mode 100644 index 00000000..bc546719 --- /dev/null +++ b/absl/synchronization/internal/kernel_timeout_test.cc @@ -0,0 +1,393 @@ +// Copyright 2023 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/synchronization/internal/kernel_timeout.h" + +#include <ctime> +#include <chrono> // NOLINT(build/c++11) +#include <limits> + +#include "absl/base/config.h" +#include "absl/random/random.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "gtest/gtest.h" + +// Test go/btm support by randomizing the value of clock_gettime() for +// CLOCK_MONOTONIC. This works by overriding a weak symbol in glibc. +// We should be resistant to this randomization when !SupportsSteadyClock(). +#if defined(__GOOGLE_GRTE_VERSION__) && \ + !defined(ABSL_HAVE_ADDRESS_SANITIZER) && \ + !defined(ABSL_HAVE_MEMORY_SANITIZER) && \ + !defined(ABSL_HAVE_THREAD_SANITIZER) +extern "C" int __clock_gettime(clockid_t c, struct timespec* ts); + +extern "C" int clock_gettime(clockid_t c, struct timespec* ts) { + if (c == CLOCK_MONOTONIC && + !absl::synchronization_internal::KernelTimeout::SupportsSteadyClock()) { + absl::SharedBitGen gen; + ts->tv_sec = absl::Uniform(gen, 0, 1'000'000'000); + ts->tv_nsec = absl::Uniform(gen, 0, 1'000'000'000); + return 0; + } + return __clock_gettime(c, ts); +} +#endif + +namespace { + +#if defined(ABSL_HAVE_ADDRESS_SANITIZER) || \ + defined(ABSL_HAVE_MEMORY_SANITIZER) || \ + defined(ABSL_HAVE_THREAD_SANITIZER) || defined(__ANDROID__) || \ + defined(__APPLE__) || defined(_WIN32) || defined(_WIN64) +constexpr absl::Duration kTimingBound = absl::Milliseconds(5); +#else +constexpr absl::Duration kTimingBound = absl::Microseconds(250); +#endif + +using absl::synchronization_internal::KernelTimeout; + +TEST(KernelTimeout, FiniteTimes) { + constexpr absl::Duration kDurationsToTest[] = { + absl::ZeroDuration(), + absl::Nanoseconds(1), + absl::Microseconds(1), + absl::Milliseconds(1), + absl::Seconds(1), + absl::Minutes(1), + absl::Hours(1), + absl::Hours(1000), + -absl::Nanoseconds(1), + -absl::Microseconds(1), + -absl::Milliseconds(1), + -absl::Seconds(1), + -absl::Minutes(1), + -absl::Hours(1), + -absl::Hours(1000), + }; + + for (auto duration : kDurationsToTest) { + const absl::Time now = absl::Now(); + const absl::Time when = now + duration; + SCOPED_TRACE(duration); + KernelTimeout t(when); + EXPECT_TRUE(t.has_timeout()); + EXPECT_TRUE(t.is_absolute_timeout()); + EXPECT_FALSE(t.is_relative_timeout()); + EXPECT_EQ(absl::TimeFromTimespec(t.MakeAbsTimespec()), when); +#ifndef _WIN32 + EXPECT_LE( + absl::AbsDuration(absl::Now() + duration - + absl::TimeFromTimespec( + t.MakeClockAbsoluteTimespec(CLOCK_REALTIME))), + absl::Milliseconds(10)); +#endif + EXPECT_LE( + absl::AbsDuration(absl::DurationFromTimespec(t.MakeRelativeTimespec()) - + std::max(duration, absl::ZeroDuration())), + kTimingBound); + EXPECT_EQ(absl::FromUnixNanos(t.MakeAbsNanos()), when); + EXPECT_LE(absl::AbsDuration(absl::Milliseconds(t.InMillisecondsFromNow()) - + std::max(duration, absl::ZeroDuration())), + absl::Milliseconds(5)); + EXPECT_LE(absl::AbsDuration(absl::FromChrono(t.ToChronoTimePoint()) - when), + absl::Microseconds(1)); + EXPECT_LE(absl::AbsDuration(absl::FromChrono(t.ToChronoDuration()) - + std::max(duration, absl::ZeroDuration())), + kTimingBound); + } +} + +TEST(KernelTimeout, InfiniteFuture) { + KernelTimeout t(absl::InfiniteFuture()); + EXPECT_FALSE(t.has_timeout()); + // Callers are expected to check has_timeout() instead of using the methods + // below, but we do try to do something reasonable if they don't. We may not + // be able to round-trip back to absl::InfiniteDuration() or + // absl::InfiniteFuture(), but we should return a very large value. + EXPECT_GT(absl::TimeFromTimespec(t.MakeAbsTimespec()), + absl::Now() + absl::Hours(100000)); +#ifndef _WIN32 + EXPECT_GT(absl::TimeFromTimespec(t.MakeClockAbsoluteTimespec(CLOCK_REALTIME)), + absl::Now() + absl::Hours(100000)); +#endif + EXPECT_GT(absl::DurationFromTimespec(t.MakeRelativeTimespec()), + absl::Hours(100000)); + EXPECT_GT(absl::FromUnixNanos(t.MakeAbsNanos()), + absl::Now() + absl::Hours(100000)); + EXPECT_EQ(t.InMillisecondsFromNow(), + std::numeric_limits<KernelTimeout::DWord>::max()); + EXPECT_EQ(t.ToChronoTimePoint(), + std::chrono::time_point<std::chrono::system_clock>::max()); + EXPECT_GE(t.ToChronoDuration(), std::chrono::nanoseconds::max()); +} + +TEST(KernelTimeout, DefaultConstructor) { + // The default constructor is equivalent to absl::InfiniteFuture(). + KernelTimeout t; + EXPECT_FALSE(t.has_timeout()); + // Callers are expected to check has_timeout() instead of using the methods + // below, but we do try to do something reasonable if they don't. We may not + // be able to round-trip back to absl::InfiniteDuration() or + // absl::InfiniteFuture(), but we should return a very large value. + EXPECT_GT(absl::TimeFromTimespec(t.MakeAbsTimespec()), + absl::Now() + absl::Hours(100000)); +#ifndef _WIN32 + EXPECT_GT(absl::TimeFromTimespec(t.MakeClockAbsoluteTimespec(CLOCK_REALTIME)), + absl::Now() + absl::Hours(100000)); +#endif + EXPECT_GT(absl::DurationFromTimespec(t.MakeRelativeTimespec()), + absl::Hours(100000)); + EXPECT_GT(absl::FromUnixNanos(t.MakeAbsNanos()), + absl::Now() + absl::Hours(100000)); + EXPECT_EQ(t.InMillisecondsFromNow(), + std::numeric_limits<KernelTimeout::DWord>::max()); + EXPECT_EQ(t.ToChronoTimePoint(), + std::chrono::time_point<std::chrono::system_clock>::max()); + EXPECT_GE(t.ToChronoDuration(), std::chrono::nanoseconds::max()); +} + +TEST(KernelTimeout, TimeMaxNanos) { + // Time >= kMaxNanos should behave as no timeout. + KernelTimeout t(absl::FromUnixNanos(std::numeric_limits<int64_t>::max())); + EXPECT_FALSE(t.has_timeout()); + // Callers are expected to check has_timeout() instead of using the methods + // below, but we do try to do something reasonable if they don't. We may not + // be able to round-trip back to absl::InfiniteDuration() or + // absl::InfiniteFuture(), but we should return a very large value. + EXPECT_GT(absl::TimeFromTimespec(t.MakeAbsTimespec()), + absl::Now() + absl::Hours(100000)); +#ifndef _WIN32 + EXPECT_GT(absl::TimeFromTimespec(t.MakeClockAbsoluteTimespec(CLOCK_REALTIME)), + absl::Now() + absl::Hours(100000)); +#endif + EXPECT_GT(absl::DurationFromTimespec(t.MakeRelativeTimespec()), + absl::Hours(100000)); + EXPECT_GT(absl::FromUnixNanos(t.MakeAbsNanos()), + absl::Now() + absl::Hours(100000)); + EXPECT_EQ(t.InMillisecondsFromNow(), + std::numeric_limits<KernelTimeout::DWord>::max()); + EXPECT_EQ(t.ToChronoTimePoint(), + std::chrono::time_point<std::chrono::system_clock>::max()); + EXPECT_GE(t.ToChronoDuration(), std::chrono::nanoseconds::max()); +} + +TEST(KernelTimeout, Never) { + // KernelTimeout::Never() is equivalent to absl::InfiniteFuture(). + KernelTimeout t = KernelTimeout::Never(); + EXPECT_FALSE(t.has_timeout()); + // Callers are expected to check has_timeout() instead of using the methods + // below, but we do try to do something reasonable if they don't. We may not + // be able to round-trip back to absl::InfiniteDuration() or + // absl::InfiniteFuture(), but we should return a very large value. + EXPECT_GT(absl::TimeFromTimespec(t.MakeAbsTimespec()), + absl::Now() + absl::Hours(100000)); +#ifndef _WIN32 + EXPECT_GT(absl::TimeFromTimespec(t.MakeClockAbsoluteTimespec(CLOCK_REALTIME)), + absl::Now() + absl::Hours(100000)); +#endif + EXPECT_GT(absl::DurationFromTimespec(t.MakeRelativeTimespec()), + absl::Hours(100000)); + EXPECT_GT(absl::FromUnixNanos(t.MakeAbsNanos()), + absl::Now() + absl::Hours(100000)); + EXPECT_EQ(t.InMillisecondsFromNow(), + std::numeric_limits<KernelTimeout::DWord>::max()); + EXPECT_EQ(t.ToChronoTimePoint(), + std::chrono::time_point<std::chrono::system_clock>::max()); + EXPECT_GE(t.ToChronoDuration(), std::chrono::nanoseconds::max()); +} + +TEST(KernelTimeout, InfinitePast) { + KernelTimeout t(absl::InfinitePast()); + EXPECT_TRUE(t.has_timeout()); + EXPECT_TRUE(t.is_absolute_timeout()); + EXPECT_FALSE(t.is_relative_timeout()); + EXPECT_LE(absl::TimeFromTimespec(t.MakeAbsTimespec()), + absl::FromUnixNanos(1)); +#ifndef _WIN32 + EXPECT_LE(absl::TimeFromTimespec(t.MakeClockAbsoluteTimespec(CLOCK_REALTIME)), + absl::FromUnixSeconds(1)); +#endif + EXPECT_EQ(absl::DurationFromTimespec(t.MakeRelativeTimespec()), + absl::ZeroDuration()); + EXPECT_LE(absl::FromUnixNanos(t.MakeAbsNanos()), absl::FromUnixNanos(1)); + EXPECT_EQ(t.InMillisecondsFromNow(), KernelTimeout::DWord{0}); + EXPECT_LT(t.ToChronoTimePoint(), std::chrono::system_clock::from_time_t(0) + + std::chrono::seconds(1)); + EXPECT_EQ(t.ToChronoDuration(), std::chrono::nanoseconds(0)); +} + +TEST(KernelTimeout, FiniteDurations) { + constexpr absl::Duration kDurationsToTest[] = { + absl::ZeroDuration(), + absl::Nanoseconds(1), + absl::Microseconds(1), + absl::Milliseconds(1), + absl::Seconds(1), + absl::Minutes(1), + absl::Hours(1), + absl::Hours(1000), + }; + + for (auto duration : kDurationsToTest) { + SCOPED_TRACE(duration); + KernelTimeout t(duration); + EXPECT_TRUE(t.has_timeout()); + EXPECT_FALSE(t.is_absolute_timeout()); + EXPECT_TRUE(t.is_relative_timeout()); + EXPECT_LE(absl::AbsDuration(absl::Now() + duration - + absl::TimeFromTimespec(t.MakeAbsTimespec())), + absl::Milliseconds(5)); +#ifndef _WIN32 + EXPECT_LE( + absl::AbsDuration(absl::Now() + duration - + absl::TimeFromTimespec( + t.MakeClockAbsoluteTimespec(CLOCK_REALTIME))), + absl::Milliseconds(5)); +#endif + EXPECT_LE( + absl::AbsDuration(absl::DurationFromTimespec(t.MakeRelativeTimespec()) - + duration), + kTimingBound); + EXPECT_LE(absl::AbsDuration(absl::Now() + duration - + absl::FromUnixNanos(t.MakeAbsNanos())), + absl::Milliseconds(5)); + EXPECT_LE(absl::Milliseconds(t.InMillisecondsFromNow()) - duration, + absl::Milliseconds(5)); + EXPECT_LE(absl::AbsDuration(absl::Now() + duration - + absl::FromChrono(t.ToChronoTimePoint())), + kTimingBound); + EXPECT_LE( + absl::AbsDuration(absl::FromChrono(t.ToChronoDuration()) - duration), + kTimingBound); + } +} + +TEST(KernelTimeout, NegativeDurations) { + constexpr absl::Duration kDurationsToTest[] = { + -absl::ZeroDuration(), + -absl::Nanoseconds(1), + -absl::Microseconds(1), + -absl::Milliseconds(1), + -absl::Seconds(1), + -absl::Minutes(1), + -absl::Hours(1), + -absl::Hours(1000), + -absl::InfiniteDuration(), + }; + + for (auto duration : kDurationsToTest) { + // Negative durations should all be converted to zero durations or "now". + SCOPED_TRACE(duration); + KernelTimeout t(duration); + EXPECT_TRUE(t.has_timeout()); + EXPECT_FALSE(t.is_absolute_timeout()); + EXPECT_TRUE(t.is_relative_timeout()); + EXPECT_LE(absl::AbsDuration(absl::Now() - + absl::TimeFromTimespec(t.MakeAbsTimespec())), + absl::Milliseconds(5)); +#ifndef _WIN32 + EXPECT_LE(absl::AbsDuration(absl::Now() - absl::TimeFromTimespec( + t.MakeClockAbsoluteTimespec( + CLOCK_REALTIME))), + absl::Milliseconds(5)); +#endif + EXPECT_EQ(absl::DurationFromTimespec(t.MakeRelativeTimespec()), + absl::ZeroDuration()); + EXPECT_LE( + absl::AbsDuration(absl::Now() - absl::FromUnixNanos(t.MakeAbsNanos())), + absl::Milliseconds(5)); + EXPECT_EQ(t.InMillisecondsFromNow(), KernelTimeout::DWord{0}); + EXPECT_LE(absl::AbsDuration(absl::Now() - + absl::FromChrono(t.ToChronoTimePoint())), + absl::Milliseconds(5)); + EXPECT_EQ(t.ToChronoDuration(), std::chrono::nanoseconds(0)); + } +} + +TEST(KernelTimeout, InfiniteDuration) { + KernelTimeout t(absl::InfiniteDuration()); + EXPECT_FALSE(t.has_timeout()); + // Callers are expected to check has_timeout() instead of using the methods + // below, but we do try to do something reasonable if they don't. We may not + // be able to round-trip back to absl::InfiniteDuration() or + // absl::InfiniteFuture(), but we should return a very large value. + EXPECT_GT(absl::TimeFromTimespec(t.MakeAbsTimespec()), + absl::Now() + absl::Hours(100000)); +#ifndef _WIN32 + EXPECT_GT(absl::TimeFromTimespec(t.MakeClockAbsoluteTimespec(CLOCK_REALTIME)), + absl::Now() + absl::Hours(100000)); +#endif + EXPECT_GT(absl::DurationFromTimespec(t.MakeRelativeTimespec()), + absl::Hours(100000)); + EXPECT_GT(absl::FromUnixNanos(t.MakeAbsNanos()), + absl::Now() + absl::Hours(100000)); + EXPECT_EQ(t.InMillisecondsFromNow(), + std::numeric_limits<KernelTimeout::DWord>::max()); + EXPECT_EQ(t.ToChronoTimePoint(), + std::chrono::time_point<std::chrono::system_clock>::max()); + EXPECT_GE(t.ToChronoDuration(), std::chrono::nanoseconds::max()); +} + +TEST(KernelTimeout, DurationMaxNanos) { + // Duration >= kMaxNanos should behave as no timeout. + KernelTimeout t(absl::Nanoseconds(std::numeric_limits<int64_t>::max())); + EXPECT_FALSE(t.has_timeout()); + // Callers are expected to check has_timeout() instead of using the methods + // below, but we do try to do something reasonable if they don't. We may not + // be able to round-trip back to absl::InfiniteDuration() or + // absl::InfiniteFuture(), but we should return a very large value. + EXPECT_GT(absl::TimeFromTimespec(t.MakeAbsTimespec()), + absl::Now() + absl::Hours(100000)); +#ifndef _WIN32 + EXPECT_GT(absl::TimeFromTimespec(t.MakeClockAbsoluteTimespec(CLOCK_REALTIME)), + absl::Now() + absl::Hours(100000)); +#endif + EXPECT_GT(absl::DurationFromTimespec(t.MakeRelativeTimespec()), + absl::Hours(100000)); + EXPECT_GT(absl::FromUnixNanos(t.MakeAbsNanos()), + absl::Now() + absl::Hours(100000)); + EXPECT_EQ(t.InMillisecondsFromNow(), + std::numeric_limits<KernelTimeout::DWord>::max()); + EXPECT_EQ(t.ToChronoTimePoint(), + std::chrono::time_point<std::chrono::system_clock>::max()); + EXPECT_GE(t.ToChronoDuration(), std::chrono::nanoseconds::max()); +} + +TEST(KernelTimeout, OverflowNanos) { + // Test what happens when KernelTimeout is constructed with an absl::Duration + // that would overflow now_nanos + duration. + int64_t now_nanos = absl::ToUnixNanos(absl::Now()); + int64_t limit = std::numeric_limits<int64_t>::max() - now_nanos; + absl::Duration duration = absl::Nanoseconds(limit) + absl::Seconds(1); + KernelTimeout t(duration); + // Timeouts should still be far in the future. + EXPECT_GT(absl::TimeFromTimespec(t.MakeAbsTimespec()), + absl::Now() + absl::Hours(100000)); +#ifndef _WIN32 + EXPECT_GT(absl::TimeFromTimespec(t.MakeClockAbsoluteTimespec(CLOCK_REALTIME)), + absl::Now() + absl::Hours(100000)); +#endif + EXPECT_GT(absl::DurationFromTimespec(t.MakeRelativeTimespec()), + absl::Hours(100000)); + EXPECT_GT(absl::FromUnixNanos(t.MakeAbsNanos()), + absl::Now() + absl::Hours(100000)); + EXPECT_LE(absl::Milliseconds(t.InMillisecondsFromNow()) - duration, + absl::Milliseconds(5)); + EXPECT_GT(t.ToChronoTimePoint(), + std::chrono::system_clock::now() + std::chrono::hours(100000)); + EXPECT_GT(t.ToChronoDuration(), std::chrono::hours(100000)); +} + +} // namespace diff --git a/absl/synchronization/internal/per_thread_sem.cc b/absl/synchronization/internal/per_thread_sem.cc index 469e8f32..c9b8dc1e 100644 --- a/absl/synchronization/internal/per_thread_sem.cc +++ b/absl/synchronization/internal/per_thread_sem.cc @@ -40,13 +40,6 @@ std::atomic<int> *PerThreadSem::GetThreadBlockedCounter() { return identity->blocked_count_ptr; } -void PerThreadSem::Init(base_internal::ThreadIdentity *identity) { - new (Waiter::GetWaiter(identity)) Waiter(); - identity->ticker.store(0, std::memory_order_relaxed); - identity->wait_start.store(0, std::memory_order_relaxed); - identity->is_idle.store(false, std::memory_order_relaxed); -} - void PerThreadSem::Tick(base_internal::ThreadIdentity *identity) { const int ticker = identity->ticker.fetch_add(1, std::memory_order_relaxed) + 1; @@ -54,7 +47,7 @@ void PerThreadSem::Tick(base_internal::ThreadIdentity *identity) { const bool is_idle = identity->is_idle.load(std::memory_order_relaxed); if (wait_start && (ticker - wait_start > Waiter::kIdlePeriods) && !is_idle) { // Wakeup the waiting thread since it is time for it to become idle. - Waiter::GetWaiter(identity)->Poke(); + ABSL_INTERNAL_C_SYMBOL(AbslInternalPerThreadSemPoke)(identity); } } @@ -64,11 +57,22 @@ ABSL_NAMESPACE_END extern "C" { +ABSL_ATTRIBUTE_WEAK void ABSL_INTERNAL_C_SYMBOL(AbslInternalPerThreadSemInit)( + absl::base_internal::ThreadIdentity *identity) { + new (absl::synchronization_internal::Waiter::GetWaiter(identity)) + absl::synchronization_internal::Waiter(); +} + ABSL_ATTRIBUTE_WEAK void ABSL_INTERNAL_C_SYMBOL(AbslInternalPerThreadSemPost)( absl::base_internal::ThreadIdentity *identity) { absl::synchronization_internal::Waiter::GetWaiter(identity)->Post(); } +ABSL_ATTRIBUTE_WEAK void ABSL_INTERNAL_C_SYMBOL(AbslInternalPerThreadSemPoke)( + absl::base_internal::ThreadIdentity *identity) { + absl::synchronization_internal::Waiter::GetWaiter(identity)->Poke(); +} + ABSL_ATTRIBUTE_WEAK bool ABSL_INTERNAL_C_SYMBOL(AbslInternalPerThreadSemWait)( absl::synchronization_internal::KernelTimeout t) { bool timeout = false; diff --git a/absl/synchronization/internal/per_thread_sem.h b/absl/synchronization/internal/per_thread_sem.h index 90a88809..144ab3cd 100644 --- a/absl/synchronization/internal/per_thread_sem.h +++ b/absl/synchronization/internal/per_thread_sem.h @@ -64,7 +64,7 @@ class PerThreadSem { private: // Create the PerThreadSem associated with "identity". Initializes count=0. // REQUIRES: May only be called by ThreadIdentity. - static void Init(base_internal::ThreadIdentity* identity); + static inline void Init(base_internal::ThreadIdentity* identity); // Increments "identity"'s count. static inline void Post(base_internal::ThreadIdentity* identity); @@ -91,12 +91,21 @@ ABSL_NAMESPACE_END // By changing our extension points to be extern "C", we dodge this // check. extern "C" { +void ABSL_INTERNAL_C_SYMBOL(AbslInternalPerThreadSemInit)( + absl::base_internal::ThreadIdentity* identity); void ABSL_INTERNAL_C_SYMBOL(AbslInternalPerThreadSemPost)( absl::base_internal::ThreadIdentity* identity); bool ABSL_INTERNAL_C_SYMBOL(AbslInternalPerThreadSemWait)( absl::synchronization_internal::KernelTimeout t); +void ABSL_INTERNAL_C_SYMBOL(AbslInternalPerThreadSemPoke)( + absl::base_internal::ThreadIdentity* identity); } // extern "C" +void absl::synchronization_internal::PerThreadSem::Init( + absl::base_internal::ThreadIdentity* identity) { + ABSL_INTERNAL_C_SYMBOL(AbslInternalPerThreadSemInit)(identity); +} + void absl::synchronization_internal::PerThreadSem::Post( absl::base_internal::ThreadIdentity* identity) { ABSL_INTERNAL_C_SYMBOL(AbslInternalPerThreadSemPost)(identity); diff --git a/absl/synchronization/internal/pthread_waiter.cc b/absl/synchronization/internal/pthread_waiter.cc new file mode 100644 index 00000000..bf700e95 --- /dev/null +++ b/absl/synchronization/internal/pthread_waiter.cc @@ -0,0 +1,167 @@ +// Copyright 2023 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/synchronization/internal/pthread_waiter.h" + +#ifdef ABSL_INTERNAL_HAVE_PTHREAD_WAITER + +#include <pthread.h> +#include <sys/time.h> +#include <unistd.h> + +#include <cassert> +#include <cerrno> + +#include "absl/base/config.h" +#include "absl/base/internal/raw_logging.h" +#include "absl/base/internal/thread_identity.h" +#include "absl/base/optimization.h" +#include "absl/synchronization/internal/kernel_timeout.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace synchronization_internal { + +namespace { +class PthreadMutexHolder { + public: + explicit PthreadMutexHolder(pthread_mutex_t *mu) : mu_(mu) { + const int err = pthread_mutex_lock(mu_); + if (err != 0) { + ABSL_RAW_LOG(FATAL, "pthread_mutex_lock failed: %d", err); + } + } + + PthreadMutexHolder(const PthreadMutexHolder &rhs) = delete; + PthreadMutexHolder &operator=(const PthreadMutexHolder &rhs) = delete; + + ~PthreadMutexHolder() { + const int err = pthread_mutex_unlock(mu_); + if (err != 0) { + ABSL_RAW_LOG(FATAL, "pthread_mutex_unlock failed: %d", err); + } + } + + private: + pthread_mutex_t *mu_; +}; +} // namespace + +#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL +constexpr char PthreadWaiter::kName[]; +#endif + +PthreadWaiter::PthreadWaiter() : waiter_count_(0), wakeup_count_(0) { + const int err = pthread_mutex_init(&mu_, 0); + if (err != 0) { + ABSL_RAW_LOG(FATAL, "pthread_mutex_init failed: %d", err); + } + + const int err2 = pthread_cond_init(&cv_, 0); + if (err2 != 0) { + ABSL_RAW_LOG(FATAL, "pthread_cond_init failed: %d", err2); + } +} + +#ifdef __APPLE__ +#define ABSL_INTERNAL_HAS_PTHREAD_COND_TIMEDWAIT_RELATIVE_NP 1 +#endif + +#if defined(__GLIBC__) && \ + (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 30)) +#define ABSL_INTERNAL_HAVE_PTHREAD_COND_CLOCKWAIT 1 +#elif defined(__ANDROID_API__) && __ANDROID_API__ >= 30 +#define ABSL_INTERNAL_HAVE_PTHREAD_COND_CLOCKWAIT 1 +#endif + +// Calls pthread_cond_timedwait() or possibly something else like +// pthread_cond_timedwait_relative_np() depending on the platform and +// KernelTimeout requested. The return value is the same as the return +// value of pthread_cond_timedwait(). +int PthreadWaiter::TimedWait(KernelTimeout t) { + assert(t.has_timeout()); + if (KernelTimeout::SupportsSteadyClock() && t.is_relative_timeout()) { +#ifdef ABSL_INTERNAL_HAS_PTHREAD_COND_TIMEDWAIT_RELATIVE_NP + const auto rel_timeout = t.MakeRelativeTimespec(); + return pthread_cond_timedwait_relative_np(&cv_, &mu_, &rel_timeout); +#elif defined(ABSL_INTERNAL_HAVE_PTHREAD_COND_CLOCKWAIT) && \ + defined(CLOCK_MONOTONIC) + const auto abs_clock_timeout = t.MakeClockAbsoluteTimespec(CLOCK_MONOTONIC); + return pthread_cond_clockwait(&cv_, &mu_, CLOCK_MONOTONIC, + &abs_clock_timeout); +#endif + } + + const auto abs_timeout = t.MakeAbsTimespec(); + return pthread_cond_timedwait(&cv_, &mu_, &abs_timeout); +} + +bool PthreadWaiter::Wait(KernelTimeout t) { + PthreadMutexHolder h(&mu_); + ++waiter_count_; + // Loop until we find a wakeup to consume or timeout. + // Note that, since the thread ticker is just reset, we don't need to check + // whether the thread is idle on the very first pass of the loop. + bool first_pass = true; + while (wakeup_count_ == 0) { + if (!first_pass) MaybeBecomeIdle(); + // No wakeups available, time to wait. + if (!t.has_timeout()) { + const int err = pthread_cond_wait(&cv_, &mu_); + if (err != 0) { + ABSL_RAW_LOG(FATAL, "pthread_cond_wait failed: %d", err); + } + } else { + const int err = TimedWait(t); + if (err == ETIMEDOUT) { + --waiter_count_; + return false; + } + if (err != 0) { + ABSL_RAW_LOG(FATAL, "PthreadWaiter::TimedWait() failed: %d", err); + } + } + first_pass = false; + } + // Consume a wakeup and we're done. + --wakeup_count_; + --waiter_count_; + return true; +} + +void PthreadWaiter::Post() { + PthreadMutexHolder h(&mu_); + ++wakeup_count_; + InternalCondVarPoke(); +} + +void PthreadWaiter::Poke() { + PthreadMutexHolder h(&mu_); + InternalCondVarPoke(); +} + +void PthreadWaiter::InternalCondVarPoke() { + if (waiter_count_ != 0) { + const int err = pthread_cond_signal(&cv_); + if (ABSL_PREDICT_FALSE(err != 0)) { + ABSL_RAW_LOG(FATAL, "pthread_cond_signal failed: %d", err); + } + } +} + +} // namespace synchronization_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_INTERNAL_HAVE_PTHREAD_WAITER diff --git a/absl/synchronization/internal/pthread_waiter.h b/absl/synchronization/internal/pthread_waiter.h new file mode 100644 index 00000000..23db76ad --- /dev/null +++ b/absl/synchronization/internal/pthread_waiter.h @@ -0,0 +1,60 @@ +// Copyright 2023 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_SYNCHRONIZATION_INTERNAL_PTHREAD_WAITER_H_ +#define ABSL_SYNCHRONIZATION_INTERNAL_PTHREAD_WAITER_H_ + +#if !defined(_WIN32) && !defined(__MINGW32__) +#include <pthread.h> + +#include "absl/base/config.h" +#include "absl/synchronization/internal/kernel_timeout.h" +#include "absl/synchronization/internal/waiter_base.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace synchronization_internal { + +#define ABSL_INTERNAL_HAVE_PTHREAD_WAITER 1 + +class PthreadWaiter : public WaiterCrtp<PthreadWaiter> { + public: + PthreadWaiter(); + + bool Wait(KernelTimeout t); + void Post(); + void Poke(); + + static constexpr char kName[] = "PthreadWaiter"; + + private: + int TimedWait(KernelTimeout t); + + // REQUIRES: mu_ must be held. + void InternalCondVarPoke(); + + pthread_mutex_t mu_; + pthread_cond_t cv_; + int waiter_count_; + int wakeup_count_; // Unclaimed wakeups. +}; + +} // namespace synchronization_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // !defined(_WIN32) && !defined(__MINGW32__) + +#endif // ABSL_SYNCHRONIZATION_INTERNAL_PTHREAD_WAITER_H_ diff --git a/absl/synchronization/internal/sem_waiter.cc b/absl/synchronization/internal/sem_waiter.cc new file mode 100644 index 00000000..d62dbdc7 --- /dev/null +++ b/absl/synchronization/internal/sem_waiter.cc @@ -0,0 +1,122 @@ +// Copyright 2023 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/synchronization/internal/sem_waiter.h" + +#ifdef ABSL_INTERNAL_HAVE_SEM_WAITER + +#include <semaphore.h> + +#include <atomic> +#include <cassert> +#include <cstdint> +#include <cerrno> + +#include "absl/base/config.h" +#include "absl/base/internal/raw_logging.h" +#include "absl/base/internal/thread_identity.h" +#include "absl/base/optimization.h" +#include "absl/synchronization/internal/kernel_timeout.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace synchronization_internal { + +#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL +constexpr char SemWaiter::kName[]; +#endif + +SemWaiter::SemWaiter() : wakeups_(0) { + if (sem_init(&sem_, 0, 0) != 0) { + ABSL_RAW_LOG(FATAL, "sem_init failed with errno %d\n", errno); + } +} + +#if defined(__GLIBC__) && \ + (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 30)) +#define ABSL_INTERNAL_HAVE_SEM_CLOCKWAIT 1 +#elif defined(__ANDROID_API__) && __ANDROID_API__ >= 30 +#define ABSL_INTERNAL_HAVE_SEM_CLOCKWAIT 1 +#endif + +// Calls sem_timedwait() or possibly something else like +// sem_clockwait() depending on the platform and +// KernelTimeout requested. The return value is the same as a call to the return +// value to a call to sem_timedwait(). +int SemWaiter::TimedWait(KernelTimeout t) { + if (KernelTimeout::SupportsSteadyClock() && t.is_relative_timeout()) { +#if defined(ABSL_INTERNAL_HAVE_SEM_CLOCKWAIT) && defined(CLOCK_MONOTONIC) + const auto abs_clock_timeout = t.MakeClockAbsoluteTimespec(CLOCK_MONOTONIC); + return sem_clockwait(&sem_, CLOCK_MONOTONIC, &abs_clock_timeout); +#endif + } + + const auto abs_timeout = t.MakeAbsTimespec(); + return sem_timedwait(&sem_, &abs_timeout); +} + +bool SemWaiter::Wait(KernelTimeout t) { + // Loop until we timeout or consume a wakeup. + // Note that, since the thread ticker is just reset, we don't need to check + // whether the thread is idle on the very first pass of the loop. + bool first_pass = true; + while (true) { + int x = wakeups_.load(std::memory_order_relaxed); + while (x != 0) { + if (!wakeups_.compare_exchange_weak(x, x - 1, + std::memory_order_acquire, + std::memory_order_relaxed)) { + continue; // Raced with someone, retry. + } + // Successfully consumed a wakeup, we're done. + return true; + } + + if (!first_pass) MaybeBecomeIdle(); + // Nothing to consume, wait (looping on EINTR). + while (true) { + if (!t.has_timeout()) { + if (sem_wait(&sem_) == 0) break; + if (errno == EINTR) continue; + ABSL_RAW_LOG(FATAL, "sem_wait failed: %d", errno); + } else { + if (TimedWait(t) == 0) break; + if (errno == EINTR) continue; + if (errno == ETIMEDOUT) return false; + ABSL_RAW_LOG(FATAL, "SemWaiter::TimedWait() failed: %d", errno); + } + } + first_pass = false; + } +} + +void SemWaiter::Post() { + // Post a wakeup. + if (wakeups_.fetch_add(1, std::memory_order_release) == 0) { + // We incremented from 0, need to wake a potential waiter. + Poke(); + } +} + +void SemWaiter::Poke() { + if (sem_post(&sem_) != 0) { // Wake any semaphore waiter. + ABSL_RAW_LOG(FATAL, "sem_post failed with errno %d\n", errno); + } +} + +} // namespace synchronization_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_INTERNAL_HAVE_SEM_WAITER diff --git a/absl/synchronization/internal/sem_waiter.h b/absl/synchronization/internal/sem_waiter.h new file mode 100644 index 00000000..c22746f9 --- /dev/null +++ b/absl/synchronization/internal/sem_waiter.h @@ -0,0 +1,65 @@ +// Copyright 2023 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_SYNCHRONIZATION_INTERNAL_SEM_WAITER_H_ +#define ABSL_SYNCHRONIZATION_INTERNAL_SEM_WAITER_H_ + +#include "absl/base/config.h" + +#ifdef ABSL_HAVE_SEMAPHORE_H +#include <semaphore.h> + +#include <atomic> +#include <cstdint> + +#include "absl/base/internal/thread_identity.h" +#include "absl/synchronization/internal/futex.h" +#include "absl/synchronization/internal/kernel_timeout.h" +#include "absl/synchronization/internal/waiter_base.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace synchronization_internal { + +#define ABSL_INTERNAL_HAVE_SEM_WAITER 1 + +class SemWaiter : public WaiterCrtp<SemWaiter> { + public: + SemWaiter(); + + bool Wait(KernelTimeout t); + void Post(); + void Poke(); + + static constexpr char kName[] = "SemWaiter"; + + private: + int TimedWait(KernelTimeout t); + + sem_t sem_; + + // This seems superfluous, but for Poke() we need to cause spurious + // wakeups on the semaphore. Hence we can't actually use the + // semaphore's count. + std::atomic<int> wakeups_; +}; + +} // namespace synchronization_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_HAVE_SEMAPHORE_H + +#endif // ABSL_SYNCHRONIZATION_INTERNAL_SEM_WAITER_H_ diff --git a/absl/synchronization/internal/stdcpp_waiter.cc b/absl/synchronization/internal/stdcpp_waiter.cc new file mode 100644 index 00000000..355718a7 --- /dev/null +++ b/absl/synchronization/internal/stdcpp_waiter.cc @@ -0,0 +1,91 @@ +// Copyright 2023 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/synchronization/internal/stdcpp_waiter.h" + +#ifdef ABSL_INTERNAL_HAVE_STDCPP_WAITER + +#include <chrono> // NOLINT(build/c++11) +#include <condition_variable> // NOLINT(build/c++11) +#include <mutex> // NOLINT(build/c++11) + +#include "absl/base/config.h" +#include "absl/base/internal/raw_logging.h" +#include "absl/base/internal/thread_identity.h" +#include "absl/base/optimization.h" +#include "absl/synchronization/internal/kernel_timeout.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace synchronization_internal { + +#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL +constexpr char StdcppWaiter::kName[]; +#endif + +StdcppWaiter::StdcppWaiter() : waiter_count_(0), wakeup_count_(0) {} + +bool StdcppWaiter::Wait(KernelTimeout t) { + std::unique_lock<std::mutex> lock(mu_); + ++waiter_count_; + + // Loop until we find a wakeup to consume or timeout. + // Note that, since the thread ticker is just reset, we don't need to check + // whether the thread is idle on the very first pass of the loop. + bool first_pass = true; + while (wakeup_count_ == 0) { + if (!first_pass) MaybeBecomeIdle(); + // No wakeups available, time to wait. + if (!t.has_timeout()) { + cv_.wait(lock); + } else { + auto wait_result = t.SupportsSteadyClock() && t.is_relative_timeout() + ? cv_.wait_for(lock, t.ToChronoDuration()) + : cv_.wait_until(lock, t.ToChronoTimePoint()); + if (wait_result == std::cv_status::timeout) { + --waiter_count_; + return false; + } + } + first_pass = false; + } + + // Consume a wakeup and we're done. + --wakeup_count_; + --waiter_count_; + return true; +} + +void StdcppWaiter::Post() { + std::lock_guard<std::mutex> lock(mu_); + ++wakeup_count_; + InternalCondVarPoke(); +} + +void StdcppWaiter::Poke() { + std::lock_guard<std::mutex> lock(mu_); + InternalCondVarPoke(); +} + +void StdcppWaiter::InternalCondVarPoke() { + if (waiter_count_ != 0) { + cv_.notify_one(); + } +} + +} // namespace synchronization_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_INTERNAL_HAVE_STDCPP_WAITER diff --git a/absl/synchronization/internal/stdcpp_waiter.h b/absl/synchronization/internal/stdcpp_waiter.h new file mode 100644 index 00000000..e592a27b --- /dev/null +++ b/absl/synchronization/internal/stdcpp_waiter.h @@ -0,0 +1,56 @@ +// Copyright 2023 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_SYNCHRONIZATION_INTERNAL_STDCPP_WAITER_H_ +#define ABSL_SYNCHRONIZATION_INTERNAL_STDCPP_WAITER_H_ + +#include <condition_variable> // NOLINT(build/c++11) +#include <mutex> // NOLINT(build/c++11) + +#include "absl/base/config.h" +#include "absl/synchronization/internal/kernel_timeout.h" +#include "absl/synchronization/internal/waiter_base.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace synchronization_internal { + +#define ABSL_INTERNAL_HAVE_STDCPP_WAITER 1 + +class StdcppWaiter : public WaiterCrtp<StdcppWaiter> { + public: + StdcppWaiter(); + + bool Wait(KernelTimeout t); + void Post(); + void Poke(); + + static constexpr char kName[] = "StdcppWaiter"; + + private: + // REQUIRES: mu_ must be held. + void InternalCondVarPoke(); + + std::mutex mu_; + std::condition_variable cv_; + int waiter_count_; + int wakeup_count_; // Unclaimed wakeups. +}; + +} // namespace synchronization_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_SYNCHRONIZATION_INTERNAL_STDCPP_WAITER_H_ diff --git a/absl/synchronization/internal/waiter.cc b/absl/synchronization/internal/waiter.cc deleted file mode 100644 index f2051d67..00000000 --- a/absl/synchronization/internal/waiter.cc +++ /dev/null @@ -1,403 +0,0 @@ -// Copyright 2017 The Abseil Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// 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/synchronization/internal/waiter.h" - -#include "absl/base/config.h" - -#ifdef _WIN32 -#include <windows.h> -#else -#include <pthread.h> -#include <sys/time.h> -#include <unistd.h> -#endif - -#ifdef __linux__ -#include <linux/futex.h> -#include <sys/syscall.h> -#endif - -#ifdef ABSL_HAVE_SEMAPHORE_H -#include <semaphore.h> -#endif - -#include <errno.h> -#include <stdio.h> -#include <time.h> - -#include <atomic> -#include <cassert> -#include <cstdint> -#include <new> -#include <type_traits> - -#include "absl/base/internal/raw_logging.h" -#include "absl/base/internal/thread_identity.h" -#include "absl/base/optimization.h" -#include "absl/synchronization/internal/kernel_timeout.h" - - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace synchronization_internal { - -static void MaybeBecomeIdle() { - base_internal::ThreadIdentity *identity = - base_internal::CurrentThreadIdentityIfPresent(); - assert(identity != nullptr); - const bool is_idle = identity->is_idle.load(std::memory_order_relaxed); - const int ticker = identity->ticker.load(std::memory_order_relaxed); - const int wait_start = identity->wait_start.load(std::memory_order_relaxed); - if (!is_idle && ticker - wait_start > Waiter::kIdlePeriods) { - identity->is_idle.store(true, std::memory_order_relaxed); - } -} - -#if ABSL_WAITER_MODE == ABSL_WAITER_MODE_FUTEX - -Waiter::Waiter() { - futex_.store(0, std::memory_order_relaxed); -} - -bool Waiter::Wait(KernelTimeout t) { - // Loop until we can atomically decrement futex from a positive - // value, waiting on a futex while we believe it is zero. - // Note that, since the thread ticker is just reset, we don't need to check - // whether the thread is idle on the very first pass of the loop. - bool first_pass = true; - - while (true) { - int32_t x = futex_.load(std::memory_order_relaxed); - while (x != 0) { - if (!futex_.compare_exchange_weak(x, x - 1, - std::memory_order_acquire, - std::memory_order_relaxed)) { - continue; // Raced with someone, retry. - } - return true; // Consumed a wakeup, we are done. - } - - if (!first_pass) MaybeBecomeIdle(); - const int err = Futex::WaitUntil(&futex_, 0, t); - if (err != 0) { - if (err == -EINTR || err == -EWOULDBLOCK) { - // Do nothing, the loop will retry. - } else if (err == -ETIMEDOUT) { - return false; - } else { - ABSL_RAW_LOG(FATAL, "Futex operation failed with error %d\n", err); - } - } - first_pass = false; - } -} - -void Waiter::Post() { - if (futex_.fetch_add(1, std::memory_order_release) == 0) { - // We incremented from 0, need to wake a potential waiter. - Poke(); - } -} - -void Waiter::Poke() { - // Wake one thread waiting on the futex. - const int err = Futex::Wake(&futex_, 1); - if (ABSL_PREDICT_FALSE(err < 0)) { - ABSL_RAW_LOG(FATAL, "Futex operation failed with error %d\n", err); - } -} - -#elif ABSL_WAITER_MODE == ABSL_WAITER_MODE_CONDVAR - -class PthreadMutexHolder { - public: - explicit PthreadMutexHolder(pthread_mutex_t *mu) : mu_(mu) { - const int err = pthread_mutex_lock(mu_); - if (err != 0) { - ABSL_RAW_LOG(FATAL, "pthread_mutex_lock failed: %d", err); - } - } - - PthreadMutexHolder(const PthreadMutexHolder &rhs) = delete; - PthreadMutexHolder &operator=(const PthreadMutexHolder &rhs) = delete; - - ~PthreadMutexHolder() { - const int err = pthread_mutex_unlock(mu_); - if (err != 0) { - ABSL_RAW_LOG(FATAL, "pthread_mutex_unlock failed: %d", err); - } - } - - private: - pthread_mutex_t *mu_; -}; - -Waiter::Waiter() { - const int err = pthread_mutex_init(&mu_, 0); - if (err != 0) { - ABSL_RAW_LOG(FATAL, "pthread_mutex_init failed: %d", err); - } - - const int err2 = pthread_cond_init(&cv_, 0); - if (err2 != 0) { - ABSL_RAW_LOG(FATAL, "pthread_cond_init failed: %d", err2); - } - - waiter_count_ = 0; - wakeup_count_ = 0; -} - -bool Waiter::Wait(KernelTimeout t) { - struct timespec abs_timeout; - if (t.has_timeout()) { - abs_timeout = t.MakeAbsTimespec(); - } - - PthreadMutexHolder h(&mu_); - ++waiter_count_; - // Loop until we find a wakeup to consume or timeout. - // Note that, since the thread ticker is just reset, we don't need to check - // whether the thread is idle on the very first pass of the loop. - bool first_pass = true; - while (wakeup_count_ == 0) { - if (!first_pass) MaybeBecomeIdle(); - // No wakeups available, time to wait. - if (!t.has_timeout()) { - const int err = pthread_cond_wait(&cv_, &mu_); - if (err != 0) { - ABSL_RAW_LOG(FATAL, "pthread_cond_wait failed: %d", err); - } - } else { - const int err = pthread_cond_timedwait(&cv_, &mu_, &abs_timeout); - if (err == ETIMEDOUT) { - --waiter_count_; - return false; - } - if (err != 0) { - ABSL_RAW_LOG(FATAL, "pthread_cond_timedwait failed: %d", err); - } - } - first_pass = false; - } - // Consume a wakeup and we're done. - --wakeup_count_; - --waiter_count_; - return true; -} - -void Waiter::Post() { - PthreadMutexHolder h(&mu_); - ++wakeup_count_; - InternalCondVarPoke(); -} - -void Waiter::Poke() { - PthreadMutexHolder h(&mu_); - InternalCondVarPoke(); -} - -void Waiter::InternalCondVarPoke() { - if (waiter_count_ != 0) { - const int err = pthread_cond_signal(&cv_); - if (ABSL_PREDICT_FALSE(err != 0)) { - ABSL_RAW_LOG(FATAL, "pthread_cond_signal failed: %d", err); - } - } -} - -#elif ABSL_WAITER_MODE == ABSL_WAITER_MODE_SEM - -Waiter::Waiter() { - if (sem_init(&sem_, 0, 0) != 0) { - ABSL_RAW_LOG(FATAL, "sem_init failed with errno %d\n", errno); - } - wakeups_.store(0, std::memory_order_relaxed); -} - -bool Waiter::Wait(KernelTimeout t) { - struct timespec abs_timeout; - if (t.has_timeout()) { - abs_timeout = t.MakeAbsTimespec(); - } - - // Loop until we timeout or consume a wakeup. - // Note that, since the thread ticker is just reset, we don't need to check - // whether the thread is idle on the very first pass of the loop. - bool first_pass = true; - while (true) { - int x = wakeups_.load(std::memory_order_relaxed); - while (x != 0) { - if (!wakeups_.compare_exchange_weak(x, x - 1, - std::memory_order_acquire, - std::memory_order_relaxed)) { - continue; // Raced with someone, retry. - } - // Successfully consumed a wakeup, we're done. - return true; - } - - if (!first_pass) MaybeBecomeIdle(); - // Nothing to consume, wait (looping on EINTR). - while (true) { - if (!t.has_timeout()) { - if (sem_wait(&sem_) == 0) break; - if (errno == EINTR) continue; - ABSL_RAW_LOG(FATAL, "sem_wait failed: %d", errno); - } else { - if (sem_timedwait(&sem_, &abs_timeout) == 0) break; - if (errno == EINTR) continue; - if (errno == ETIMEDOUT) return false; - ABSL_RAW_LOG(FATAL, "sem_timedwait failed: %d", errno); - } - } - first_pass = false; - } -} - -void Waiter::Post() { - // Post a wakeup. - if (wakeups_.fetch_add(1, std::memory_order_release) == 0) { - // We incremented from 0, need to wake a potential waiter. - Poke(); - } -} - -void Waiter::Poke() { - if (sem_post(&sem_) != 0) { // Wake any semaphore waiter. - ABSL_RAW_LOG(FATAL, "sem_post failed with errno %d\n", errno); - } -} - -#elif ABSL_WAITER_MODE == ABSL_WAITER_MODE_WIN32 - -class Waiter::WinHelper { - public: - static SRWLOCK *GetLock(Waiter *w) { - return reinterpret_cast<SRWLOCK *>(&w->mu_storage_); - } - - static CONDITION_VARIABLE *GetCond(Waiter *w) { - return reinterpret_cast<CONDITION_VARIABLE *>(&w->cv_storage_); - } - - static_assert(sizeof(SRWLOCK) == sizeof(void *), - "`mu_storage_` does not have the same size as SRWLOCK"); - static_assert(alignof(SRWLOCK) == alignof(void *), - "`mu_storage_` does not have the same alignment as SRWLOCK"); - - static_assert(sizeof(CONDITION_VARIABLE) == sizeof(void *), - "`ABSL_CONDITION_VARIABLE_STORAGE` does not have the same size " - "as `CONDITION_VARIABLE`"); - static_assert( - alignof(CONDITION_VARIABLE) == alignof(void *), - "`cv_storage_` does not have the same alignment as `CONDITION_VARIABLE`"); - - // The SRWLOCK and CONDITION_VARIABLE types must be trivially constructible - // and destructible because we never call their constructors or destructors. - static_assert(std::is_trivially_constructible<SRWLOCK>::value, - "The `SRWLOCK` type must be trivially constructible"); - static_assert( - std::is_trivially_constructible<CONDITION_VARIABLE>::value, - "The `CONDITION_VARIABLE` type must be trivially constructible"); - static_assert(std::is_trivially_destructible<SRWLOCK>::value, - "The `SRWLOCK` type must be trivially destructible"); - static_assert(std::is_trivially_destructible<CONDITION_VARIABLE>::value, - "The `CONDITION_VARIABLE` type must be trivially destructible"); -}; - -class LockHolder { - public: - explicit LockHolder(SRWLOCK* mu) : mu_(mu) { - AcquireSRWLockExclusive(mu_); - } - - LockHolder(const LockHolder&) = delete; - LockHolder& operator=(const LockHolder&) = delete; - - ~LockHolder() { - ReleaseSRWLockExclusive(mu_); - } - - private: - SRWLOCK* mu_; -}; - -Waiter::Waiter() { - auto *mu = ::new (static_cast<void *>(&mu_storage_)) SRWLOCK; - auto *cv = ::new (static_cast<void *>(&cv_storage_)) CONDITION_VARIABLE; - InitializeSRWLock(mu); - InitializeConditionVariable(cv); - waiter_count_ = 0; - wakeup_count_ = 0; -} - -bool Waiter::Wait(KernelTimeout t) { - SRWLOCK *mu = WinHelper::GetLock(this); - CONDITION_VARIABLE *cv = WinHelper::GetCond(this); - - LockHolder h(mu); - ++waiter_count_; - - // Loop until we find a wakeup to consume or timeout. - // Note that, since the thread ticker is just reset, we don't need to check - // whether the thread is idle on the very first pass of the loop. - bool first_pass = true; - while (wakeup_count_ == 0) { - if (!first_pass) MaybeBecomeIdle(); - // No wakeups available, time to wait. - if (!SleepConditionVariableSRW(cv, mu, t.InMillisecondsFromNow(), 0)) { - // GetLastError() returns a Win32 DWORD, but we assign to - // unsigned long to simplify the ABSL_RAW_LOG case below. The uniform - // initialization guarantees this is not a narrowing conversion. - const unsigned long err{GetLastError()}; // NOLINT(runtime/int) - if (err == ERROR_TIMEOUT) { - --waiter_count_; - return false; - } else { - ABSL_RAW_LOG(FATAL, "SleepConditionVariableSRW failed: %lu", err); - } - } - first_pass = false; - } - // Consume a wakeup and we're done. - --wakeup_count_; - --waiter_count_; - return true; -} - -void Waiter::Post() { - LockHolder h(WinHelper::GetLock(this)); - ++wakeup_count_; - InternalCondVarPoke(); -} - -void Waiter::Poke() { - LockHolder h(WinHelper::GetLock(this)); - InternalCondVarPoke(); -} - -void Waiter::InternalCondVarPoke() { - if (waiter_count_ != 0) { - WakeConditionVariable(WinHelper::GetCond(this)); - } -} - -#else -#error Unknown ABSL_WAITER_MODE -#endif - -} // namespace synchronization_internal -ABSL_NAMESPACE_END -} // namespace absl diff --git a/absl/synchronization/internal/waiter.h b/absl/synchronization/internal/waiter.h index b8adfeb5..6ba204be 100644 --- a/absl/synchronization/internal/waiter.h +++ b/absl/synchronization/internal/waiter.h @@ -17,142 +17,50 @@ #define ABSL_SYNCHRONIZATION_INTERNAL_WAITER_H_ #include "absl/base/config.h" - -#ifdef _WIN32 -#include <sdkddkver.h> -#else -#include <pthread.h> -#endif - -#ifdef __linux__ -#include <linux/futex.h> -#endif - -#ifdef ABSL_HAVE_SEMAPHORE_H -#include <semaphore.h> -#endif - -#include <atomic> -#include <cstdint> - -#include "absl/base/internal/thread_identity.h" -#include "absl/synchronization/internal/futex.h" -#include "absl/synchronization/internal/kernel_timeout.h" +#include "absl/synchronization/internal/futex_waiter.h" +#include "absl/synchronization/internal/pthread_waiter.h" +#include "absl/synchronization/internal/sem_waiter.h" +#include "absl/synchronization/internal/stdcpp_waiter.h" +#include "absl/synchronization/internal/win32_waiter.h" // May be chosen at compile time via -DABSL_FORCE_WAITER_MODE=<index> #define ABSL_WAITER_MODE_FUTEX 0 #define ABSL_WAITER_MODE_SEM 1 #define ABSL_WAITER_MODE_CONDVAR 2 #define ABSL_WAITER_MODE_WIN32 3 +#define ABSL_WAITER_MODE_STDCPP 4 #if defined(ABSL_FORCE_WAITER_MODE) #define ABSL_WAITER_MODE ABSL_FORCE_WAITER_MODE -#elif defined(_WIN32) && _WIN32_WINNT >= _WIN32_WINNT_VISTA +#elif defined(ABSL_INTERNAL_HAVE_WIN32_WAITER) #define ABSL_WAITER_MODE ABSL_WAITER_MODE_WIN32 -#elif defined(ABSL_INTERNAL_HAVE_FUTEX) +#elif defined(ABSL_INTERNAL_HAVE_FUTEX_WAITER) #define ABSL_WAITER_MODE ABSL_WAITER_MODE_FUTEX -#elif defined(ABSL_HAVE_SEMAPHORE_H) +#elif defined(ABSL_INTERNAL_HAVE_SEM_WAITER) #define ABSL_WAITER_MODE ABSL_WAITER_MODE_SEM -#else +#elif defined(ABSL_INTERNAL_HAVE_PTHREAD_WAITER) #define ABSL_WAITER_MODE ABSL_WAITER_MODE_CONDVAR +#elif defined(ABSL_INTERNAL_HAVE_STDCPP_WAITER) +#define ABSL_WAITER_MODE ABSL_WAITER_MODE_STDCPP +#else +#error ABSL_WAITER_MODE is undefined #endif namespace absl { ABSL_NAMESPACE_BEGIN namespace synchronization_internal { -// Waiter is an OS-specific semaphore. -class Waiter { - public: - // Prepare any data to track waits. - Waiter(); - - // Not copyable or movable - Waiter(const Waiter&) = delete; - Waiter& operator=(const Waiter&) = delete; - - // Blocks the calling thread until a matching call to `Post()` or - // `t` has passed. Returns `true` if woken (`Post()` called), - // `false` on timeout. - bool Wait(KernelTimeout t); - - // Restart the caller of `Wait()` as with a normal semaphore. - void Post(); - - // If anyone is waiting, wake them up temporarily and cause them to - // call `MaybeBecomeIdle()`. They will then return to waiting for a - // `Post()` or timeout. - void Poke(); - - // Returns the Waiter associated with the identity. - static Waiter* GetWaiter(base_internal::ThreadIdentity* identity) { - static_assert( - sizeof(Waiter) <= sizeof(base_internal::ThreadIdentity::WaiterState), - "Insufficient space for Waiter"); - return reinterpret_cast<Waiter*>(identity->waiter_state.data); - } - - // How many periods to remain idle before releasing resources -#ifndef ABSL_HAVE_THREAD_SANITIZER - static constexpr int kIdlePeriods = 60; -#else - // Memory consumption under ThreadSanitizer is a serious concern, - // so we release resources sooner. The value of 1 leads to 1 to 2 second - // delay before marking a thread as idle. - static const int kIdlePeriods = 1; -#endif - - private: - // The destructor must not be called since Mutex/CondVar - // can use PerThreadSem/Waiter after the thread exits. - // Waiter objects are embedded in ThreadIdentity objects, - // which are reused via a freelist and are never destroyed. - ~Waiter() = delete; - #if ABSL_WAITER_MODE == ABSL_WAITER_MODE_FUTEX - // Futexes are defined by specification to be 32-bits. - // Thus std::atomic<int32_t> must be just an int32_t with lockfree methods. - std::atomic<int32_t> futex_; - static_assert(sizeof(int32_t) == sizeof(futex_), "Wrong size for futex"); - -#elif ABSL_WAITER_MODE == ABSL_WAITER_MODE_CONDVAR - // REQUIRES: mu_ must be held. - void InternalCondVarPoke(); - - pthread_mutex_t mu_; - pthread_cond_t cv_; - int waiter_count_; - int wakeup_count_; // Unclaimed wakeups. - +using Waiter = FutexWaiter; #elif ABSL_WAITER_MODE == ABSL_WAITER_MODE_SEM - sem_t sem_; - // This seems superfluous, but for Poke() we need to cause spurious - // wakeups on the semaphore. Hence we can't actually use the - // semaphore's count. - std::atomic<int> wakeups_; - +using Waiter = SemWaiter; +#elif ABSL_WAITER_MODE == ABSL_WAITER_MODE_CONDVAR +using Waiter = PthreadWaiter; #elif ABSL_WAITER_MODE == ABSL_WAITER_MODE_WIN32 - // WinHelper - Used to define utilities for accessing the lock and - // condition variable storage once the types are complete. - class WinHelper; - - // REQUIRES: WinHelper::GetLock(this) must be held. - void InternalCondVarPoke(); - - // We can't include Windows.h in our headers, so we use aligned character - // buffers to define the storage of SRWLOCK and CONDITION_VARIABLE. - // SRW locks and condition variables do not need to be explicitly destroyed. - // https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-initializesrwlock - // https://stackoverflow.com/questions/28975958/why-does-windows-have-no-deleteconditionvariable-function-to-go-together-with - alignas(void*) unsigned char mu_storage_[sizeof(void*)]; - alignas(void*) unsigned char cv_storage_[sizeof(void*)]; - int waiter_count_; - int wakeup_count_; - -#else - #error Unknown ABSL_WAITER_MODE +using Waiter = Win32Waiter; +#elif ABSL_WAITER_MODE == ABSL_WAITER_MODE_STDCPP +using Waiter = StdcppWaiter; #endif -}; } // namespace synchronization_internal ABSL_NAMESPACE_END diff --git a/absl/synchronization/internal/waiter_base.cc b/absl/synchronization/internal/waiter_base.cc new file mode 100644 index 00000000..46928b40 --- /dev/null +++ b/absl/synchronization/internal/waiter_base.cc @@ -0,0 +1,42 @@ +// Copyright 2023 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/synchronization/internal/waiter_base.h" + +#include "absl/base/config.h" +#include "absl/base/internal/thread_identity.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace synchronization_internal { + +#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL +constexpr int WaiterBase::kIdlePeriods; +#endif + +void WaiterBase::MaybeBecomeIdle() { + base_internal::ThreadIdentity *identity = + base_internal::CurrentThreadIdentityIfPresent(); + assert(identity != nullptr); + const bool is_idle = identity->is_idle.load(std::memory_order_relaxed); + const int ticker = identity->ticker.load(std::memory_order_relaxed); + const int wait_start = identity->wait_start.load(std::memory_order_relaxed); + if (!is_idle && ticker - wait_start > kIdlePeriods) { + identity->is_idle.store(true, std::memory_order_relaxed); + } +} + +} // namespace synchronization_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/synchronization/internal/waiter_base.h b/absl/synchronization/internal/waiter_base.h new file mode 100644 index 00000000..cf175481 --- /dev/null +++ b/absl/synchronization/internal/waiter_base.h @@ -0,0 +1,90 @@ +// Copyright 2023 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_SYNCHRONIZATION_INTERNAL_WAITER_BASE_H_ +#define ABSL_SYNCHRONIZATION_INTERNAL_WAITER_BASE_H_ + +#include "absl/base/config.h" +#include "absl/base/internal/thread_identity.h" +#include "absl/synchronization/internal/kernel_timeout.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace synchronization_internal { + +// `Waiter` is a platform specific semaphore implementation that `PerThreadSem` +// waits on to implement blocking in `absl::Mutex`. Implementations should +// inherit from `WaiterCrtp` and must implement `Wait()`, `Post()`, and `Poke()` +// as described in `WaiterBase`. `waiter.h` selects the implementation and uses +// static-dispatch for performance. +class WaiterBase { + public: + WaiterBase() = default; + + // Not copyable or movable + WaiterBase(const WaiterBase&) = delete; + WaiterBase& operator=(const WaiterBase&) = delete; + + // Blocks the calling thread until a matching call to `Post()` or + // `t` has passed. Returns `true` if woken (`Post()` called), + // `false` on timeout. + // + // bool Wait(KernelTimeout t); + + // Restart the caller of `Wait()` as with a normal semaphore. + // + // void Post(); + + // If anyone is waiting, wake them up temporarily and cause them to + // call `MaybeBecomeIdle()`. They will then return to waiting for a + // `Post()` or timeout. + // + // void Poke(); + + // Returns the name of this implementation. Used only for debugging. + // + // static constexpr char kName[]; + + // How many periods to remain idle before releasing resources +#ifndef ABSL_HAVE_THREAD_SANITIZER + static constexpr int kIdlePeriods = 60; +#else + // Memory consumption under ThreadSanitizer is a serious concern, + // so we release resources sooner. The value of 1 leads to 1 to 2 second + // delay before marking a thread as idle. + static constexpr int kIdlePeriods = 1; +#endif + + protected: + static void MaybeBecomeIdle(); +}; + +template <typename T> +class WaiterCrtp : public WaiterBase { + public: + // Returns the Waiter associated with the identity. + static T* GetWaiter(base_internal::ThreadIdentity* identity) { + static_assert( + sizeof(T) <= sizeof(base_internal::ThreadIdentity::WaiterState), + "Insufficient space for Waiter"); + return reinterpret_cast<T*>(identity->waiter_state.data); + } +}; + +} // namespace synchronization_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_SYNCHRONIZATION_INTERNAL_WAITER_BASE_H_ diff --git a/absl/synchronization/internal/waiter_test.cc b/absl/synchronization/internal/waiter_test.cc new file mode 100644 index 00000000..992db29b --- /dev/null +++ b/absl/synchronization/internal/waiter_test.cc @@ -0,0 +1,180 @@ +// Copyright 2023 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/synchronization/internal/waiter.h" + +#include <ctime> +#include <iostream> +#include <ostream> + +#include "absl/base/config.h" +#include "absl/random/random.h" +#include "absl/synchronization/internal/create_thread_identity.h" +#include "absl/synchronization/internal/futex_waiter.h" +#include "absl/synchronization/internal/kernel_timeout.h" +#include "absl/synchronization/internal/pthread_waiter.h" +#include "absl/synchronization/internal/sem_waiter.h" +#include "absl/synchronization/internal/stdcpp_waiter.h" +#include "absl/synchronization/internal/thread_pool.h" +#include "absl/synchronization/internal/win32_waiter.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "gtest/gtest.h" + +// Test go/btm support by randomizing the value of clock_gettime() for +// CLOCK_MONOTONIC. This works by overriding a weak symbol in glibc. +// We should be resistant to this randomization when !SupportsSteadyClock(). +#if defined(__GOOGLE_GRTE_VERSION__) && \ + !defined(ABSL_HAVE_ADDRESS_SANITIZER) && \ + !defined(ABSL_HAVE_MEMORY_SANITIZER) && \ + !defined(ABSL_HAVE_THREAD_SANITIZER) +extern "C" int __clock_gettime(clockid_t c, struct timespec* ts); + +extern "C" int clock_gettime(clockid_t c, struct timespec* ts) { + if (c == CLOCK_MONOTONIC && + !absl::synchronization_internal::KernelTimeout::SupportsSteadyClock()) { + absl::SharedBitGen gen; + ts->tv_sec = absl::Uniform(gen, 0, 1'000'000'000); + ts->tv_nsec = absl::Uniform(gen, 0, 1'000'000'000); + return 0; + } + return __clock_gettime(c, ts); +} +#endif + +namespace { + +TEST(Waiter, PrintPlatformImplementation) { + // Allows us to verify that the platform is using the expected implementation. + std::cout << absl::synchronization_internal::Waiter::kName << std::endl; +} + +template <typename T> +class WaiterTest : public ::testing::Test { + public: + // Waiter implementations assume that a ThreadIdentity has already been + // created. + WaiterTest() { + absl::synchronization_internal::GetOrCreateCurrentThreadIdentity(); + } +}; + +TYPED_TEST_SUITE_P(WaiterTest); + +absl::Duration WithTolerance(absl::Duration d) { return d * 0.95; } + +TYPED_TEST_P(WaiterTest, WaitNoTimeout) { + absl::synchronization_internal::ThreadPool tp(1); + TypeParam waiter; + tp.Schedule([&]() { + // Include some `Poke()` calls to ensure they don't cause `waiter` to return + // from `Wait()`. + waiter.Poke(); + absl::SleepFor(absl::Seconds(1)); + waiter.Poke(); + absl::SleepFor(absl::Seconds(1)); + waiter.Post(); + }); + absl::Time start = absl::Now(); + EXPECT_TRUE( + waiter.Wait(absl::synchronization_internal::KernelTimeout::Never())); + absl::Duration waited = absl::Now() - start; + EXPECT_GE(waited, WithTolerance(absl::Seconds(2))); +} + +TYPED_TEST_P(WaiterTest, WaitDurationWoken) { + absl::synchronization_internal::ThreadPool tp(1); + TypeParam waiter; + tp.Schedule([&]() { + // Include some `Poke()` calls to ensure they don't cause `waiter` to return + // from `Wait()`. + waiter.Poke(); + absl::SleepFor(absl::Milliseconds(500)); + waiter.Post(); + }); + absl::Time start = absl::Now(); + EXPECT_TRUE(waiter.Wait( + absl::synchronization_internal::KernelTimeout(absl::Seconds(10)))); + absl::Duration waited = absl::Now() - start; + EXPECT_GE(waited, WithTolerance(absl::Milliseconds(500))); + EXPECT_LT(waited, absl::Seconds(2)); +} + +TYPED_TEST_P(WaiterTest, WaitTimeWoken) { + absl::synchronization_internal::ThreadPool tp(1); + TypeParam waiter; + tp.Schedule([&]() { + // Include some `Poke()` calls to ensure they don't cause `waiter` to return + // from `Wait()`. + waiter.Poke(); + absl::SleepFor(absl::Milliseconds(500)); + waiter.Post(); + }); + absl::Time start = absl::Now(); + EXPECT_TRUE(waiter.Wait(absl::synchronization_internal::KernelTimeout( + start + absl::Seconds(10)))); + absl::Duration waited = absl::Now() - start; + EXPECT_GE(waited, WithTolerance(absl::Milliseconds(500))); + EXPECT_LT(waited, absl::Seconds(2)); +} + +TYPED_TEST_P(WaiterTest, WaitDurationReached) { + TypeParam waiter; + absl::Time start = absl::Now(); + EXPECT_FALSE(waiter.Wait( + absl::synchronization_internal::KernelTimeout(absl::Milliseconds(500)))); + absl::Duration waited = absl::Now() - start; + EXPECT_GE(waited, WithTolerance(absl::Milliseconds(500))); + EXPECT_LT(waited, absl::Seconds(1)); +} + +TYPED_TEST_P(WaiterTest, WaitTimeReached) { + TypeParam waiter; + absl::Time start = absl::Now(); + EXPECT_FALSE(waiter.Wait(absl::synchronization_internal::KernelTimeout( + start + absl::Milliseconds(500)))); + absl::Duration waited = absl::Now() - start; + EXPECT_GE(waited, WithTolerance(absl::Milliseconds(500))); + EXPECT_LT(waited, absl::Seconds(1)); +} + +REGISTER_TYPED_TEST_SUITE_P(WaiterTest, + WaitNoTimeout, + WaitDurationWoken, + WaitTimeWoken, + WaitDurationReached, + WaitTimeReached); + +#ifdef ABSL_INTERNAL_HAVE_FUTEX_WAITER +INSTANTIATE_TYPED_TEST_SUITE_P(Futex, WaiterTest, + absl::synchronization_internal::FutexWaiter); +#endif +#ifdef ABSL_INTERNAL_HAVE_PTHREAD_WAITER +INSTANTIATE_TYPED_TEST_SUITE_P(Pthread, WaiterTest, + absl::synchronization_internal::PthreadWaiter); +#endif +#ifdef ABSL_INTERNAL_HAVE_SEM_WAITER +INSTANTIATE_TYPED_TEST_SUITE_P(Sem, WaiterTest, + absl::synchronization_internal::SemWaiter); +#endif +#ifdef ABSL_INTERNAL_HAVE_WIN32_WAITER +INSTANTIATE_TYPED_TEST_SUITE_P(Win32, WaiterTest, + absl::synchronization_internal::Win32Waiter); +#endif +#ifdef ABSL_INTERNAL_HAVE_STDCPP_WAITER +INSTANTIATE_TYPED_TEST_SUITE_P(Stdcpp, WaiterTest, + absl::synchronization_internal::StdcppWaiter); +#endif + +} // namespace diff --git a/absl/synchronization/internal/win32_waiter.cc b/absl/synchronization/internal/win32_waiter.cc new file mode 100644 index 00000000..bd95ff08 --- /dev/null +++ b/absl/synchronization/internal/win32_waiter.cc @@ -0,0 +1,151 @@ +// Copyright 2023 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/synchronization/internal/win32_waiter.h" + +#ifdef ABSL_INTERNAL_HAVE_WIN32_WAITER + +#include <windows.h> + +#include "absl/base/config.h" +#include "absl/base/internal/raw_logging.h" +#include "absl/base/internal/thread_identity.h" +#include "absl/base/optimization.h" +#include "absl/synchronization/internal/kernel_timeout.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace synchronization_internal { + +#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL +constexpr char Win32Waiter::kName[]; +#endif + +class Win32Waiter::WinHelper { + public: + static SRWLOCK *GetLock(Win32Waiter *w) { + return reinterpret_cast<SRWLOCK *>(&w->mu_storage_); + } + + static CONDITION_VARIABLE *GetCond(Win32Waiter *w) { + return reinterpret_cast<CONDITION_VARIABLE *>(&w->cv_storage_); + } + + static_assert(sizeof(SRWLOCK) == sizeof(void *), + "`mu_storage_` does not have the same size as SRWLOCK"); + static_assert(alignof(SRWLOCK) == alignof(void *), + "`mu_storage_` does not have the same alignment as SRWLOCK"); + + static_assert(sizeof(CONDITION_VARIABLE) == sizeof(void *), + "`ABSL_CONDITION_VARIABLE_STORAGE` does not have the same size " + "as `CONDITION_VARIABLE`"); + static_assert( + alignof(CONDITION_VARIABLE) == alignof(void *), + "`cv_storage_` does not have the same alignment as `CONDITION_VARIABLE`"); + + // The SRWLOCK and CONDITION_VARIABLE types must be trivially constructible + // and destructible because we never call their constructors or destructors. + static_assert(std::is_trivially_constructible<SRWLOCK>::value, + "The `SRWLOCK` type must be trivially constructible"); + static_assert( + std::is_trivially_constructible<CONDITION_VARIABLE>::value, + "The `CONDITION_VARIABLE` type must be trivially constructible"); + static_assert(std::is_trivially_destructible<SRWLOCK>::value, + "The `SRWLOCK` type must be trivially destructible"); + static_assert(std::is_trivially_destructible<CONDITION_VARIABLE>::value, + "The `CONDITION_VARIABLE` type must be trivially destructible"); +}; + +class LockHolder { + public: + explicit LockHolder(SRWLOCK* mu) : mu_(mu) { + AcquireSRWLockExclusive(mu_); + } + + LockHolder(const LockHolder&) = delete; + LockHolder& operator=(const LockHolder&) = delete; + + ~LockHolder() { + ReleaseSRWLockExclusive(mu_); + } + + private: + SRWLOCK* mu_; +}; + +Win32Waiter::Win32Waiter() { + auto *mu = ::new (static_cast<void *>(&mu_storage_)) SRWLOCK; + auto *cv = ::new (static_cast<void *>(&cv_storage_)) CONDITION_VARIABLE; + InitializeSRWLock(mu); + InitializeConditionVariable(cv); + waiter_count_ = 0; + wakeup_count_ = 0; +} + +bool Win32Waiter::Wait(KernelTimeout t) { + SRWLOCK *mu = WinHelper::GetLock(this); + CONDITION_VARIABLE *cv = WinHelper::GetCond(this); + + LockHolder h(mu); + ++waiter_count_; + + // Loop until we find a wakeup to consume or timeout. + // Note that, since the thread ticker is just reset, we don't need to check + // whether the thread is idle on the very first pass of the loop. + bool first_pass = true; + while (wakeup_count_ == 0) { + if (!first_pass) MaybeBecomeIdle(); + // No wakeups available, time to wait. + if (!SleepConditionVariableSRW(cv, mu, t.InMillisecondsFromNow(), 0)) { + // GetLastError() returns a Win32 DWORD, but we assign to + // unsigned long to simplify the ABSL_RAW_LOG case below. The uniform + // initialization guarantees this is not a narrowing conversion. + const unsigned long err{GetLastError()}; // NOLINT(runtime/int) + if (err == ERROR_TIMEOUT) { + --waiter_count_; + return false; + } else { + ABSL_RAW_LOG(FATAL, "SleepConditionVariableSRW failed: %lu", err); + } + } + first_pass = false; + } + // Consume a wakeup and we're done. + --wakeup_count_; + --waiter_count_; + return true; +} + +void Win32Waiter::Post() { + LockHolder h(WinHelper::GetLock(this)); + ++wakeup_count_; + InternalCondVarPoke(); +} + +void Win32Waiter::Poke() { + LockHolder h(WinHelper::GetLock(this)); + InternalCondVarPoke(); +} + +void Win32Waiter::InternalCondVarPoke() { + if (waiter_count_ != 0) { + WakeConditionVariable(WinHelper::GetCond(this)); + } +} + +} // namespace synchronization_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_INTERNAL_HAVE_WIN32_WAITER diff --git a/absl/synchronization/internal/win32_waiter.h b/absl/synchronization/internal/win32_waiter.h new file mode 100644 index 00000000..fdab264e --- /dev/null +++ b/absl/synchronization/internal/win32_waiter.h @@ -0,0 +1,72 @@ +// Copyright 2023 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_SYNCHRONIZATION_INTERNAL_WIN32_WAITER_H_ +#define ABSL_SYNCHRONIZATION_INTERNAL_WIN32_WAITER_H_ + +#ifdef _WIN32 +#include <sdkddkver.h> +#endif + +#if defined(_WIN32) && !defined(__MINGW32__) && \ + _WIN32_WINNT >= _WIN32_WINNT_VISTA + +#include "absl/base/config.h" +#include "absl/synchronization/internal/kernel_timeout.h" +#include "absl/synchronization/internal/waiter_base.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace synchronization_internal { + +#define ABSL_INTERNAL_HAVE_WIN32_WAITER 1 + +class Win32Waiter : public WaiterCrtp<Win32Waiter> { + public: + Win32Waiter(); + + bool Wait(KernelTimeout t); + void Post(); + void Poke(); + + static constexpr char kName[] = "Win32Waiter"; + + private: + // WinHelper - Used to define utilities for accessing the lock and + // condition variable storage once the types are complete. + class WinHelper; + + // REQUIRES: WinHelper::GetLock(this) must be held. + void InternalCondVarPoke(); + + // We can't include Windows.h in our headers, so we use aligned character + // buffers to define the storage of SRWLOCK and CONDITION_VARIABLE. + // SRW locks and condition variables do not need to be explicitly destroyed. + // https://docs.microsoft.com/en-us/windows/win32/api/synchapi/nf-synchapi-initializesrwlock + // https://stackoverflow.com/questions/28975958/why-does-windows-have-no-deleteconditionvariable-function-to-go-together-with + alignas(void*) unsigned char mu_storage_[sizeof(void*)]; + alignas(void*) unsigned char cv_storage_[sizeof(void*)]; + int waiter_count_; + int wakeup_count_; +}; + +} // namespace synchronization_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // defined(_WIN32) && !defined(__MINGW32__) && + // _WIN32_WINNT >= _WIN32_WINNT_VISTA + +#endif // ABSL_SYNCHRONIZATION_INTERNAL_WIN32_WAITER_H_ diff --git a/absl/synchronization/lifetime_test.cc b/absl/synchronization/lifetime_test.cc index e6274232..d5ce35a1 100644 --- a/absl/synchronization/lifetime_test.cc +++ b/absl/synchronization/lifetime_test.cc @@ -18,8 +18,8 @@ #include "absl/base/attributes.h" #include "absl/base/const_init.h" -#include "absl/base/internal/raw_logging.h" #include "absl/base/thread_annotations.h" +#include "absl/log/check.h" #include "absl/synchronization/mutex.h" #include "absl/synchronization/notification.h" @@ -35,20 +35,20 @@ namespace { // Thread two waits on 'notification', then sets 'state' inside the 'mutex', // signalling the change via 'condvar'. // -// These tests use ABSL_RAW_CHECK to validate invariants, rather than EXPECT or -// ASSERT from gUnit, because we need to invoke them during global destructors, -// when gUnit teardown would have already begun. +// These tests use CHECK to validate invariants, rather than EXPECT or ASSERT +// from gUnit, because we need to invoke them during global destructors, when +// gUnit teardown would have already begun. void ThreadOne(absl::Mutex* mutex, absl::CondVar* condvar, absl::Notification* notification, bool* state) { // Test that the notification is in a valid initial state. - ABSL_RAW_CHECK(!notification->HasBeenNotified(), "invalid Notification"); - ABSL_RAW_CHECK(*state == false, "*state not initialized"); + CHECK(!notification->HasBeenNotified()) << "invalid Notification"; + CHECK(!*state) << "*state not initialized"; { absl::MutexLock lock(mutex); notification->Notify(); - ABSL_RAW_CHECK(notification->HasBeenNotified(), "invalid Notification"); + CHECK(notification->HasBeenNotified()) << "invalid Notification"; while (*state == false) { condvar->Wait(mutex); @@ -58,11 +58,11 @@ void ThreadOne(absl::Mutex* mutex, absl::CondVar* condvar, void ThreadTwo(absl::Mutex* mutex, absl::CondVar* condvar, absl::Notification* notification, bool* state) { - ABSL_RAW_CHECK(*state == false, "*state not initialized"); + CHECK(!*state) << "*state not initialized"; // Wake thread one notification->WaitForNotification(); - ABSL_RAW_CHECK(notification->HasBeenNotified(), "invalid Notification"); + CHECK(notification->HasBeenNotified()) << "invalid Notification"; { absl::MutexLock lock(mutex); *state = true; diff --git a/absl/synchronization/mutex.cc b/absl/synchronization/mutex.cc index 064ccb74..cb3c7e74 100644 --- a/absl/synchronization/mutex.cc +++ b/absl/synchronization/mutex.cc @@ -35,10 +35,9 @@ #include <algorithm> #include <atomic> -#include <cinttypes> #include <cstddef> +#include <cstdlib> #include <cstring> -#include <iterator> #include <thread> // NOLINT(build/c++11) #include "absl/base/attributes.h" @@ -55,7 +54,6 @@ #include "absl/base/internal/thread_identity.h" #include "absl/base/internal/tsan_mutex_interface.h" #include "absl/base/optimization.h" -#include "absl/base/port.h" #include "absl/debugging/stacktrace.h" #include "absl/debugging/symbolize.h" #include "absl/synchronization/internal/graphcycles.h" @@ -63,6 +61,7 @@ #include "absl/time/time.h" using absl::base_internal::CurrentThreadIdentityIfPresent; +using absl::base_internal::CycleClock; using absl::base_internal::PerThreadSynch; using absl::base_internal::SchedulingGuard; using absl::base_internal::ThreadIdentity; @@ -98,18 +97,15 @@ ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES absl::base_internal::AtomicHook<void (*)(int64_t wait_cycles)> submit_profile_data; ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES absl::base_internal::AtomicHook<void (*)( - const char *msg, const void *obj, int64_t wait_cycles)> + const char* msg, const void* obj, int64_t wait_cycles)> mutex_tracer; ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES - absl::base_internal::AtomicHook<void (*)(const char *msg, const void *cv)> - cond_var_tracer; -ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES absl::base_internal::AtomicHook< - bool (*)(const void *pc, char *out, int out_size)> - symbolizer(absl::Symbolize); +absl::base_internal::AtomicHook<void (*)(const char* msg, const void* cv)> + cond_var_tracer; } // namespace -static inline bool EvalConditionAnnotated(const Condition *cond, Mutex *mu, +static inline bool EvalConditionAnnotated(const Condition* cond, Mutex* mu, bool locking, bool trylock, bool read_lock); @@ -117,19 +113,15 @@ void RegisterMutexProfiler(void (*fn)(int64_t wait_cycles)) { submit_profile_data.Store(fn); } -void RegisterMutexTracer(void (*fn)(const char *msg, const void *obj, +void RegisterMutexTracer(void (*fn)(const char* msg, const void* obj, int64_t wait_cycles)) { mutex_tracer.Store(fn); } -void RegisterCondVarTracer(void (*fn)(const char *msg, const void *cv)) { +void RegisterCondVarTracer(void (*fn)(const char* msg, const void* cv)) { cond_var_tracer.Store(fn); } -void RegisterSymbolizer(bool (*fn)(const void *pc, char *out, int out_size)) { - symbolizer.Store(fn); -} - namespace { // Represents the strategy for spin and yield. // See the comment in GetMutexGlobals() for more information. @@ -137,46 +129,46 @@ enum DelayMode { AGGRESSIVE, GENTLE }; struct ABSL_CACHELINE_ALIGNED MutexGlobals { absl::once_flag once; - int spinloop_iterations = 0; + // Note: this variable is initialized separately in Mutex::LockSlow, + // so that Mutex::Lock does not have a stack frame in optimized build. + std::atomic<int> spinloop_iterations{0}; int32_t mutex_sleep_spins[2] = {}; absl::Duration mutex_sleep_time; }; +ABSL_CONST_INIT static MutexGlobals globals; + 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. - // 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_spins[AGGRESSIVE] = 5000; - data.mutex_sleep_spins[GENTLE] = 250; - data.mutex_sleep_time = absl::Microseconds(10); +const MutexGlobals& GetMutexGlobals() { + absl::base_internal::LowLevelCallOnce(&globals.once, [&]() { + if (absl::base_internal::NumCPUs() > 1) { + // 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. + globals.mutex_sleep_spins[AGGRESSIVE] = 5000; + globals.mutex_sleep_spins[GENTLE] = 250; + globals.mutex_sleep_time = absl::Microseconds(10); } else { - 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)); + // 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. + globals.mutex_sleep_spins[AGGRESSIVE] = 0; + globals.mutex_sleep_spins[GENTLE] = 0; + globals.mutex_sleep_time = MeasureTimeToYield() * 5; + globals.mutex_sleep_time = + std::min(globals.mutex_sleep_time, absl::Milliseconds(1)); + globals.mutex_sleep_time = + std::max(globals.mutex_sleep_time, absl::Microseconds(10)); } }); - return data; + return globals; } } // namespace @@ -211,33 +203,23 @@ int MutexDelay(int32_t c, int mode) { // Ensure that "(*pv & bits) == bits" by doing an atomic update of "*pv" to // "*pv | bits" if necessary. Wait until (*pv & wait_until_clear)==0 // before making any change. +// Returns true if bits were previously unset and set by the call. // This is used to set flags in mutex and condition variable words. -static void AtomicSetBits(std::atomic<intptr_t>* pv, intptr_t bits, +static bool AtomicSetBits(std::atomic<intptr_t>* pv, intptr_t bits, intptr_t wait_until_clear) { - intptr_t v; - do { - v = pv->load(std::memory_order_relaxed); - } while ((v & bits) != bits && - ((v & wait_until_clear) != 0 || - !pv->compare_exchange_weak(v, v | bits, - std::memory_order_release, - std::memory_order_relaxed))); -} - -// Ensure that "(*pv & bits) == 0" by doing an atomic update of "*pv" to -// "*pv & ~bits" if necessary. Wait until (*pv & wait_until_clear)==0 -// before making any change. -// This is used to unset flags in mutex and condition variable words. -static void AtomicClearBits(std::atomic<intptr_t>* pv, intptr_t bits, - intptr_t wait_until_clear) { - intptr_t v; - do { - v = pv->load(std::memory_order_relaxed); - } while ((v & bits) != 0 && - ((v & wait_until_clear) != 0 || - !pv->compare_exchange_weak(v, v & ~bits, - std::memory_order_release, - std::memory_order_relaxed))); + for (;;) { + intptr_t v = pv->load(std::memory_order_relaxed); + if ((v & bits) == bits) { + return false; + } + if ((v & wait_until_clear) != 0) { + continue; + } + if (pv->compare_exchange_weak(v, v | bits, std::memory_order_release, + std::memory_order_relaxed)) { + return true; + } + } } //------------------------------------------------------------------ @@ -247,7 +229,7 @@ ABSL_CONST_INIT static absl::base_internal::SpinLock deadlock_graph_mu( absl::kConstInit, base_internal::SCHEDULE_KERNEL_ONLY); // Graph used to detect deadlocks. -ABSL_CONST_INIT static GraphCycles *deadlock_graph +ABSL_CONST_INIT static GraphCycles* deadlock_graph ABSL_GUARDED_BY(deadlock_graph_mu) ABSL_PT_GUARDED_BY(deadlock_graph_mu); //------------------------------------------------------------------ @@ -291,7 +273,7 @@ enum { // Event flags // Properties of the events. static const struct { int flags; - const char *msg; + const char* msg; } event_properties[] = { {SYNCH_F_LCK_W | SYNCH_F_TRY, "TryLock succeeded "}, {0, "TryLock failed "}, @@ -316,12 +298,12 @@ ABSL_CONST_INIT static absl::base_internal::SpinLock synch_event_mu( // Can't be too small, as it's used for deadlock detection information. static constexpr uint32_t kNSynchEvent = 1031; -static struct SynchEvent { // this is a trivial hash table for the events +static struct SynchEvent { // this is a trivial hash table for the events // struct is freed when refcount reaches 0 int refcount ABSL_GUARDED_BY(synch_event_mu); // buckets have linear, 0-terminated chains - SynchEvent *next ABSL_GUARDED_BY(synch_event_mu); + SynchEvent* next ABSL_GUARDED_BY(synch_event_mu); // Constant after initialization uintptr_t masked_addr; // object at this address is called "name" @@ -329,13 +311,13 @@ static struct SynchEvent { // this is a trivial hash table for the events // No explicit synchronization used. Instead we assume that the // client who enables/disables invariants/logging on a Mutex does so // while the Mutex is not being concurrently accessed by others. - void (*invariant)(void *arg); // called on each event - void *arg; // first arg to (*invariant)() - bool log; // logging turned on + void (*invariant)(void* arg); // called on each event + void* arg; // first arg to (*invariant)() + bool log; // logging turned on // Constant after initialization - char name[1]; // actually longer---NUL-terminated string -} * synch_event[kNSynchEvent] ABSL_GUARDED_BY(synch_event_mu); + char name[1]; // actually longer---NUL-terminated string +}* synch_event[kNSynchEvent] ABSL_GUARDED_BY(synch_event_mu); // Ensure that the object at "addr" has a SynchEvent struct associated with it, // set "bits" in the word there (waiting until lockbit is clear before doing @@ -344,88 +326,94 @@ static struct SynchEvent { // this is a trivial hash table for the events // the string name is copied into it. // When used with a mutex, the caller should also ensure that kMuEvent // is set in the mutex word, and similarly for condition variables and kCVEvent. -static SynchEvent *EnsureSynchEvent(std::atomic<intptr_t> *addr, - const char *name, intptr_t bits, +static SynchEvent* EnsureSynchEvent(std::atomic<intptr_t>* addr, + const char* name, intptr_t bits, intptr_t lockbit) { uint32_t h = reinterpret_cast<uintptr_t>(addr) % kNSynchEvent; - SynchEvent *e; - // first look for existing SynchEvent struct.. synch_event_mu.Lock(); - for (e = synch_event[h]; - e != nullptr && e->masked_addr != base_internal::HidePtr(addr); - e = e->next) { + // When a Mutex/CondVar is destroyed, we don't remove the associated + // SynchEvent to keep destructors empty in release builds for performance + // reasons. If the current call is the first to set bits (kMuEvent/kCVEvent), + // we don't look up the existing even because (if it exists, it must be for + // the previous Mutex/CondVar that existed at the same address). + // The leaking events must not be a problem for tests, which should create + // bounded amount of events. And debug logging is not supposed to be enabled + // in production. However, if it's accidentally enabled, or briefly enabled + // for some debugging, we don't want to crash the program. Instead we drop + // all events, if we accumulated too many of them. Size of a single event + // is ~48 bytes, so 100K events is ~5 MB. + // Additionally we could delete the old event for the same address, + // but it would require a better hashmap (if we accumulate too many events, + // linked lists will grow and traversing them will be very slow). + constexpr size_t kMaxSynchEventCount = 100 << 10; + // Total number of live synch events. + static size_t synch_event_count ABSL_GUARDED_BY(synch_event_mu); + if (++synch_event_count > kMaxSynchEventCount) { + synch_event_count = 0; + ABSL_RAW_LOG(ERROR, + "Accumulated %zu Mutex debug objects. If you see this" + " in production, it may mean that the production code" + " accidentally calls " + "Mutex/CondVar::EnableDebugLog/EnableInvariantDebugging.", + kMaxSynchEventCount); + for (auto*& head : synch_event) { + for (auto* e = head; e != nullptr;) { + SynchEvent* next = e->next; + if (--(e->refcount) == 0) { + base_internal::LowLevelAlloc::Free(e); + } + e = next; + } + head = nullptr; + } + } + SynchEvent* e = nullptr; + if (!AtomicSetBits(addr, bits, lockbit)) { + for (e = synch_event[h]; + e != nullptr && e->masked_addr != base_internal::HidePtr(addr); + e = e->next) { + } } if (e == nullptr) { // no SynchEvent struct found; make one. if (name == nullptr) { name = ""; } size_t l = strlen(name); - e = reinterpret_cast<SynchEvent *>( + e = reinterpret_cast<SynchEvent*>( base_internal::LowLevelAlloc::Alloc(sizeof(*e) + l)); - e->refcount = 2; // one for return value, one for linked list + e->refcount = 2; // one for return value, one for linked list e->masked_addr = base_internal::HidePtr(addr); e->invariant = nullptr; e->arg = nullptr; e->log = false; strcpy(e->name, name); // NOLINT(runtime/printf) e->next = synch_event[h]; - AtomicSetBits(addr, bits, lockbit); synch_event[h] = e; } else { - e->refcount++; // for return value + e->refcount++; // for return value } synch_event_mu.Unlock(); return e; } -// Deallocate the SynchEvent *e, whose refcount has fallen to zero. -static void DeleteSynchEvent(SynchEvent *e) { - base_internal::LowLevelAlloc::Free(e); -} - // Decrement the reference count of *e, or do nothing if e==null. -static void UnrefSynchEvent(SynchEvent *e) { +static void UnrefSynchEvent(SynchEvent* e) { if (e != nullptr) { synch_event_mu.Lock(); bool del = (--(e->refcount) == 0); synch_event_mu.Unlock(); if (del) { - DeleteSynchEvent(e); + base_internal::LowLevelAlloc::Free(e); } } } -// Forget the mapping from the object (Mutex or CondVar) at address addr -// to SynchEvent object, and clear "bits" in its word (waiting until lockbit -// is clear before doing so). -static void ForgetSynchEvent(std::atomic<intptr_t> *addr, intptr_t bits, - intptr_t lockbit) { - uint32_t h = reinterpret_cast<uintptr_t>(addr) % kNSynchEvent; - SynchEvent **pe; - SynchEvent *e; - synch_event_mu.Lock(); - for (pe = &synch_event[h]; - (e = *pe) != nullptr && e->masked_addr != base_internal::HidePtr(addr); - pe = &e->next) { - } - bool del = false; - if (e != nullptr) { - *pe = e->next; - del = (--(e->refcount) == 0); - } - AtomicClearBits(addr, bits, lockbit); - synch_event_mu.Unlock(); - if (del) { - DeleteSynchEvent(e); - } -} - // Return a refcounted reference to the SynchEvent of the object at address // "addr", if any. The pointer returned is valid until the UnrefSynchEvent() is // called. -static SynchEvent *GetSynchEvent(const void *addr) { +static SynchEvent* GetSynchEvent(const void* addr) { uint32_t h = reinterpret_cast<uintptr_t>(addr) % kNSynchEvent; - SynchEvent *e; + SynchEvent* e; synch_event_mu.Lock(); for (e = synch_event[h]; e != nullptr && e->masked_addr != base_internal::HidePtr(addr); @@ -440,17 +428,17 @@ static SynchEvent *GetSynchEvent(const void *addr) { // Called when an event "ev" occurs on a Mutex of CondVar "obj" // if event recording is on -static void PostSynchEvent(void *obj, int ev) { - SynchEvent *e = GetSynchEvent(obj); +static void PostSynchEvent(void* obj, int ev) { + SynchEvent* e = GetSynchEvent(obj); // logging is on if event recording is on and either there's no event struct, // or it explicitly says to log if (e == nullptr || e->log) { - void *pcs[40]; + void* pcs[40]; int n = absl::GetStackTrace(pcs, ABSL_ARRAYSIZE(pcs), 1); // A buffer with enough space for the ASCII for all the PCs, even on a // 64-bit machine. char buffer[ABSL_ARRAYSIZE(pcs) * 24]; - int pos = snprintf(buffer, sizeof (buffer), " @"); + int pos = snprintf(buffer, sizeof(buffer), " @"); for (int i = 0; i != n; i++) { int b = snprintf(&buffer[pos], sizeof(buffer) - static_cast<size_t>(pos), " %p", pcs[i]); @@ -472,13 +460,13 @@ static void PostSynchEvent(void *obj, int ev) { // get false positive race reports later. // Reuse EvalConditionAnnotated to properly call into user code. struct local { - static bool pred(SynchEvent *ev) { + static bool pred(SynchEvent* ev) { (*ev->invariant)(ev->arg); return false; } }; Condition cond(&local::pred, e); - Mutex *mu = static_cast<Mutex *>(obj); + Mutex* mu = static_cast<Mutex*>(obj); const bool locking = (flags & SYNCH_F_UNLOCK) == 0; const bool trylock = (flags & SYNCH_F_TRY) != 0; const bool read_lock = (flags & SYNCH_F_R) != 0; @@ -504,32 +492,32 @@ static void PostSynchEvent(void *obj, int ev) { // PerThreadSynch struct points at the most recent SynchWaitParams struct when // the thread is on a Mutex's waiter queue. struct SynchWaitParams { - SynchWaitParams(Mutex::MuHow how_arg, const Condition *cond_arg, - KernelTimeout timeout_arg, Mutex *cvmu_arg, - PerThreadSynch *thread_arg, - std::atomic<intptr_t> *cv_word_arg) + SynchWaitParams(Mutex::MuHow how_arg, const Condition* cond_arg, + KernelTimeout timeout_arg, Mutex* cvmu_arg, + PerThreadSynch* thread_arg, + std::atomic<intptr_t>* cv_word_arg) : how(how_arg), cond(cond_arg), timeout(timeout_arg), cvmu(cvmu_arg), thread(thread_arg), cv_word(cv_word_arg), - contention_start_cycles(base_internal::CycleClock::Now()), + contention_start_cycles(CycleClock::Now()), should_submit_contention_data(false) {} const Mutex::MuHow how; // How this thread needs to wait. - const Condition *cond; // The condition that this thread is waiting for. - // In Mutex, this field is set to zero if a timeout - // expires. + const Condition* cond; // The condition that this thread is waiting for. + // In Mutex, this field is set to zero if a timeout + // expires. KernelTimeout timeout; // timeout expiry---absolute time // In Mutex, this field is set to zero if a timeout // expires. - Mutex *const cvmu; // used for transfer from cond var to mutex - PerThreadSynch *const thread; // thread that is waiting + Mutex* const cvmu; // used for transfer from cond var to mutex + PerThreadSynch* const thread; // thread that is waiting // If not null, thread should be enqueued on the CondVar whose state // word is cv_word instead of queueing normally on the Mutex. - std::atomic<intptr_t> *cv_word; + std::atomic<intptr_t>* cv_word; int64_t contention_start_cycles; // Time (in cycles) when this thread started // to contend for the mutex. @@ -537,12 +525,12 @@ struct SynchWaitParams { }; struct SynchLocksHeld { - int n; // number of valid entries in locks[] - bool overflow; // true iff we overflowed the array at some point + int n; // number of valid entries in locks[] + bool overflow; // true iff we overflowed the array at some point struct { - Mutex *mu; // lock acquired - int32_t count; // times acquired - GraphId id; // deadlock_graph id of acquired lock + Mutex* mu; // lock acquired + int32_t count; // times acquired + GraphId id; // deadlock_graph id of acquired lock } locks[40]; // If a thread overfills the array during deadlock detection, we // continue, discarding information as needed. If no overflow has @@ -552,11 +540,11 @@ struct SynchLocksHeld { // A sentinel value in lists that is not 0. // A 0 value is used to mean "not on a list". -static PerThreadSynch *const kPerThreadSynchNull = - reinterpret_cast<PerThreadSynch *>(1); +static PerThreadSynch* const kPerThreadSynchNull = + reinterpret_cast<PerThreadSynch*>(1); -static SynchLocksHeld *LocksHeldAlloc() { - SynchLocksHeld *ret = reinterpret_cast<SynchLocksHeld *>( +static SynchLocksHeld* LocksHeldAlloc() { + SynchLocksHeld* ret = reinterpret_cast<SynchLocksHeld*>( base_internal::LowLevelAlloc::Alloc(sizeof(SynchLocksHeld))); ret->n = 0; ret->overflow = false; @@ -564,24 +552,24 @@ static SynchLocksHeld *LocksHeldAlloc() { } // Return the PerThreadSynch-struct for this thread. -static PerThreadSynch *Synch_GetPerThread() { - ThreadIdentity *identity = GetOrCreateCurrentThreadIdentity(); +static PerThreadSynch* Synch_GetPerThread() { + ThreadIdentity* identity = GetOrCreateCurrentThreadIdentity(); return &identity->per_thread_synch; } -static PerThreadSynch *Synch_GetPerThreadAnnotated(Mutex *mu) { +static PerThreadSynch* Synch_GetPerThreadAnnotated(Mutex* mu) { if (mu) { ABSL_TSAN_MUTEX_PRE_DIVERT(mu, 0); } - PerThreadSynch *w = Synch_GetPerThread(); + PerThreadSynch* w = Synch_GetPerThread(); if (mu) { ABSL_TSAN_MUTEX_POST_DIVERT(mu, 0); } return w; } -static SynchLocksHeld *Synch_GetAllLocks() { - PerThreadSynch *s = Synch_GetPerThread(); +static SynchLocksHeld* Synch_GetAllLocks() { + PerThreadSynch* s = Synch_GetPerThread(); if (s->all_locks == nullptr) { s->all_locks = LocksHeldAlloc(); // Freed by ReclaimThreadIdentity. } @@ -589,32 +577,26 @@ static SynchLocksHeld *Synch_GetAllLocks() { } // Post on "w"'s associated PerThreadSem. -void Mutex::IncrementSynchSem(Mutex *mu, PerThreadSynch *w) { - if (mu) { - ABSL_TSAN_MUTEX_PRE_DIVERT(mu, 0); - // We miss synchronization around passing PerThreadSynch between threads - // since it happens inside of the Mutex code, so we need to ignore all - // accesses to the object. - ABSL_ANNOTATE_IGNORE_READS_AND_WRITES_BEGIN(); - PerThreadSem::Post(w->thread_identity()); - ABSL_ANNOTATE_IGNORE_READS_AND_WRITES_END(); - ABSL_TSAN_MUTEX_POST_DIVERT(mu, 0); - } else { - PerThreadSem::Post(w->thread_identity()); - } +void Mutex::IncrementSynchSem(Mutex* mu, PerThreadSynch* w) { + static_cast<void>(mu); // Prevent unused param warning in non-TSAN builds. + ABSL_TSAN_MUTEX_PRE_DIVERT(mu, 0); + // We miss synchronization around passing PerThreadSynch between threads + // since it happens inside of the Mutex code, so we need to ignore all + // accesses to the object. + ABSL_ANNOTATE_IGNORE_READS_AND_WRITES_BEGIN(); + PerThreadSem::Post(w->thread_identity()); + ABSL_ANNOTATE_IGNORE_READS_AND_WRITES_END(); + ABSL_TSAN_MUTEX_POST_DIVERT(mu, 0); } // Wait on "w"'s associated PerThreadSem; returns false if timeout expired. -bool Mutex::DecrementSynchSem(Mutex *mu, PerThreadSynch *w, KernelTimeout t) { - if (mu) { - ABSL_TSAN_MUTEX_PRE_DIVERT(mu, 0); - } +bool Mutex::DecrementSynchSem(Mutex* mu, PerThreadSynch* w, KernelTimeout t) { + static_cast<void>(mu); // Prevent unused param warning in non-TSAN builds. + ABSL_TSAN_MUTEX_PRE_DIVERT(mu, 0); assert(w == Synch_GetPerThread()); static_cast<void>(w); bool res = PerThreadSem::Wait(t); - if (mu) { - ABSL_TSAN_MUTEX_POST_DIVERT(mu, 0); - } + ABSL_TSAN_MUTEX_POST_DIVERT(mu, 0); return res; } @@ -626,7 +608,7 @@ bool Mutex::DecrementSynchSem(Mutex *mu, PerThreadSynch *w, KernelTimeout t) { // Mutex code checking that the "waitp" field has not been reused. void Mutex::InternalAttemptToUseMutexInFatalSignalHandler() { // Fix the per-thread state only if it exists. - ThreadIdentity *identity = CurrentThreadIdentityIfPresent(); + ThreadIdentity* identity = CurrentThreadIdentityIfPresent(); if (identity != nullptr) { identity->per_thread_synch.suppress_fatal_errors = true; } @@ -635,21 +617,6 @@ void Mutex::InternalAttemptToUseMutexInFatalSignalHandler() { std::memory_order_release); } -// --------------------------time support - -// Return the current time plus the timeout. Use the same clock as -// PerThreadSem::Wait() for consistency. Unfortunately, we don't have -// such a choice when a deadline is given directly. -static absl::Time DeadlineFromTimeout(absl::Duration timeout) { -#ifndef _WIN32 - struct timeval tv; - gettimeofday(&tv, nullptr); - return absl::TimeFromTimeval(tv) + timeout; -#else - return absl::Now() + timeout; -#endif -} - // --------------------------Mutexes // In the layout below, the msb of the bottom byte is currently unused. Also, @@ -660,24 +627,29 @@ static absl::Time DeadlineFromTimeout(absl::Duration timeout) { // bit-twiddling trick in Mutex::Unlock(). // o kMuWriter / kMuReader == kMuWrWait / kMuWait, // to enable the bit-twiddling trick in CheckForMutexCorruption(). -static const intptr_t kMuReader = 0x0001L; // a reader holds the lock -static const intptr_t kMuDesig = 0x0002L; // there's a designated waker -static const intptr_t kMuWait = 0x0004L; // threads are waiting -static const intptr_t kMuWriter = 0x0008L; // a writer holds the lock -static const intptr_t kMuEvent = 0x0010L; // record this mutex's events +static const intptr_t kMuReader = 0x0001L; // a reader holds the lock +// There's a designated waker. // INVARIANT1: there's a thread that was blocked on the mutex, is // no longer, yet has not yet acquired the mutex. If there's a // designated waker, all threads can avoid taking the slow path in // unlock because the designated waker will subsequently acquire // the lock and wake someone. To maintain INVARIANT1 the bit is // set when a thread is unblocked(INV1a), and threads that were -// unblocked reset the bit when they either acquire or re-block -// (INV1b). -static const intptr_t kMuWrWait = 0x0020L; // runnable writer is waiting - // for a reader -static const intptr_t kMuSpin = 0x0040L; // spinlock protects wait list -static const intptr_t kMuLow = 0x00ffL; // mask all mutex bits -static const intptr_t kMuHigh = ~kMuLow; // mask pointer/reader count +// unblocked reset the bit when they either acquire or re-block (INV1b). +static const intptr_t kMuDesig = 0x0002L; +static const intptr_t kMuWait = 0x0004L; // threads are waiting +static const intptr_t kMuWriter = 0x0008L; // a writer holds the lock +static const intptr_t kMuEvent = 0x0010L; // record this mutex's events +// Runnable writer is waiting for a reader. +// If set, new readers will not lock the mutex to avoid writer starvation. +// Note: if a reader has higher priority than the writer, it will still lock +// the mutex ahead of the waiting writer, but in a very inefficient manner: +// the reader will first queue itself and block, but then the last unlocking +// reader will wake it. +static const intptr_t kMuWrWait = 0x0020L; +static const intptr_t kMuSpin = 0x0040L; // spinlock protects wait list +static const intptr_t kMuLow = 0x00ffL; // mask all mutex bits +static const intptr_t kMuHigh = ~kMuLow; // mask pointer/reader count // Hack to make constant values available to gdb pretty printer enum { @@ -703,6 +675,7 @@ static const intptr_t kMuOne = 0x0100; // a count of one reader // flags passed to Enqueue and LockSlow{,WithTimeout,Loop} static const int kMuHasBlocked = 0x01; // already blocked (MUST == 1) static const int kMuIsCond = 0x02; // conditional waiter (CV or Condition) +static const int kMuIsFer = 0x04; // wait morphing from a CondVar static_assert(PerThreadSynch::kAlignment > kMuLow, "PerThreadSynch::kAlignment must be greater than kMuLow"); @@ -758,40 +731,59 @@ static unsigned TsanFlags(Mutex::MuHow how) { } #endif -static bool DebugOnlyIsExiting() { - return false; -} +#if defined(__APPLE__) || defined(ABSL_BUILD_DLL) +// When building a dll symbol export lists may reference the destructor +// and want it to be an exported symbol rather than an inline function. +// Some apple builds also do dynamic library build but don't say it explicitly. +Mutex::~Mutex() { Dtor(); } +#endif -Mutex::~Mutex() { - intptr_t v = mu_.load(std::memory_order_relaxed); - if ((v & kMuEvent) != 0 && !DebugOnlyIsExiting()) { - ForgetSynchEvent(&this->mu_, kMuEvent, kMuSpin); - } +#if !defined(NDEBUG) || defined(ABSL_HAVE_THREAD_SANITIZER) +void Mutex::Dtor() { if (kDebugMode) { this->ForgetDeadlockInfo(); } ABSL_TSAN_MUTEX_DESTROY(this, __tsan_mutex_not_static); } +#endif -void Mutex::EnableDebugLog(const char *name) { - SynchEvent *e = EnsureSynchEvent(&this->mu_, name, kMuEvent, kMuSpin); +void Mutex::EnableDebugLog(const char* name) { + // Need to disable writes here and in EnableInvariantDebugging to prevent + // false race reports on SynchEvent objects. TSan ignores synchronization + // on synch_event_mu in Lock/Unlock/etc methods due to mutex annotations, + // but it sees few accesses to SynchEvent in EvalConditionAnnotated. + // If we don't ignore accesses here, it can result in false races + // between EvalConditionAnnotated and SynchEvent reuse in EnsureSynchEvent. + ABSL_ANNOTATE_IGNORE_WRITES_BEGIN(); + SynchEvent* e = EnsureSynchEvent(&this->mu_, name, kMuEvent, kMuSpin); e->log = true; UnrefSynchEvent(e); + // This prevents "error: undefined symbol: absl::Mutex::~Mutex()" + // in a release build (NDEBUG defined) when a test does "#undef NDEBUG" + // to use assert macro. In such case, the test does not get the dtor + // definition because it's supposed to be outline when NDEBUG is not defined, + // and this source file does not define one either because NDEBUG is defined. + // Since it's not possible to take address of a destructor, we move the + // actual destructor code into the separate Dtor function and force the + // compiler to emit this function even if it's inline by taking its address. + ABSL_ATTRIBUTE_UNUSED volatile auto dtor = &Mutex::Dtor; + ABSL_ANNOTATE_IGNORE_WRITES_END(); } void EnableMutexInvariantDebugging(bool enabled) { synch_check_invariants.store(enabled, std::memory_order_release); } -void Mutex::EnableInvariantDebugging(void (*invariant)(void *), - void *arg) { +void Mutex::EnableInvariantDebugging(void (*invariant)(void*), void* arg) { + ABSL_ANNOTATE_IGNORE_WRITES_BEGIN(); if (synch_check_invariants.load(std::memory_order_acquire) && invariant != nullptr) { - SynchEvent *e = EnsureSynchEvent(&this->mu_, nullptr, kMuEvent, kMuSpin); + SynchEvent* e = EnsureSynchEvent(&this->mu_, nullptr, kMuEvent, kMuSpin); e->invariant = invariant; e->arg = arg; UnrefSynchEvent(e); } + ABSL_ANNOTATE_IGNORE_WRITES_END(); } void SetMutexDeadlockDetectionMode(OnDeadlockCycle mode) { @@ -803,15 +795,15 @@ void SetMutexDeadlockDetectionMode(OnDeadlockCycle mode) { // waiters with the same condition, type of lock, and thread priority. // // Requires that x and y be waiting on the same Mutex queue. -static bool MuEquivalentWaiter(PerThreadSynch *x, PerThreadSynch *y) { +static bool MuEquivalentWaiter(PerThreadSynch* x, PerThreadSynch* y) { return x->waitp->how == y->waitp->how && x->priority == y->priority && Condition::GuaranteedEqual(x->waitp->cond, y->waitp->cond); } // Given the contents of a mutex word containing a PerThreadSynch pointer, // return the pointer. -static inline PerThreadSynch *GetPerThreadSynch(intptr_t v) { - return reinterpret_cast<PerThreadSynch *>(v & kMuHigh); +static inline PerThreadSynch* GetPerThreadSynch(intptr_t v) { + return reinterpret_cast<PerThreadSynch*>(v & kMuHigh); } // The next several routines maintain the per-thread next and skip fields @@ -869,17 +861,17 @@ static inline PerThreadSynch *GetPerThreadSynch(intptr_t v) { // except those in the added node and the former "head" node. This implies // that the new node is added after head, and so must be the new head or the // new front of the queue. -static PerThreadSynch *Skip(PerThreadSynch *x) { - PerThreadSynch *x0 = nullptr; - PerThreadSynch *x1 = x; - PerThreadSynch *x2 = x->skip; +static PerThreadSynch* Skip(PerThreadSynch* x) { + PerThreadSynch* x0 = nullptr; + PerThreadSynch* x1 = x; + PerThreadSynch* x2 = x->skip; if (x2 != nullptr) { // Each iteration attempts to advance sequence (x0,x1,x2) to next sequence // such that x1 == x0->skip && x2 == x1->skip while ((x0 = x1, x1 = x2, x2 = x2->skip) != nullptr) { - x0->skip = x2; // short-circuit skip from x0 to x2 + x0->skip = x2; // short-circuit skip from x0 to x2 } - x->skip = x1; // short-circuit skip from x to result + x->skip = x1; // short-circuit skip from x to result } return x1; } @@ -888,7 +880,7 @@ static PerThreadSynch *Skip(PerThreadSynch *x) { // The latter is going to be removed out of order, because of a timeout. // Check whether "ancestor" has a skip field pointing to "to_be_removed", // and fix it if it does. -static void FixSkip(PerThreadSynch *ancestor, PerThreadSynch *to_be_removed) { +static void FixSkip(PerThreadSynch* ancestor, PerThreadSynch* to_be_removed) { if (ancestor->skip == to_be_removed) { // ancestor->skip left dangling if (to_be_removed->skip != nullptr) { ancestor->skip = to_be_removed->skip; // can skip past to_be_removed @@ -900,7 +892,7 @@ static void FixSkip(PerThreadSynch *ancestor, PerThreadSynch *to_be_removed) { } } -static void CondVarEnqueue(SynchWaitParams *waitp); +static void CondVarEnqueue(SynchWaitParams* waitp); // Enqueue thread "waitp->thread" on a waiter queue. // Called with mutex spinlock held if head != nullptr @@ -921,8 +913,8 @@ static void CondVarEnqueue(SynchWaitParams *waitp); // returned. This mechanism is used by CondVar to queue a thread on the // condition variable queue instead of the mutex queue in implementing Wait(). // In this case, Enqueue() can return nullptr (if head==nullptr). -static PerThreadSynch *Enqueue(PerThreadSynch *head, - SynchWaitParams *waitp, intptr_t mu, int flags) { +static PerThreadSynch* Enqueue(PerThreadSynch* head, SynchWaitParams* waitp, + intptr_t mu, int flags) { // If we have been given a cv_word, call CondVarEnqueue() and return // the previous head of the Mutex waiter queue. if (waitp->cv_word != nullptr) { @@ -930,30 +922,25 @@ static PerThreadSynch *Enqueue(PerThreadSynch *head, return head; } - PerThreadSynch *s = waitp->thread; + PerThreadSynch* s = waitp->thread; ABSL_RAW_CHECK( s->waitp == nullptr || // normal case s->waitp == waitp || // Fer()---transfer from condition variable s->suppress_fatal_errors, "detected illegal recursion into Mutex code"); s->waitp = waitp; - s->skip = nullptr; // maintain skip invariant (see above) - s->may_skip = true; // always true on entering queue - s->wake = false; // not being woken + s->skip = nullptr; // maintain skip invariant (see above) + s->may_skip = true; // always true on entering queue + s->wake = false; // not being woken s->cond_waiter = ((flags & kMuIsCond) != 0); - if (head == nullptr) { // s is the only waiter - s->next = s; // it's the only entry in the cycle - s->readers = mu; // reader count is from mu word - s->maybe_unlocking = false; // no one is searching an empty list - head = s; // s is new head - } else { - PerThreadSynch *enqueue_after = nullptr; // we'll put s after this element #ifdef ABSL_HAVE_PTHREAD_GETSCHEDPARAM - int64_t now_cycles = base_internal::CycleClock::Now(); + if ((flags & kMuIsFer) == 0) { + assert(s == Synch_GetPerThread()); + int64_t now_cycles = CycleClock::Now(); if (s->next_priority_read_cycles < now_cycles) { // Every so often, update our idea of the thread's priority. // pthread_getschedparam() is 5% of the block/wakeup time; - // base_internal::CycleClock::Now() is 0.5%. + // CycleClock::Now() is 0.5%. int policy; struct sched_param param; const int err = pthread_getschedparam(pthread_self(), &policy, ¶m); @@ -962,10 +949,19 @@ static PerThreadSynch *Enqueue(PerThreadSynch *head, } else { s->priority = param.sched_priority; s->next_priority_read_cycles = - now_cycles + - static_cast<int64_t>(base_internal::CycleClock::Frequency()); + now_cycles + static_cast<int64_t>(CycleClock::Frequency()); } } + } +#endif + if (head == nullptr) { // s is the only waiter + s->next = s; // it's the only entry in the cycle + s->readers = mu; // reader count is from mu word + s->maybe_unlocking = false; // no one is searching an empty list + head = s; // s is new head + } else { + PerThreadSynch* enqueue_after = nullptr; // we'll put s after this element +#ifdef ABSL_HAVE_PTHREAD_GETSCHEDPARAM if (s->priority > head->priority) { // s's priority is above head's // try to put s in priority-fifo order, or failing that at the front. if (!head->maybe_unlocking) { @@ -975,20 +971,19 @@ static PerThreadSynch *Enqueue(PerThreadSynch *head, // Within a skip chain, all waiters have the same priority, so we can // skip forward through the chains until we find one with a lower // priority than the waiter to be enqueued. - PerThreadSynch *advance_to = head; // next value of enqueue_after + PerThreadSynch* advance_to = head; // next value of enqueue_after do { enqueue_after = advance_to; // (side-effect: optimizes skip chain) advance_to = Skip(enqueue_after->next); } while (s->priority <= advance_to->priority); - // termination guaranteed because s->priority > head->priority - // and head is the end of a skip chain - } else if (waitp->how == kExclusive && - Condition::GuaranteedEqual(waitp->cond, nullptr)) { + // termination guaranteed because s->priority > head->priority + // and head is the end of a skip chain + } else if (waitp->how == kExclusive && waitp->cond == nullptr) { // An unlocker could be scanning the queue, but we know it will recheck // the queue front for writers that have no condition, which is what s // is, so an insert at front is safe. - enqueue_after = head; // add after head, at front + enqueue_after = head; // add after head, at front } } #endif @@ -1013,12 +1008,30 @@ static PerThreadSynch *Enqueue(PerThreadSynch *head, enqueue_after->skip = enqueue_after->next; } if (MuEquivalentWaiter(s, s->next)) { // s->may_skip is known to be true - s->skip = s->next; // s may skip to its successor + s->skip = s->next; // s may skip to its successor + } + } else if ((flags & kMuHasBlocked) && + (s->priority >= head->next->priority) && + (!head->maybe_unlocking || + (waitp->how == kExclusive && + Condition::GuaranteedEqual(waitp->cond, nullptr)))) { + // This thread has already waited, then was woken, then failed to acquire + // the mutex and now tries to requeue. Try to requeue it at head, + // otherwise it can suffer bad latency (wait whole queue several times). + // However, we need to be conservative. First, we need to ensure that we + // respect priorities. Then, we need to be careful to not break wait + // queue invariants: we require either that unlocker is not scanning + // the queue or that the current thread is a writer with no condition + // (unlocker will recheck the queue for such waiters). + s->next = head->next; + head->next = s; + if (MuEquivalentWaiter(s, s->next)) { // s->may_skip is known to be true + s->skip = s->next; // s may skip to its successor } - } else { // enqueue not done any other way, so - // we're inserting s at the back + } else { // enqueue not done any other way, so + // we're inserting s at the back // s will become new head; copy data from head into it - s->next = head->next; // add s after head + s->next = head->next; // add s after head head->next = s; s->readers = head->readers; // reader count is from previous head s->maybe_unlocking = head->maybe_unlocking; // same for unlock hint @@ -1037,17 +1050,17 @@ static PerThreadSynch *Enqueue(PerThreadSynch *head, // whose last element is head. The new head element is returned, or null // if the list is made empty. // Dequeue is called with both spinlock and Mutex held. -static PerThreadSynch *Dequeue(PerThreadSynch *head, PerThreadSynch *pw) { - PerThreadSynch *w = pw->next; - pw->next = w->next; // snip w out of list - if (head == w) { // we removed the head +static PerThreadSynch* Dequeue(PerThreadSynch* head, PerThreadSynch* pw) { + PerThreadSynch* w = pw->next; + pw->next = w->next; // snip w out of list + if (head == w) { // we removed the head head = (pw == w) ? nullptr : pw; // either emptied list, or pw is new head } else if (pw != head && MuEquivalentWaiter(pw, pw->next)) { // pw can skip to its new successor if (pw->next->skip != nullptr) { // either skip to its successors skip target pw->skip = pw->next->skip; - } else { // or to pw's successor + } else { // or to pw's successor pw->skip = pw->next; } } @@ -1060,27 +1073,27 @@ static PerThreadSynch *Dequeue(PerThreadSynch *head, PerThreadSynch *pw) { // singly-linked list wake_list in the order found. Assumes that // there is only one such element if the element has how == kExclusive. // Return the new head. -static PerThreadSynch *DequeueAllWakeable(PerThreadSynch *head, - PerThreadSynch *pw, - PerThreadSynch **wake_tail) { - PerThreadSynch *orig_h = head; - PerThreadSynch *w = pw->next; +static PerThreadSynch* DequeueAllWakeable(PerThreadSynch* head, + PerThreadSynch* pw, + PerThreadSynch** wake_tail) { + PerThreadSynch* orig_h = head; + PerThreadSynch* w = pw->next; bool skipped = false; do { - if (w->wake) { // remove this element + if (w->wake) { // remove this element ABSL_RAW_CHECK(pw->skip == nullptr, "bad skip in DequeueAllWakeable"); // we're removing pw's successor so either pw->skip is zero or we should // already have removed pw since if pw->skip!=null, pw has the same // condition as w. head = Dequeue(head, pw); - w->next = *wake_tail; // keep list terminated - *wake_tail = w; // add w to wake_list; - wake_tail = &w->next; // next addition to end + w->next = *wake_tail; // keep list terminated + *wake_tail = w; // add w to wake_list; + wake_tail = &w->next; // next addition to end if (w->waitp->how == kExclusive) { // wake at most 1 writer break; } - } else { // not waking this one; skip - pw = Skip(w); // skip as much as possible + } else { // not waking this one; skip + pw = Skip(w); // skip as much as possible skipped = true; } w = pw->next; @@ -1098,7 +1111,7 @@ static PerThreadSynch *DequeueAllWakeable(PerThreadSynch *head, // Try to remove thread s from the list of waiters on this mutex. // Does nothing if s is not on the waiter list. -void Mutex::TryRemove(PerThreadSynch *s) { +void Mutex::TryRemove(PerThreadSynch* s) { SchedulingGuard::ScopedDisable disable_rescheduling; intptr_t v = mu_.load(std::memory_order_relaxed); // acquire spinlock & lock @@ -1106,16 +1119,16 @@ void Mutex::TryRemove(PerThreadSynch *s) { mu_.compare_exchange_strong(v, v | kMuSpin | kMuWriter, std::memory_order_acquire, std::memory_order_relaxed)) { - PerThreadSynch *h = GetPerThreadSynch(v); + PerThreadSynch* h = GetPerThreadSynch(v); if (h != nullptr) { - PerThreadSynch *pw = h; // pw is w's predecessor - PerThreadSynch *w; + PerThreadSynch* pw = h; // pw is w's predecessor + PerThreadSynch* w; if ((w = pw->next) != s) { // search for thread, do { // processing at least one element // If the current element isn't equivalent to the waiter to be // removed, we can skip the entire chain. if (!MuEquivalentWaiter(s, w)) { - pw = Skip(w); // so skip all that won't match + pw = Skip(w); // so skip all that won't match // we don't have to worry about dangling skip fields // in the threads we skipped; none can point to s // because they are in a different equivalence class. @@ -1127,7 +1140,7 @@ void Mutex::TryRemove(PerThreadSynch *s) { // process the first thread again. } while ((w = pw->next) != s && pw != h); } - if (w == s) { // found thread; remove it + if (w == s) { // found thread; remove it // pw->skip may be non-zero here; the loop above ensured that // no ancestor of s can skip to s, so removal is safe anyway. h = Dequeue(h, pw); @@ -1136,16 +1149,15 @@ void Mutex::TryRemove(PerThreadSynch *s) { } } intptr_t nv; - do { // release spinlock and lock + do { // release spinlock and lock v = mu_.load(std::memory_order_relaxed); nv = v & (kMuDesig | kMuEvent); if (h != nullptr) { nv |= kMuWait | reinterpret_cast<intptr_t>(h); - h->readers = 0; // we hold writer lock + h->readers = 0; // we hold writer lock h->maybe_unlocking = false; // finished unlocking } - } while (!mu_.compare_exchange_weak(v, nv, - std::memory_order_release, + } while (!mu_.compare_exchange_weak(v, nv, std::memory_order_release, std::memory_order_relaxed)); } } @@ -1155,7 +1167,7 @@ void Mutex::TryRemove(PerThreadSynch *s) { // if the wait extends past the absolute time specified, even if "s" is still // on the mutex queue. In this case, remove "s" from the queue and return // true, otherwise return false. -void Mutex::Block(PerThreadSynch *s) { +void Mutex::Block(PerThreadSynch* s) { while (s->state.load(std::memory_order_acquire) == PerThreadSynch::kQueued) { if (!DecrementSynchSem(this, s, s->waitp->timeout)) { // After a timeout, we go into a spin loop until we remove ourselves @@ -1174,7 +1186,7 @@ void Mutex::Block(PerThreadSynch *s) { // is not on the queue. this->TryRemove(s); } - s->waitp->timeout = KernelTimeout::Never(); // timeout is satisfied + s->waitp->timeout = KernelTimeout::Never(); // timeout is satisfied s->waitp->cond = nullptr; // condition no longer relevant for wakeups } } @@ -1184,8 +1196,8 @@ void Mutex::Block(PerThreadSynch *s) { } // Wake thread w, and return the next thread in the list. -PerThreadSynch *Mutex::Wakeup(PerThreadSynch *w) { - PerThreadSynch *next = w->next; +PerThreadSynch* Mutex::Wakeup(PerThreadSynch* w) { + PerThreadSynch* next = w->next; w->next = nullptr; w->state.store(PerThreadSynch::kAvailable, std::memory_order_release); IncrementSynchSem(this, w); @@ -1193,7 +1205,7 @@ PerThreadSynch *Mutex::Wakeup(PerThreadSynch *w) { return next; } -static GraphId GetGraphIdLocked(Mutex *mu) +static GraphId GetGraphIdLocked(Mutex* mu) ABSL_EXCLUSIVE_LOCKS_REQUIRED(deadlock_graph_mu) { if (!deadlock_graph) { // (re)create the deadlock graph. deadlock_graph = @@ -1203,7 +1215,7 @@ static GraphId GetGraphIdLocked(Mutex *mu) return deadlock_graph->GetId(mu); } -static GraphId GetGraphId(Mutex *mu) ABSL_LOCKS_EXCLUDED(deadlock_graph_mu) { +static GraphId GetGraphId(Mutex* mu) ABSL_LOCKS_EXCLUDED(deadlock_graph_mu) { deadlock_graph_mu.Lock(); GraphId id = GetGraphIdLocked(mu); deadlock_graph_mu.Unlock(); @@ -1213,7 +1225,7 @@ static GraphId GetGraphId(Mutex *mu) ABSL_LOCKS_EXCLUDED(deadlock_graph_mu) { // Record a lock acquisition. This is used in debug mode for deadlock // detection. The held_locks pointer points to the relevant data // structure for each case. -static void LockEnter(Mutex* mu, GraphId id, SynchLocksHeld *held_locks) { +static void LockEnter(Mutex* mu, GraphId id, SynchLocksHeld* held_locks) { int n = held_locks->n; int i = 0; while (i != n && held_locks->locks[i].id != id) { @@ -1237,7 +1249,7 @@ static void LockEnter(Mutex* mu, GraphId id, SynchLocksHeld *held_locks) { // eventually followed by a call to LockLeave(mu, id, x) by the same thread. // It does not process the event if is not needed when deadlock detection is // disabled. -static void LockLeave(Mutex* mu, GraphId id, SynchLocksHeld *held_locks) { +static void LockLeave(Mutex* mu, GraphId id, SynchLocksHeld* held_locks) { int n = held_locks->n; int i = 0; while (i != n && held_locks->locks[i].id != id) { @@ -1252,11 +1264,11 @@ static void LockLeave(Mutex* mu, GraphId id, SynchLocksHeld *held_locks) { i++; } if (i == n) { // mu missing means releasing unheld lock - SynchEvent *mu_events = GetSynchEvent(mu); + SynchEvent* mu_events = GetSynchEvent(mu); ABSL_RAW_LOG(FATAL, "thread releasing lock it does not hold: %p %s; " , - static_cast<void *>(mu), + static_cast<void*>(mu), mu_events == nullptr ? "" : mu_events->name); } } @@ -1273,7 +1285,7 @@ static void LockLeave(Mutex* mu, GraphId id, SynchLocksHeld *held_locks) { } // Call LockEnter() if in debug mode and deadlock detection is enabled. -static inline void DebugOnlyLockEnter(Mutex *mu) { +static inline void DebugOnlyLockEnter(Mutex* mu) { if (kDebugMode) { if (synch_deadlock_detection.load(std::memory_order_acquire) != OnDeadlockCycle::kIgnore) { @@ -1283,7 +1295,7 @@ static inline void DebugOnlyLockEnter(Mutex *mu) { } // Call LockEnter() if in debug mode and deadlock detection is enabled. -static inline void DebugOnlyLockEnter(Mutex *mu, GraphId id) { +static inline void DebugOnlyLockEnter(Mutex* mu, GraphId id) { if (kDebugMode) { if (synch_deadlock_detection.load(std::memory_order_acquire) != OnDeadlockCycle::kIgnore) { @@ -1293,7 +1305,7 @@ static inline void DebugOnlyLockEnter(Mutex *mu, GraphId id) { } // Call LockLeave() if in debug mode and deadlock detection is enabled. -static inline void DebugOnlyLockLeave(Mutex *mu) { +static inline void DebugOnlyLockLeave(Mutex* mu) { if (kDebugMode) { if (synch_deadlock_detection.load(std::memory_order_acquire) != OnDeadlockCycle::kIgnore) { @@ -1302,9 +1314,9 @@ static inline void DebugOnlyLockLeave(Mutex *mu) { } } -static char *StackString(void **pcs, int n, char *buf, int maxlen, +static char* StackString(void** pcs, int n, char* buf, int maxlen, bool symbolize) { - static const int kSymLen = 200; + static constexpr int kSymLen = 200; char sym[kSymLen]; int len = 0; for (int i = 0; i != n; i++) { @@ -1312,7 +1324,7 @@ static char *StackString(void **pcs, int n, char *buf, int maxlen, return buf; size_t count = static_cast<size_t>(maxlen - len); if (symbolize) { - if (!symbolizer(pcs[i], sym, kSymLen)) { + if (!absl::Symbolize(pcs[i], sym, kSymLen)) { sym[0] = '\0'; } snprintf(buf + len, count, "%s\t@ %p %s\n", (i == 0 ? "\n" : ""), pcs[i], @@ -1325,15 +1337,17 @@ static char *StackString(void **pcs, int n, char *buf, int maxlen, return buf; } -static char *CurrentStackString(char *buf, int maxlen, bool symbolize) { - void *pcs[40]; +static char* CurrentStackString(char* buf, int maxlen, bool symbolize) { + void* pcs[40]; return StackString(pcs, absl::GetStackTrace(pcs, ABSL_ARRAYSIZE(pcs), 2), buf, maxlen, symbolize); } namespace { -enum { kMaxDeadlockPathLen = 10 }; // maximum length of a deadlock cycle; - // a path this long would be remarkable +enum { + kMaxDeadlockPathLen = 10 +}; // maximum length of a deadlock cycle; + // a path this long would be remarkable // Buffers required to report a deadlock. // We do not allocate them on stack to avoid large stack frame. struct DeadlockReportBuffers { @@ -1343,11 +1357,11 @@ struct DeadlockReportBuffers { struct ScopedDeadlockReportBuffers { ScopedDeadlockReportBuffers() { - b = reinterpret_cast<DeadlockReportBuffers *>( + b = reinterpret_cast<DeadlockReportBuffers*>( base_internal::LowLevelAlloc::Alloc(sizeof(*b))); } ~ScopedDeadlockReportBuffers() { base_internal::LowLevelAlloc::Free(b); } - DeadlockReportBuffers *b; + DeadlockReportBuffers* b; }; // Helper to pass to GraphCycles::UpdateStackTrace. @@ -1358,13 +1372,13 @@ int GetStack(void** stack, int max_depth) { // Called in debug mode when a thread is about to acquire a lock in a way that // may block. -static GraphId DeadlockCheck(Mutex *mu) { +static GraphId DeadlockCheck(Mutex* mu) { if (synch_deadlock_detection.load(std::memory_order_acquire) == OnDeadlockCycle::kIgnore) { return InvalidGraphId(); } - SynchLocksHeld *all_locks = Synch_GetAllLocks(); + SynchLocksHeld* all_locks = Synch_GetAllLocks(); absl::base_internal::SpinLockHolder lock(&deadlock_graph_mu); const GraphId mu_id = GetGraphIdLocked(mu); @@ -1386,8 +1400,8 @@ static GraphId DeadlockCheck(Mutex *mu) { // For each other mutex already held by this thread: for (int i = 0; i != all_locks->n; i++) { const GraphId other_node_id = all_locks->locks[i].id; - const Mutex *other = - static_cast<const Mutex *>(deadlock_graph->Ptr(other_node_id)); + const Mutex* other = + static_cast<const Mutex*>(deadlock_graph->Ptr(other_node_id)); if (other == nullptr) { // Ignore stale lock continue; @@ -1396,7 +1410,7 @@ static GraphId DeadlockCheck(Mutex *mu) { // Add the acquired-before edge to the graph. if (!deadlock_graph->InsertEdge(other_node_id, mu_id)) { ScopedDeadlockReportBuffers scoped_buffers; - DeadlockReportBuffers *b = scoped_buffers.b; + DeadlockReportBuffers* b = scoped_buffers.b; static int number_of_reported_deadlocks = 0; number_of_reported_deadlocks++; // Symbolize only 2 first deadlock report to avoid huge slowdowns. @@ -1407,37 +1421,40 @@ static GraphId DeadlockCheck(Mutex *mu) { for (int j = 0; j != all_locks->n; j++) { void* pr = deadlock_graph->Ptr(all_locks->locks[j].id); if (pr != nullptr) { - snprintf(b->buf + len, sizeof (b->buf) - len, " %p", pr); + snprintf(b->buf + len, sizeof(b->buf) - len, " %p", pr); len += strlen(&b->buf[len]); } } ABSL_RAW_LOG(ERROR, "Acquiring absl::Mutex %p while holding %s; a cycle in the " "historical lock ordering graph has been observed", - static_cast<void *>(mu), b->buf); + static_cast<void*>(mu), b->buf); ABSL_RAW_LOG(ERROR, "Cycle: "); - int path_len = deadlock_graph->FindPath( - mu_id, other_node_id, ABSL_ARRAYSIZE(b->path), b->path); - for (int j = 0; j != path_len; j++) { + int path_len = deadlock_graph->FindPath(mu_id, other_node_id, + ABSL_ARRAYSIZE(b->path), b->path); + for (int j = 0; j != path_len && j != ABSL_ARRAYSIZE(b->path); j++) { GraphId id = b->path[j]; - Mutex *path_mu = static_cast<Mutex *>(deadlock_graph->Ptr(id)); + Mutex* path_mu = static_cast<Mutex*>(deadlock_graph->Ptr(id)); if (path_mu == nullptr) continue; void** stack; int depth = deadlock_graph->GetStackTrace(id, &stack); snprintf(b->buf, sizeof(b->buf), - "mutex@%p stack: ", static_cast<void *>(path_mu)); + "mutex@%p stack: ", static_cast<void*>(path_mu)); StackString(stack, depth, b->buf + strlen(b->buf), static_cast<int>(sizeof(b->buf) - strlen(b->buf)), symbolize); ABSL_RAW_LOG(ERROR, "%s", b->buf); } + if (path_len > static_cast<int>(ABSL_ARRAYSIZE(b->path))) { + ABSL_RAW_LOG(ERROR, "(long cycle; list truncated)"); + } if (synch_deadlock_detection.load(std::memory_order_acquire) == OnDeadlockCycle::kAbort) { deadlock_graph_mu.Unlock(); // avoid deadlock in fatal sighandler ABSL_RAW_LOG(FATAL, "dying due to potential deadlock"); return mu_id; } - break; // report at most one potential deadlock per acquisition + break; // report at most one potential deadlock per acquisition } } @@ -1446,7 +1463,7 @@ static GraphId DeadlockCheck(Mutex *mu) { // Invoke DeadlockCheck() iff we're in debug mode and // deadlock checking has been enabled. -static inline GraphId DebugOnlyDeadlockCheck(Mutex *mu) { +static inline GraphId DebugOnlyDeadlockCheck(Mutex* mu) { if (kDebugMode && synch_deadlock_detection.load(std::memory_order_acquire) != OnDeadlockCycle::kIgnore) { return DeadlockCheck(mu); @@ -1473,13 +1490,13 @@ void Mutex::AssertNotHeld() const { (mu_.load(std::memory_order_relaxed) & (kMuWriter | kMuReader)) != 0 && synch_deadlock_detection.load(std::memory_order_acquire) != OnDeadlockCycle::kIgnore) { - GraphId id = GetGraphId(const_cast<Mutex *>(this)); - SynchLocksHeld *locks = Synch_GetAllLocks(); + GraphId id = GetGraphId(const_cast<Mutex*>(this)); + SynchLocksHeld* locks = Synch_GetAllLocks(); for (int i = 0; i != locks->n; i++) { if (locks->locks[i].id == id) { - SynchEvent *mu_events = GetSynchEvent(this); + SynchEvent* mu_events = GetSynchEvent(this); ABSL_RAW_LOG(FATAL, "thread should not hold mutex %p %s", - static_cast<const void *>(this), + static_cast<const void*>(this), (mu_events == nullptr ? "" : mu_events->name)); } } @@ -1489,11 +1506,11 @@ void Mutex::AssertNotHeld() const { // Attempt to acquire *mu, and return whether successful. The implementation // may spin for a short while if the lock cannot be acquired immediately. static bool TryAcquireWithSpinning(std::atomic<intptr_t>* mu) { - int c = GetMutexGlobals().spinloop_iterations; + int c = globals.spinloop_iterations.load(std::memory_order_relaxed); do { // do/while somewhat faster on AMD intptr_t v = mu->load(std::memory_order_relaxed); - if ((v & (kMuReader|kMuEvent)) != 0) { - return false; // a reader or tracing -> give up + if ((v & (kMuReader | kMuEvent)) != 0) { + return false; // a reader or tracing -> give up } else if (((v & kMuWriter) == 0) && // no holder -> try to acquire mu->compare_exchange_strong(v, kMuWriter | v, std::memory_order_acquire, @@ -1509,12 +1526,12 @@ void Mutex::Lock() { GraphId id = DebugOnlyDeadlockCheck(this); intptr_t v = mu_.load(std::memory_order_relaxed); // try fast acquire, then spin loop - if ((v & (kMuWriter | kMuReader | kMuEvent)) != 0 || - !mu_.compare_exchange_strong(v, kMuWriter | v, - std::memory_order_acquire, - std::memory_order_relaxed)) { + if (ABSL_PREDICT_FALSE((v & (kMuWriter | kMuReader | kMuEvent)) != 0) || + ABSL_PREDICT_FALSE(!mu_.compare_exchange_strong( + v, kMuWriter | v, std::memory_order_acquire, + std::memory_order_relaxed))) { // try spin acquire, then slow loop - if (!TryAcquireWithSpinning(&this->mu_)) { + if (ABSL_PREDICT_FALSE(!TryAcquireWithSpinning(&this->mu_))) { this->LockSlow(kExclusive, nullptr, 0); } } @@ -1526,139 +1543,95 @@ void Mutex::ReaderLock() { ABSL_TSAN_MUTEX_PRE_LOCK(this, __tsan_mutex_read_lock); GraphId id = DebugOnlyDeadlockCheck(this); intptr_t v = mu_.load(std::memory_order_relaxed); - // try fast acquire, then slow loop - if ((v & (kMuWriter | kMuWait | kMuEvent)) != 0 || - !mu_.compare_exchange_strong(v, (kMuReader | v) + kMuOne, - std::memory_order_acquire, - std::memory_order_relaxed)) { - this->LockSlow(kShared, nullptr, 0); + for (;;) { + // If there are non-readers holding the lock, use the slow loop. + if (ABSL_PREDICT_FALSE(v & (kMuWriter | kMuWait | kMuEvent)) != 0) { + this->LockSlow(kShared, nullptr, 0); + break; + } + // We can avoid the loop and only use the CAS when the lock is free or + // only held by readers. + if (ABSL_PREDICT_TRUE(mu_.compare_exchange_weak( + v, (kMuReader | v) + kMuOne, std::memory_order_acquire, + std::memory_order_relaxed))) { + break; + } } DebugOnlyLockEnter(this, id); ABSL_TSAN_MUTEX_POST_LOCK(this, __tsan_mutex_read_lock, 0); } -void Mutex::LockWhen(const Condition &cond) { - ABSL_TSAN_MUTEX_PRE_LOCK(this, 0); - GraphId id = DebugOnlyDeadlockCheck(this); - this->LockSlow(kExclusive, &cond, 0); - DebugOnlyLockEnter(this, id); - ABSL_TSAN_MUTEX_POST_LOCK(this, 0, 0); -} - -bool Mutex::LockWhenWithTimeout(const Condition &cond, absl::Duration timeout) { - return LockWhenWithDeadline(cond, DeadlineFromTimeout(timeout)); -} - -bool Mutex::LockWhenWithDeadline(const Condition &cond, absl::Time deadline) { - ABSL_TSAN_MUTEX_PRE_LOCK(this, 0); - GraphId id = DebugOnlyDeadlockCheck(this); - bool res = LockSlowWithDeadline(kExclusive, &cond, - KernelTimeout(deadline), 0); - DebugOnlyLockEnter(this, id); - ABSL_TSAN_MUTEX_POST_LOCK(this, 0, 0); - return res; -} - -void Mutex::ReaderLockWhen(const Condition &cond) { - ABSL_TSAN_MUTEX_PRE_LOCK(this, __tsan_mutex_read_lock); - GraphId id = DebugOnlyDeadlockCheck(this); - this->LockSlow(kShared, &cond, 0); - DebugOnlyLockEnter(this, id); - ABSL_TSAN_MUTEX_POST_LOCK(this, __tsan_mutex_read_lock, 0); -} - -bool Mutex::ReaderLockWhenWithTimeout(const Condition &cond, - absl::Duration timeout) { - return ReaderLockWhenWithDeadline(cond, DeadlineFromTimeout(timeout)); -} - -bool Mutex::ReaderLockWhenWithDeadline(const Condition &cond, - absl::Time deadline) { - ABSL_TSAN_MUTEX_PRE_LOCK(this, __tsan_mutex_read_lock); +bool Mutex::LockWhenCommon(const Condition& cond, + synchronization_internal::KernelTimeout t, + bool write) { + MuHow how = write ? kExclusive : kShared; + ABSL_TSAN_MUTEX_PRE_LOCK(this, TsanFlags(how)); GraphId id = DebugOnlyDeadlockCheck(this); - bool res = LockSlowWithDeadline(kShared, &cond, KernelTimeout(deadline), 0); + bool res = LockSlowWithDeadline(how, &cond, t, 0); DebugOnlyLockEnter(this, id); - ABSL_TSAN_MUTEX_POST_LOCK(this, __tsan_mutex_read_lock, 0); + ABSL_TSAN_MUTEX_POST_LOCK(this, TsanFlags(how), 0); return res; } -void Mutex::Await(const Condition &cond) { - if (cond.Eval()) { // condition already true; nothing to do - if (kDebugMode) { - this->AssertReaderHeld(); - } - } else { // normal case - ABSL_RAW_CHECK(this->AwaitCommon(cond, KernelTimeout::Never()), - "condition untrue on return from Await"); +bool Mutex::AwaitCommon(const Condition& cond, KernelTimeout t) { + if (kDebugMode) { + this->AssertReaderHeld(); } -} - -bool Mutex::AwaitWithTimeout(const Condition &cond, absl::Duration timeout) { - return AwaitWithDeadline(cond, DeadlineFromTimeout(timeout)); -} - -bool Mutex::AwaitWithDeadline(const Condition &cond, absl::Time deadline) { - if (cond.Eval()) { // condition already true; nothing to do - if (kDebugMode) { - this->AssertReaderHeld(); - } + if (cond.Eval()) { // condition already true; nothing to do return true; } - - KernelTimeout t{deadline}; - bool res = this->AwaitCommon(cond, t); - ABSL_RAW_CHECK(res || t.has_timeout(), - "condition untrue on return from Await"); - return res; -} - -bool Mutex::AwaitCommon(const Condition &cond, KernelTimeout t) { - this->AssertReaderHeld(); MuHow how = (mu_.load(std::memory_order_relaxed) & kMuWriter) ? kExclusive : kShared; ABSL_TSAN_MUTEX_PRE_UNLOCK(this, TsanFlags(how)); - SynchWaitParams waitp( - how, &cond, t, nullptr /*no cvmu*/, Synch_GetPerThreadAnnotated(this), - nullptr /*no cv_word*/); - int flags = kMuHasBlocked; - if (!Condition::GuaranteedEqual(&cond, nullptr)) { - flags |= kMuIsCond; - } + SynchWaitParams waitp(how, &cond, t, nullptr /*no cvmu*/, + Synch_GetPerThreadAnnotated(this), + nullptr /*no cv_word*/); this->UnlockSlow(&waitp); this->Block(waitp.thread); ABSL_TSAN_MUTEX_POST_UNLOCK(this, TsanFlags(how)); ABSL_TSAN_MUTEX_PRE_LOCK(this, TsanFlags(how)); - this->LockSlowLoop(&waitp, flags); + this->LockSlowLoop(&waitp, kMuHasBlocked | kMuIsCond); bool res = waitp.cond != nullptr || // => cond known true from LockSlowLoop EvalConditionAnnotated(&cond, this, true, false, how == kShared); ABSL_TSAN_MUTEX_POST_LOCK(this, TsanFlags(how), 0); + ABSL_RAW_CHECK(res || t.has_timeout(), + "condition untrue on return from Await"); return res; } bool Mutex::TryLock() { ABSL_TSAN_MUTEX_PRE_LOCK(this, __tsan_mutex_try_lock); intptr_t v = mu_.load(std::memory_order_relaxed); - if ((v & (kMuWriter | kMuReader | kMuEvent)) == 0 && // try fast acquire - mu_.compare_exchange_strong(v, kMuWriter | v, - std::memory_order_acquire, - std::memory_order_relaxed)) { - DebugOnlyLockEnter(this); - ABSL_TSAN_MUTEX_POST_LOCK(this, __tsan_mutex_try_lock, 0); - return true; - } - if ((v & kMuEvent) != 0) { // we're recording events - if ((v & kExclusive->slow_need_zero) == 0 && // try fast acquire - mu_.compare_exchange_strong( - v, (kExclusive->fast_or | v) + kExclusive->fast_add, - std::memory_order_acquire, std::memory_order_relaxed)) { + // Try fast acquire. + if (ABSL_PREDICT_TRUE((v & (kMuWriter | kMuReader | kMuEvent)) == 0)) { + if (ABSL_PREDICT_TRUE(mu_.compare_exchange_strong( + v, kMuWriter | v, std::memory_order_acquire, + std::memory_order_relaxed))) { DebugOnlyLockEnter(this); - PostSynchEvent(this, SYNCH_EV_TRYLOCK_SUCCESS); ABSL_TSAN_MUTEX_POST_LOCK(this, __tsan_mutex_try_lock, 0); return true; - } else { - PostSynchEvent(this, SYNCH_EV_TRYLOCK_FAILED); } + } else if (ABSL_PREDICT_FALSE((v & kMuEvent) != 0)) { + // We're recording events. + return TryLockSlow(); + } + ABSL_TSAN_MUTEX_POST_LOCK( + this, __tsan_mutex_try_lock | __tsan_mutex_try_lock_failed, 0); + return false; +} + +ABSL_ATTRIBUTE_NOINLINE bool Mutex::TryLockSlow() { + intptr_t v = mu_.load(std::memory_order_relaxed); + if ((v & kExclusive->slow_need_zero) == 0 && // try fast acquire + mu_.compare_exchange_strong( + v, (kExclusive->fast_or | v) + kExclusive->fast_add, + std::memory_order_acquire, std::memory_order_relaxed)) { + DebugOnlyLockEnter(this); + PostSynchEvent(this, SYNCH_EV_TRYLOCK_SUCCESS); + ABSL_TSAN_MUTEX_POST_LOCK(this, __tsan_mutex_try_lock, 0); + return true; } + PostSynchEvent(this, SYNCH_EV_TRYLOCK_FAILED); ABSL_TSAN_MUTEX_POST_LOCK( this, __tsan_mutex_try_lock | __tsan_mutex_try_lock_failed, 0); return false; @@ -1668,41 +1641,57 @@ bool Mutex::ReaderTryLock() { ABSL_TSAN_MUTEX_PRE_LOCK(this, __tsan_mutex_read_lock | __tsan_mutex_try_lock); intptr_t v = mu_.load(std::memory_order_relaxed); + // Clang tends to unroll the loop when compiling with optimization. + // But in this case it just unnecessary increases code size. + // If CAS is failing due to contention, the jump cost is negligible. +#if defined(__clang__) +#pragma nounroll +#endif // The while-loops (here and below) iterate only if the mutex word keeps - // changing (typically because the reader count changes) under the CAS. We - // limit the number of attempts to avoid having to think about livelock. - int loop_limit = 5; - while ((v & (kMuWriter|kMuWait|kMuEvent)) == 0 && loop_limit != 0) { - if (mu_.compare_exchange_strong(v, (kMuReader | v) + kMuOne, - std::memory_order_acquire, - std::memory_order_relaxed)) { + // changing (typically because the reader count changes) under the CAS. + // We limit the number of attempts to avoid having to think about livelock. + for (int loop_limit = 5; loop_limit != 0; loop_limit--) { + if (ABSL_PREDICT_FALSE((v & (kMuWriter | kMuWait | kMuEvent)) != 0)) { + break; + } + if (ABSL_PREDICT_TRUE(mu_.compare_exchange_strong( + v, (kMuReader | v) + kMuOne, std::memory_order_acquire, + std::memory_order_relaxed))) { DebugOnlyLockEnter(this); ABSL_TSAN_MUTEX_POST_LOCK( this, __tsan_mutex_read_lock | __tsan_mutex_try_lock, 0); return true; } - loop_limit--; - v = mu_.load(std::memory_order_relaxed); } - if ((v & kMuEvent) != 0) { // we're recording events - loop_limit = 5; - while ((v & kShared->slow_need_zero) == 0 && loop_limit != 0) { - if (mu_.compare_exchange_strong(v, (kMuReader | v) + kMuOne, - std::memory_order_acquire, - std::memory_order_relaxed)) { - DebugOnlyLockEnter(this); - PostSynchEvent(this, SYNCH_EV_READERTRYLOCK_SUCCESS); - ABSL_TSAN_MUTEX_POST_LOCK( - this, __tsan_mutex_read_lock | __tsan_mutex_try_lock, 0); - return true; - } - loop_limit--; - v = mu_.load(std::memory_order_relaxed); - } - if ((v & kMuEvent) != 0) { - PostSynchEvent(this, SYNCH_EV_READERTRYLOCK_FAILED); + if (ABSL_PREDICT_TRUE((v & kMuEvent) == 0)) { + ABSL_TSAN_MUTEX_POST_LOCK(this, + __tsan_mutex_read_lock | __tsan_mutex_try_lock | + __tsan_mutex_try_lock_failed, + 0); + return false; + } + // we're recording events + return ReaderTryLockSlow(); +} + +ABSL_ATTRIBUTE_NOINLINE bool Mutex::ReaderTryLockSlow() { + intptr_t v = mu_.load(std::memory_order_relaxed); +#if defined(__clang__) +#pragma nounroll +#endif + for (int loop_limit = 5; loop_limit != 0; loop_limit--) { + if ((v & kShared->slow_need_zero) == 0 && + mu_.compare_exchange_strong(v, (kMuReader | v) + kMuOne, + std::memory_order_acquire, + std::memory_order_relaxed)) { + DebugOnlyLockEnter(this); + PostSynchEvent(this, SYNCH_EV_READERTRYLOCK_SUCCESS); + ABSL_TSAN_MUTEX_POST_LOCK( + this, __tsan_mutex_read_lock | __tsan_mutex_try_lock, 0); + return true; } } + PostSynchEvent(this, SYNCH_EV_READERTRYLOCK_FAILED); ABSL_TSAN_MUTEX_POST_LOCK(this, __tsan_mutex_read_lock | __tsan_mutex_try_lock | __tsan_mutex_try_lock_failed, @@ -1723,7 +1712,7 @@ void Mutex::Unlock() { // should_try_cas is whether we'll try a compare-and-swap immediately. // NOTE: optimized out when kDebugMode is false. bool should_try_cas = ((v & (kMuEvent | kMuWriter)) == kMuWriter && - (v & (kMuWait | kMuDesig)) != kMuWait); + (v & (kMuWait | kMuDesig)) != kMuWait); // But, we can use an alternate computation of it, that compilers // currently don't find on their own. When that changes, this function // can be simplified. @@ -1740,10 +1729,9 @@ void Mutex::Unlock() { static_cast<long long>(v), static_cast<long long>(x), static_cast<long long>(y)); } - if (x < y && - mu_.compare_exchange_strong(v, v & ~(kMuWrWait | kMuWriter), - std::memory_order_release, - std::memory_order_relaxed)) { + if (x < y && mu_.compare_exchange_strong(v, v & ~(kMuWrWait | kMuWriter), + std::memory_order_release, + std::memory_order_relaxed)) { // fast writer release (writer with no waiters or with designated waker) } else { this->UnlockSlow(nullptr /*no waitp*/); // take slow path @@ -1753,7 +1741,7 @@ void Mutex::Unlock() { // Requires v to represent a reader-locked state. static bool ExactlyOneReader(intptr_t v) { - assert((v & (kMuWriter|kMuReader)) == kMuReader); + assert((v & (kMuWriter | kMuReader)) == kMuReader); assert((v & kMuHigh) != 0); // The more straightforward "(v & kMuHigh) == kMuOne" also works, but // on some architectures the following generates slightly smaller code. @@ -1766,18 +1754,21 @@ void Mutex::ReaderUnlock() { ABSL_TSAN_MUTEX_PRE_UNLOCK(this, __tsan_mutex_read_lock); DebugOnlyLockLeave(this); intptr_t v = mu_.load(std::memory_order_relaxed); - assert((v & (kMuWriter|kMuReader)) == kMuReader); - if ((v & (kMuReader|kMuWait|kMuEvent)) == kMuReader) { + assert((v & (kMuWriter | kMuReader)) == kMuReader); + for (;;) { + if (ABSL_PREDICT_FALSE((v & (kMuReader | kMuWait | kMuEvent)) != + kMuReader)) { + this->UnlockSlow(nullptr /*no waitp*/); // take slow path + break; + } // fast reader release (reader with no waiters) - intptr_t clear = ExactlyOneReader(v) ? kMuReader|kMuOne : kMuOne; - if (mu_.compare_exchange_strong(v, v - clear, - std::memory_order_release, - std::memory_order_relaxed)) { - ABSL_TSAN_MUTEX_POST_UNLOCK(this, __tsan_mutex_read_lock); - return; + intptr_t clear = ExactlyOneReader(v) ? kMuReader | kMuOne : kMuOne; + if (ABSL_PREDICT_TRUE( + mu_.compare_exchange_strong(v, v - clear, std::memory_order_release, + std::memory_order_relaxed))) { + break; } } - this->UnlockSlow(nullptr /*no waitp*/); // take slow path ABSL_TSAN_MUTEX_POST_UNLOCK(this, __tsan_mutex_read_lock); } @@ -1810,15 +1801,31 @@ static intptr_t IgnoreWaitingWritersMask(int flag) { } // Internal version of LockWhen(). See LockSlowWithDeadline() -ABSL_ATTRIBUTE_NOINLINE void Mutex::LockSlow(MuHow how, const Condition *cond, +ABSL_ATTRIBUTE_NOINLINE void Mutex::LockSlow(MuHow how, const Condition* cond, int flags) { + // Note: we specifically initialize spinloop_iterations after the first use + // in TryAcquireWithSpinning so that Lock function does not have any non-tail + // calls and consequently a stack frame. It's fine to have spinloop_iterations + // uninitialized (meaning no spinning) in all initial uncontended Lock calls + // and in the first contended call. After that we will have + // spinloop_iterations properly initialized. + if (ABSL_PREDICT_FALSE( + globals.spinloop_iterations.load(std::memory_order_relaxed) == 0)) { + if (absl::base_internal::NumCPUs() > 1) { + // If this is multiprocessor, allow spinning. + globals.spinloop_iterations.store(1500, std::memory_order_relaxed); + } else { + // If this a uniprocessor, only yield/sleep. + globals.spinloop_iterations.store(-1, std::memory_order_relaxed); + } + } ABSL_RAW_CHECK( this->LockSlowWithDeadline(how, cond, KernelTimeout::Never(), flags), "condition untrue on return from LockSlow"); } // Compute cond->Eval() and tell race detectors that we do it under mutex mu. -static inline bool EvalConditionAnnotated(const Condition *cond, Mutex *mu, +static inline bool EvalConditionAnnotated(const Condition* cond, Mutex* mu, bool locking, bool trylock, bool read_lock) { // Delicate annotation dance. @@ -1868,7 +1875,7 @@ static inline bool EvalConditionAnnotated(const Condition *cond, Mutex *mu, // tsan). As the result there is no tsan-visible synchronization between the // addition and this thread. So if we would enable race detection here, // it would race with the predicate initialization. -static inline bool EvalConditionIgnored(Mutex *mu, const Condition *cond) { +static inline bool EvalConditionIgnored(Mutex* mu, const Condition* cond) { // Memory accesses are already ignored inside of lock/unlock operations, // but synchronization operations are also ignored. When we evaluate the // predicate we must ignore only memory accesses but not synchronization, @@ -1893,7 +1900,7 @@ static inline bool EvalConditionIgnored(Mutex *mu, const Condition *cond) { // obstruct this call // - kMuIsCond indicates that this is a conditional acquire (condition variable, // Await, LockWhen) so contention profiling should be suppressed. -bool Mutex::LockSlowWithDeadline(MuHow how, const Condition *cond, +bool Mutex::LockSlowWithDeadline(MuHow how, const Condition* cond, KernelTimeout t, int flags) { intptr_t v = mu_.load(std::memory_order_relaxed); bool unlock = false; @@ -1910,10 +1917,10 @@ bool Mutex::LockSlowWithDeadline(MuHow how, const Condition *cond, } unlock = true; } - SynchWaitParams waitp( - how, cond, t, nullptr /*no cvmu*/, Synch_GetPerThreadAnnotated(this), - nullptr /*no cv_word*/); - if (!Condition::GuaranteedEqual(cond, nullptr)) { + SynchWaitParams waitp(how, cond, t, nullptr /*no cvmu*/, + Synch_GetPerThreadAnnotated(this), + nullptr /*no cv_word*/); + if (cond != nullptr) { flags |= kMuIsCond; } if (unlock) { @@ -1953,20 +1960,20 @@ static void CheckForMutexCorruption(intptr_t v, const char* label) { if (ABSL_PREDICT_TRUE((w & (w << 3) & (kMuWriter | kMuWrWait)) == 0)) return; RAW_CHECK_FMT((v & (kMuWriter | kMuReader)) != (kMuWriter | kMuReader), "%s: Mutex corrupt: both reader and writer lock held: %p", - label, reinterpret_cast<void *>(v)); + label, reinterpret_cast<void*>(v)); RAW_CHECK_FMT((v & (kMuWait | kMuWrWait)) != kMuWrWait, - "%s: Mutex corrupt: waiting writer with no waiters: %p", - label, reinterpret_cast<void *>(v)); + "%s: Mutex corrupt: waiting writer with no waiters: %p", label, + reinterpret_cast<void*>(v)); assert(false); } -void Mutex::LockSlowLoop(SynchWaitParams *waitp, int flags) { +void Mutex::LockSlowLoop(SynchWaitParams* waitp, int flags) { SchedulingGuard::ScopedDisable disable_rescheduling; int c = 0; intptr_t v = mu_.load(std::memory_order_relaxed); if ((v & kMuEvent) != 0) { - PostSynchEvent(this, - waitp->how == kExclusive? SYNCH_EV_LOCK: SYNCH_EV_READERLOCK); + PostSynchEvent( + this, waitp->how == kExclusive ? SYNCH_EV_LOCK : SYNCH_EV_READERLOCK); } ABSL_RAW_CHECK( waitp->thread->waitp == nullptr || waitp->thread->suppress_fatal_errors, @@ -1991,11 +1998,11 @@ void Mutex::LockSlowLoop(SynchWaitParams *waitp, int flags) { flags |= kMuHasBlocked; c = 0; } - } else { // need to access waiter list + } else { // need to access waiter list bool dowait = false; - if ((v & (kMuSpin|kMuWait)) == 0) { // no waiters + if ((v & (kMuSpin | kMuWait)) == 0) { // no waiters // This thread tries to become the one and only waiter. - PerThreadSynch *new_h = Enqueue(nullptr, waitp, v, flags); + PerThreadSynch* new_h = Enqueue(nullptr, waitp, v, flags); intptr_t nv = (v & ClearDesignatedWakerMask(flags & kMuHasBlocked) & kMuLow) | kMuWait; @@ -2007,7 +2014,7 @@ void Mutex::LockSlowLoop(SynchWaitParams *waitp, int flags) { v, reinterpret_cast<intptr_t>(new_h) | nv, std::memory_order_release, std::memory_order_relaxed)) { dowait = true; - } else { // attempted Enqueue() failed + } else { // attempted Enqueue() failed // zero out the waitp field set by Enqueue() waitp->thread->waitp = nullptr; } @@ -2020,9 +2027,9 @@ void Mutex::LockSlowLoop(SynchWaitParams *waitp, int flags) { (v & ClearDesignatedWakerMask(flags & kMuHasBlocked)) | kMuSpin | kMuReader, std::memory_order_acquire, std::memory_order_relaxed)) { - PerThreadSynch *h = GetPerThreadSynch(v); - h->readers += kMuOne; // inc reader count in waiter - do { // release spinlock + PerThreadSynch* h = GetPerThreadSynch(v); + h->readers += kMuOne; // inc reader count in waiter + do { // release spinlock v = mu_.load(std::memory_order_relaxed); } while (!mu_.compare_exchange_weak(v, (v & ~kMuSpin) | kMuReader, std::memory_order_release, @@ -2032,7 +2039,7 @@ void Mutex::LockSlowLoop(SynchWaitParams *waitp, int flags) { waitp->how == kShared)) { break; // we timed out, or condition true, so return } - this->UnlockSlow(waitp); // got lock but condition false + this->UnlockSlow(waitp); // got lock but condition false this->Block(waitp->thread); flags |= kMuHasBlocked; c = 0; @@ -2043,18 +2050,19 @@ void Mutex::LockSlowLoop(SynchWaitParams *waitp, int flags) { (v & ClearDesignatedWakerMask(flags & kMuHasBlocked)) | kMuSpin | kMuWait, std::memory_order_acquire, std::memory_order_relaxed)) { - PerThreadSynch *h = GetPerThreadSynch(v); - PerThreadSynch *new_h = Enqueue(h, waitp, v, flags); + PerThreadSynch* h = GetPerThreadSynch(v); + PerThreadSynch* new_h = Enqueue(h, waitp, v, flags); intptr_t wr_wait = 0; ABSL_RAW_CHECK(new_h != nullptr, "Enqueue to list failed"); if (waitp->how == kExclusive && (v & kMuReader) != 0) { - wr_wait = kMuWrWait; // give priority to a waiting writer + wr_wait = kMuWrWait; // give priority to a waiting writer } - do { // release spinlock + do { // release spinlock v = mu_.load(std::memory_order_relaxed); } while (!mu_.compare_exchange_weak( - v, (v & (kMuLow & ~kMuSpin)) | kMuWait | wr_wait | - reinterpret_cast<intptr_t>(new_h), + v, + (v & (kMuLow & ~kMuSpin)) | kMuWait | wr_wait | + reinterpret_cast<intptr_t>(new_h), std::memory_order_release, std::memory_order_relaxed)); dowait = true; } @@ -2074,9 +2082,9 @@ void Mutex::LockSlowLoop(SynchWaitParams *waitp, int flags) { waitp->thread->waitp == nullptr || waitp->thread->suppress_fatal_errors, "detected illegal recursion into Mutex code"); if ((v & kMuEvent) != 0) { - PostSynchEvent(this, - waitp->how == kExclusive? SYNCH_EV_LOCK_RETURNING : - SYNCH_EV_READERLOCK_RETURNING); + PostSynchEvent(this, waitp->how == kExclusive + ? SYNCH_EV_LOCK_RETURNING + : SYNCH_EV_READERLOCK_RETURNING); } } @@ -2085,28 +2093,27 @@ void Mutex::LockSlowLoop(SynchWaitParams *waitp, int flags) { // which holds the lock but is not runnable because its condition is false // or it is in the process of blocking on a condition variable; it must requeue // itself on the mutex/condvar to wait for its condition to become true. -ABSL_ATTRIBUTE_NOINLINE void Mutex::UnlockSlow(SynchWaitParams *waitp) { +ABSL_ATTRIBUTE_NOINLINE void Mutex::UnlockSlow(SynchWaitParams* waitp) { SchedulingGuard::ScopedDisable disable_rescheduling; intptr_t v = mu_.load(std::memory_order_relaxed); this->AssertReaderHeld(); CheckForMutexCorruption(v, "Unlock"); if ((v & kMuEvent) != 0) { - PostSynchEvent(this, - (v & kMuWriter) != 0? SYNCH_EV_UNLOCK: SYNCH_EV_READERUNLOCK); + PostSynchEvent( + this, (v & kMuWriter) != 0 ? SYNCH_EV_UNLOCK : SYNCH_EV_READERUNLOCK); } int c = 0; // the waiter under consideration to wake, or zero - PerThreadSynch *w = nullptr; + PerThreadSynch* w = nullptr; // the predecessor to w or zero - PerThreadSynch *pw = nullptr; + PerThreadSynch* pw = nullptr; // head of the list searched previously, or zero - PerThreadSynch *old_h = nullptr; + PerThreadSynch* old_h = nullptr; // a condition that's known to be false. - const Condition *known_false = nullptr; - PerThreadSynch *wake_list = kPerThreadSynchNull; // list of threads to wake - intptr_t wr_wait = 0; // set to kMuWrWait if we wake a reader and a - // later writer could have acquired the lock - // (starvation avoidance) + PerThreadSynch* wake_list = kPerThreadSynchNull; // list of threads to wake + intptr_t wr_wait = 0; // set to kMuWrWait if we wake a reader and a + // later writer could have acquired the lock + // (starvation avoidance) ABSL_RAW_CHECK(waitp == nullptr || waitp->thread->waitp == nullptr || waitp->thread->suppress_fatal_errors, "detected illegal recursion into Mutex code"); @@ -2126,8 +2133,7 @@ ABSL_ATTRIBUTE_NOINLINE void Mutex::UnlockSlow(SynchWaitParams *waitp) { } else if ((v & (kMuReader | kMuWait)) == kMuReader && waitp == nullptr) { // fast reader release (reader with no waiters) intptr_t clear = ExactlyOneReader(v) ? kMuReader | kMuOne : kMuOne; - if (mu_.compare_exchange_strong(v, v - clear, - std::memory_order_release, + if (mu_.compare_exchange_strong(v, v - clear, std::memory_order_release, std::memory_order_relaxed)) { return; } @@ -2135,16 +2141,16 @@ ABSL_ATTRIBUTE_NOINLINE void Mutex::UnlockSlow(SynchWaitParams *waitp) { mu_.compare_exchange_strong(v, v | kMuSpin, std::memory_order_acquire, std::memory_order_relaxed)) { - if ((v & kMuWait) == 0) { // no one to wake + if ((v & kMuWait) == 0) { // no one to wake intptr_t nv; bool do_enqueue = true; // always Enqueue() the first time ABSL_RAW_CHECK(waitp != nullptr, "UnlockSlow is confused"); // about to sleep - do { // must loop to release spinlock as reader count may change + do { // must loop to release spinlock as reader count may change v = mu_.load(std::memory_order_relaxed); // decrement reader count if there are readers - intptr_t new_readers = (v >= kMuOne)? v - kMuOne : v; - PerThreadSynch *new_h = nullptr; + intptr_t new_readers = (v >= kMuOne) ? v - kMuOne : v; + PerThreadSynch* new_h = nullptr; if (do_enqueue) { // If we are enqueuing on a CondVar (waitp->cv_word != nullptr) then // we must not retry here. The initial attempt will always have @@ -2168,21 +2174,20 @@ ABSL_ATTRIBUTE_NOINLINE void Mutex::UnlockSlow(SynchWaitParams *waitp) { } // release spinlock & our lock; retry if reader-count changed // (writer count cannot change since we hold lock) - } while (!mu_.compare_exchange_weak(v, nv, - std::memory_order_release, + } while (!mu_.compare_exchange_weak(v, nv, std::memory_order_release, std::memory_order_relaxed)); break; } // There are waiters. // Set h to the head of the circular waiter list. - PerThreadSynch *h = GetPerThreadSynch(v); + PerThreadSynch* h = GetPerThreadSynch(v); if ((v & kMuReader) != 0 && (h->readers & kMuHigh) > kMuOne) { // a reader but not the last - h->readers -= kMuOne; // release our lock - intptr_t nv = v; // normally just release spinlock + h->readers -= kMuOne; // release our lock + intptr_t nv = v; // normally just release spinlock if (waitp != nullptr) { // but waitp!=nullptr => must queue ourselves - PerThreadSynch *new_h = Enqueue(h, waitp, v, kMuIsCond); + PerThreadSynch* new_h = Enqueue(h, waitp, v, kMuIsCond); ABSL_RAW_CHECK(new_h != nullptr, "waiters disappeared during Enqueue()!"); nv &= kMuLow; @@ -2200,17 +2205,17 @@ ABSL_ATTRIBUTE_NOINLINE void Mutex::UnlockSlow(SynchWaitParams *waitp) { // The lock is becoming free, and there's a waiter if (old_h != nullptr && - !old_h->may_skip) { // we used old_h as a terminator - old_h->may_skip = true; // allow old_h to skip once more + !old_h->may_skip) { // we used old_h as a terminator + old_h->may_skip = true; // allow old_h to skip once more ABSL_RAW_CHECK(old_h->skip == nullptr, "illegal skip from head"); if (h != old_h && MuEquivalentWaiter(old_h, old_h->next)) { old_h->skip = old_h->next; // old_h not head & can skip to successor } } if (h->next->waitp->how == kExclusive && - Condition::GuaranteedEqual(h->next->waitp->cond, nullptr)) { + h->next->waitp->cond == nullptr) { // easy case: writer with no condition; no need to search - pw = h; // wake w, the successor of h (=pw) + pw = h; // wake w, the successor of h (=pw) w = h->next; w->wake = true; // We are waking up a writer. This writer may be racing against @@ -2233,13 +2238,13 @@ ABSL_ATTRIBUTE_NOINLINE void Mutex::UnlockSlow(SynchWaitParams *waitp) { // waiter has a condition or is a reader. We avoid searching over // waiters we've searched on previous iterations by starting at // old_h if it's set. If old_h==h, there's no one to wakeup at all. - if (old_h == h) { // we've searched before, and nothing's new - // so there's no one to wake. - intptr_t nv = (v & ~(kMuReader|kMuWriter|kMuWrWait)); + if (old_h == h) { // we've searched before, and nothing's new + // so there's no one to wake. + intptr_t nv = (v & ~(kMuReader | kMuWriter | kMuWrWait)); h->readers = 0; - h->maybe_unlocking = false; // finished unlocking - if (waitp != nullptr) { // we must queue ourselves and sleep - PerThreadSynch *new_h = Enqueue(h, waitp, v, kMuIsCond); + h->maybe_unlocking = false; // finished unlocking + if (waitp != nullptr) { // we must queue ourselves and sleep + PerThreadSynch* new_h = Enqueue(h, waitp, v, kMuIsCond); nv &= kMuLow; if (new_h != nullptr) { nv |= kMuWait | reinterpret_cast<intptr_t>(new_h); @@ -2253,12 +2258,12 @@ ABSL_ATTRIBUTE_NOINLINE void Mutex::UnlockSlow(SynchWaitParams *waitp) { } // set up to walk the list - PerThreadSynch *w_walk; // current waiter during list walk - PerThreadSynch *pw_walk; // previous waiter during list walk + PerThreadSynch* w_walk; // current waiter during list walk + PerThreadSynch* pw_walk; // previous waiter during list walk if (old_h != nullptr) { // we've searched up to old_h before pw_walk = old_h; w_walk = old_h->next; - } else { // no prior search, start at beginning + } else { // no prior search, start at beginning pw_walk = nullptr; // h->next's predecessor may change; don't record it w_walk = h->next; @@ -2284,36 +2289,32 @@ ABSL_ATTRIBUTE_NOINLINE void Mutex::UnlockSlow(SynchWaitParams *waitp) { // to walk the path from w_walk to h inclusive. (TryRemove() can remove // a waiter anywhere, but it acquires both the spinlock and the Mutex) - old_h = h; // remember we searched to here + old_h = h; // remember we searched to here // Walk the path upto and including h looking for waiters we can wake. while (pw_walk != h) { w_walk->wake = false; if (w_walk->waitp->cond == nullptr || // no condition => vacuously true OR - (w_walk->waitp->cond != known_false && - // this thread's condition is not known false, AND - // is in fact true - EvalConditionIgnored(this, w_walk->waitp->cond))) { + // this thread's condition is true + EvalConditionIgnored(this, w_walk->waitp->cond)) { if (w == nullptr) { - w_walk->wake = true; // can wake this waiter + w_walk->wake = true; // can wake this waiter w = w_walk; pw = pw_walk; if (w_walk->waitp->how == kExclusive) { wr_wait = kMuWrWait; - break; // bail if waking this writer + break; // bail if waking this writer } } else if (w_walk->waitp->how == kShared) { // wake if a reader w_walk->wake = true; - } else { // writer with true condition + } else { // writer with true condition wr_wait = kMuWrWait; } - } else { // can't wake; condition false - known_false = w_walk->waitp->cond; // remember last false condition } - if (w_walk->wake) { // we're waking reader w_walk - pw_walk = w_walk; // don't skip similar waiters - } else { // not waking; skip as much as possible + if (w_walk->wake) { // we're waking reader w_walk + pw_walk = w_walk; // don't skip similar waiters + } else { // not waking; skip as much as possible pw_walk = Skip(w_walk); } // If pw_walk == h, then load of pw_walk->next can race with @@ -2340,8 +2341,8 @@ ABSL_ATTRIBUTE_NOINLINE void Mutex::UnlockSlow(SynchWaitParams *waitp) { h = DequeueAllWakeable(h, pw, &wake_list); intptr_t nv = (v & kMuEvent) | kMuDesig; - // assume no waiters left, - // set kMuDesig for INV1a + // assume no waiters left, + // set kMuDesig for INV1a if (waitp != nullptr) { // we must queue ourselves and sleep h = Enqueue(h, waitp, v, kMuIsCond); @@ -2354,7 +2355,7 @@ ABSL_ATTRIBUTE_NOINLINE void Mutex::UnlockSlow(SynchWaitParams *waitp) { if (h != nullptr) { // there are waiters left h->readers = 0; - h->maybe_unlocking = false; // finished unlocking + h->maybe_unlocking = false; // finished unlocking nv |= wr_wait | kMuWait | reinterpret_cast<intptr_t>(h); } @@ -2365,12 +2366,12 @@ ABSL_ATTRIBUTE_NOINLINE void Mutex::UnlockSlow(SynchWaitParams *waitp) { } // aggressive here; no one can proceed till we do c = synchronization_internal::MutexDelay(c, AGGRESSIVE); - } // end of for(;;)-loop + } // end of for(;;)-loop if (wake_list != kPerThreadSynchNull) { int64_t total_wait_cycles = 0; int64_t max_wait_cycles = 0; - int64_t now = base_internal::CycleClock::Now(); + int64_t now = CycleClock::Now(); do { // Profile lock contention events only if the waiter was trying to acquire // the lock, not waiting on a condition variable or Condition. @@ -2382,7 +2383,7 @@ ABSL_ATTRIBUTE_NOINLINE void Mutex::UnlockSlow(SynchWaitParams *waitp) { wake_list->waitp->contention_start_cycles = now; wake_list->waitp->should_submit_contention_data = true; } - wake_list = Wakeup(wake_list); // wake waiters + wake_list = Wakeup(wake_list); // wake waiters } while (wake_list != kPerThreadSynchNull); if (total_wait_cycles > 0) { mutex_tracer("slow release", this, total_wait_cycles); @@ -2410,15 +2411,15 @@ void Mutex::Trans(MuHow how) { // condition variable. If this mutex is free, we simply wake the thread. // It will later acquire the mutex with high probability. Otherwise, we // enqueue thread w on this mutex. -void Mutex::Fer(PerThreadSynch *w) { +void Mutex::Fer(PerThreadSynch* w) { SchedulingGuard::ScopedDisable disable_rescheduling; int c = 0; ABSL_RAW_CHECK(w->waitp->cond == nullptr, "Mutex::Fer while waiting on Condition"); - ABSL_RAW_CHECK(!w->waitp->timeout.has_timeout(), - "Mutex::Fer while in timed wait"); ABSL_RAW_CHECK(w->waitp->cv_word == nullptr, "Mutex::Fer with pending CondVar queueing"); + // The CondVar timeout is not relevant for the Mutex wait. + w->waitp->timeout = {}; for (;;) { intptr_t v = mu_.load(std::memory_order_relaxed); // Note: must not queue if the mutex is unlocked (nobody will wake it). @@ -2435,9 +2436,10 @@ void Mutex::Fer(PerThreadSynch *w) { IncrementSynchSem(this, w); return; } else { - if ((v & (kMuSpin|kMuWait)) == 0) { // no waiters + if ((v & (kMuSpin | kMuWait)) == 0) { // no waiters // This thread tries to become the one and only waiter. - PerThreadSynch *new_h = Enqueue(nullptr, w->waitp, v, kMuIsCond); + PerThreadSynch* new_h = + Enqueue(nullptr, w->waitp, v, kMuIsCond | kMuIsFer); ABSL_RAW_CHECK(new_h != nullptr, "Enqueue failed"); // we must queue ourselves if (mu_.compare_exchange_strong( @@ -2447,8 +2449,8 @@ void Mutex::Fer(PerThreadSynch *w) { } } else if ((v & kMuSpin) == 0 && mu_.compare_exchange_strong(v, v | kMuSpin | kMuWait)) { - PerThreadSynch *h = GetPerThreadSynch(v); - PerThreadSynch *new_h = Enqueue(h, w->waitp, v, kMuIsCond); + PerThreadSynch* h = GetPerThreadSynch(v); + PerThreadSynch* new_h = Enqueue(h, w->waitp, v, kMuIsCond | kMuIsFer); ABSL_RAW_CHECK(new_h != nullptr, "Enqueue failed"); // we must queue ourselves do { @@ -2467,19 +2469,18 @@ void Mutex::Fer(PerThreadSynch *w) { void Mutex::AssertHeld() const { if ((mu_.load(std::memory_order_relaxed) & kMuWriter) == 0) { - SynchEvent *e = GetSynchEvent(this); + SynchEvent* e = GetSynchEvent(this); ABSL_RAW_LOG(FATAL, "thread should hold write lock on Mutex %p %s", - static_cast<const void *>(this), - (e == nullptr ? "" : e->name)); + static_cast<const void*>(this), (e == nullptr ? "" : e->name)); } } void Mutex::AssertReaderHeld() const { if ((mu_.load(std::memory_order_relaxed) & (kMuReader | kMuWriter)) == 0) { - SynchEvent *e = GetSynchEvent(this); - ABSL_RAW_LOG( - FATAL, "thread should hold at least a read lock on Mutex %p %s", - static_cast<const void *>(this), (e == nullptr ? "" : e->name)); + SynchEvent* e = GetSynchEvent(this); + ABSL_RAW_LOG(FATAL, + "thread should hold at least a read lock on Mutex %p %s", + static_cast<const void*>(this), (e == nullptr ? "" : e->name)); } } @@ -2490,42 +2491,38 @@ static const intptr_t kCvEvent = 0x0002L; // record events static const intptr_t kCvLow = 0x0003L; // low order bits of CV // Hack to make constant values available to gdb pretty printer -enum { kGdbCvSpin = kCvSpin, kGdbCvEvent = kCvEvent, kGdbCvLow = kCvLow, }; +enum { + kGdbCvSpin = kCvSpin, + kGdbCvEvent = kCvEvent, + kGdbCvLow = kCvLow, +}; static_assert(PerThreadSynch::kAlignment > kCvLow, "PerThreadSynch::kAlignment must be greater than kCvLow"); -void CondVar::EnableDebugLog(const char *name) { - SynchEvent *e = EnsureSynchEvent(&this->cv_, name, kCvEvent, kCvSpin); +void CondVar::EnableDebugLog(const char* name) { + SynchEvent* e = EnsureSynchEvent(&this->cv_, name, kCvEvent, kCvSpin); e->log = true; UnrefSynchEvent(e); } -CondVar::~CondVar() { - if ((cv_.load(std::memory_order_relaxed) & kCvEvent) != 0) { - ForgetSynchEvent(&this->cv_, kCvEvent, kCvSpin); - } -} - - // Remove thread s from the list of waiters on this condition variable. -void CondVar::Remove(PerThreadSynch *s) { +void CondVar::Remove(PerThreadSynch* s) { SchedulingGuard::ScopedDisable disable_rescheduling; intptr_t v; int c = 0; for (v = cv_.load(std::memory_order_relaxed);; v = cv_.load(std::memory_order_relaxed)) { if ((v & kCvSpin) == 0 && // attempt to acquire spinlock - cv_.compare_exchange_strong(v, v | kCvSpin, - std::memory_order_acquire, + cv_.compare_exchange_strong(v, v | kCvSpin, std::memory_order_acquire, std::memory_order_relaxed)) { - PerThreadSynch *h = reinterpret_cast<PerThreadSynch *>(v & ~kCvLow); + PerThreadSynch* h = reinterpret_cast<PerThreadSynch*>(v & ~kCvLow); if (h != nullptr) { - PerThreadSynch *w = h; + PerThreadSynch* w = h; while (w->next != s && w->next != h) { // search for thread w = w->next; } - if (w->next == s) { // found thread; remove it + if (w->next == s) { // found thread; remove it w->next = s->next; if (h == s) { h = (w == s) ? nullptr : w; @@ -2534,7 +2531,7 @@ void CondVar::Remove(PerThreadSynch *s) { s->state.store(PerThreadSynch::kAvailable, std::memory_order_release); } } - // release spinlock + // release spinlock cv_.store((v & kCvEvent) | reinterpret_cast<intptr_t>(h), std::memory_order_release); return; @@ -2557,14 +2554,14 @@ void CondVar::Remove(PerThreadSynch *s) { // variable queue just before the mutex is to be unlocked, and (most // importantly) after any call to an external routine that might re-enter the // mutex code. -static void CondVarEnqueue(SynchWaitParams *waitp) { +static void CondVarEnqueue(SynchWaitParams* waitp) { // This thread might be transferred to the Mutex queue by Fer() when // we are woken. To make sure that is what happens, Enqueue() doesn't // call CondVarEnqueue() again but instead uses its normal code. We // must do this before we queue ourselves so that cv_word will be null // when seen by the dequeuer, who may wish immediately to requeue // this thread on another queue. - std::atomic<intptr_t> *cv_word = waitp->cv_word; + std::atomic<intptr_t>* cv_word = waitp->cv_word; waitp->cv_word = nullptr; intptr_t v = cv_word->load(std::memory_order_relaxed); @@ -2577,8 +2574,8 @@ static void CondVarEnqueue(SynchWaitParams *waitp) { v = cv_word->load(std::memory_order_relaxed); } ABSL_RAW_CHECK(waitp->thread->waitp == nullptr, "waiting when shouldn't be"); - waitp->thread->waitp = waitp; // prepare ourselves for waiting - PerThreadSynch *h = reinterpret_cast<PerThreadSynch *>(v & ~kCvLow); + waitp->thread->waitp = waitp; // prepare ourselves for waiting + PerThreadSynch* h = reinterpret_cast<PerThreadSynch*>(v & ~kCvLow); if (h == nullptr) { // add this thread to waiter list waitp->thread->next = waitp->thread; } else { @@ -2591,8 +2588,8 @@ static void CondVarEnqueue(SynchWaitParams *waitp) { std::memory_order_release); } -bool CondVar::WaitCommon(Mutex *mutex, KernelTimeout t) { - bool rc = false; // return value; true iff we timed-out +bool CondVar::WaitCommon(Mutex* mutex, KernelTimeout t) { + bool rc = false; // return value; true iff we timed-out intptr_t mutex_v = mutex->mu_.load(std::memory_order_relaxed); Mutex::MuHow mutex_how = ((mutex_v & kMuWriter) != 0) ? kExclusive : kShared; @@ -2659,35 +2656,6 @@ bool CondVar::WaitCommon(Mutex *mutex, KernelTimeout t) { return rc; } -bool CondVar::WaitWithTimeout(Mutex *mu, absl::Duration timeout) { - return WaitWithDeadline(mu, DeadlineFromTimeout(timeout)); -} - -bool CondVar::WaitWithDeadline(Mutex *mu, absl::Time deadline) { - return WaitCommon(mu, KernelTimeout(deadline)); -} - -void CondVar::Wait(Mutex *mu) { - WaitCommon(mu, KernelTimeout::Never()); -} - -// Wake thread w -// If it was a timed wait, w will be waiting on w->cv -// Otherwise, if it was not a Mutex mutex, w will be waiting on w->sem -// Otherwise, w is transferred to the Mutex mutex via Mutex::Fer(). -void CondVar::Wakeup(PerThreadSynch *w) { - if (w->waitp->timeout.has_timeout() || w->waitp->cvmu == nullptr) { - // The waiting thread only needs to observe "w->state == kAvailable" to be - // released, we must cache "cvmu" before clearing "next". - Mutex *mu = w->waitp->cvmu; - w->next = nullptr; - w->state.store(PerThreadSynch::kAvailable, std::memory_order_release); - Mutex::IncrementSynchSem(mu, w); - } else { - w->waitp->cvmu->Fer(w); - } -} - void CondVar::Signal() { SchedulingGuard::ScopedDisable disable_rescheduling; ABSL_TSAN_MUTEX_PRE_SIGNAL(nullptr, 0); @@ -2696,11 +2664,10 @@ void CondVar::Signal() { for (v = cv_.load(std::memory_order_relaxed); v != 0; v = cv_.load(std::memory_order_relaxed)) { if ((v & kCvSpin) == 0 && // attempt to acquire spinlock - cv_.compare_exchange_strong(v, v | kCvSpin, - std::memory_order_acquire, + cv_.compare_exchange_strong(v, v | kCvSpin, std::memory_order_acquire, std::memory_order_relaxed)) { - PerThreadSynch *h = reinterpret_cast<PerThreadSynch *>(v & ~kCvLow); - PerThreadSynch *w = nullptr; + PerThreadSynch* h = reinterpret_cast<PerThreadSynch*>(v & ~kCvLow); + PerThreadSynch* w = nullptr; if (h != nullptr) { // remove first waiter w = h->next; if (w == h) { @@ -2709,11 +2676,11 @@ void CondVar::Signal() { h->next = w->next; } } - // release spinlock + // release spinlock cv_.store((v & kCvEvent) | reinterpret_cast<intptr_t>(h), std::memory_order_release); if (w != nullptr) { - CondVar::Wakeup(w); // wake waiter, if there was one + w->waitp->cvmu->Fer(w); // wake waiter, if there was one cond_var_tracer("Signal wakeup", this); } if ((v & kCvEvent) != 0) { @@ -2728,7 +2695,7 @@ void CondVar::Signal() { ABSL_TSAN_MUTEX_POST_SIGNAL(nullptr, 0); } -void CondVar::SignalAll () { +void CondVar::SignalAll() { ABSL_TSAN_MUTEX_PRE_SIGNAL(nullptr, 0); intptr_t v; int c = 0; @@ -2742,14 +2709,14 @@ void CondVar::SignalAll () { if ((v & kCvSpin) == 0 && cv_.compare_exchange_strong(v, v & kCvEvent, std::memory_order_acquire, std::memory_order_relaxed)) { - PerThreadSynch *h = reinterpret_cast<PerThreadSynch *>(v & ~kCvLow); + PerThreadSynch* h = reinterpret_cast<PerThreadSynch*>(v & ~kCvLow); if (h != nullptr) { - PerThreadSynch *w; - PerThreadSynch *n = h->next; - do { // for every thread, wake it up + PerThreadSynch* w; + PerThreadSynch* n = h->next; + do { // for every thread, wake it up w = n; n = n->next; - CondVar::Wakeup(w); + w->waitp->cvmu->Fer(w); } while (w != h); cond_var_tracer("SignalAll wakeup", this); } @@ -2774,57 +2741,50 @@ void ReleasableMutexLock::Release() { } #ifdef ABSL_HAVE_THREAD_SANITIZER -extern "C" void __tsan_read1(void *addr); +extern "C" void __tsan_read1(void* addr); #else #define __tsan_read1(addr) // do nothing if TSan not enabled #endif // A function that just returns its argument, dereferenced -static bool Dereference(void *arg) { +static bool Dereference(void* arg) { // ThreadSanitizer does not instrument this file for memory accesses. // This function dereferences a user variable that can participate // in a data race, so we need to manually tell TSan about this memory access. __tsan_read1(arg); - return *(static_cast<bool *>(arg)); + return *(static_cast<bool*>(arg)); } ABSL_CONST_INIT const Condition Condition::kTrue; -Condition::Condition(bool (*func)(void *), void *arg) - : eval_(&CallVoidPtrFunction), - arg_(arg) { +Condition::Condition(bool (*func)(void*), void* arg) + : eval_(&CallVoidPtrFunction), arg_(arg) { static_assert(sizeof(&func) <= sizeof(callback_), "An overlarge function pointer passed to Condition."); StoreCallback(func); } -bool Condition::CallVoidPtrFunction(const Condition *c) { - using FunctionPointer = bool (*)(void *); +bool Condition::CallVoidPtrFunction(const Condition* c) { + using FunctionPointer = bool (*)(void*); FunctionPointer function_pointer; std::memcpy(&function_pointer, c->callback_, sizeof(function_pointer)); return (*function_pointer)(c->arg_); } -Condition::Condition(const bool *cond) +Condition::Condition(const bool* cond) : eval_(CallVoidPtrFunction), // const_cast is safe since Dereference does not modify arg - arg_(const_cast<bool *>(cond)) { - using FunctionPointer = bool (*)(void *); + arg_(const_cast<bool*>(cond)) { + using FunctionPointer = bool (*)(void*); const FunctionPointer dereference = Dereference; StoreCallback(dereference); } -bool Condition::Eval() const { - // eval_ == null for kTrue - return (this->eval_ == nullptr) || (*this->eval_)(this); -} +bool Condition::Eval() const { return (*this->eval_)(this); } -bool Condition::GuaranteedEqual(const Condition *a, const Condition *b) { - // kTrue logic. - if (a == nullptr || a->eval_ == nullptr) { - return b == nullptr || b->eval_ == nullptr; - } else if (b == nullptr || b->eval_ == nullptr) { - return false; +bool Condition::GuaranteedEqual(const Condition* a, const Condition* b) { + if (a == nullptr || b == nullptr) { + return a == b; } // Check equality of the representative fields. return a->eval_ == b->eval_ && a->arg_ == b->arg_ && diff --git a/absl/synchronization/mutex.h b/absl/synchronization/mutex.h index f793cc0e..d53a22bb 100644 --- a/absl/synchronization/mutex.h +++ b/absl/synchronization/mutex.h @@ -64,6 +64,7 @@ #include <iterator> #include <string> +#include "absl/base/attributes.h" #include "absl/base/const_init.h" #include "absl/base/internal/identity.h" #include "absl/base/internal/low_level_alloc.h" @@ -92,26 +93,42 @@ struct SynchWaitParams; // // A `Mutex` has two basic operations: `Mutex::Lock()` and `Mutex::Unlock()`. // The `Lock()` operation *acquires* a `Mutex` (in a state known as an -// *exclusive* -- or write -- lock), while the `Unlock()` operation *releases* a +// *exclusive* -- or *write* -- lock), and the `Unlock()` operation *releases* a // Mutex. During the span of time between the Lock() and Unlock() operations, -// a mutex is said to be *held*. By design all mutexes support exclusive/write +// a mutex is said to be *held*. By design, all mutexes support exclusive/write // locks, as this is the most common way to use a mutex. // +// Mutex operations are only allowed under certain conditions; otherwise an +// operation is "invalid", and disallowed by the API. The conditions concern +// both the current state of the mutex and the identity of the threads that +// are performing the operations. +// // The `Mutex` state machine for basic lock/unlock operations is quite simple: // -// | | Lock() | Unlock() | -// |----------------+------------+----------| -// | Free | Exclusive | invalid | -// | Exclusive | blocks | Free | +// | | Lock() | Unlock() | +// |----------------+------------------------+----------| +// | Free | Exclusive | invalid | +// | Exclusive | blocks, then exclusive | Free | +// +// The full conditions are as follows. +// +// * Calls to `Unlock()` require that the mutex be held, and must be made in the +// same thread that performed the corresponding `Lock()` operation which +// acquired the mutex; otherwise the call is invalid. // -// Attempts to `Unlock()` must originate from the thread that performed the -// corresponding `Lock()` operation. +// * The mutex being non-reentrant (or non-recursive) means that a call to +// `Lock()` or `TryLock()` must not be made in a thread that already holds the +// mutex; such a call is invalid. // -// An "invalid" operation is disallowed by the API. The `Mutex` implementation -// is allowed to do anything on an invalid call, including but not limited to +// * In other words, the state of being "held" has both a temporal component +// (from `Lock()` until `Unlock()`) as well as a thread identity component: +// the mutex is held *by a particular thread*. +// +// An "invalid" operation has undefined behavior. The `Mutex` implementation +// is allowed to do anything on an invalid call, including, but not limited to, // crashing with a useful error message, silently succeeding, or corrupting -// data structures. In debug mode, the implementation attempts to crash with a -// useful error message. +// data structures. In debug mode, the implementation may crash with a useful +// error message. // // `Mutex` is not guaranteed to be "fair" in prioritizing waiting threads; it // is, however, approximately fair over long periods, and starvation-free for @@ -125,8 +142,9 @@ struct SynchWaitParams; // issues that could potentially result in race conditions and deadlocks. // // For more information about the lock annotations, please see -// [Thread Safety Analysis](http://clang.llvm.org/docs/ThreadSafetyAnalysis.html) -// in the Clang documentation. +// [Thread Safety +// Analysis](http://clang.llvm.org/docs/ThreadSafetyAnalysis.html) in the Clang +// documentation. // // See also `MutexLock`, below, for scoped `Mutex` acquisition. @@ -257,7 +275,7 @@ class ABSL_LOCKABLE Mutex { // Aliases for `Mutex::Lock()`, `Mutex::Unlock()`, and `Mutex::TryLock()`. // // These methods may be used (along with the complementary `Reader*()` - // methods) to distingish simple exclusive `Mutex` usage (`Lock()`, + // methods) to distinguish simple exclusive `Mutex` usage (`Lock()`, // etc.) from reader/writer lock usage. void WriterLock() ABSL_EXCLUSIVE_LOCK_FUNCTION() { this->Lock(); } @@ -307,7 +325,9 @@ class ABSL_LOCKABLE Mutex { // `true`, `Await()` *may* skip the release/re-acquire step. // // `Await()` requires that this thread holds this `Mutex` in some mode. - void Await(const Condition &cond); + void Await(const Condition& cond) { + AwaitCommon(cond, synchronization_internal::KernelTimeout::Never()); + } // Mutex::LockWhen() // Mutex::ReaderLockWhen() @@ -317,11 +337,17 @@ class ABSL_LOCKABLE Mutex { // be acquired, then atomically acquires this `Mutex`. `LockWhen()` is // logically equivalent to `*Lock(); Await();` though they may have different // performance characteristics. - void LockWhen(const Condition &cond) ABSL_EXCLUSIVE_LOCK_FUNCTION(); + void LockWhen(const Condition& cond) ABSL_EXCLUSIVE_LOCK_FUNCTION() { + LockWhenCommon(cond, synchronization_internal::KernelTimeout::Never(), + true); + } - void ReaderLockWhen(const Condition &cond) ABSL_SHARED_LOCK_FUNCTION(); + void ReaderLockWhen(const Condition& cond) ABSL_SHARED_LOCK_FUNCTION() { + LockWhenCommon(cond, synchronization_internal::KernelTimeout::Never(), + false); + } - void WriterLockWhen(const Condition &cond) ABSL_EXCLUSIVE_LOCK_FUNCTION() { + void WriterLockWhen(const Condition& cond) ABSL_EXCLUSIVE_LOCK_FUNCTION() { this->LockWhen(cond); } @@ -346,9 +372,13 @@ class ABSL_LOCKABLE Mutex { // Negative timeouts are equivalent to a zero timeout. // // This method requires that this thread holds this `Mutex` in some mode. - bool AwaitWithTimeout(const Condition &cond, absl::Duration timeout); + bool AwaitWithTimeout(const Condition& cond, absl::Duration timeout) { + return AwaitCommon(cond, synchronization_internal::KernelTimeout{timeout}); + } - bool AwaitWithDeadline(const Condition &cond, absl::Time deadline); + bool AwaitWithDeadline(const Condition& cond, absl::Time deadline) { + return AwaitCommon(cond, synchronization_internal::KernelTimeout{deadline}); + } // Mutex::LockWhenWithTimeout() // Mutex::ReaderLockWhenWithTimeout() @@ -361,11 +391,17 @@ class ABSL_LOCKABLE Mutex { // `true` on return. // // Negative timeouts are equivalent to a zero timeout. - bool LockWhenWithTimeout(const Condition &cond, absl::Duration timeout) - ABSL_EXCLUSIVE_LOCK_FUNCTION(); - bool ReaderLockWhenWithTimeout(const Condition &cond, absl::Duration timeout) - ABSL_SHARED_LOCK_FUNCTION(); - bool WriterLockWhenWithTimeout(const Condition &cond, absl::Duration timeout) + bool LockWhenWithTimeout(const Condition& cond, absl::Duration timeout) + ABSL_EXCLUSIVE_LOCK_FUNCTION() { + return LockWhenCommon( + cond, synchronization_internal::KernelTimeout{timeout}, true); + } + bool ReaderLockWhenWithTimeout(const Condition& cond, absl::Duration timeout) + ABSL_SHARED_LOCK_FUNCTION() { + return LockWhenCommon( + cond, synchronization_internal::KernelTimeout{timeout}, false); + } + bool WriterLockWhenWithTimeout(const Condition& cond, absl::Duration timeout) ABSL_EXCLUSIVE_LOCK_FUNCTION() { return this->LockWhenWithTimeout(cond, timeout); } @@ -381,11 +417,17 @@ class ABSL_LOCKABLE Mutex { // on return. // // Deadlines in the past are equivalent to an immediate deadline. - bool LockWhenWithDeadline(const Condition &cond, absl::Time deadline) - ABSL_EXCLUSIVE_LOCK_FUNCTION(); - bool ReaderLockWhenWithDeadline(const Condition &cond, absl::Time deadline) - ABSL_SHARED_LOCK_FUNCTION(); - bool WriterLockWhenWithDeadline(const Condition &cond, absl::Time deadline) + bool LockWhenWithDeadline(const Condition& cond, absl::Time deadline) + ABSL_EXCLUSIVE_LOCK_FUNCTION() { + return LockWhenCommon( + cond, synchronization_internal::KernelTimeout{deadline}, true); + } + bool ReaderLockWhenWithDeadline(const Condition& cond, absl::Time deadline) + ABSL_SHARED_LOCK_FUNCTION() { + return LockWhenCommon( + cond, synchronization_internal::KernelTimeout{deadline}, false); + } + bool WriterLockWhenWithDeadline(const Condition& cond, absl::Time deadline) ABSL_EXCLUSIVE_LOCK_FUNCTION() { return this->LockWhenWithDeadline(cond, deadline); } @@ -407,7 +449,7 @@ class ABSL_LOCKABLE Mutex { // substantially reduce `Mutex` performance; it should be set only for // non-production runs. Optimization options may also disable invariant // checks. - void EnableInvariantDebugging(void (*invariant)(void *), void *arg); + void EnableInvariantDebugging(void (*invariant)(void*), void* arg); // Mutex::EnableDebugLog() // @@ -416,7 +458,7 @@ class ABSL_LOCKABLE Mutex { // call to `EnableInvariantDebugging()` or `EnableDebugLog()` has been made. // // Note: This method substantially reduces `Mutex` performance. - void EnableDebugLog(const char *name); + void EnableDebugLog(const char* name); // Deadlock detection @@ -444,7 +486,7 @@ class ABSL_LOCKABLE Mutex { // A `MuHow` is a constant that indicates how a lock should be acquired. // Internal implementation detail. Clients should ignore. - typedef const struct MuHowS *MuHow; + typedef const struct MuHowS* MuHow; // Mutex::InternalAttemptToUseMutexInFatalSignalHandler() // @@ -466,37 +508,44 @@ class ABSL_LOCKABLE Mutex { // Post()/Wait() versus associated PerThreadSem; in class for required // friendship with PerThreadSem. - static void IncrementSynchSem(Mutex *mu, base_internal::PerThreadSynch *w); - static bool DecrementSynchSem(Mutex *mu, base_internal::PerThreadSynch *w, + static void IncrementSynchSem(Mutex* mu, base_internal::PerThreadSynch* w); + static bool DecrementSynchSem(Mutex* mu, base_internal::PerThreadSynch* w, synchronization_internal::KernelTimeout t); // slow path acquire - void LockSlowLoop(SynchWaitParams *waitp, int flags); + void LockSlowLoop(SynchWaitParams* waitp, int flags); // wrappers around LockSlowLoop() - bool LockSlowWithDeadline(MuHow how, const Condition *cond, + bool LockSlowWithDeadline(MuHow how, const Condition* cond, synchronization_internal::KernelTimeout t, int flags); - void LockSlow(MuHow how, const Condition *cond, + void LockSlow(MuHow how, const Condition* cond, int flags) ABSL_ATTRIBUTE_COLD; // slow path release - void UnlockSlow(SynchWaitParams *waitp) ABSL_ATTRIBUTE_COLD; + void UnlockSlow(SynchWaitParams* waitp) ABSL_ATTRIBUTE_COLD; + // TryLock slow path. + bool TryLockSlow(); + // ReaderTryLock slow path. + bool ReaderTryLockSlow(); // Common code between Await() and AwaitWithTimeout/Deadline() - bool AwaitCommon(const Condition &cond, + bool AwaitCommon(const Condition& cond, synchronization_internal::KernelTimeout t); + bool LockWhenCommon(const Condition& cond, + synchronization_internal::KernelTimeout t, bool write); // Attempt to remove thread s from queue. - void TryRemove(base_internal::PerThreadSynch *s); + void TryRemove(base_internal::PerThreadSynch* s); // Block a thread on mutex. - void Block(base_internal::PerThreadSynch *s); + void Block(base_internal::PerThreadSynch* s); // Wake a thread; return successor. - base_internal::PerThreadSynch *Wakeup(base_internal::PerThreadSynch *w); + base_internal::PerThreadSynch* Wakeup(base_internal::PerThreadSynch* w); + void Dtor(); friend class CondVar; // for access to Trans()/Fer(). void Trans(MuHow how); // used for CondVar->Mutex transfer void Fer( - base_internal::PerThreadSynch *w); // used for CondVar->Mutex transfer + base_internal::PerThreadSynch* w); // used for CondVar->Mutex transfer // Catch the error of writing Mutex when intending MutexLock. - Mutex(const volatile Mutex * /*ignored*/) {} // NOLINT(runtime/explicit) + explicit Mutex(const volatile Mutex* /*ignored*/) {} Mutex(const Mutex&) = delete; Mutex& operator=(const Mutex&) = delete; @@ -531,28 +580,28 @@ class ABSL_SCOPED_LOCKABLE MutexLock { // Calls `mu->Lock()` and returns when that call returns. That is, `*mu` is // guaranteed to be locked when this object is constructed. Requires that // `mu` be dereferenceable. - explicit MutexLock(Mutex *mu) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) : mu_(mu) { + explicit MutexLock(Mutex* mu) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) : mu_(mu) { this->mu_->Lock(); } // Like above, but calls `mu->LockWhen(cond)` instead. That is, in addition to // the above, the condition given by `cond` is also guaranteed to hold when // this object is constructed. - explicit MutexLock(Mutex *mu, const Condition &cond) + explicit MutexLock(Mutex* mu, const Condition& cond) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) : mu_(mu) { this->mu_->LockWhen(cond); } - MutexLock(const MutexLock &) = delete; // NOLINT(runtime/mutex) - MutexLock(MutexLock&&) = delete; // NOLINT(runtime/mutex) + MutexLock(const MutexLock&) = delete; // NOLINT(runtime/mutex) + MutexLock(MutexLock&&) = delete; // NOLINT(runtime/mutex) MutexLock& operator=(const MutexLock&) = delete; MutexLock& operator=(MutexLock&&) = delete; ~MutexLock() ABSL_UNLOCK_FUNCTION() { this->mu_->Unlock(); } private: - Mutex *const mu_; + Mutex* const mu_; }; // ReaderMutexLock @@ -561,11 +610,11 @@ class ABSL_SCOPED_LOCKABLE MutexLock { // releases a shared lock on a `Mutex` via RAII. class ABSL_SCOPED_LOCKABLE ReaderMutexLock { public: - explicit ReaderMutexLock(Mutex *mu) ABSL_SHARED_LOCK_FUNCTION(mu) : mu_(mu) { + explicit ReaderMutexLock(Mutex* mu) ABSL_SHARED_LOCK_FUNCTION(mu) : mu_(mu) { mu->ReaderLock(); } - explicit ReaderMutexLock(Mutex *mu, const Condition &cond) + explicit ReaderMutexLock(Mutex* mu, const Condition& cond) ABSL_SHARED_LOCK_FUNCTION(mu) : mu_(mu) { mu->ReaderLockWhen(cond); @@ -579,7 +628,7 @@ class ABSL_SCOPED_LOCKABLE ReaderMutexLock { ~ReaderMutexLock() ABSL_UNLOCK_FUNCTION() { this->mu_->ReaderUnlock(); } private: - Mutex *const mu_; + Mutex* const mu_; }; // WriterMutexLock @@ -588,12 +637,12 @@ class ABSL_SCOPED_LOCKABLE ReaderMutexLock { // releases a write (exclusive) lock on a `Mutex` via RAII. class ABSL_SCOPED_LOCKABLE WriterMutexLock { public: - explicit WriterMutexLock(Mutex *mu) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) + explicit WriterMutexLock(Mutex* mu) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) : mu_(mu) { mu->WriterLock(); } - explicit WriterMutexLock(Mutex *mu, const Condition &cond) + explicit WriterMutexLock(Mutex* mu, const Condition& cond) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) : mu_(mu) { mu->WriterLockWhen(cond); @@ -607,7 +656,7 @@ class ABSL_SCOPED_LOCKABLE WriterMutexLock { ~WriterMutexLock() ABSL_UNLOCK_FUNCTION() { this->mu_->WriterUnlock(); } private: - Mutex *const mu_; + Mutex* const mu_; }; // ----------------------------------------------------------------------------- @@ -665,7 +714,7 @@ class ABSL_SCOPED_LOCKABLE WriterMutexLock { class Condition { public: // A Condition that returns the result of "(*func)(arg)" - Condition(bool (*func)(void *), void *arg); + Condition(bool (*func)(void*), void* arg); // Templated version for people who are averse to casts. // @@ -676,27 +725,43 @@ class Condition { // Note: lambdas in this case must contain no bound variables. // // See class comment for performance advice. - template<typename T> - Condition(bool (*func)(T *), T *arg); + template <typename T> + Condition(bool (*func)(T*), T* arg); + + // Same as above, but allows for cases where `arg` comes from a pointer that + // is convertible to the function parameter type `T*` but not an exact match. + // + // For example, the argument might be `X*` but the function takes `const X*`, + // or the argument might be `Derived*` while the function takes `Base*`, and + // so on for cases where the argument pointer can be implicitly converted. + // + // Implementation notes: This constructor overload is required in addition to + // the one above to allow deduction of `T` from `arg` for cases such as where + // a function template is passed as `func`. Also, the dummy `typename = void` + // template parameter exists just to work around a MSVC mangling bug. + template <typename T, typename = void> + Condition(bool (*func)(T*), + typename absl::internal::type_identity<T>::type* arg); // Templated version for invoking a method that returns a `bool`. // // `Condition(object, &Class::Method)` constructs a `Condition` that evaluates // `object->Method()`. // - // Implementation Note: `absl::internal::identity` is used to allow methods to - // come from base classes. A simpler signature like + // Implementation Note: `absl::internal::type_identity` is used to allow + // methods to come from base classes. A simpler signature like // `Condition(T*, bool (T::*)())` does not suffice. - template<typename T> - Condition(T *object, bool (absl::internal::identity<T>::type::* method)()); + template <typename T> + Condition(T* object, + bool (absl::internal::type_identity<T>::type::*method)()); // Same as above, for const members - template<typename T> - Condition(const T *object, - bool (absl::internal::identity<T>::type::* method)() const); + template <typename T> + Condition(const T* object, + bool (absl::internal::type_identity<T>::type::*method)() const); // A Condition that returns the value of `*cond` - explicit Condition(const bool *cond); + explicit Condition(const bool* cond); // Templated version for invoking a functor that returns a `bool`. // This approach accepts pointers to non-mutable lambdas, `std::function`, @@ -723,12 +788,22 @@ class Condition { // Implementation note: The second template parameter ensures that this // constructor doesn't participate in overload resolution if T doesn't have // `bool operator() const`. - template <typename T, typename E = decltype( - static_cast<bool (T::*)() const>(&T::operator()))> - explicit Condition(const T *obj) + template <typename T, typename E = decltype(static_cast<bool (T::*)() const>( + &T::operator()))> + explicit Condition(const T* obj) : Condition(obj, static_cast<bool (T::*)() const>(&T::operator())) {} // A Condition that always returns `true`. + // kTrue is only useful in a narrow set of circumstances, mostly when + // it's passed conditionally. For example: + // + // mu.LockWhen(some_flag ? kTrue : SomeOtherCondition); + // + // Note: {LockWhen,Await}With{Deadline,Timeout} methods with kTrue condition + // don't return immediately when the timeout happens, they still block until + // the Mutex becomes available. The return value of these methods does + // not indicate if the timeout was reached; rather it indicates whether or + // not the condition is true. ABSL_CONST_INIT static const Condition kTrue; // Evaluates the condition. @@ -741,7 +816,7 @@ class Condition { // Two `Condition` values are guaranteed equal if both their `func` and `arg` // components are the same. A null pointer is equivalent to a `true` // condition. - static bool GuaranteedEqual(const Condition *a, const Condition *b); + static bool GuaranteedEqual(const Condition* a, const Condition* b); private: // Sizing an allocation for a method pointer can be subtle. In the Itanium @@ -769,12 +844,14 @@ class Condition { bool (*eval_)(const Condition*) = nullptr; // Either an argument for a function call or an object for a method call. - void *arg_ = nullptr; + void* arg_ = nullptr; // Various functions eval_ can point to: static bool CallVoidPtrFunction(const Condition*); - template <typename T> static bool CastAndCallFunction(const Condition* c); - template <typename T> static bool CastAndCallMethod(const Condition* c); + template <typename T> + static bool CastAndCallFunction(const Condition* c); + template <typename T, typename ConditionMethodPtr> + static bool CastAndCallMethod(const Condition* c); // Helper methods for storing, validating, and reading callback arguments. template <typename T> @@ -786,12 +863,14 @@ class Condition { } template <typename T> - inline void ReadCallback(T *callback) const { + inline void ReadCallback(T* callback) const { std::memcpy(callback, callback_, sizeof(*callback)); } + static bool AlwaysTrue(const Condition*) { return true; } + // Used only to create kTrue. - constexpr Condition() = default; + constexpr Condition() : eval_(AlwaysTrue), arg_(nullptr) {} }; // ----------------------------------------------------------------------------- @@ -834,7 +913,6 @@ class CondVar { // A `CondVar` allocated on the heap or on the stack can use the this // constructor. CondVar(); - ~CondVar(); // CondVar::Wait() // @@ -843,7 +921,9 @@ class CondVar { // spurious wakeup), then reacquires the `Mutex` and returns. // // Requires and ensures that the current thread holds the `Mutex`. - void Wait(Mutex *mu); + void Wait(Mutex* mu) { + WaitCommon(mu, synchronization_internal::KernelTimeout::Never()); + } // CondVar::WaitWithTimeout() // @@ -858,7 +938,9 @@ class CondVar { // to return `true` or `false`. // // Requires and ensures that the current thread holds the `Mutex`. - bool WaitWithTimeout(Mutex *mu, absl::Duration timeout); + bool WaitWithTimeout(Mutex* mu, absl::Duration timeout) { + return WaitCommon(mu, synchronization_internal::KernelTimeout(timeout)); + } // CondVar::WaitWithDeadline() // @@ -875,7 +957,9 @@ class CondVar { // to return `true` or `false`. // // Requires and ensures that the current thread holds the `Mutex`. - bool WaitWithDeadline(Mutex *mu, absl::Time deadline); + bool WaitWithDeadline(Mutex* mu, absl::Time deadline) { + return WaitCommon(mu, synchronization_internal::KernelTimeout(deadline)); + } // CondVar::Signal() // @@ -892,18 +976,16 @@ class CondVar { // Causes all subsequent uses of this `CondVar` to be logged via // `ABSL_RAW_LOG(INFO)`. Log entries are tagged with `name` if `name != 0`. // Note: this method substantially reduces `CondVar` performance. - void EnableDebugLog(const char *name); + void EnableDebugLog(const char* name); private: - bool WaitCommon(Mutex *mutex, synchronization_internal::KernelTimeout t); - void Remove(base_internal::PerThreadSynch *s); - void Wakeup(base_internal::PerThreadSynch *w); + bool WaitCommon(Mutex* mutex, synchronization_internal::KernelTimeout t); + void Remove(base_internal::PerThreadSynch* s); std::atomic<intptr_t> cv_; // Condition variable state. CondVar(const CondVar&) = delete; CondVar& operator=(const CondVar&) = delete; }; - // Variants of MutexLock. // // If you find yourself using one of these, consider instead using @@ -914,14 +996,14 @@ class CondVar { // MutexLockMaybe is like MutexLock, but is a no-op when mu is null. class ABSL_SCOPED_LOCKABLE MutexLockMaybe { public: - explicit MutexLockMaybe(Mutex *mu) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) + explicit MutexLockMaybe(Mutex* mu) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) : mu_(mu) { if (this->mu_ != nullptr) { this->mu_->Lock(); } } - explicit MutexLockMaybe(Mutex *mu, const Condition &cond) + explicit MutexLockMaybe(Mutex* mu, const Condition& cond) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) : mu_(mu) { if (this->mu_ != nullptr) { @@ -930,11 +1012,13 @@ class ABSL_SCOPED_LOCKABLE MutexLockMaybe { } ~MutexLockMaybe() ABSL_UNLOCK_FUNCTION() { - if (this->mu_ != nullptr) { this->mu_->Unlock(); } + if (this->mu_ != nullptr) { + this->mu_->Unlock(); + } } private: - Mutex *const mu_; + Mutex* const mu_; MutexLockMaybe(const MutexLockMaybe&) = delete; MutexLockMaybe(MutexLockMaybe&&) = delete; MutexLockMaybe& operator=(const MutexLockMaybe&) = delete; @@ -947,25 +1031,27 @@ class ABSL_SCOPED_LOCKABLE MutexLockMaybe { // mutex before destruction. `Release()` may be called at most once. class ABSL_SCOPED_LOCKABLE ReleasableMutexLock { public: - explicit ReleasableMutexLock(Mutex *mu) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) + explicit ReleasableMutexLock(Mutex* mu) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) : mu_(mu) { this->mu_->Lock(); } - explicit ReleasableMutexLock(Mutex *mu, const Condition &cond) + explicit ReleasableMutexLock(Mutex* mu, const Condition& cond) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) : mu_(mu) { this->mu_->LockWhen(cond); } ~ReleasableMutexLock() ABSL_UNLOCK_FUNCTION() { - if (this->mu_ != nullptr) { this->mu_->Unlock(); } + if (this->mu_ != nullptr) { + this->mu_->Unlock(); + } } void Release() ABSL_UNLOCK_FUNCTION(); private: - Mutex *mu_; + Mutex* mu_; ReleasableMutexLock(const ReleasableMutexLock&) = delete; ReleasableMutexLock(ReleasableMutexLock&&) = delete; ReleasableMutexLock& operator=(const ReleasableMutexLock&) = delete; @@ -978,51 +1064,71 @@ inline Mutex::Mutex() : mu_(0) { inline constexpr Mutex::Mutex(absl::ConstInitType) : mu_(0) {} +#if !defined(__APPLE__) && !defined(ABSL_BUILD_DLL) +ABSL_ATTRIBUTE_ALWAYS_INLINE +inline Mutex::~Mutex() { Dtor(); } +#endif + +#if defined(NDEBUG) && !defined(ABSL_HAVE_THREAD_SANITIZER) +// Use default (empty) destructor in release build for performance reasons. +// We need to mark both Dtor and ~Mutex as always inline for inconsistent +// builds that use both NDEBUG and !NDEBUG with dynamic libraries. In these +// cases we want the empty functions to dissolve entirely rather than being +// exported from dynamic libraries and potentially override the non-empty ones. +ABSL_ATTRIBUTE_ALWAYS_INLINE +inline void Mutex::Dtor() {} +#endif + inline CondVar::CondVar() : cv_(0) {} // static -template <typename T> -bool Condition::CastAndCallMethod(const Condition *c) { - T *object = static_cast<T *>(c->arg_); - bool (T::*method_pointer)(); - c->ReadCallback(&method_pointer); - return (object->*method_pointer)(); +template <typename T, typename ConditionMethodPtr> +bool Condition::CastAndCallMethod(const Condition* c) { + T* object = static_cast<T*>(c->arg_); + ConditionMethodPtr condition_method_pointer; + c->ReadCallback(&condition_method_pointer); + return (object->*condition_method_pointer)(); } // static template <typename T> -bool Condition::CastAndCallFunction(const Condition *c) { - bool (*function)(T *); +bool Condition::CastAndCallFunction(const Condition* c) { + bool (*function)(T*); c->ReadCallback(&function); - T *argument = static_cast<T *>(c->arg_); + T* argument = static_cast<T*>(c->arg_); return (*function)(argument); } template <typename T> -inline Condition::Condition(bool (*func)(T *), T *arg) +inline Condition::Condition(bool (*func)(T*), T* arg) : eval_(&CastAndCallFunction<T>), - arg_(const_cast<void *>(static_cast<const void *>(arg))) { + arg_(const_cast<void*>(static_cast<const void*>(arg))) { static_assert(sizeof(&func) <= sizeof(callback_), "An overlarge function pointer was passed to Condition."); StoreCallback(func); } +template <typename T, typename> +inline Condition::Condition( + bool (*func)(T*), typename absl::internal::type_identity<T>::type* arg) + // Just delegate to the overload above. + : Condition(func, arg) {} + template <typename T> -inline Condition::Condition(T *object, - bool (absl::internal::identity<T>::type::*method)()) - : eval_(&CastAndCallMethod<T>), - arg_(object) { +inline Condition::Condition( + T* object, bool (absl::internal::type_identity<T>::type::*method)()) + : eval_(&CastAndCallMethod<T, decltype(method)>), arg_(object) { static_assert(sizeof(&method) <= sizeof(callback_), "An overlarge method pointer was passed to Condition."); StoreCallback(method); } template <typename T> -inline Condition::Condition(const T *object, - bool (absl::internal::identity<T>::type::*method)() - const) - : eval_(&CastAndCallMethod<T>), - arg_(reinterpret_cast<void *>(const_cast<T *>(object))) { +inline Condition::Condition( + const T* object, + bool (absl::internal::type_identity<T>::type::*method)() const) + : eval_(&CastAndCallMethod<const T, decltype(method)>), + arg_(reinterpret_cast<void*>(const_cast<T*>(object))) { StoreCallback(method); } @@ -1052,7 +1158,7 @@ void RegisterMutexProfiler(void (*fn)(int64_t wait_cycles)); // // This has the same ordering and single-use limitations as // RegisterMutexProfiler() above. -void RegisterMutexTracer(void (*fn)(const char *msg, const void *obj, +void RegisterMutexTracer(void (*fn)(const char* msg, const void* obj, int64_t wait_cycles)); // Register a hook for CondVar tracing. @@ -1067,24 +1173,7 @@ void RegisterMutexTracer(void (*fn)(const char *msg, const void *obj, // // This has the same ordering and single-use limitations as // RegisterMutexProfiler() above. -void RegisterCondVarTracer(void (*fn)(const char *msg, const void *cv)); - -// Register a hook for symbolizing stack traces in deadlock detector reports. -// -// 'pc' is the program counter being symbolized, 'out' is the buffer to write -// into, and 'out_size' is the size of the buffer. This function can return -// false if symbolizing failed, or true if a NUL-terminated symbol was written -// to 'out.' -// -// This has the same ordering and single-use limitations as -// RegisterMutexProfiler() above. -// -// DEPRECATED: The default symbolizer function is absl::Symbolize() and the -// ability to register a different hook for symbolizing stack traces will be -// removed on or after 2023-05-01. -ABSL_DEPRECATED("absl::RegisterSymbolizer() is deprecated and will be removed " - "on or after 2023-05-01") -void RegisterSymbolizer(bool (*fn)(const void *pc, char *out, int out_size)); +void RegisterCondVarTracer(void (*fn)(const char* msg, const void* cv)); // EnableMutexInvariantDebugging() // @@ -1101,7 +1190,7 @@ void EnableMutexInvariantDebugging(bool enabled); enum class OnDeadlockCycle { kIgnore, // Neither report on nor attempt to track cycles in lock ordering kReport, // Report lock cycles to stderr when detected - kAbort, // Report lock cycles to stderr when detected, then abort + kAbort, // Report lock cycles to stderr when detected, then abort }; // SetMutexDeadlockDetectionMode() diff --git a/absl/synchronization/mutex_benchmark.cc b/absl/synchronization/mutex_benchmark.cc index b5d2fbc4..06888dfe 100644 --- a/absl/synchronization/mutex_benchmark.cc +++ b/absl/synchronization/mutex_benchmark.cc @@ -19,6 +19,7 @@ #include "absl/base/config.h" #include "absl/base/internal/cycleclock.h" #include "absl/base/internal/spinlock.h" +#include "absl/base/no_destructor.h" #include "absl/synchronization/blocking_counter.h" #include "absl/synchronization/internal/thread_pool.h" #include "absl/synchronization/mutex.h" @@ -27,13 +28,41 @@ namespace { void BM_Mutex(benchmark::State& state) { - static absl::Mutex* mu = new absl::Mutex; + static absl::NoDestructor<absl::Mutex> mu; for (auto _ : state) { - absl::MutexLock lock(mu); + absl::MutexLock lock(mu.get()); } } BENCHMARK(BM_Mutex)->UseRealTime()->Threads(1)->ThreadPerCpu(); +void BM_ReaderLock(benchmark::State& state) { + static absl::NoDestructor<absl::Mutex> mu; + for (auto _ : state) { + absl::ReaderMutexLock lock(mu.get()); + } +} +BENCHMARK(BM_ReaderLock)->UseRealTime()->Threads(1)->ThreadPerCpu(); + +void BM_TryLock(benchmark::State& state) { + absl::Mutex mu; + for (auto _ : state) { + if (mu.TryLock()) { + mu.Unlock(); + } + } +} +BENCHMARK(BM_TryLock); + +void BM_ReaderTryLock(benchmark::State& state) { + static absl::NoDestructor<absl::Mutex> mu; + for (auto _ : state) { + if (mu->ReaderTryLock()) { + mu->ReaderUnlock(); + } + } +} +BENCHMARK(BM_ReaderTryLock)->UseRealTime()->Threads(1)->ThreadPerCpu(); + static void DelayNs(int64_t ns, int* data) { int64_t end = absl::base_internal::CycleClock::Now() + ns * absl::base_internal::CycleClock::Frequency() / 1e9; @@ -105,7 +134,7 @@ void BM_MutexEnqueue(benchmark::State& state) { std::atomic<int> blocked_threads{0}; std::atomic<bool> thread_has_mutex{false}; }; - static Shared* shared = new Shared; + static absl::NoDestructor<Shared> shared; // Set up 'blocked_threads' to count how many threads are currently blocked // in Abseil synchronization code. @@ -183,7 +212,7 @@ void BM_Contended(benchmark::State& state) { MutexType mu; int data = 0; }; - static auto* shared = new Shared; + static absl::NoDestructor<Shared> shared; int local = 0; for (auto _ : state) { // Here we model both local work outside of the critical section as well as diff --git a/absl/synchronization/mutex_method_pointer_test.cc b/absl/synchronization/mutex_method_pointer_test.cc index 1ec801a0..f4c82d27 100644 --- a/absl/synchronization/mutex_method_pointer_test.cc +++ b/absl/synchronization/mutex_method_pointer_test.cc @@ -26,8 +26,8 @@ class IncompleteClass; #ifdef _MSC_VER // These tests verify expectations about sizes of MSVC pointers to methods. -// Pointers to methods are distinguished by whether their class hierachies -// contain single inheritance, multiple inheritance, or virtual inheritence. +// Pointers to methods are distinguished by whether their class hierarchies +// contain single inheritance, multiple inheritance, or virtual inheritance. // Declare classes of the various MSVC inheritance types. class __single_inheritance SingleInheritance{}; diff --git a/absl/synchronization/mutex_test.cc b/absl/synchronization/mutex_test.cc index 34751cb1..bb93abc9 100644 --- a/absl/synchronization/mutex_test.cc +++ b/absl/synchronization/mutex_test.cc @@ -32,13 +32,20 @@ #include "gtest/gtest.h" #include "absl/base/attributes.h" #include "absl/base/config.h" -#include "absl/base/internal/raw_logging.h" #include "absl/base/internal/sysinfo.h" +#include "absl/log/check.h" +#include "absl/log/log.h" #include "absl/memory/memory.h" +#include "absl/synchronization/internal/create_thread_identity.h" #include "absl/synchronization/internal/thread_pool.h" #include "absl/time/clock.h" #include "absl/time/time.h" +#ifdef ABSL_HAVE_PTHREAD_GETSCHEDPARAM +#include <pthread.h> +#include <string.h> +#endif + namespace { // TODO(dmauro): Replace with a commandline flag. @@ -65,6 +72,11 @@ static void ScheduleAfter(absl::synchronization_internal::ThreadPool *tp, }); } +struct ScopedInvariantDebugging { + ScopedInvariantDebugging() { absl::EnableMutexInvariantDebugging(true); } + ~ScopedInvariantDebugging() { absl::EnableMutexInvariantDebugging(false); } +}; + struct TestContext { int iterations; int threads; @@ -87,7 +99,7 @@ static void SetInvariantChecked(bool new_value) { static void CheckSumG0G1(void *v) { TestContext *cxt = static_cast<TestContext *>(v); - ABSL_RAW_CHECK(cxt->g0 == -cxt->g1, "Error in CheckSumG0G1"); + CHECK_EQ(cxt->g0, -cxt->g1) << "Error in CheckSumG0G1"; SetInvariantChecked(true); } @@ -132,7 +144,7 @@ static void TestRW(TestContext *cxt, int c) { } else { for (int i = 0; i != cxt->iterations; i++) { absl::ReaderMutexLock l(&cxt->mu); - ABSL_RAW_CHECK(cxt->g0 == -cxt->g1, "Error in TestRW"); + CHECK_EQ(cxt->g0, -cxt->g1) << "Error in TestRW"; cxt->mu.AssertReaderHeld(); } } @@ -157,7 +169,7 @@ static void TestAwait(TestContext *cxt, int c) { cxt->mu.AssertHeld(); while (cxt->g0 < cxt->iterations) { cxt->mu.Await(absl::Condition(&mc, &MyContext::MyTurn)); - ABSL_RAW_CHECK(mc.MyTurn(), "Error in TestAwait"); + CHECK(mc.MyTurn()) << "Error in TestAwait"; cxt->mu.AssertHeld(); if (cxt->g0 < cxt->iterations) { int a = cxt->g0 + 1; @@ -185,7 +197,7 @@ static void TestSignalAll(TestContext *cxt, int c) { } static void TestSignal(TestContext *cxt, int c) { - ABSL_RAW_CHECK(cxt->threads == 2, "TestSignal should use 2 threads"); + CHECK_EQ(cxt->threads, 2) << "TestSignal should use 2 threads"; int target = c; absl::MutexLock l(&cxt->mu); cxt->mu.AssertHeld(); @@ -222,8 +234,8 @@ static void TestCVTimeout(TestContext *cxt, int c) { static bool G0GE2(TestContext *cxt) { return cxt->g0 >= 2; } static void TestTime(TestContext *cxt, int c, bool use_cv) { - ABSL_RAW_CHECK(cxt->iterations == 1, "TestTime should only use 1 iteration"); - ABSL_RAW_CHECK(cxt->threads > 2, "TestTime should use more than 2 threads"); + CHECK_EQ(cxt->iterations, 1) << "TestTime should only use 1 iteration"; + CHECK_GT(cxt->threads, 2) << "TestTime should use more than 2 threads"; const bool kFalse = false; absl::Condition false_cond(&kFalse); absl::Condition g0ge2(G0GE2, cxt); @@ -234,26 +246,24 @@ static void TestTime(TestContext *cxt, int c, bool use_cv) { if (use_cv) { cxt->cv.WaitWithTimeout(&cxt->mu, absl::Seconds(1)); } else { - ABSL_RAW_CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1)), - "TestTime failed"); + CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1))) + << "TestTime failed"; } absl::Duration elapsed = absl::Now() - start; - ABSL_RAW_CHECK( - absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0), - "TestTime failed"); - ABSL_RAW_CHECK(cxt->g0 == 1, "TestTime failed"); + CHECK(absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0)) + << "TestTime failed"; + CHECK_EQ(cxt->g0, 1) << "TestTime failed"; start = absl::Now(); if (use_cv) { cxt->cv.WaitWithTimeout(&cxt->mu, absl::Seconds(1)); } else { - ABSL_RAW_CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1)), - "TestTime failed"); + CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1))) + << "TestTime failed"; } elapsed = absl::Now() - start; - ABSL_RAW_CHECK( - absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0), - "TestTime failed"); + CHECK(absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0)) + << "TestTime failed"; cxt->g0++; if (use_cv) { cxt->cv.Signal(); @@ -263,26 +273,24 @@ static void TestTime(TestContext *cxt, int c, bool use_cv) { if (use_cv) { cxt->cv.WaitWithTimeout(&cxt->mu, absl::Seconds(4)); } else { - ABSL_RAW_CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(4)), - "TestTime failed"); + CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(4))) + << "TestTime failed"; } elapsed = absl::Now() - start; - ABSL_RAW_CHECK( - absl::Seconds(3.9) <= elapsed && elapsed <= absl::Seconds(6.0), - "TestTime failed"); - ABSL_RAW_CHECK(cxt->g0 >= 3, "TestTime failed"); + CHECK(absl::Seconds(3.9) <= elapsed && elapsed <= absl::Seconds(6.0)) + << "TestTime failed"; + CHECK_GE(cxt->g0, 3) << "TestTime failed"; start = absl::Now(); if (use_cv) { cxt->cv.WaitWithTimeout(&cxt->mu, absl::Seconds(1)); } else { - ABSL_RAW_CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1)), - "TestTime failed"); + CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1))) + << "TestTime failed"; } elapsed = absl::Now() - start; - ABSL_RAW_CHECK( - absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0), - "TestTime failed"); + CHECK(absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0)) + << "TestTime failed"; if (use_cv) { cxt->cv.SignalAll(); } @@ -291,14 +299,13 @@ static void TestTime(TestContext *cxt, int c, bool use_cv) { if (use_cv) { cxt->cv.WaitWithTimeout(&cxt->mu, absl::Seconds(1)); } else { - ABSL_RAW_CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1)), - "TestTime failed"); + CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Seconds(1))) + << "TestTime failed"; } elapsed = absl::Now() - start; - ABSL_RAW_CHECK( - absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0), - "TestTime failed"); - ABSL_RAW_CHECK(cxt->g0 == cxt->threads, "TestTime failed"); + CHECK(absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0)) + << "TestTime failed"; + CHECK_EQ(cxt->g0, cxt->threads) << "TestTime failed"; } else if (c == 1) { absl::MutexLock l(&cxt->mu); @@ -306,14 +313,12 @@ static void TestTime(TestContext *cxt, int c, bool use_cv) { if (use_cv) { cxt->cv.WaitWithTimeout(&cxt->mu, absl::Milliseconds(500)); } else { - ABSL_RAW_CHECK( - !cxt->mu.AwaitWithTimeout(false_cond, absl::Milliseconds(500)), - "TestTime failed"); + CHECK(!cxt->mu.AwaitWithTimeout(false_cond, absl::Milliseconds(500))) + << "TestTime failed"; } const absl::Duration elapsed = absl::Now() - start; - ABSL_RAW_CHECK( - absl::Seconds(0.4) <= elapsed && elapsed <= absl::Seconds(0.9), - "TestTime failed"); + CHECK(absl::Seconds(0.4) <= elapsed && elapsed <= absl::Seconds(0.9)) + << "TestTime failed"; cxt->g0++; } else if (c == 2) { absl::MutexLock l(&cxt->mu); @@ -322,8 +327,8 @@ static void TestTime(TestContext *cxt, int c, bool use_cv) { cxt->cv.WaitWithTimeout(&cxt->mu, absl::Seconds(100)); } } else { - ABSL_RAW_CHECK(cxt->mu.AwaitWithTimeout(g0ge2, absl::Seconds(100)), - "TestTime failed"); + CHECK(cxt->mu.AwaitWithTimeout(g0ge2, absl::Seconds(100))) + << "TestTime failed"; } cxt->g0++; } else { @@ -395,13 +400,12 @@ static int RunTestWithInvariantDebugging(void (*test)(TestContext *cxt, int), int threads, int iterations, int operations, void (*invariant)(void *)) { - absl::EnableMutexInvariantDebugging(true); + ScopedInvariantDebugging scoped_debugging; SetInvariantChecked(false); TestContext cxt; cxt.mu.EnableInvariantDebugging(invariant, &cxt); int ret = RunTestCommon(&cxt, test, threads, iterations, operations); - ABSL_RAW_CHECK(GetInvariantChecked(), "Invariant not checked"); - absl::EnableMutexInvariantDebugging(false); // Restore. + CHECK(GetInvariantChecked()) << "Invariant not checked"; return ret; } #endif @@ -872,6 +876,120 @@ TEST(Mutex, LockedMutexDestructionBug) ABSL_NO_THREAD_SAFETY_ANALYSIS { } } +// Some functions taking pointers to non-const. +bool Equals42(int *p) { return *p == 42; } +bool Equals43(int *p) { return *p == 43; } + +// Some functions taking pointers to const. +bool ConstEquals42(const int *p) { return *p == 42; } +bool ConstEquals43(const int *p) { return *p == 43; } + +// Some function templates taking pointers. Note it's possible for `T` to be +// deduced as non-const or const, which creates the potential for ambiguity, +// but which the implementation is careful to avoid. +template <typename T> +bool TemplateEquals42(T *p) { + return *p == 42; +} +template <typename T> +bool TemplateEquals43(T *p) { + return *p == 43; +} + +TEST(Mutex, FunctionPointerCondition) { + // Some arguments. + int x = 42; + const int const_x = 42; + + // Parameter non-const, argument non-const. + EXPECT_TRUE(absl::Condition(Equals42, &x).Eval()); + EXPECT_FALSE(absl::Condition(Equals43, &x).Eval()); + + // Parameter const, argument non-const. + EXPECT_TRUE(absl::Condition(ConstEquals42, &x).Eval()); + EXPECT_FALSE(absl::Condition(ConstEquals43, &x).Eval()); + + // Parameter const, argument const. + EXPECT_TRUE(absl::Condition(ConstEquals42, &const_x).Eval()); + EXPECT_FALSE(absl::Condition(ConstEquals43, &const_x).Eval()); + + // Parameter type deduced, argument non-const. + EXPECT_TRUE(absl::Condition(TemplateEquals42, &x).Eval()); + EXPECT_FALSE(absl::Condition(TemplateEquals43, &x).Eval()); + + // Parameter type deduced, argument const. + EXPECT_TRUE(absl::Condition(TemplateEquals42, &const_x).Eval()); + EXPECT_FALSE(absl::Condition(TemplateEquals43, &const_x).Eval()); + + // Parameter non-const, argument const is not well-formed. + EXPECT_FALSE((std::is_constructible<absl::Condition, decltype(Equals42), + decltype(&const_x)>::value)); + // Validate use of is_constructible by contrasting to a well-formed case. + EXPECT_TRUE((std::is_constructible<absl::Condition, decltype(ConstEquals42), + decltype(&const_x)>::value)); +} + +// Example base and derived class for use in predicates and test below. Not a +// particularly realistic example, but it suffices for testing purposes. +struct Base { + explicit Base(int v) : value(v) {} + int value; +}; +struct Derived : Base { + explicit Derived(int v) : Base(v) {} +}; + +// Some functions taking pointer to non-const `Base`. +bool BaseEquals42(Base *p) { return p->value == 42; } +bool BaseEquals43(Base *p) { return p->value == 43; } + +// Some functions taking pointer to const `Base`. +bool ConstBaseEquals42(const Base *p) { return p->value == 42; } +bool ConstBaseEquals43(const Base *p) { return p->value == 43; } + +TEST(Mutex, FunctionPointerConditionWithDerivedToBaseConversion) { + // Some arguments. + Derived derived(42); + const Derived const_derived(42); + + // Parameter non-const base, argument derived non-const. + EXPECT_TRUE(absl::Condition(BaseEquals42, &derived).Eval()); + EXPECT_FALSE(absl::Condition(BaseEquals43, &derived).Eval()); + + // Parameter const base, argument derived non-const. + EXPECT_TRUE(absl::Condition(ConstBaseEquals42, &derived).Eval()); + EXPECT_FALSE(absl::Condition(ConstBaseEquals43, &derived).Eval()); + + // Parameter const base, argument derived const. + EXPECT_TRUE(absl::Condition(ConstBaseEquals42, &const_derived).Eval()); + EXPECT_FALSE(absl::Condition(ConstBaseEquals43, &const_derived).Eval()); + + // Parameter const base, argument derived const. + EXPECT_TRUE(absl::Condition(ConstBaseEquals42, &const_derived).Eval()); + EXPECT_FALSE(absl::Condition(ConstBaseEquals43, &const_derived).Eval()); + + // Parameter derived, argument base is not well-formed. + bool (*derived_pred)(const Derived *) = [](const Derived *) { return true; }; + EXPECT_FALSE((std::is_constructible<absl::Condition, decltype(derived_pred), + Base *>::value)); + EXPECT_FALSE((std::is_constructible<absl::Condition, decltype(derived_pred), + const Base *>::value)); + // Validate use of is_constructible by contrasting to well-formed cases. + EXPECT_TRUE((std::is_constructible<absl::Condition, decltype(derived_pred), + Derived *>::value)); + EXPECT_TRUE((std::is_constructible<absl::Condition, decltype(derived_pred), + const Derived *>::value)); +} + +struct Constable { + bool WotsAllThisThen() const { return true; } +}; + +TEST(Mutex, FunctionPointerConditionWithConstMethod) { + const Constable chapman; + EXPECT_TRUE(absl::Condition(&chapman, &Constable::WotsAllThisThen).Eval()); +} + struct True { template <class... Args> bool operator()(Args...) const { @@ -920,6 +1038,19 @@ TEST(Mutex, FunctorCondition) { } } +TEST(Mutex, ConditionSwap) { + // Ensure that Conditions can be swap'ed. + bool b1 = true; + absl::Condition c1(&b1); + bool b2 = false; + absl::Condition c2(&b2); + EXPECT_TRUE(c1.Eval()); + EXPECT_FALSE(c2.Eval()); + std::swap(c1, c2); + EXPECT_FALSE(c1.Eval()); + EXPECT_TRUE(c2.Eval()); +} + // -------------------------------------------------------- // Test for bug with pattern of readers using a condvar. The bug was that if a // reader went to sleep on a condition variable while one or more other readers @@ -988,7 +1119,7 @@ static bool ConditionWithAcquire(AcquireFromConditionStruct *x) { absl::Milliseconds(100)); x->mu1.Unlock(); } - ABSL_RAW_CHECK(x->value < 4, "should not be invoked a fourth time"); + CHECK_LT(x->value, 4) << "should not be invoked a fourth time"; // We arrange for the condition to return true on only the 2nd and 3rd calls. return x->value == 2 || x->value == 3; @@ -1131,6 +1262,25 @@ TEST(Mutex, DeadlockDetectorBazelWarning) { absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kAbort); } +TEST(Mutex, DeadlockDetectorLongCycle) { + absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kReport); + + // This test generates a warning if it passes, and crashes otherwise. + // Cause bazel to ignore the warning. + ScopedDisableBazelTestWarnings disable_bazel_test_warnings; + + // Check that we survive a deadlock with a lock cycle. + std::vector<absl::Mutex> mutex(100); + for (size_t i = 0; i != mutex.size(); i++) { + mutex[i].Lock(); + mutex[(i + 1) % mutex.size()].Lock(); + mutex[i].Unlock(); + mutex[(i + 1) % mutex.size()].Unlock(); + } + + absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kAbort); +} + // This test is tagged with NO_THREAD_SAFETY_ANALYSIS because the // annotation-based static thread-safety analysis is not currently // predicate-aware and cannot tell if the two for-loops that acquire and @@ -1216,11 +1366,9 @@ static bool DelayIsWithinBounds(absl::Duration expected_delay, // different clock than absl::Now(), but these cases should be handled by the // the retry mechanism in each TimeoutTest. if (actual_delay < expected_delay) { - ABSL_RAW_LOG(WARNING, - "Actual delay %s was too short, expected %s (difference %s)", - absl::FormatDuration(actual_delay).c_str(), - absl::FormatDuration(expected_delay).c_str(), - absl::FormatDuration(actual_delay - expected_delay).c_str()); + LOG(WARNING) << "Actual delay " << actual_delay + << " was too short, expected " << expected_delay + << " (difference " << actual_delay - expected_delay << ")"; pass = false; } // If the expected delay is <= zero then allow a small error tolerance, since @@ -1231,11 +1379,9 @@ static bool DelayIsWithinBounds(absl::Duration expected_delay, ? absl::Milliseconds(10) : TimeoutTestAllowedSchedulingDelay(); if (actual_delay > expected_delay + tolerance) { - ABSL_RAW_LOG(WARNING, - "Actual delay %s was too long, expected %s (difference %s)", - absl::FormatDuration(actual_delay).c_str(), - absl::FormatDuration(expected_delay).c_str(), - absl::FormatDuration(actual_delay - expected_delay).c_str()); + LOG(WARNING) << "Actual delay " << actual_delay + << " was too long, expected " << expected_delay + << " (difference " << actual_delay - expected_delay << ")"; pass = false; } return pass; @@ -1285,12 +1431,6 @@ std::ostream &operator<<(std::ostream &os, const TimeoutTestParam ¶m) { << " expected_delay: " << param.expected_delay; } -std::string FormatString(const TimeoutTestParam ¶m) { - std::ostringstream os; - os << param; - return os.str(); -} - // Like `thread::Executor::ScheduleAt` except: // a) Delays zero or negative are executed immediately in the current thread. // b) Infinite delays are never scheduled. @@ -1420,13 +1560,13 @@ INSTANTIATE_TEST_SUITE_P(All, TimeoutTest, TEST_P(TimeoutTest, Await) { const TimeoutTestParam params = GetParam(); - ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str()); + LOG(INFO) << "Params: " << params; // Because this test asserts bounds on scheduling delays it is flaky. To // compensate it loops forever until it passes. Failures express as test // timeouts, in which case the test log can be used to diagnose the issue. for (int attempt = 1;; ++attempt) { - ABSL_RAW_LOG(INFO, "Attempt %d", attempt); + LOG(INFO) << "Attempt " << attempt; absl::Mutex mu; bool value = false; // condition value (under mu) @@ -1454,13 +1594,13 @@ TEST_P(TimeoutTest, Await) { TEST_P(TimeoutTest, LockWhen) { const TimeoutTestParam params = GetParam(); - ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str()); + LOG(INFO) << "Params: " << params; // Because this test asserts bounds on scheduling delays it is flaky. To // compensate it loops forever until it passes. Failures express as test // timeouts, in which case the test log can be used to diagnose the issue. for (int attempt = 1;; ++attempt) { - ABSL_RAW_LOG(INFO, "Attempt %d", attempt); + LOG(INFO) << "Attempt " << attempt; absl::Mutex mu; bool value = false; // condition value (under mu) @@ -1489,13 +1629,13 @@ TEST_P(TimeoutTest, LockWhen) { TEST_P(TimeoutTest, ReaderLockWhen) { const TimeoutTestParam params = GetParam(); - ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str()); + LOG(INFO) << "Params: " << params; // Because this test asserts bounds on scheduling delays it is flaky. To // compensate it loops forever until it passes. Failures express as test // timeouts, in which case the test log can be used to diagnose the issue. for (int attempt = 0;; ++attempt) { - ABSL_RAW_LOG(INFO, "Attempt %d", attempt); + LOG(INFO) << "Attempt " << attempt; absl::Mutex mu; bool value = false; // condition value (under mu) @@ -1525,13 +1665,13 @@ TEST_P(TimeoutTest, ReaderLockWhen) { TEST_P(TimeoutTest, Wait) { const TimeoutTestParam params = GetParam(); - ABSL_RAW_LOG(INFO, "Params: %s", FormatString(params).c_str()); + LOG(INFO) << "Params: " << params; // Because this test asserts bounds on scheduling delays it is flaky. To // compensate it loops forever until it passes. Failures express as test // timeouts, in which case the test log can be used to diagnose the issue. for (int attempt = 0;; ++attempt) { - ABSL_RAW_LOG(INFO, "Attempt %d", attempt); + LOG(INFO) << "Attempt " << attempt; absl::Mutex mu; bool value = false; // condition value (under mu) @@ -1582,6 +1722,61 @@ TEST(Mutex, Logging) { logged_cv.SignalAll(); } +TEST(Mutex, LoggingAddressReuse) { + // Repeatedly re-create a Mutex with debug logging at the same address. + ScopedInvariantDebugging scoped_debugging; + alignas(absl::Mutex) char storage[sizeof(absl::Mutex)]; + auto invariant = + +[](void *alive) { EXPECT_TRUE(*static_cast<bool *>(alive)); }; + constexpr size_t kIters = 10; + bool alive[kIters] = {}; + for (size_t i = 0; i < kIters; ++i) { + absl::Mutex *mu = new (storage) absl::Mutex; + alive[i] = true; + mu->EnableDebugLog("Mutex"); + mu->EnableInvariantDebugging(invariant, &alive[i]); + mu->Lock(); + mu->Unlock(); + mu->~Mutex(); + alive[i] = false; + } +} + +TEST(Mutex, LoggingBankrupcy) { + // Test the case with too many live Mutexes with debug logging. + ScopedInvariantDebugging scoped_debugging; + std::vector<absl::Mutex> mus(1 << 20); + for (auto &mu : mus) { + mu.EnableDebugLog("Mutex"); + } +} + +TEST(Mutex, SynchEventRace) { + // Regression test for a false TSan race report in + // EnableInvariantDebugging/EnableDebugLog related to SynchEvent reuse. + ScopedInvariantDebugging scoped_debugging; + std::vector<std::thread> threads; + for (size_t i = 0; i < 5; i++) { + threads.emplace_back([&] { + for (size_t j = 0; j < (1 << 17); j++) { + { + absl::Mutex mu; + mu.EnableInvariantDebugging([](void *) {}, nullptr); + mu.Lock(); + mu.Unlock(); + } + { + absl::Mutex mu; + mu.EnableDebugLog("Mutex"); + } + } + }); + } + for (auto &thread : threads) { + thread.join(); + } +} + // -------------------------------------------------------- // Generate the vector of thread counts for tests parameterized on thread count. @@ -1730,4 +1925,113 @@ TEST(Mutex, SignalExitedThread) { for (auto &th : top) th.join(); } +TEST(Mutex, WriterPriority) { + absl::Mutex mu; + bool wrote = false; + std::atomic<bool> saw_wrote{false}; + auto readfunc = [&]() { + for (size_t i = 0; i < 10; ++i) { + absl::ReaderMutexLock lock(&mu); + if (wrote) { + saw_wrote = true; + break; + } + absl::SleepFor(absl::Seconds(1)); + } + }; + std::thread t1(readfunc); + absl::SleepFor(absl::Milliseconds(500)); + std::thread t2(readfunc); + // Note: this test guards against a bug that was related to an uninit + // PerThreadSynch::priority, so the writer intentionally runs on a new thread. + std::thread t3([&]() { + // The writer should be able squeeze between the two alternating readers. + absl::MutexLock lock(&mu); + wrote = true; + }); + t1.join(); + t2.join(); + t3.join(); + EXPECT_TRUE(saw_wrote.load()); +} + +#ifdef ABSL_HAVE_PTHREAD_GETSCHEDPARAM +TEST(Mutex, CondVarPriority) { + // A regression test for a bug in condition variable wait morphing, + // which resulted in the waiting thread getting priority of the waking thread. + int err = 0; + sched_param param; + param.sched_priority = 7; + std::thread test([&]() { + err = pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m); + }); + test.join(); + if (err) { + // Setting priority usually requires special privileges. + GTEST_SKIP() << "failed to set priority: " << strerror(err); + } + absl::Mutex mu; + absl::CondVar cv; + bool locked = false; + bool notified = false; + bool waiting = false; + bool morph = false; + std::thread th([&]() { + EXPECT_EQ(0, pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m)); + mu.Lock(); + locked = true; + mu.Await(absl::Condition(¬ified)); + mu.Unlock(); + EXPECT_EQ(absl::synchronization_internal::GetOrCreateCurrentThreadIdentity() + ->per_thread_synch.priority, + param.sched_priority); + mu.Lock(); + mu.Await(absl::Condition(&waiting)); + morph = true; + absl::SleepFor(absl::Seconds(1)); + cv.Signal(); + mu.Unlock(); + }); + mu.Lock(); + mu.Await(absl::Condition(&locked)); + notified = true; + mu.Unlock(); + mu.Lock(); + waiting = true; + while (!morph) { + cv.Wait(&mu); + } + mu.Unlock(); + th.join(); + EXPECT_NE(absl::synchronization_internal::GetOrCreateCurrentThreadIdentity() + ->per_thread_synch.priority, + param.sched_priority); +} +#endif + +TEST(Mutex, LockWhenWithTimeoutResult) { + // Check various corner cases for Await/LockWhen return value + // with always true/always false conditions. + absl::Mutex mu; + const bool kAlwaysTrue = true, kAlwaysFalse = false; + const absl::Condition kTrueCond(&kAlwaysTrue), kFalseCond(&kAlwaysFalse); + EXPECT_TRUE(mu.LockWhenWithTimeout(kTrueCond, absl::Milliseconds(1))); + mu.Unlock(); + EXPECT_FALSE(mu.LockWhenWithTimeout(kFalseCond, absl::Milliseconds(1))); + EXPECT_TRUE(mu.AwaitWithTimeout(kTrueCond, absl::Milliseconds(1))); + EXPECT_FALSE(mu.AwaitWithTimeout(kFalseCond, absl::Milliseconds(1))); + std::thread th1([&]() { + EXPECT_TRUE(mu.LockWhenWithTimeout(kTrueCond, absl::Milliseconds(1))); + mu.Unlock(); + }); + std::thread th2([&]() { + EXPECT_FALSE(mu.LockWhenWithTimeout(kFalseCond, absl::Milliseconds(1))); + mu.Unlock(); + }); + absl::SleepFor(absl::Milliseconds(100)); + mu.Unlock(); + th1.join(); + th2.join(); +} + } // namespace diff --git a/absl/synchronization/notification_test.cc b/absl/synchronization/notification_test.cc index 100ea76f..49ce61a5 100644 --- a/absl/synchronization/notification_test.cc +++ b/absl/synchronization/notification_test.cc @@ -79,7 +79,7 @@ static void BasicTests(bool notify_before_waiting, Notification* notification) { // Allow for a slight early return, to account for quality of implementation // issues on various platforms. - const absl::Duration slop = absl::Microseconds(200); + const absl::Duration slop = absl::Milliseconds(5); EXPECT_LE(delay - slop, elapsed) << "WaitForNotificationWithTimeout returned " << delay - elapsed << " early (with " << slop << " slop), start time was " << start; diff --git a/absl/time/BUILD.bazel b/absl/time/BUILD.bazel index c7b07c2f..e3fe705b 100644 --- a/absl/time/BUILD.bazel +++ b/absl/time/BUILD.bazel @@ -21,7 +21,14 @@ load( "ABSL_TEST_COPTS", ) -package(default_visibility = ["//visibility:public"]) +package( + default_visibility = ["//visibility:public"], + features = [ + "header_modules", + "layering_check", + "parse_headers", + ], +) licenses(["notice"]) @@ -91,7 +98,9 @@ cc_test( "//absl/base:config", "//absl/base:core_headers", "//absl/numeric:int128", + "//absl/strings:str_format", "//absl/time/internal/cctz:time_zone", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -116,6 +125,7 @@ cc_test( ":time", "//absl/flags:flag", "//absl/flags:reflection", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/time/CMakeLists.txt b/absl/time/CMakeLists.txt index 7b720540..e1ade7a3 100644 --- a/absl/time/CMakeLists.txt +++ b/absl/time/CMakeLists.txt @@ -54,10 +54,6 @@ absl_cc_library( ${ABSL_DEFAULT_COPTS} ) -if(APPLE) - find_library(CoreFoundation CoreFoundation) -endif() - absl_cc_library( NAME time_zone @@ -84,7 +80,10 @@ absl_cc_library( COPTS ${ABSL_DEFAULT_COPTS} DEPS - $<$<PLATFORM_ID:Darwin>:${CoreFoundation}> + Threads::Threads + # TODO(#1495): Use $<LINK_LIBRARY:FRAMEWORK,CoreFoundation> once our + # minimum CMake version >= 3.24 + $<$<PLATFORM_ID:Darwin>:-Wl,-framework,CoreFoundation> ) # Internal-only target, do not depend on directly. @@ -122,6 +121,8 @@ absl_cc_test( absl::time absl::config absl::core_headers + absl::strings + absl::str_format absl::time_zone GTest::gmock_main ) diff --git a/absl/time/civil_time.h b/absl/time/civil_time.h index 5855bc73..3e904a11 100644 --- a/absl/time/civil_time.h +++ b/absl/time/civil_time.h @@ -462,6 +462,32 @@ std::string FormatCivilTime(CivilDay c); std::string FormatCivilTime(CivilMonth c); std::string FormatCivilTime(CivilYear c); +// Support for StrFormat(), StrCat(), etc +template <typename Sink> +void AbslStringify(Sink& sink, CivilSecond c) { + sink.Append(FormatCivilTime(c)); +} +template <typename Sink> +void AbslStringify(Sink& sink, CivilMinute c) { + sink.Append(FormatCivilTime(c)); +} +template <typename Sink> +void AbslStringify(Sink& sink, CivilHour c) { + sink.Append(FormatCivilTime(c)); +} +template <typename Sink> +void AbslStringify(Sink& sink, CivilDay c) { + sink.Append(FormatCivilTime(c)); +} +template <typename Sink> +void AbslStringify(Sink& sink, CivilMonth c) { + sink.Append(FormatCivilTime(c)); +} +template <typename Sink> +void AbslStringify(Sink& sink, CivilYear c) { + sink.Append(FormatCivilTime(c)); +} + // absl::ParseCivilTime() // // Parses a civil-time value from the specified `absl::string_view` into the diff --git a/absl/time/civil_time_benchmark.cc b/absl/time/civil_time_benchmark.cc index f04dbe20..2de0233d 100644 --- a/absl/time/civil_time_benchmark.cc +++ b/absl/time/civil_time_benchmark.cc @@ -14,7 +14,9 @@ #include "absl/time/civil_time.h" +#include <cstddef> #include <numeric> +#include <string> #include <vector> #include "absl/hash/hash.h" @@ -42,7 +44,7 @@ void BM_Difference_Days(benchmark::State& state) { const absl::CivilDay c(2014, 8, 22); const absl::CivilDay epoch(1970, 1, 1); while (state.KeepRunning()) { - const absl::civil_diff_t n = c - epoch; + absl::civil_diff_t n = c - epoch; benchmark::DoNotOptimize(n); } } @@ -60,7 +62,7 @@ BENCHMARK(BM_Step_Days); void BM_Format(benchmark::State& state) { const absl::CivilSecond c(2014, 1, 2, 3, 4, 5); while (state.KeepRunning()) { - const std::string s = absl::FormatCivilTime(c); + std::string s = absl::FormatCivilTime(c); benchmark::DoNotOptimize(s); } } @@ -70,7 +72,7 @@ void BM_Parse(benchmark::State& state) { const std::string f = "2014-01-02T03:04:05"; absl::CivilSecond c; while (state.KeepRunning()) { - const bool b = absl::ParseCivilTime(f, &c); + bool b = absl::ParseCivilTime(f, &c); benchmark::DoNotOptimize(b); } } @@ -80,7 +82,7 @@ void BM_RoundTripFormatParse(benchmark::State& state) { const absl::CivilSecond c(2014, 1, 2, 3, 4, 5); absl::CivilSecond out; while (state.KeepRunning()) { - const bool b = absl::ParseCivilTime(absl::FormatCivilTime(c), &out); + bool b = absl::ParseCivilTime(absl::FormatCivilTime(c), &out); benchmark::DoNotOptimize(b); } } @@ -95,7 +97,8 @@ void BM_CivilTimeAbslHash(benchmark::State& state) { absl::Hash<T> absl_hasher; while (state.KeepRunningBatch(kSize)) { for (const T civil_time : civil_times) { - benchmark::DoNotOptimize(absl_hasher(civil_time)); + size_t hash = absl_hasher(civil_time); + benchmark::DoNotOptimize(hash); } } } diff --git a/absl/time/civil_time_test.cc b/absl/time/civil_time_test.cc index 0ebd97ad..19292a9c 100644 --- a/absl/time/civil_time_test.cc +++ b/absl/time/civil_time_test.cc @@ -14,12 +14,14 @@ #include "absl/time/civil_time.h" +#include <iomanip> #include <limits> #include <sstream> #include <type_traits> -#include "absl/base/macros.h" #include "gtest/gtest.h" +#include "absl/base/macros.h" +#include "absl/strings/str_format.h" namespace { @@ -868,6 +870,23 @@ TEST(CivilTime, ParseEdgeCases) { EXPECT_FALSE(absl::ParseLenientCivilTime("9223372036854775808", &y)) << y; } +TEST(CivilTime, AbslStringify) { + EXPECT_EQ("2015-01-02T03:04:05", + absl::StrFormat("%v", absl::CivilSecond(2015, 1, 2, 3, 4, 5))); + + EXPECT_EQ("2015-01-02T03:04", + absl::StrFormat("%v", absl::CivilMinute(2015, 1, 2, 3, 4))); + + EXPECT_EQ("2015-01-02T03", + absl::StrFormat("%v", absl::CivilHour(2015, 1, 2, 3))); + + EXPECT_EQ("2015-01-02", absl::StrFormat("%v", absl::CivilDay(2015, 1, 2))); + + EXPECT_EQ("2015-01", absl::StrFormat("%v", absl::CivilMonth(2015, 1))); + + EXPECT_EQ("2015", absl::StrFormat("%v", absl::CivilYear(2015))); +} + TEST(CivilTime, OutputStream) { absl::CivilSecond cs(2016, 2, 3, 4, 5, 6); { @@ -1228,7 +1247,7 @@ TEST(CivilTime, DocumentationExample) { EXPECT_EQ(0, day_floor.hour()); // 09:09:09 is floored EXPECT_EQ(absl::CivilDay(2015, 1, 2), day_floor); - // Unspecified fields default to their minium value + // Unspecified fields default to their minimum value absl::CivilDay day_default(2015); // Defaults to Jan 1 EXPECT_EQ(absl::CivilDay(2015, 1, 1), day_default); diff --git a/absl/time/clock.cc b/absl/time/clock.cc index 2bf53d9c..aa74367b 100644 --- a/absl/time/clock.cc +++ b/absl/time/clock.cc @@ -48,17 +48,16 @@ Time Now() { ABSL_NAMESPACE_END } // namespace absl -// Decide if we should use the fast GetCurrentTimeNanos() algorithm -// based on the cyclecounter, otherwise just get the time directly -// from the OS on every call. This can be chosen at compile-time via +// Decide if we should use the fast GetCurrentTimeNanos() algorithm based on the +// cyclecounter, otherwise just get the time directly from the OS on every call. +// By default, the fast algorithm based on the cyclecount is disabled because in +// certain situations, for example, if the OS enters a "sleep" mode, it may +// produce incorrect values immediately upon waking. +// This can be chosen at compile-time via // -DABSL_USE_CYCLECLOCK_FOR_GET_CURRENT_TIME_NANOS=[0|1] #ifndef ABSL_USE_CYCLECLOCK_FOR_GET_CURRENT_TIME_NANOS -#if ABSL_USE_UNSCALED_CYCLECLOCK -#define ABSL_USE_CYCLECLOCK_FOR_GET_CURRENT_TIME_NANOS 1 -#else #define ABSL_USE_CYCLECLOCK_FOR_GET_CURRENT_TIME_NANOS 0 #endif -#endif #if defined(__APPLE__) || defined(_WIN32) #include "absl/time/internal/get_current_time_chrono.inc" diff --git a/absl/time/clock.h b/absl/time/clock.h index 5fe244d6..41d2cf27 100644 --- a/absl/time/clock.h +++ b/absl/time/clock.h @@ -22,6 +22,9 @@ #ifndef ABSL_TIME_CLOCK_H_ #define ABSL_TIME_CLOCK_H_ +#include <cstdint> + +#include "absl/base/config.h" #include "absl/base/macros.h" #include "absl/time/time.h" @@ -64,7 +67,8 @@ ABSL_NAMESPACE_END // By changing our extension points to be extern "C", we dodge this // check. extern "C" { -void ABSL_INTERNAL_C_SYMBOL(AbslInternalSleepFor)(absl::Duration duration); +ABSL_DLL void ABSL_INTERNAL_C_SYMBOL(AbslInternalSleepFor)( + absl::Duration duration); } // extern "C" inline void absl::SleepFor(absl::Duration duration) { diff --git a/absl/time/duration.cc b/absl/time/duration.cc index 911e80f8..bdb16e21 100644 --- a/absl/time/duration.cc +++ b/absl/time/duration.cc @@ -55,8 +55,7 @@ #include <algorithm> #include <cassert> -#include <cctype> -#include <cerrno> +#include <chrono> // NOLINT(build/c++11) #include <cmath> #include <cstdint> #include <cstdlib> @@ -66,8 +65,9 @@ #include <limits> #include <string> +#include "absl/base/attributes.h" #include "absl/base/casts.h" -#include "absl/base/macros.h" +#include "absl/base/config.h" #include "absl/numeric/int128.h" #include "absl/strings/string_view.h" #include "absl/strings/strip.h" @@ -96,13 +96,6 @@ inline bool IsValidDivisor(double d) { return d != 0.0; } -// Can't use std::round() because it is only available in C++11. -// Note that we ignore the possibility of floating-point over/underflow. -template <typename Double> -inline double Round(Double d) { - return d < 0 ? std::ceil(d - 0.5) : std::floor(d + 0.5); -} - // *sec may be positive or negative. *ticks must be in the range // -kTicksPerSecond < *ticks < kTicksPerSecond. If *ticks is negative it // will be normalized to a positive value by adjusting *sec accordingly. @@ -260,7 +253,7 @@ inline Duration ScaleDouble(Duration d, double r) { double lo_frac = std::modf(lo_doub, &lo_int); // Rolls lo into hi if necessary. - int64_t lo64 = Round(lo_frac * kTicksPerSecond); + int64_t lo64 = std::round(lo_frac * kTicksPerSecond); Duration ans; if (!SafeAddRepHi(hi_int, lo_int, &ans)) return ans; @@ -407,16 +400,18 @@ int64_t IDivDuration(bool satq, const Duration num, const Duration den, Duration& Duration::operator+=(Duration rhs) { if (time_internal::IsInfiniteDuration(*this)) return *this; if (time_internal::IsInfiniteDuration(rhs)) return *this = rhs; - const int64_t orig_rep_hi = rep_hi_; - rep_hi_ = - DecodeTwosComp(EncodeTwosComp(rep_hi_) + EncodeTwosComp(rhs.rep_hi_)); + const int64_t orig_rep_hi = rep_hi_.Get(); + rep_hi_ = DecodeTwosComp(EncodeTwosComp(rep_hi_.Get()) + + EncodeTwosComp(rhs.rep_hi_.Get())); if (rep_lo_ >= kTicksPerSecond - rhs.rep_lo_) { - rep_hi_ = DecodeTwosComp(EncodeTwosComp(rep_hi_) + 1); + rep_hi_ = DecodeTwosComp(EncodeTwosComp(rep_hi_.Get()) + 1); rep_lo_ -= kTicksPerSecond; } rep_lo_ += rhs.rep_lo_; - if (rhs.rep_hi_ < 0 ? rep_hi_ > orig_rep_hi : rep_hi_ < orig_rep_hi) { - return *this = rhs.rep_hi_ < 0 ? -InfiniteDuration() : InfiniteDuration(); + if (rhs.rep_hi_.Get() < 0 ? rep_hi_.Get() > orig_rep_hi + : rep_hi_.Get() < orig_rep_hi) { + return *this = + rhs.rep_hi_.Get() < 0 ? -InfiniteDuration() : InfiniteDuration(); } return *this; } @@ -424,18 +419,21 @@ Duration& Duration::operator+=(Duration rhs) { Duration& Duration::operator-=(Duration rhs) { if (time_internal::IsInfiniteDuration(*this)) return *this; if (time_internal::IsInfiniteDuration(rhs)) { - return *this = rhs.rep_hi_ >= 0 ? -InfiniteDuration() : InfiniteDuration(); + return *this = rhs.rep_hi_.Get() >= 0 ? -InfiniteDuration() + : InfiniteDuration(); } - const int64_t orig_rep_hi = rep_hi_; - rep_hi_ = - DecodeTwosComp(EncodeTwosComp(rep_hi_) - EncodeTwosComp(rhs.rep_hi_)); + const int64_t orig_rep_hi = rep_hi_.Get(); + rep_hi_ = DecodeTwosComp(EncodeTwosComp(rep_hi_.Get()) - + EncodeTwosComp(rhs.rep_hi_.Get())); if (rep_lo_ < rhs.rep_lo_) { - rep_hi_ = DecodeTwosComp(EncodeTwosComp(rep_hi_) - 1); + rep_hi_ = DecodeTwosComp(EncodeTwosComp(rep_hi_.Get()) - 1); rep_lo_ += kTicksPerSecond; } rep_lo_ -= rhs.rep_lo_; - if (rhs.rep_hi_ < 0 ? rep_hi_ < orig_rep_hi : rep_hi_ > orig_rep_hi) { - return *this = rhs.rep_hi_ >= 0 ? -InfiniteDuration() : InfiniteDuration(); + if (rhs.rep_hi_.Get() < 0 ? rep_hi_.Get() < orig_rep_hi + : rep_hi_.Get() > orig_rep_hi) { + return *this = rhs.rep_hi_.Get() >= 0 ? -InfiniteDuration() + : InfiniteDuration(); } return *this; } @@ -446,7 +444,7 @@ Duration& Duration::operator-=(Duration rhs) { Duration& Duration::operator*=(int64_t r) { if (time_internal::IsInfiniteDuration(*this)) { - const bool is_neg = (r < 0) != (rep_hi_ < 0); + const bool is_neg = (r < 0) != (rep_hi_.Get() < 0); return *this = is_neg ? -InfiniteDuration() : InfiniteDuration(); } return *this = ScaleFixed<SafeMultiply>(*this, r); @@ -454,7 +452,7 @@ Duration& Duration::operator*=(int64_t r) { Duration& Duration::operator*=(double r) { if (time_internal::IsInfiniteDuration(*this) || !IsFinite(r)) { - const bool is_neg = (std::signbit(r) != 0) != (rep_hi_ < 0); + const bool is_neg = std::signbit(r) != (rep_hi_.Get() < 0); return *this = is_neg ? -InfiniteDuration() : InfiniteDuration(); } return *this = ScaleDouble<std::multiplies>(*this, r); @@ -462,7 +460,7 @@ Duration& Duration::operator*=(double r) { Duration& Duration::operator/=(int64_t r) { if (time_internal::IsInfiniteDuration(*this) || r == 0) { - const bool is_neg = (r < 0) != (rep_hi_ < 0); + const bool is_neg = (r < 0) != (rep_hi_.Get() < 0); return *this = is_neg ? -InfiniteDuration() : InfiniteDuration(); } return *this = ScaleFixed<std::divides>(*this, r); @@ -470,7 +468,7 @@ Duration& Duration::operator/=(int64_t r) { Duration& Duration::operator/=(double r) { if (time_internal::IsInfiniteDuration(*this) || !IsValidDivisor(r)) { - const bool is_neg = (std::signbit(r) != 0) != (rep_hi_ < 0); + const bool is_neg = std::signbit(r) != (rep_hi_.Get() < 0); return *this = is_neg ? -InfiniteDuration() : InfiniteDuration(); } return *this = ScaleDouble<std::divides>(*this, r); @@ -741,7 +739,7 @@ void AppendNumberUnit(std::string* out, double n, DisplayUnit unit) { char buf[kBufferSize]; // also large enough to hold integer part char* ep = buf + sizeof(buf); double d = 0; - int64_t frac_part = Round(std::modf(n, &d) * unit.pow10); + int64_t frac_part = std::round(std::modf(n, &d) * unit.pow10); int64_t int_part = d; if (int_part != 0 || frac_part != 0) { char* bp = Format64(ep, 0, int_part); // always < 1000 diff --git a/absl/time/duration_test.cc b/absl/time/duration_test.cc index b7abf4ba..dcf7aad6 100644 --- a/absl/time/duration_test.cc +++ b/absl/time/duration_test.cc @@ -16,8 +16,9 @@ #include <winsock2.h> // for timeval #endif -#include <chrono> // NOLINT(build/c++11) +#include <array> #include <cfloat> +#include <chrono> // NOLINT(build/c++11) #include <cmath> #include <cstdint> #include <ctime> @@ -28,6 +29,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/strings/str_format.h" #include "absl/time/time.h" namespace { @@ -349,11 +351,6 @@ TEST(Duration, ToChrono) { } TEST(Duration, FactoryOverloads) { -#if defined(ABSL_SKIP_TIME_TESTS_BROKEN_ON_MSVC_OPT) && \ - ABSL_SKIP_TIME_TESTS_BROKEN_ON_MSVC_OPT - GTEST_SKIP(); -#endif - enum E { kOne = 1 }; #define TEST_FACTORY_OVERLOADS(NAME) \ EXPECT_EQ(1, NAME(kOne) / NAME(kOne)); \ @@ -884,11 +881,6 @@ TEST(Duration, RelationalOperators) { } TEST(Duration, Addition) { -#if defined(ABSL_SKIP_TIME_TESTS_BROKEN_ON_MSVC_OPT) && \ - ABSL_SKIP_TIME_TESTS_BROKEN_ON_MSVC_OPT - GTEST_SKIP(); -#endif - #define TEST_ADD_OPS(UNIT) \ do { \ EXPECT_EQ(UNIT(2), UNIT(1) + UNIT(1)); \ @@ -982,11 +974,6 @@ TEST(Duration, Negation) { } TEST(Duration, AbsoluteValue) { -#if defined(ABSL_SKIP_TIME_TESTS_BROKEN_ON_MSVC_OPT) && \ - ABSL_SKIP_TIME_TESTS_BROKEN_ON_MSVC_OPT - GTEST_SKIP(); -#endif - EXPECT_EQ(absl::ZeroDuration(), AbsDuration(absl::ZeroDuration())); EXPECT_EQ(absl::Seconds(1), AbsDuration(absl::Seconds(1))); EXPECT_EQ(absl::Seconds(1), AbsDuration(absl::Seconds(-1))); @@ -1004,11 +991,6 @@ TEST(Duration, AbsoluteValue) { } TEST(Duration, Multiplication) { -#if defined(ABSL_SKIP_TIME_TESTS_BROKEN_ON_MSVC_OPT) && \ - ABSL_SKIP_TIME_TESTS_BROKEN_ON_MSVC_OPT - GTEST_SKIP(); -#endif - #define TEST_MUL_OPS(UNIT) \ do { \ EXPECT_EQ(UNIT(5), UNIT(2) * 2.5); \ @@ -1261,11 +1243,6 @@ TEST(Duration, RoundTripUnits) { } TEST(Duration, TruncConversions) { -#if defined(ABSL_SKIP_TIME_TESTS_BROKEN_ON_MSVC_OPT) && \ - ABSL_SKIP_TIME_TESTS_BROKEN_ON_MSVC_OPT - GTEST_SKIP(); -#endif - // Tests ToTimespec()/DurationFromTimespec() const struct { absl::Duration d; @@ -1562,11 +1539,6 @@ TEST(Duration, ConversionSaturation) { } TEST(Duration, FormatDuration) { -#if defined(ABSL_SKIP_TIME_TESTS_BROKEN_ON_MSVC_OPT) && \ - ABSL_SKIP_TIME_TESTS_BROKEN_ON_MSVC_OPT - GTEST_SKIP(); -#endif - // Example from Go's docs. EXPECT_EQ("72h3m0.5s", absl::FormatDuration(absl::Hours(72) + absl::Minutes(3) + @@ -1701,11 +1673,6 @@ TEST(Duration, FormatDuration) { } TEST(Duration, ParseDuration) { -#if defined(ABSL_SKIP_TIME_TESTS_BROKEN_ON_MSVC_OPT) && \ - ABSL_SKIP_TIME_TESTS_BROKEN_ON_MSVC_OPT - GTEST_SKIP(); -#endif - absl::Duration d; // No specified unit. Should only work for zero and infinity. @@ -1853,4 +1820,18 @@ TEST(Duration, FormatParseRoundTrip) { #undef TEST_PARSE_ROUNDTRIP } +TEST(Duration, AbslStringify) { + // FormatDuration is already well tested, so just use one test case here to + // verify that StrFormat("%v", d) works as expected. + absl::Duration d = absl::Seconds(1); + EXPECT_EQ(absl::StrFormat("%v", d), absl::FormatDuration(d)); +} + +TEST(Duration, NoPadding) { + // Should match the size of a struct with uint32_t alignment and no padding. + using NoPadding = std::array<uint32_t, 3>; + EXPECT_EQ(sizeof(NoPadding), sizeof(absl::Duration)); + EXPECT_EQ(alignof(NoPadding), alignof(absl::Duration)); +} + } // namespace diff --git a/absl/time/internal/cctz/BUILD.bazel b/absl/time/internal/cctz/BUILD.bazel index edeabd81..0b43bb12 100644 --- a/absl/time/internal/cctz/BUILD.bazel +++ b/absl/time/internal/cctz/BUILD.bazel @@ -12,7 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. -package(features = ["-parse_headers"]) +package(features = [ + "header_modules", + "layering_check", + "parse_headers", +]) licenses(["notice"]) @@ -53,10 +57,11 @@ cc_library( "include/cctz/time_zone.h", "include/cctz/zone_info_source.h", ], - # OS X and iOS no longer use `linkopts = ["-framework CoreFoundation"]` - # as (1) bazel adds it automatically, and (2) it caused problems when - # cross-compiling for Android. - # See https://github.com/abseil/abseil-cpp/issues/326 for details. + linkopts = select({ + "@platforms//os:osx": ["-Wl,-framework,CoreFoundation"], + "@platforms//os:ios": ["-Wl,-framework,CoreFoundation"], + "//conditions:default": [], + }), visibility = ["//visibility:public"], deps = [ ":civil_time", @@ -82,6 +87,7 @@ cc_test( deps = [ ":civil_time", "//absl/base:config", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -102,6 +108,7 @@ cc_test( ":civil_time", ":time_zone", "//absl/base:config", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -123,6 +130,7 @@ cc_test( ":civil_time", ":time_zone", "//absl/base:config", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/time/internal/cctz/include/cctz/civil_time_detail.h b/absl/time/internal/cctz/include/cctz/civil_time_detail.h index a5b084e6..2b0aed56 100644 --- a/absl/time/internal/cctz/include/cctz/civil_time_detail.h +++ b/absl/time/internal/cctz/include/cctz/civil_time_detail.h @@ -401,11 +401,11 @@ class civil_time { : civil_time(ct.f_) {} // Factories for the maximum/minimum representable civil_time. - static CONSTEXPR_F civil_time(max)() { + static CONSTEXPR_F auto(max)() -> civil_time { const auto max_year = (std::numeric_limits<std::int_least64_t>::max)(); return civil_time(max_year, 12, 31, 23, 59, 59); } - static CONSTEXPR_F civil_time(min)() { + static CONSTEXPR_F auto(min)() -> civil_time { const auto min_year = (std::numeric_limits<std::int_least64_t>::min)(); return civil_time(min_year, 1, 1, 0, 0, 0); } diff --git a/absl/time/internal/cctz/include/cctz/time_zone.h b/absl/time/internal/cctz/include/cctz/time_zone.h index 6e382dc6..b2b0cf6f 100644 --- a/absl/time/internal/cctz/include/cctz/time_zone.h +++ b/absl/time/internal/cctz/include/cctz/time_zone.h @@ -23,6 +23,7 @@ #include <chrono> #include <cstdint> #include <limits> +#include <ratio> // NOLINT: We use std::ratio in this header #include <string> #include <utility> diff --git a/absl/time/internal/cctz/src/cctz_benchmark.cc b/absl/time/internal/cctz/src/cctz_benchmark.cc index c64f3801..11f9ba6c 100644 --- a/absl/time/internal/cctz/src/cctz_benchmark.cc +++ b/absl/time/internal/cctz/src/cctz_benchmark.cc @@ -110,7 +110,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Africa/Addis_Ababa", "Africa/Algiers", "Africa/Asmara", - "Africa/Asmera", "Africa/Bamako", "Africa/Bangui", "Africa/Banjul", @@ -166,7 +165,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "America/Araguaina", "America/Argentina/Buenos_Aires", "America/Argentina/Catamarca", - "America/Argentina/ComodRivadavia", "America/Argentina/Cordoba", "America/Argentina/Jujuy", "America/Argentina/La_Rioja", @@ -190,18 +188,16 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "America/Boa_Vista", "America/Bogota", "America/Boise", - "America/Buenos_Aires", "America/Cambridge_Bay", "America/Campo_Grande", "America/Cancun", "America/Caracas", - "America/Catamarca", "America/Cayenne", "America/Cayman", "America/Chicago", "America/Chihuahua", + "America/Ciudad_Juarez", "America/Coral_Harbour", - "America/Cordoba", "America/Costa_Rica", "America/Creston", "America/Cuiaba", @@ -217,7 +213,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "America/El_Salvador", "America/Ensenada", "America/Fort_Nelson", - "America/Fort_Wayne", "America/Fortaleza", "America/Glace_Bay", "America/Godthab", @@ -239,20 +234,16 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "America/Indiana/Vevay", "America/Indiana/Vincennes", "America/Indiana/Winamac", - "America/Indianapolis", "America/Inuvik", "America/Iqaluit", "America/Jamaica", - "America/Jujuy", "America/Juneau", "America/Kentucky/Louisville", "America/Kentucky/Monticello", - "America/Knox_IN", "America/Kralendijk", "America/La_Paz", "America/Lima", "America/Los_Angeles", - "America/Louisville", "America/Lower_Princes", "America/Maceio", "America/Managua", @@ -261,7 +252,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "America/Martinique", "America/Matamoros", "America/Mazatlan", - "America/Mendoza", "America/Menominee", "America/Merida", "America/Metlakatla", @@ -298,7 +288,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "America/Regina", "America/Resolute", "America/Rio_Branco", - "America/Rosario", "America/Santa_Isabel", "America/Santarem", "America/Santiago", @@ -334,7 +323,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Antarctica/McMurdo", "Antarctica/Palmer", "Antarctica/Rothera", - "Antarctica/South_Pole", "Antarctica/Syowa", "Antarctica/Troll", "Antarctica/Vostok", @@ -346,7 +334,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Asia/Aqtau", "Asia/Aqtobe", "Asia/Ashgabat", - "Asia/Ashkhabad", "Asia/Atyrau", "Asia/Baghdad", "Asia/Bahrain", @@ -356,13 +343,10 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Asia/Beirut", "Asia/Bishkek", "Asia/Brunei", - "Asia/Calcutta", "Asia/Chita", "Asia/Choibalsan", "Asia/Chongqing", - "Asia/Chungking", "Asia/Colombo", - "Asia/Dacca", "Asia/Damascus", "Asia/Dhaka", "Asia/Dili", @@ -385,14 +369,12 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Asia/Karachi", "Asia/Kashgar", "Asia/Kathmandu", - "Asia/Katmandu", "Asia/Khandyga", "Asia/Kolkata", "Asia/Krasnoyarsk", "Asia/Kuala_Lumpur", "Asia/Kuching", "Asia/Kuwait", - "Asia/Macao", "Asia/Macau", "Asia/Magadan", "Asia/Makassar", @@ -409,9 +391,7 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Asia/Qatar", "Asia/Qostanay", "Asia/Qyzylorda", - "Asia/Rangoon", "Asia/Riyadh", - "Asia/Saigon", "Asia/Sakhalin", "Asia/Samarkand", "Asia/Seoul", @@ -423,13 +403,10 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Asia/Tbilisi", "Asia/Tehran", "Asia/Tel_Aviv", - "Asia/Thimbu", "Asia/Thimphu", "Asia/Tokyo", "Asia/Tomsk", - "Asia/Ujung_Pandang", "Asia/Ulaanbaatar", - "Asia/Ulan_Bator", "Asia/Urumqi", "Asia/Ust-Nera", "Asia/Vientiane", @@ -442,7 +419,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Atlantic/Bermuda", "Atlantic/Canary", "Atlantic/Cape_Verde", - "Atlantic/Faeroe", "Atlantic/Faroe", "Atlantic/Jan_Mayen", "Atlantic/Madeira", @@ -450,7 +426,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Atlantic/South_Georgia", "Atlantic/St_Helena", "Atlantic/Stanley", - "Australia/ACT", "Australia/Adelaide", "Australia/Brisbane", "Australia/Broken_Hill", @@ -459,42 +434,12 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Australia/Darwin", "Australia/Eucla", "Australia/Hobart", - "Australia/LHI", "Australia/Lindeman", "Australia/Lord_Howe", "Australia/Melbourne", - "Australia/NSW", - "Australia/North", "Australia/Perth", - "Australia/Queensland", - "Australia/South", "Australia/Sydney", - "Australia/Tasmania", - "Australia/Victoria", - "Australia/West", "Australia/Yancowinna", - "Brazil/Acre", - "Brazil/DeNoronha", - "Brazil/East", - "Brazil/West", - "CET", - "CST6CDT", - "Canada/Atlantic", - "Canada/Central", - "Canada/Eastern", - "Canada/Mountain", - "Canada/Newfoundland", - "Canada/Pacific", - "Canada/Saskatchewan", - "Canada/Yukon", - "Chile/Continental", - "Chile/EasterIsland", - "Cuba", - "EET", - "EST", - "EST5EDT", - "Egypt", - "Eire", "Etc/GMT", "Etc/GMT+0", "Etc/GMT+1", @@ -552,7 +497,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Europe/Istanbul", "Europe/Jersey", "Europe/Kaliningrad", - "Europe/Kiev", "Europe/Kirov", "Europe/Kyiv", "Europe/Lisbon", @@ -584,7 +528,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Europe/Tirane", "Europe/Tiraspol", "Europe/Ulyanovsk", - "Europe/Uzhgorod", "Europe/Vaduz", "Europe/Vatican", "Europe/Vienna", @@ -592,19 +535,8 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Europe/Volgograd", "Europe/Warsaw", "Europe/Zagreb", - "Europe/Zaporozhye", "Europe/Zurich", "Factory", - "GB", - "GB-Eire", - "GMT", - "GMT+0", - "GMT-0", - "GMT0", - "Greenwich", - "HST", - "Hongkong", - "Iceland", "Indian/Antananarivo", "Indian/Chagos", "Indian/Christmas", @@ -616,23 +548,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Indian/Mauritius", "Indian/Mayotte", "Indian/Reunion", - "Iran", - "Israel", - "Jamaica", - "Japan", - "Kwajalein", - "Libya", - "MET", - "MST", - "MST7MDT", - "Mexico/BajaNorte", - "Mexico/BajaSur", - "Mexico/General", - "NZ", - "NZ-CHAT", - "Navajo", - "PRC", - "PST8PDT", "Pacific/Apia", "Pacific/Auckland", "Pacific/Bougainville", @@ -640,7 +555,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Pacific/Chuuk", "Pacific/Easter", "Pacific/Efate", - "Pacific/Enderbury", "Pacific/Fakaofo", "Pacific/Fiji", "Pacific/Funafuti", @@ -665,7 +579,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Pacific/Palau", "Pacific/Pitcairn", "Pacific/Pohnpei", - "Pacific/Ponape", "Pacific/Port_Moresby", "Pacific/Rarotonga", "Pacific/Saipan", @@ -673,34 +586,10 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Pacific/Tahiti", "Pacific/Tarawa", "Pacific/Tongatapu", - "Pacific/Truk", "Pacific/Wake", "Pacific/Wallis", "Pacific/Yap", - "Poland", - "Portugal", - "ROC", - "ROK", - "Singapore", - "Turkey", - "UCT", - "US/Alaska", - "US/Aleutian", - "US/Arizona", - "US/Central", - "US/East-Indiana", - "US/Eastern", - "US/Hawaii", - "US/Indiana-Starke", - "US/Michigan", - "US/Mountain", - "US/Pacific", - "US/Samoa", "UTC", - "Universal", - "W-SU", - "WET", - "Zulu", nullptr}; std::vector<std::string> AllTimeZoneNames() { diff --git a/absl/time/internal/cctz/src/time_zone_fixed.cc b/absl/time/internal/cctz/src/time_zone_fixed.cc index f2b3294e..e09654ea 100644 --- a/absl/time/internal/cctz/src/time_zone_fixed.cc +++ b/absl/time/internal/cctz/src/time_zone_fixed.cc @@ -105,7 +105,7 @@ std::string FixedOffsetToName(const seconds& offset) { offset_minutes %= 60; const std::size_t prefix_len = sizeof(kFixedZonePrefix) - 1; char buf[prefix_len + sizeof("-24:00:00")]; - char* ep = std::copy(kFixedZonePrefix, kFixedZonePrefix + prefix_len, buf); + char* ep = std::copy_n(kFixedZonePrefix, prefix_len, buf); *ep++ = sign; ep = Format02d(ep, offset_hours); *ep++ = ':'; diff --git a/absl/time/internal/cctz/src/time_zone_format.cc b/absl/time/internal/cctz/src/time_zone_format.cc index 2e5f5329..e7e30a2f 100644 --- a/absl/time/internal/cctz/src/time_zone_format.cc +++ b/absl/time/internal/cctz/src/time_zone_format.cc @@ -13,14 +13,14 @@ // limitations under the License. #if !defined(HAS_STRPTIME) -#if !defined(_MSC_VER) && !defined(__MINGW32__) -#define HAS_STRPTIME 1 // assume everyone has strptime() except windows +#if !defined(_MSC_VER) && !defined(__MINGW32__) && !defined(__VXWORKS__) +#define HAS_STRPTIME 1 // Assume everyone else has strptime(). #endif #endif #if defined(HAS_STRPTIME) && HAS_STRPTIME -#if !defined(_XOPEN_SOURCE) && !defined(__OpenBSD__) -#define _XOPEN_SOURCE // Definedness suffices for strptime. +#if !defined(_XOPEN_SOURCE) && !defined(__FreeBSD__) && !defined(__OpenBSD__) +#define _XOPEN_SOURCE 500 // Exposes definitions for SUSv2 (UNIX 98). #endif #endif diff --git a/absl/time/internal/cctz/src/time_zone_format_test.cc b/absl/time/internal/cctz/src/time_zone_format_test.cc index f1f79a20..4a6c71f1 100644 --- a/absl/time/internal/cctz/src/time_zone_format_test.cc +++ b/absl/time/internal/cctz/src/time_zone_format_test.cc @@ -64,10 +64,13 @@ const char RFC1123_no_wday[] = "%d %b %Y %H:%M:%S %z"; template <typename D> void TestFormatSpecifier(time_point<D> tp, time_zone tz, const std::string& fmt, const std::string& ans) { - EXPECT_EQ(ans, format(fmt, tp, tz)) << fmt; - EXPECT_EQ("xxx " + ans, format("xxx " + fmt, tp, tz)); - EXPECT_EQ(ans + " yyy", format(fmt + " yyy", tp, tz)); - EXPECT_EQ("xxx " + ans + " yyy", format("xxx " + fmt + " yyy", tp, tz)); + EXPECT_EQ(ans, absl::time_internal::cctz::format(fmt, tp, tz)) << fmt; + EXPECT_EQ("xxx " + ans, + absl::time_internal::cctz::format("xxx " + fmt, tp, tz)); + EXPECT_EQ(ans + " yyy", + absl::time_internal::cctz::format(fmt + " yyy", tp, tz)); + EXPECT_EQ("xxx " + ans + " yyy", + absl::time_internal::cctz::format("xxx " + fmt + " yyy", tp, tz)); } } // namespace @@ -83,26 +86,29 @@ TEST(Format, TimePointResolution) { chrono::system_clock::from_time_t(1420167845) + chrono::milliseconds(123) + chrono::microseconds(456) + chrono::nanoseconds(789); - EXPECT_EQ( - "03:04:05.123456789", - format(kFmt, chrono::time_point_cast<chrono::nanoseconds>(t0), utc)); - EXPECT_EQ( - "03:04:05.123456", - format(kFmt, chrono::time_point_cast<chrono::microseconds>(t0), utc)); - EXPECT_EQ( - "03:04:05.123", - format(kFmt, chrono::time_point_cast<chrono::milliseconds>(t0), utc)); + EXPECT_EQ("03:04:05.123456789", + absl::time_internal::cctz::format( + kFmt, chrono::time_point_cast<chrono::nanoseconds>(t0), utc)); + EXPECT_EQ("03:04:05.123456", + absl::time_internal::cctz::format( + kFmt, chrono::time_point_cast<chrono::microseconds>(t0), utc)); + EXPECT_EQ("03:04:05.123", + absl::time_internal::cctz::format( + kFmt, chrono::time_point_cast<chrono::milliseconds>(t0), utc)); EXPECT_EQ("03:04:05", - format(kFmt, chrono::time_point_cast<chrono::seconds>(t0), utc)); + absl::time_internal::cctz::format( + kFmt, chrono::time_point_cast<chrono::seconds>(t0), utc)); EXPECT_EQ( "03:04:05", - format(kFmt, - chrono::time_point_cast<absl::time_internal::cctz::seconds>(t0), - utc)); + absl::time_internal::cctz::format( + kFmt, chrono::time_point_cast<absl::time_internal::cctz::seconds>(t0), + utc)); EXPECT_EQ("03:04:00", - format(kFmt, chrono::time_point_cast<chrono::minutes>(t0), utc)); + absl::time_internal::cctz::format( + kFmt, chrono::time_point_cast<chrono::minutes>(t0), utc)); EXPECT_EQ("03:00:00", - format(kFmt, chrono::time_point_cast<chrono::hours>(t0), utc)); + absl::time_internal::cctz::format( + kFmt, chrono::time_point_cast<chrono::hours>(t0), utc)); } TEST(Format, TimePointExtendedResolution) { @@ -137,24 +143,28 @@ TEST(Format, Basics) { time_point<chrono::nanoseconds> tp = chrono::system_clock::from_time_t(0); // Starts with a couple basic edge cases. - EXPECT_EQ("", format("", tp, tz)); - EXPECT_EQ(" ", format(" ", tp, tz)); - EXPECT_EQ(" ", format(" ", tp, tz)); - EXPECT_EQ("xxx", format("xxx", tp, tz)); + EXPECT_EQ("", absl::time_internal::cctz::format("", tp, tz)); + EXPECT_EQ(" ", absl::time_internal::cctz::format(" ", tp, tz)); + EXPECT_EQ(" ", absl::time_internal::cctz::format(" ", tp, tz)); + EXPECT_EQ("xxx", absl::time_internal::cctz::format("xxx", tp, tz)); std::string big(128, 'x'); - EXPECT_EQ(big, format(big, tp, tz)); + EXPECT_EQ(big, absl::time_internal::cctz::format(big, tp, tz)); // Cause the 1024-byte buffer to grow. std::string bigger(100000, 'x'); - EXPECT_EQ(bigger, format(bigger, tp, tz)); + EXPECT_EQ(bigger, absl::time_internal::cctz::format(bigger, tp, tz)); tp += chrono::hours(13) + chrono::minutes(4) + chrono::seconds(5); tp += chrono::milliseconds(6) + chrono::microseconds(7) + chrono::nanoseconds(8); - EXPECT_EQ("1970-01-01", format("%Y-%m-%d", tp, tz)); - EXPECT_EQ("13:04:05", format("%H:%M:%S", tp, tz)); - EXPECT_EQ("13:04:05.006", format("%H:%M:%E3S", tp, tz)); - EXPECT_EQ("13:04:05.006007", format("%H:%M:%E6S", tp, tz)); - EXPECT_EQ("13:04:05.006007008", format("%H:%M:%E9S", tp, tz)); + EXPECT_EQ("1970-01-01", + absl::time_internal::cctz::format("%Y-%m-%d", tp, tz)); + EXPECT_EQ("13:04:05", absl::time_internal::cctz::format("%H:%M:%S", tp, tz)); + EXPECT_EQ("13:04:05.006", + absl::time_internal::cctz::format("%H:%M:%E3S", tp, tz)); + EXPECT_EQ("13:04:05.006007", + absl::time_internal::cctz::format("%H:%M:%E6S", tp, tz)); + EXPECT_EQ("13:04:05.006007008", + absl::time_internal::cctz::format("%H:%M:%E9S", tp, tz)); } TEST(Format, PosixConversions) { @@ -211,7 +221,8 @@ TEST(Format, LocaleSpecific) { TestFormatSpecifier(tp, tz, "%B", "January"); // %c should at least produce the numeric year and time-of-day. - const std::string s = format("%c", tp, utc_time_zone()); + const std::string s = + absl::time_internal::cctz::format("%c", tp, utc_time_zone()); EXPECT_THAT(s, testing::HasSubstr("1970")); EXPECT_THAT(s, testing::HasSubstr("00:00:00")); @@ -277,49 +288,61 @@ TEST(Format, ExtendedSeconds) { // No subseconds. time_point<chrono::nanoseconds> tp = chrono::system_clock::from_time_t(0); tp += chrono::seconds(5); - EXPECT_EQ("05", format("%E*S", tp, tz)); - EXPECT_EQ("05", format("%E0S", tp, tz)); - EXPECT_EQ("05.0", format("%E1S", tp, tz)); - EXPECT_EQ("05.00", format("%E2S", tp, tz)); - EXPECT_EQ("05.000", format("%E3S", tp, tz)); - EXPECT_EQ("05.0000", format("%E4S", tp, tz)); - EXPECT_EQ("05.00000", format("%E5S", tp, tz)); - EXPECT_EQ("05.000000", format("%E6S", tp, tz)); - EXPECT_EQ("05.0000000", format("%E7S", tp, tz)); - EXPECT_EQ("05.00000000", format("%E8S", tp, tz)); - EXPECT_EQ("05.000000000", format("%E9S", tp, tz)); - EXPECT_EQ("05.0000000000", format("%E10S", tp, tz)); - EXPECT_EQ("05.00000000000", format("%E11S", tp, tz)); - EXPECT_EQ("05.000000000000", format("%E12S", tp, tz)); - EXPECT_EQ("05.0000000000000", format("%E13S", tp, tz)); - EXPECT_EQ("05.00000000000000", format("%E14S", tp, tz)); - EXPECT_EQ("05.000000000000000", format("%E15S", tp, tz)); + EXPECT_EQ("05", absl::time_internal::cctz::format("%E*S", tp, tz)); + EXPECT_EQ("05", absl::time_internal::cctz::format("%E0S", tp, tz)); + EXPECT_EQ("05.0", absl::time_internal::cctz::format("%E1S", tp, tz)); + EXPECT_EQ("05.00", absl::time_internal::cctz::format("%E2S", tp, tz)); + EXPECT_EQ("05.000", absl::time_internal::cctz::format("%E3S", tp, tz)); + EXPECT_EQ("05.0000", absl::time_internal::cctz::format("%E4S", tp, tz)); + EXPECT_EQ("05.00000", absl::time_internal::cctz::format("%E5S", tp, tz)); + EXPECT_EQ("05.000000", absl::time_internal::cctz::format("%E6S", tp, tz)); + EXPECT_EQ("05.0000000", absl::time_internal::cctz::format("%E7S", tp, tz)); + EXPECT_EQ("05.00000000", absl::time_internal::cctz::format("%E8S", tp, tz)); + EXPECT_EQ("05.000000000", absl::time_internal::cctz::format("%E9S", tp, tz)); + EXPECT_EQ("05.0000000000", + absl::time_internal::cctz::format("%E10S", tp, tz)); + EXPECT_EQ("05.00000000000", + absl::time_internal::cctz::format("%E11S", tp, tz)); + EXPECT_EQ("05.000000000000", + absl::time_internal::cctz::format("%E12S", tp, tz)); + EXPECT_EQ("05.0000000000000", + absl::time_internal::cctz::format("%E13S", tp, tz)); + EXPECT_EQ("05.00000000000000", + absl::time_internal::cctz::format("%E14S", tp, tz)); + EXPECT_EQ("05.000000000000000", + absl::time_internal::cctz::format("%E15S", tp, tz)); // With subseconds. tp += chrono::milliseconds(6) + chrono::microseconds(7) + chrono::nanoseconds(8); - EXPECT_EQ("05.006007008", format("%E*S", tp, tz)); - EXPECT_EQ("05", format("%E0S", tp, tz)); - EXPECT_EQ("05.0", format("%E1S", tp, tz)); - EXPECT_EQ("05.00", format("%E2S", tp, tz)); - EXPECT_EQ("05.006", format("%E3S", tp, tz)); - EXPECT_EQ("05.0060", format("%E4S", tp, tz)); - EXPECT_EQ("05.00600", format("%E5S", tp, tz)); - EXPECT_EQ("05.006007", format("%E6S", tp, tz)); - EXPECT_EQ("05.0060070", format("%E7S", tp, tz)); - EXPECT_EQ("05.00600700", format("%E8S", tp, tz)); - EXPECT_EQ("05.006007008", format("%E9S", tp, tz)); - EXPECT_EQ("05.0060070080", format("%E10S", tp, tz)); - EXPECT_EQ("05.00600700800", format("%E11S", tp, tz)); - EXPECT_EQ("05.006007008000", format("%E12S", tp, tz)); - EXPECT_EQ("05.0060070080000", format("%E13S", tp, tz)); - EXPECT_EQ("05.00600700800000", format("%E14S", tp, tz)); - EXPECT_EQ("05.006007008000000", format("%E15S", tp, tz)); + EXPECT_EQ("05.006007008", absl::time_internal::cctz::format("%E*S", tp, tz)); + EXPECT_EQ("05", absl::time_internal::cctz::format("%E0S", tp, tz)); + EXPECT_EQ("05.0", absl::time_internal::cctz::format("%E1S", tp, tz)); + EXPECT_EQ("05.00", absl::time_internal::cctz::format("%E2S", tp, tz)); + EXPECT_EQ("05.006", absl::time_internal::cctz::format("%E3S", tp, tz)); + EXPECT_EQ("05.0060", absl::time_internal::cctz::format("%E4S", tp, tz)); + EXPECT_EQ("05.00600", absl::time_internal::cctz::format("%E5S", tp, tz)); + EXPECT_EQ("05.006007", absl::time_internal::cctz::format("%E6S", tp, tz)); + EXPECT_EQ("05.0060070", absl::time_internal::cctz::format("%E7S", tp, tz)); + EXPECT_EQ("05.00600700", absl::time_internal::cctz::format("%E8S", tp, tz)); + EXPECT_EQ("05.006007008", absl::time_internal::cctz::format("%E9S", tp, tz)); + EXPECT_EQ("05.0060070080", + absl::time_internal::cctz::format("%E10S", tp, tz)); + EXPECT_EQ("05.00600700800", + absl::time_internal::cctz::format("%E11S", tp, tz)); + EXPECT_EQ("05.006007008000", + absl::time_internal::cctz::format("%E12S", tp, tz)); + EXPECT_EQ("05.0060070080000", + absl::time_internal::cctz::format("%E13S", tp, tz)); + EXPECT_EQ("05.00600700800000", + absl::time_internal::cctz::format("%E14S", tp, tz)); + EXPECT_EQ("05.006007008000000", + absl::time_internal::cctz::format("%E15S", tp, tz)); // Times before the Unix epoch. tp = chrono::system_clock::from_time_t(0) + chrono::microseconds(-1); EXPECT_EQ("1969-12-31 23:59:59.999999", - format("%Y-%m-%d %H:%M:%E*S", tp, tz)); + absl::time_internal::cctz::format("%Y-%m-%d %H:%M:%E*S", tp, tz)); // Here is a "%E*S" case we got wrong for a while. While the first // instant below is correctly rendered as "...:07.333304", the second @@ -327,10 +350,10 @@ TEST(Format, ExtendedSeconds) { tp = chrono::system_clock::from_time_t(0) + chrono::microseconds(1395024427333304); EXPECT_EQ("2014-03-17 02:47:07.333304", - format("%Y-%m-%d %H:%M:%E*S", tp, tz)); + absl::time_internal::cctz::format("%Y-%m-%d %H:%M:%E*S", tp, tz)); tp += chrono::microseconds(1); EXPECT_EQ("2014-03-17 02:47:07.333305", - format("%Y-%m-%d %H:%M:%E*S", tp, tz)); + absl::time_internal::cctz::format("%Y-%m-%d %H:%M:%E*S", tp, tz)); } TEST(Format, ExtendedSubeconds) { @@ -339,60 +362,69 @@ TEST(Format, ExtendedSubeconds) { // No subseconds. time_point<chrono::nanoseconds> tp = chrono::system_clock::from_time_t(0); tp += chrono::seconds(5); - EXPECT_EQ("0", format("%E*f", tp, tz)); - EXPECT_EQ("", format("%E0f", tp, tz)); - EXPECT_EQ("0", format("%E1f", tp, tz)); - EXPECT_EQ("00", format("%E2f", tp, tz)); - EXPECT_EQ("000", format("%E3f", tp, tz)); - EXPECT_EQ("0000", format("%E4f", tp, tz)); - EXPECT_EQ("00000", format("%E5f", tp, tz)); - EXPECT_EQ("000000", format("%E6f", tp, tz)); - EXPECT_EQ("0000000", format("%E7f", tp, tz)); - EXPECT_EQ("00000000", format("%E8f", tp, tz)); - EXPECT_EQ("000000000", format("%E9f", tp, tz)); - EXPECT_EQ("0000000000", format("%E10f", tp, tz)); - EXPECT_EQ("00000000000", format("%E11f", tp, tz)); - EXPECT_EQ("000000000000", format("%E12f", tp, tz)); - EXPECT_EQ("0000000000000", format("%E13f", tp, tz)); - EXPECT_EQ("00000000000000", format("%E14f", tp, tz)); - EXPECT_EQ("000000000000000", format("%E15f", tp, tz)); + EXPECT_EQ("0", absl::time_internal::cctz::format("%E*f", tp, tz)); + EXPECT_EQ("", absl::time_internal::cctz::format("%E0f", tp, tz)); + EXPECT_EQ("0", absl::time_internal::cctz::format("%E1f", tp, tz)); + EXPECT_EQ("00", absl::time_internal::cctz::format("%E2f", tp, tz)); + EXPECT_EQ("000", absl::time_internal::cctz::format("%E3f", tp, tz)); + EXPECT_EQ("0000", absl::time_internal::cctz::format("%E4f", tp, tz)); + EXPECT_EQ("00000", absl::time_internal::cctz::format("%E5f", tp, tz)); + EXPECT_EQ("000000", absl::time_internal::cctz::format("%E6f", tp, tz)); + EXPECT_EQ("0000000", absl::time_internal::cctz::format("%E7f", tp, tz)); + EXPECT_EQ("00000000", absl::time_internal::cctz::format("%E8f", tp, tz)); + EXPECT_EQ("000000000", absl::time_internal::cctz::format("%E9f", tp, tz)); + EXPECT_EQ("0000000000", absl::time_internal::cctz::format("%E10f", tp, tz)); + EXPECT_EQ("00000000000", absl::time_internal::cctz::format("%E11f", tp, tz)); + EXPECT_EQ("000000000000", absl::time_internal::cctz::format("%E12f", tp, tz)); + EXPECT_EQ("0000000000000", + absl::time_internal::cctz::format("%E13f", tp, tz)); + EXPECT_EQ("00000000000000", + absl::time_internal::cctz::format("%E14f", tp, tz)); + EXPECT_EQ("000000000000000", + absl::time_internal::cctz::format("%E15f", tp, tz)); // With subseconds. tp += chrono::milliseconds(6) + chrono::microseconds(7) + chrono::nanoseconds(8); - EXPECT_EQ("006007008", format("%E*f", tp, tz)); - EXPECT_EQ("", format("%E0f", tp, tz)); - EXPECT_EQ("0", format("%E1f", tp, tz)); - EXPECT_EQ("00", format("%E2f", tp, tz)); - EXPECT_EQ("006", format("%E3f", tp, tz)); - EXPECT_EQ("0060", format("%E4f", tp, tz)); - EXPECT_EQ("00600", format("%E5f", tp, tz)); - EXPECT_EQ("006007", format("%E6f", tp, tz)); - EXPECT_EQ("0060070", format("%E7f", tp, tz)); - EXPECT_EQ("00600700", format("%E8f", tp, tz)); - EXPECT_EQ("006007008", format("%E9f", tp, tz)); - EXPECT_EQ("0060070080", format("%E10f", tp, tz)); - EXPECT_EQ("00600700800", format("%E11f", tp, tz)); - EXPECT_EQ("006007008000", format("%E12f", tp, tz)); - EXPECT_EQ("0060070080000", format("%E13f", tp, tz)); - EXPECT_EQ("00600700800000", format("%E14f", tp, tz)); - EXPECT_EQ("006007008000000", format("%E15f", tp, tz)); + EXPECT_EQ("006007008", absl::time_internal::cctz::format("%E*f", tp, tz)); + EXPECT_EQ("", absl::time_internal::cctz::format("%E0f", tp, tz)); + EXPECT_EQ("0", absl::time_internal::cctz::format("%E1f", tp, tz)); + EXPECT_EQ("00", absl::time_internal::cctz::format("%E2f", tp, tz)); + EXPECT_EQ("006", absl::time_internal::cctz::format("%E3f", tp, tz)); + EXPECT_EQ("0060", absl::time_internal::cctz::format("%E4f", tp, tz)); + EXPECT_EQ("00600", absl::time_internal::cctz::format("%E5f", tp, tz)); + EXPECT_EQ("006007", absl::time_internal::cctz::format("%E6f", tp, tz)); + EXPECT_EQ("0060070", absl::time_internal::cctz::format("%E7f", tp, tz)); + EXPECT_EQ("00600700", absl::time_internal::cctz::format("%E8f", tp, tz)); + EXPECT_EQ("006007008", absl::time_internal::cctz::format("%E9f", tp, tz)); + EXPECT_EQ("0060070080", absl::time_internal::cctz::format("%E10f", tp, tz)); + EXPECT_EQ("00600700800", absl::time_internal::cctz::format("%E11f", tp, tz)); + EXPECT_EQ("006007008000", absl::time_internal::cctz::format("%E12f", tp, tz)); + EXPECT_EQ("0060070080000", + absl::time_internal::cctz::format("%E13f", tp, tz)); + EXPECT_EQ("00600700800000", + absl::time_internal::cctz::format("%E14f", tp, tz)); + EXPECT_EQ("006007008000000", + absl::time_internal::cctz::format("%E15f", tp, tz)); // Times before the Unix epoch. tp = chrono::system_clock::from_time_t(0) + chrono::microseconds(-1); - EXPECT_EQ("1969-12-31 23:59:59.999999", - format("%Y-%m-%d %H:%M:%S.%E*f", tp, tz)); + EXPECT_EQ( + "1969-12-31 23:59:59.999999", + absl::time_internal::cctz::format("%Y-%m-%d %H:%M:%S.%E*f", tp, tz)); // Here is a "%E*S" case we got wrong for a while. While the first // instant below is correctly rendered as "...:07.333304", the second // one used to appear as "...:07.33330499999999999". tp = chrono::system_clock::from_time_t(0) + chrono::microseconds(1395024427333304); - EXPECT_EQ("2014-03-17 02:47:07.333304", - format("%Y-%m-%d %H:%M:%S.%E*f", tp, tz)); + EXPECT_EQ( + "2014-03-17 02:47:07.333304", + absl::time_internal::cctz::format("%Y-%m-%d %H:%M:%S.%E*f", tp, tz)); tp += chrono::microseconds(1); - EXPECT_EQ("2014-03-17 02:47:07.333305", - format("%Y-%m-%d %H:%M:%S.%E*f", tp, tz)); + EXPECT_EQ( + "2014-03-17 02:47:07.333305", + absl::time_internal::cctz::format("%Y-%m-%d %H:%M:%S.%E*f", tp, tz)); } TEST(Format, CompareExtendSecondsVsSubseconds) { @@ -408,15 +440,17 @@ TEST(Format, CompareExtendSecondsVsSubseconds) { time_point<chrono::nanoseconds> tp = chrono::system_clock::from_time_t(0); tp += chrono::seconds(5); // ... %E*S and %S.%E*f are different. - EXPECT_EQ("05", format(fmt_A("*"), tp, tz)); - EXPECT_EQ("05.0", format(fmt_B("*"), tp, tz)); + EXPECT_EQ("05", absl::time_internal::cctz::format(fmt_A("*"), tp, tz)); + EXPECT_EQ("05.0", absl::time_internal::cctz::format(fmt_B("*"), tp, tz)); // ... %E0S and %S.%E0f are different. - EXPECT_EQ("05", format(fmt_A("0"), tp, tz)); - EXPECT_EQ("05.", format(fmt_B("0"), tp, tz)); + EXPECT_EQ("05", absl::time_internal::cctz::format(fmt_A("0"), tp, tz)); + EXPECT_EQ("05.", absl::time_internal::cctz::format(fmt_B("0"), tp, tz)); // ... %E<prec>S and %S.%E<prec>f are the same for prec in [1:15]. for (int prec = 1; prec <= 15; ++prec) { - const std::string a = format(fmt_A(std::to_string(prec)), tp, tz); - const std::string b = format(fmt_B(std::to_string(prec)), tp, tz); + const std::string a = + absl::time_internal::cctz::format(fmt_A(std::to_string(prec)), tp, tz); + const std::string b = + absl::time_internal::cctz::format(fmt_B(std::to_string(prec)), tp, tz); EXPECT_EQ(a, b) << "prec=" << prec; } @@ -424,15 +458,19 @@ TEST(Format, CompareExtendSecondsVsSubseconds) { // ... %E*S and %S.%E*f are the same. tp += chrono::milliseconds(6) + chrono::microseconds(7) + chrono::nanoseconds(8); - EXPECT_EQ("05.006007008", format(fmt_A("*"), tp, tz)); - EXPECT_EQ("05.006007008", format(fmt_B("*"), tp, tz)); + EXPECT_EQ("05.006007008", + absl::time_internal::cctz::format(fmt_A("*"), tp, tz)); + EXPECT_EQ("05.006007008", + absl::time_internal::cctz::format(fmt_B("*"), tp, tz)); // ... %E0S and %S.%E0f are different. - EXPECT_EQ("05", format(fmt_A("0"), tp, tz)); - EXPECT_EQ("05.", format(fmt_B("0"), tp, tz)); + EXPECT_EQ("05", absl::time_internal::cctz::format(fmt_A("0"), tp, tz)); + EXPECT_EQ("05.", absl::time_internal::cctz::format(fmt_B("0"), tp, tz)); // ... %E<prec>S and %S.%E<prec>f are the same for prec in [1:15]. for (int prec = 1; prec <= 15; ++prec) { - const std::string a = format(fmt_A(std::to_string(prec)), tp, tz); - const std::string b = format(fmt_B(std::to_string(prec)), tp, tz); + const std::string a = + absl::time_internal::cctz::format(fmt_A(std::to_string(prec)), tp, tz); + const std::string b = + absl::time_internal::cctz::format(fmt_B(std::to_string(prec)), tp, tz); EXPECT_EQ(a, b) << "prec=" << prec; } } @@ -605,31 +643,31 @@ TEST(Format, ExtendedYears) { // %E4Y zero-pads the year to produce at least 4 chars, including the sign. auto tp = convert(civil_second(-999, 11, 27, 0, 0, 0), utc); - EXPECT_EQ("-9991127", format(e4y_fmt, tp, utc)); + EXPECT_EQ("-9991127", absl::time_internal::cctz::format(e4y_fmt, tp, utc)); tp = convert(civil_second(-99, 11, 27, 0, 0, 0), utc); - EXPECT_EQ("-0991127", format(e4y_fmt, tp, utc)); + EXPECT_EQ("-0991127", absl::time_internal::cctz::format(e4y_fmt, tp, utc)); tp = convert(civil_second(-9, 11, 27, 0, 0, 0), utc); - EXPECT_EQ("-0091127", format(e4y_fmt, tp, utc)); + EXPECT_EQ("-0091127", absl::time_internal::cctz::format(e4y_fmt, tp, utc)); tp = convert(civil_second(-1, 11, 27, 0, 0, 0), utc); - EXPECT_EQ("-0011127", format(e4y_fmt, tp, utc)); + EXPECT_EQ("-0011127", absl::time_internal::cctz::format(e4y_fmt, tp, utc)); tp = convert(civil_second(0, 11, 27, 0, 0, 0), utc); - EXPECT_EQ("00001127", format(e4y_fmt, tp, utc)); + EXPECT_EQ("00001127", absl::time_internal::cctz::format(e4y_fmt, tp, utc)); tp = convert(civil_second(1, 11, 27, 0, 0, 0), utc); - EXPECT_EQ("00011127", format(e4y_fmt, tp, utc)); + EXPECT_EQ("00011127", absl::time_internal::cctz::format(e4y_fmt, tp, utc)); tp = convert(civil_second(9, 11, 27, 0, 0, 0), utc); - EXPECT_EQ("00091127", format(e4y_fmt, tp, utc)); + EXPECT_EQ("00091127", absl::time_internal::cctz::format(e4y_fmt, tp, utc)); tp = convert(civil_second(99, 11, 27, 0, 0, 0), utc); - EXPECT_EQ("00991127", format(e4y_fmt, tp, utc)); + EXPECT_EQ("00991127", absl::time_internal::cctz::format(e4y_fmt, tp, utc)); tp = convert(civil_second(999, 11, 27, 0, 0, 0), utc); - EXPECT_EQ("09991127", format(e4y_fmt, tp, utc)); + EXPECT_EQ("09991127", absl::time_internal::cctz::format(e4y_fmt, tp, utc)); tp = convert(civil_second(9999, 11, 27, 0, 0, 0), utc); - EXPECT_EQ("99991127", format(e4y_fmt, tp, utc)); + EXPECT_EQ("99991127", absl::time_internal::cctz::format(e4y_fmt, tp, utc)); // When the year is outside [-999:9999], more than 4 chars are produced. tp = convert(civil_second(-1000, 11, 27, 0, 0, 0), utc); - EXPECT_EQ("-10001127", format(e4y_fmt, tp, utc)); + EXPECT_EQ("-10001127", absl::time_internal::cctz::format(e4y_fmt, tp, utc)); tp = convert(civil_second(10000, 11, 27, 0, 0, 0), utc); - EXPECT_EQ("100001127", format(e4y_fmt, tp, utc)); + EXPECT_EQ("100001127", absl::time_internal::cctz::format(e4y_fmt, tp, utc)); } TEST(Format, RFC3339Format) { @@ -638,45 +676,64 @@ TEST(Format, RFC3339Format) { time_point<chrono::nanoseconds> tp = convert(civil_second(1977, 6, 28, 9, 8, 7), tz); - EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_full, tp, tz)); - EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07-07:00", + absl::time_internal::cctz::format(RFC3339_full, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07-07:00", + absl::time_internal::cctz::format(RFC3339_sec, tp, tz)); tp += chrono::milliseconds(100); - EXPECT_EQ("1977-06-28T09:08:07.1-07:00", format(RFC3339_full, tp, tz)); - EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07.1-07:00", + absl::time_internal::cctz::format(RFC3339_full, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07-07:00", + absl::time_internal::cctz::format(RFC3339_sec, tp, tz)); tp += chrono::milliseconds(20); - EXPECT_EQ("1977-06-28T09:08:07.12-07:00", format(RFC3339_full, tp, tz)); - EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07.12-07:00", + absl::time_internal::cctz::format(RFC3339_full, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07-07:00", + absl::time_internal::cctz::format(RFC3339_sec, tp, tz)); tp += chrono::milliseconds(3); - EXPECT_EQ("1977-06-28T09:08:07.123-07:00", format(RFC3339_full, tp, tz)); - EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07.123-07:00", + absl::time_internal::cctz::format(RFC3339_full, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07-07:00", + absl::time_internal::cctz::format(RFC3339_sec, tp, tz)); tp += chrono::microseconds(400); - EXPECT_EQ("1977-06-28T09:08:07.1234-07:00", format(RFC3339_full, tp, tz)); - EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07.1234-07:00", + absl::time_internal::cctz::format(RFC3339_full, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07-07:00", + absl::time_internal::cctz::format(RFC3339_sec, tp, tz)); tp += chrono::microseconds(50); - EXPECT_EQ("1977-06-28T09:08:07.12345-07:00", format(RFC3339_full, tp, tz)); - EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07.12345-07:00", + absl::time_internal::cctz::format(RFC3339_full, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07-07:00", + absl::time_internal::cctz::format(RFC3339_sec, tp, tz)); tp += chrono::microseconds(6); - EXPECT_EQ("1977-06-28T09:08:07.123456-07:00", format(RFC3339_full, tp, tz)); - EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07.123456-07:00", + absl::time_internal::cctz::format(RFC3339_full, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07-07:00", + absl::time_internal::cctz::format(RFC3339_sec, tp, tz)); tp += chrono::nanoseconds(700); - EXPECT_EQ("1977-06-28T09:08:07.1234567-07:00", format(RFC3339_full, tp, tz)); - EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07.1234567-07:00", + absl::time_internal::cctz::format(RFC3339_full, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07-07:00", + absl::time_internal::cctz::format(RFC3339_sec, tp, tz)); tp += chrono::nanoseconds(80); - EXPECT_EQ("1977-06-28T09:08:07.12345678-07:00", format(RFC3339_full, tp, tz)); - EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07.12345678-07:00", + absl::time_internal::cctz::format(RFC3339_full, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07-07:00", + absl::time_internal::cctz::format(RFC3339_sec, tp, tz)); tp += chrono::nanoseconds(9); EXPECT_EQ("1977-06-28T09:08:07.123456789-07:00", - format(RFC3339_full, tp, tz)); - EXPECT_EQ("1977-06-28T09:08:07-07:00", format(RFC3339_sec, tp, tz)); + absl::time_internal::cctz::format(RFC3339_full, tp, tz)); + EXPECT_EQ("1977-06-28T09:08:07-07:00", + absl::time_internal::cctz::format(RFC3339_sec, tp, tz)); } TEST(Format, RFC1123Format) { // locale specific @@ -684,36 +741,50 @@ TEST(Format, RFC1123Format) { // locale specific EXPECT_TRUE(load_time_zone("America/Los_Angeles", &tz)); auto tp = convert(civil_second(1977, 6, 28, 9, 8, 7), tz); - EXPECT_EQ("Tue, 28 Jun 1977 09:08:07 -0700", format(RFC1123_full, tp, tz)); - EXPECT_EQ("28 Jun 1977 09:08:07 -0700", format(RFC1123_no_wday, tp, tz)); + EXPECT_EQ("Tue, 28 Jun 1977 09:08:07 -0700", + absl::time_internal::cctz::format(RFC1123_full, tp, tz)); + EXPECT_EQ("28 Jun 1977 09:08:07 -0700", + absl::time_internal::cctz::format(RFC1123_no_wday, tp, tz)); } TEST(Format, Week) { const time_zone utc = utc_time_zone(); auto tp = convert(civil_second(2017, 1, 1, 0, 0, 0), utc); - EXPECT_EQ("2017-01-7", format("%Y-%U-%u", tp, utc)); - EXPECT_EQ("2017-00-0", format("%Y-%W-%w", tp, utc)); + EXPECT_EQ("2017-01-7", + absl::time_internal::cctz::format("%Y-%U-%u", tp, utc)); + EXPECT_EQ("2017-00-0", + absl::time_internal::cctz::format("%Y-%W-%w", tp, utc)); tp = convert(civil_second(2017, 12, 31, 0, 0, 0), utc); - EXPECT_EQ("2017-53-7", format("%Y-%U-%u", tp, utc)); - EXPECT_EQ("2017-52-0", format("%Y-%W-%w", tp, utc)); + EXPECT_EQ("2017-53-7", + absl::time_internal::cctz::format("%Y-%U-%u", tp, utc)); + EXPECT_EQ("2017-52-0", + absl::time_internal::cctz::format("%Y-%W-%w", tp, utc)); tp = convert(civil_second(2018, 1, 1, 0, 0, 0), utc); - EXPECT_EQ("2018-00-1", format("%Y-%U-%u", tp, utc)); - EXPECT_EQ("2018-01-1", format("%Y-%W-%w", tp, utc)); + EXPECT_EQ("2018-00-1", + absl::time_internal::cctz::format("%Y-%U-%u", tp, utc)); + EXPECT_EQ("2018-01-1", + absl::time_internal::cctz::format("%Y-%W-%w", tp, utc)); tp = convert(civil_second(2018, 12, 31, 0, 0, 0), utc); - EXPECT_EQ("2018-52-1", format("%Y-%U-%u", tp, utc)); - EXPECT_EQ("2018-53-1", format("%Y-%W-%w", tp, utc)); + EXPECT_EQ("2018-52-1", + absl::time_internal::cctz::format("%Y-%U-%u", tp, utc)); + EXPECT_EQ("2018-53-1", + absl::time_internal::cctz::format("%Y-%W-%w", tp, utc)); tp = convert(civil_second(2019, 1, 1, 0, 0, 0), utc); - EXPECT_EQ("2019-00-2", format("%Y-%U-%u", tp, utc)); - EXPECT_EQ("2019-00-2", format("%Y-%W-%w", tp, utc)); + EXPECT_EQ("2019-00-2", + absl::time_internal::cctz::format("%Y-%U-%u", tp, utc)); + EXPECT_EQ("2019-00-2", + absl::time_internal::cctz::format("%Y-%W-%w", tp, utc)); tp = convert(civil_second(2019, 12, 31, 0, 0, 0), utc); - EXPECT_EQ("2019-52-2", format("%Y-%U-%u", tp, utc)); - EXPECT_EQ("2019-52-2", format("%Y-%W-%w", tp, utc)); + EXPECT_EQ("2019-52-2", + absl::time_internal::cctz::format("%Y-%U-%u", tp, utc)); + EXPECT_EQ("2019-52-2", + absl::time_internal::cctz::format("%Y-%W-%w", tp, utc)); } // @@ -726,39 +797,46 @@ TEST(Parse, TimePointResolution) { time_point<chrono::nanoseconds> tp_ns; EXPECT_TRUE(parse(kFmt, "03:04:05.123456789", utc, &tp_ns)); - EXPECT_EQ("03:04:05.123456789", format(kFmt, tp_ns, utc)); + EXPECT_EQ("03:04:05.123456789", + absl::time_internal::cctz::format(kFmt, tp_ns, utc)); EXPECT_TRUE(parse(kFmt, "03:04:05.123456", utc, &tp_ns)); - EXPECT_EQ("03:04:05.123456", format(kFmt, tp_ns, utc)); + EXPECT_EQ("03:04:05.123456", + absl::time_internal::cctz::format(kFmt, tp_ns, utc)); time_point<chrono::microseconds> tp_us; EXPECT_TRUE(parse(kFmt, "03:04:05.123456789", utc, &tp_us)); - EXPECT_EQ("03:04:05.123456", format(kFmt, tp_us, utc)); + EXPECT_EQ("03:04:05.123456", + absl::time_internal::cctz::format(kFmt, tp_us, utc)); EXPECT_TRUE(parse(kFmt, "03:04:05.123456", utc, &tp_us)); - EXPECT_EQ("03:04:05.123456", format(kFmt, tp_us, utc)); + EXPECT_EQ("03:04:05.123456", + absl::time_internal::cctz::format(kFmt, tp_us, utc)); EXPECT_TRUE(parse(kFmt, "03:04:05.123", utc, &tp_us)); - EXPECT_EQ("03:04:05.123", format(kFmt, tp_us, utc)); + EXPECT_EQ("03:04:05.123", + absl::time_internal::cctz::format(kFmt, tp_us, utc)); time_point<chrono::milliseconds> tp_ms; EXPECT_TRUE(parse(kFmt, "03:04:05.123456", utc, &tp_ms)); - EXPECT_EQ("03:04:05.123", format(kFmt, tp_ms, utc)); + EXPECT_EQ("03:04:05.123", + absl::time_internal::cctz::format(kFmt, tp_ms, utc)); EXPECT_TRUE(parse(kFmt, "03:04:05.123", utc, &tp_ms)); - EXPECT_EQ("03:04:05.123", format(kFmt, tp_ms, utc)); + EXPECT_EQ("03:04:05.123", + absl::time_internal::cctz::format(kFmt, tp_ms, utc)); EXPECT_TRUE(parse(kFmt, "03:04:05", utc, &tp_ms)); - EXPECT_EQ("03:04:05", format(kFmt, tp_ms, utc)); + EXPECT_EQ("03:04:05", absl::time_internal::cctz::format(kFmt, tp_ms, utc)); time_point<chrono::seconds> tp_s; EXPECT_TRUE(parse(kFmt, "03:04:05.123", utc, &tp_s)); - EXPECT_EQ("03:04:05", format(kFmt, tp_s, utc)); + EXPECT_EQ("03:04:05", absl::time_internal::cctz::format(kFmt, tp_s, utc)); EXPECT_TRUE(parse(kFmt, "03:04:05", utc, &tp_s)); - EXPECT_EQ("03:04:05", format(kFmt, tp_s, utc)); + EXPECT_EQ("03:04:05", absl::time_internal::cctz::format(kFmt, tp_s, utc)); time_point<chrono::minutes> tp_m; EXPECT_TRUE(parse(kFmt, "03:04:05", utc, &tp_m)); - EXPECT_EQ("03:04:00", format(kFmt, tp_m, utc)); + EXPECT_EQ("03:04:00", absl::time_internal::cctz::format(kFmt, tp_m, utc)); time_point<chrono::hours> tp_h; EXPECT_TRUE(parse(kFmt, "03:04:05", utc, &tp_h)); - EXPECT_EQ("03:00:00", format(kFmt, tp_h, utc)); + EXPECT_EQ("03:00:00", absl::time_internal::cctz::format(kFmt, tp_h, utc)); } TEST(Parse, TimePointExtendedResolution) { @@ -1550,7 +1628,7 @@ TEST(Parse, TimePointOverflow) { parse(RFC3339_full, "2262-04-11T23:47:16.8547758079+00:00", utc, &tp)); EXPECT_EQ(tp, time_point<D>::max()); EXPECT_EQ("2262-04-11T23:47:16.854775807+00:00", - format(RFC3339_full, tp, utc)); + absl::time_internal::cctz::format(RFC3339_full, tp, utc)); #if 0 // TODO(#199): Will fail until cctz::parse() properly detects overflow. EXPECT_FALSE( @@ -1559,7 +1637,7 @@ TEST(Parse, TimePointOverflow) { parse(RFC3339_full, "1677-09-21T00:12:43.1452241920+00:00", utc, &tp)); EXPECT_EQ(tp, time_point<D>::min()); EXPECT_EQ("1677-09-21T00:12:43.145224192+00:00", - format(RFC3339_full, tp, utc)); + absl::time_internal::cctz::format(RFC3339_full, tp, utc)); EXPECT_FALSE( parse(RFC3339_full, "1677-09-21T00:12:43.1452241919+00:00", utc, &tp)); #endif @@ -1569,12 +1647,14 @@ TEST(Parse, TimePointOverflow) { EXPECT_TRUE(parse(RFC3339_full, "1970-01-01T00:02:07.9+00:00", utc, &stp)); EXPECT_EQ(stp, time_point<DS>::max()); - EXPECT_EQ("1970-01-01T00:02:07+00:00", format(RFC3339_full, stp, utc)); + EXPECT_EQ("1970-01-01T00:02:07+00:00", + absl::time_internal::cctz::format(RFC3339_full, stp, utc)); EXPECT_FALSE(parse(RFC3339_full, "1970-01-01T00:02:08+00:00", utc, &stp)); EXPECT_TRUE(parse(RFC3339_full, "1969-12-31T23:57:52+00:00", utc, &stp)); EXPECT_EQ(stp, time_point<DS>::min()); - EXPECT_EQ("1969-12-31T23:57:52+00:00", format(RFC3339_full, stp, utc)); + EXPECT_EQ("1969-12-31T23:57:52+00:00", + absl::time_internal::cctz::format(RFC3339_full, stp, utc)); EXPECT_FALSE(parse(RFC3339_full, "1969-12-31T23:57:51.9+00:00", utc, &stp)); using DM = chrono::duration<std::int8_t, chrono::minutes::period>; @@ -1582,12 +1662,14 @@ TEST(Parse, TimePointOverflow) { EXPECT_TRUE(parse(RFC3339_full, "1970-01-01T02:07:59+00:00", utc, &mtp)); EXPECT_EQ(mtp, time_point<DM>::max()); - EXPECT_EQ("1970-01-01T02:07:00+00:00", format(RFC3339_full, mtp, utc)); + EXPECT_EQ("1970-01-01T02:07:00+00:00", + absl::time_internal::cctz::format(RFC3339_full, mtp, utc)); EXPECT_FALSE(parse(RFC3339_full, "1970-01-01T02:08:00+00:00", utc, &mtp)); EXPECT_TRUE(parse(RFC3339_full, "1969-12-31T21:52:00+00:00", utc, &mtp)); EXPECT_EQ(mtp, time_point<DM>::min()); - EXPECT_EQ("1969-12-31T21:52:00+00:00", format(RFC3339_full, mtp, utc)); + EXPECT_EQ("1969-12-31T21:52:00+00:00", + absl::time_internal::cctz::format(RFC3339_full, mtp, utc)); EXPECT_FALSE(parse(RFC3339_full, "1969-12-31T21:51:59+00:00", utc, &mtp)); } @@ -1601,7 +1683,7 @@ TEST(Parse, TimePointOverflowFloor) { parse(RFC3339_full, "294247-01-10T04:00:54.7758079+00:00", utc, &tp)); EXPECT_EQ(tp, time_point<D>::max()); EXPECT_EQ("294247-01-10T04:00:54.775807+00:00", - format(RFC3339_full, tp, utc)); + absl::time_internal::cctz::format(RFC3339_full, tp, utc)); #if 0 // TODO(#199): Will fail until cctz::parse() properly detects overflow. EXPECT_FALSE( @@ -1610,7 +1692,7 @@ TEST(Parse, TimePointOverflowFloor) { parse(RFC3339_full, "-290308-12-21T19:59:05.2241920+00:00", utc, &tp)); EXPECT_EQ(tp, time_point<D>::min()); EXPECT_EQ("-290308-12-21T19:59:05.224192+00:00", - format(RFC3339_full, tp, utc)); + absl::time_internal::cctz::format(RFC3339_full, tp, utc)); EXPECT_FALSE( parse(RFC3339_full, "-290308-12-21T19:59:05.2241919+00:00", utc, &tp)); #endif @@ -1629,7 +1711,8 @@ TEST(FormatParse, RoundTrip) { // RFC3339, which renders subseconds. { time_point<chrono::nanoseconds> out; - const std::string s = format(RFC3339_full, in + subseconds, lax); + const std::string s = + absl::time_internal::cctz::format(RFC3339_full, in + subseconds, lax); EXPECT_TRUE(parse(RFC3339_full, s, lax, &out)) << s; EXPECT_EQ(in + subseconds, out); // RFC3339_full includes %Ez } @@ -1637,7 +1720,8 @@ TEST(FormatParse, RoundTrip) { // RFC1123, which only does whole seconds. { time_point<chrono::nanoseconds> out; - const std::string s = format(RFC1123_full, in, lax); + const std::string s = + absl::time_internal::cctz::format(RFC1123_full, in, lax); EXPECT_TRUE(parse(RFC1123_full, s, lax, &out)) << s; EXPECT_EQ(in, out); // RFC1123_full includes %z } @@ -1655,7 +1739,7 @@ TEST(FormatParse, RoundTrip) { { time_point<chrono::nanoseconds> out; time_zone utc = utc_time_zone(); - const std::string s = format("%c", in, utc); + const std::string s = absl::time_internal::cctz::format("%c", in, utc); EXPECT_TRUE(parse("%c", s, utc, &out)) << s; EXPECT_EQ(in, out); } @@ -1666,7 +1750,8 @@ TEST(FormatParse, RoundTripDistantFuture) { const time_zone utc = utc_time_zone(); const time_point<absl::time_internal::cctz::seconds> in = time_point<absl::time_internal::cctz::seconds>::max(); - const std::string s = format(RFC3339_full, in, utc); + const std::string s = + absl::time_internal::cctz::format(RFC3339_full, in, utc); time_point<absl::time_internal::cctz::seconds> out; EXPECT_TRUE(parse(RFC3339_full, s, utc, &out)) << s; EXPECT_EQ(in, out); @@ -1676,7 +1761,8 @@ TEST(FormatParse, RoundTripDistantPast) { const time_zone utc = utc_time_zone(); const time_point<absl::time_internal::cctz::seconds> in = time_point<absl::time_internal::cctz::seconds>::min(); - const std::string s = format(RFC3339_full, in, utc); + const std::string s = + absl::time_internal::cctz::format(RFC3339_full, in, utc); time_point<absl::time_internal::cctz::seconds> out; EXPECT_TRUE(parse(RFC3339_full, s, utc, &out)) << s; EXPECT_EQ(in, out); diff --git a/absl/time/internal/cctz/src/time_zone_if.cc b/absl/time/internal/cctz/src/time_zone_if.cc index 0319b2f9..0e65cd9e 100644 --- a/absl/time/internal/cctz/src/time_zone_if.cc +++ b/absl/time/internal/cctz/src/time_zone_if.cc @@ -23,17 +23,19 @@ ABSL_NAMESPACE_BEGIN namespace time_internal { namespace cctz { -std::unique_ptr<TimeZoneIf> TimeZoneIf::Load(const std::string& name) { +std::unique_ptr<TimeZoneIf> TimeZoneIf::UTC() { return TimeZoneInfo::UTC(); } + +std::unique_ptr<TimeZoneIf> TimeZoneIf::Make(const std::string& name) { // Support "libc:localtime" and "libc:*" to access the legacy // localtime and UTC support respectively from the C library. + // NOTE: The "libc:*" zones are internal, test-only interfaces, and + // are subject to change/removal without notice. Do not use them. if (name.compare(0, 5, "libc:") == 0) { - return std::unique_ptr<TimeZoneIf>(new TimeZoneLibC(name.substr(5))); + return TimeZoneLibC::Make(name.substr(5)); } - // Otherwise use the "zoneinfo" implementation by default. - std::unique_ptr<TimeZoneInfo> tz(new TimeZoneInfo); - if (!tz->Load(name)) tz.reset(); - return std::unique_ptr<TimeZoneIf>(tz.release()); + // Otherwise use the "zoneinfo" implementation. + return TimeZoneInfo::Make(name); } // Defined out-of-line to avoid emitting a weak vtable in all TUs. diff --git a/absl/time/internal/cctz/src/time_zone_if.h b/absl/time/internal/cctz/src/time_zone_if.h index 7d3e42d3..bec9beb5 100644 --- a/absl/time/internal/cctz/src/time_zone_if.h +++ b/absl/time/internal/cctz/src/time_zone_if.h @@ -33,8 +33,9 @@ namespace cctz { // Subclasses implement the functions for civil-time conversions in the zone. class TimeZoneIf { public: - // A factory function for TimeZoneIf implementations. - static std::unique_ptr<TimeZoneIf> Load(const std::string& name); + // Factory functions for TimeZoneIf implementations. + static std::unique_ptr<TimeZoneIf> UTC(); // never fails + static std::unique_ptr<TimeZoneIf> Make(const std::string& name); virtual ~TimeZoneIf(); @@ -51,7 +52,9 @@ class TimeZoneIf { virtual std::string Description() const = 0; protected: - TimeZoneIf() {} + TimeZoneIf() = default; + TimeZoneIf(const TimeZoneIf&) = delete; + TimeZoneIf& operator=(const TimeZoneIf&) = delete; }; // Convert between time_point<seconds> and a count of seconds since the diff --git a/absl/time/internal/cctz/src/time_zone_impl.cc b/absl/time/internal/cctz/src/time_zone_impl.cc index f34e3aec..aadbb77d 100644 --- a/absl/time/internal/cctz/src/time_zone_impl.cc +++ b/absl/time/internal/cctz/src/time_zone_impl.cc @@ -99,11 +99,13 @@ void time_zone::Impl::ClearTimeZoneMapTestOnly() { } } +time_zone::Impl::Impl() : name_("UTC"), zone_(TimeZoneIf::UTC()) {} + time_zone::Impl::Impl(const std::string& name) - : name_(name), zone_(TimeZoneIf::Load(name_)) {} + : name_(name), zone_(TimeZoneIf::Make(name_)) {} const time_zone::Impl* time_zone::Impl::UTCImpl() { - static const Impl* utc_impl = new Impl("UTC"); // never fails + static const Impl* utc_impl = new Impl; return utc_impl; } diff --git a/absl/time/internal/cctz/src/time_zone_impl.h b/absl/time/internal/cctz/src/time_zone_impl.h index 7d747ba9..8308a3b4 100644 --- a/absl/time/internal/cctz/src/time_zone_impl.h +++ b/absl/time/internal/cctz/src/time_zone_impl.h @@ -78,7 +78,11 @@ class time_zone::Impl { std::string Description() const { return zone_->Description(); } private: + Impl(); explicit Impl(const std::string& name); + Impl(const Impl&) = delete; + Impl& operator=(const Impl&) = delete; + static const Impl* UTCImpl(); const std::string name_; diff --git a/absl/time/internal/cctz/src/time_zone_info.cc b/absl/time/internal/cctz/src/time_zone_info.cc index 787426f7..b7178a6b 100644 --- a/absl/time/internal/cctz/src/time_zone_info.cc +++ b/absl/time/internal/cctz/src/time_zone_info.cc @@ -45,6 +45,7 @@ #include <sstream> #include <string> #include <utility> +#include <vector> #include "absl/base/config.h" #include "absl/time/internal/cctz/include/cctz/civil_time.h" @@ -134,6 +135,49 @@ std::int_fast64_t Decode64(const char* cp) { return static_cast<std::int_fast64_t>(v - s64maxU - 1) - s64max - 1; } +struct Header { // counts of: + std::size_t timecnt; // transition times + std::size_t typecnt; // transition types + std::size_t charcnt; // zone abbreviation characters + std::size_t leapcnt; // leap seconds (we expect none) + std::size_t ttisstdcnt; // UTC/local indicators (unused) + std::size_t ttisutcnt; // standard/wall indicators (unused) + + bool Build(const tzhead& tzh); + std::size_t DataLength(std::size_t time_len) const; +}; + +// Builds the in-memory header using the raw bytes from the file. +bool Header::Build(const tzhead& tzh) { + std::int_fast32_t v; + if ((v = Decode32(tzh.tzh_timecnt)) < 0) return false; + timecnt = static_cast<std::size_t>(v); + if ((v = Decode32(tzh.tzh_typecnt)) < 0) return false; + typecnt = static_cast<std::size_t>(v); + if ((v = Decode32(tzh.tzh_charcnt)) < 0) return false; + charcnt = static_cast<std::size_t>(v); + if ((v = Decode32(tzh.tzh_leapcnt)) < 0) return false; + leapcnt = static_cast<std::size_t>(v); + if ((v = Decode32(tzh.tzh_ttisstdcnt)) < 0) return false; + ttisstdcnt = static_cast<std::size_t>(v); + if ((v = Decode32(tzh.tzh_ttisutcnt)) < 0) return false; + ttisutcnt = static_cast<std::size_t>(v); + return true; +} + +// How many bytes of data are associated with this header. The result +// depends upon whether this is a section with 4-byte or 8-byte times. +std::size_t Header::DataLength(std::size_t time_len) const { + std::size_t len = 0; + len += (time_len + 1) * timecnt; // unix_time + type_index + len += (4 + 1 + 1) * typecnt; // utc_offset + is_dst + abbr_index + len += 1 * charcnt; // abbreviations + len += (time_len + 4) * leapcnt; // leap-time + TAI-UTC + len += 1 * ttisstdcnt; // UTC/local indicators + len += 1 * ttisutcnt; // standard/wall indicators + return len; +} + // Does the rule for future transitions call for year-round daylight time? // See tz/zic.c:stringzone() for the details on how such rules are encoded. bool AllYearDST(const PosixTimeZone& posix) { @@ -217,98 +261,6 @@ inline civil_second YearShift(const civil_second& cs, year_t shift) { } // namespace -// What (no leap-seconds) UTC+seconds zoneinfo would look like. -bool TimeZoneInfo::ResetToBuiltinUTC(const seconds& offset) { - transition_types_.resize(1); - TransitionType& tt(transition_types_.back()); - tt.utc_offset = static_cast<std::int_least32_t>(offset.count()); - tt.is_dst = false; - tt.abbr_index = 0; - - // We temporarily add some redundant, contemporary (2015 through 2025) - // transitions for performance reasons. See TimeZoneInfo::LocalTime(). - // TODO: Fix the performance issue and remove the extra transitions. - transitions_.clear(); - transitions_.reserve(12); - for (const std::int_fast64_t unix_time : { - -(1LL << 59), // a "first half" transition - 1420070400LL, // 2015-01-01T00:00:00+00:00 - 1451606400LL, // 2016-01-01T00:00:00+00:00 - 1483228800LL, // 2017-01-01T00:00:00+00:00 - 1514764800LL, // 2018-01-01T00:00:00+00:00 - 1546300800LL, // 2019-01-01T00:00:00+00:00 - 1577836800LL, // 2020-01-01T00:00:00+00:00 - 1609459200LL, // 2021-01-01T00:00:00+00:00 - 1640995200LL, // 2022-01-01T00:00:00+00:00 - 1672531200LL, // 2023-01-01T00:00:00+00:00 - 1704067200LL, // 2024-01-01T00:00:00+00:00 - 1735689600LL, // 2025-01-01T00:00:00+00:00 - }) { - Transition& tr(*transitions_.emplace(transitions_.end())); - tr.unix_time = unix_time; - tr.type_index = 0; - tr.civil_sec = LocalTime(tr.unix_time, tt).cs; - tr.prev_civil_sec = tr.civil_sec - 1; - } - - default_transition_type_ = 0; - abbreviations_ = FixedOffsetToAbbr(offset); - abbreviations_.append(1, '\0'); - future_spec_.clear(); // never needed for a fixed-offset zone - extended_ = false; - - tt.civil_max = LocalTime(seconds::max().count(), tt).cs; - tt.civil_min = LocalTime(seconds::min().count(), tt).cs; - - transitions_.shrink_to_fit(); - return true; -} - -// Builds the in-memory header using the raw bytes from the file. -bool TimeZoneInfo::Header::Build(const tzhead& tzh) { - std::int_fast32_t v; - if ((v = Decode32(tzh.tzh_timecnt)) < 0) return false; - timecnt = static_cast<std::size_t>(v); - if ((v = Decode32(tzh.tzh_typecnt)) < 0) return false; - typecnt = static_cast<std::size_t>(v); - if ((v = Decode32(tzh.tzh_charcnt)) < 0) return false; - charcnt = static_cast<std::size_t>(v); - if ((v = Decode32(tzh.tzh_leapcnt)) < 0) return false; - leapcnt = static_cast<std::size_t>(v); - if ((v = Decode32(tzh.tzh_ttisstdcnt)) < 0) return false; - ttisstdcnt = static_cast<std::size_t>(v); - if ((v = Decode32(tzh.tzh_ttisutcnt)) < 0) return false; - ttisutcnt = static_cast<std::size_t>(v); - return true; -} - -// How many bytes of data are associated with this header. The result -// depends upon whether this is a section with 4-byte or 8-byte times. -std::size_t TimeZoneInfo::Header::DataLength(std::size_t time_len) const { - std::size_t len = 0; - len += (time_len + 1) * timecnt; // unix_time + type_index - len += (4 + 1 + 1) * typecnt; // utc_offset + is_dst + abbr_index - len += 1 * charcnt; // abbreviations - len += (time_len + 4) * leapcnt; // leap-time + TAI-UTC - len += 1 * ttisstdcnt; // UTC/local indicators - len += 1 * ttisutcnt; // standard/wall indicators - return len; -} - -// zic(8) can generate no-op transitions when a zone changes rules at an -// instant when there is actually no discontinuity. So we check whether -// two transitions have equivalent types (same offset/is_dst/abbr). -bool TimeZoneInfo::EquivTransitions(std::uint_fast8_t tt1_index, - std::uint_fast8_t tt2_index) const { - if (tt1_index == tt2_index) return true; - const TransitionType& tt1(transition_types_[tt1_index]); - const TransitionType& tt2(transition_types_[tt2_index]); - if (tt1.utc_offset != tt2.utc_offset) return false; - if (tt1.is_dst != tt2.is_dst) return false; - if (tt1.abbr_index != tt2.abbr_index) return false; - return true; -} - // Find/make a transition type with these attributes. bool TimeZoneInfo::GetTransitionType(std::int_fast32_t utc_offset, bool is_dst, const std::string& abbr, @@ -341,6 +293,20 @@ bool TimeZoneInfo::GetTransitionType(std::int_fast32_t utc_offset, bool is_dst, return true; } +// zic(8) can generate no-op transitions when a zone changes rules at an +// instant when there is actually no discontinuity. So we check whether +// two transitions have equivalent types (same offset/is_dst/abbr). +bool TimeZoneInfo::EquivTransitions(std::uint_fast8_t tt1_index, + std::uint_fast8_t tt2_index) const { + if (tt1_index == tt2_index) return true; + const TransitionType& tt1(transition_types_[tt1_index]); + const TransitionType& tt2(transition_types_[tt2_index]); + if (tt1.utc_offset != tt2.utc_offset) return false; + if (tt1.is_dst != tt2.is_dst) return false; + if (tt1.abbr_index != tt2.abbr_index) return false; + return true; +} + // Use the POSIX-TZ-environment-variable-style string to handle times // in years after the last transition stored in the zoneinfo data. bool TimeZoneInfo::ExtendTransitions() { @@ -372,11 +338,13 @@ bool TimeZoneInfo::ExtendTransitions() { return EquivTransitions(transitions_.back().type_index, dst_ti); } - // Extend the transitions for an additional 400 years using the - // future specification. Years beyond those can be handled by - // mapping back to a cycle-equivalent year within that range. - // We may need two additional transitions for the current year. - transitions_.reserve(transitions_.size() + 400 * 2 + 2); + // Extend the transitions for an additional 401 years using the future + // specification. Years beyond those can be handled by mapping back to + // a cycle-equivalent year within that range. Note that we need 401 + // (well, at least the first transition in the 401st year) so that the + // end of the 400th year is mapped back to an extended year. And first + // we may also need two additional transitions for the current year. + transitions_.reserve(transitions_.size() + 2 + 401 * 2); extended_ = true; const Transition& last(transitions_.back()); @@ -390,7 +358,7 @@ bool TimeZoneInfo::ExtendTransitions() { Transition dst = {0, dst_ti, civil_second(), civil_second()}; Transition std = {0, std_ti, civil_second(), civil_second()}; - for (const year_t limit = last_year_ + 400;; ++last_year_) { + for (const year_t limit = last_year_ + 401;; ++last_year_) { auto dst_trans_off = TransOffset(leap_year, jan1_weekday, posix.dst_start); auto std_trans_off = TransOffset(leap_year, jan1_weekday, posix.dst_end); dst.unix_time = jan1_time + dst_trans_off - posix.std_offset; @@ -410,193 +378,6 @@ bool TimeZoneInfo::ExtendTransitions() { return true; } -bool TimeZoneInfo::Load(ZoneInfoSource* zip) { - // Read and validate the header. - tzhead tzh; - if (zip->Read(&tzh, sizeof(tzh)) != sizeof(tzh)) return false; - if (strncmp(tzh.tzh_magic, TZ_MAGIC, sizeof(tzh.tzh_magic)) != 0) - return false; - Header hdr; - if (!hdr.Build(tzh)) return false; - std::size_t time_len = 4; - if (tzh.tzh_version[0] != '\0') { - // Skip the 4-byte data. - if (zip->Skip(hdr.DataLength(time_len)) != 0) return false; - // Read and validate the header for the 8-byte data. - if (zip->Read(&tzh, sizeof(tzh)) != sizeof(tzh)) return false; - if (strncmp(tzh.tzh_magic, TZ_MAGIC, sizeof(tzh.tzh_magic)) != 0) - return false; - if (tzh.tzh_version[0] == '\0') return false; - if (!hdr.Build(tzh)) return false; - time_len = 8; - } - if (hdr.typecnt == 0) return false; - if (hdr.leapcnt != 0) { - // This code assumes 60-second minutes so we do not want - // the leap-second encoded zoneinfo. We could reverse the - // compensation, but the "right" encoding is rarely used - // so currently we simply reject such data. - return false; - } - if (hdr.ttisstdcnt != 0 && hdr.ttisstdcnt != hdr.typecnt) return false; - if (hdr.ttisutcnt != 0 && hdr.ttisutcnt != hdr.typecnt) return false; - - // Read the data into a local buffer. - std::size_t len = hdr.DataLength(time_len); - std::vector<char> tbuf(len); - if (zip->Read(tbuf.data(), len) != len) return false; - const char* bp = tbuf.data(); - - // Decode and validate the transitions. - transitions_.reserve(hdr.timecnt + 2); - transitions_.resize(hdr.timecnt); - for (std::size_t i = 0; i != hdr.timecnt; ++i) { - transitions_[i].unix_time = (time_len == 4) ? Decode32(bp) : Decode64(bp); - bp += time_len; - if (i != 0) { - // Check that the transitions are ordered by time (as zic guarantees). - if (!Transition::ByUnixTime()(transitions_[i - 1], transitions_[i])) - return false; // out of order - } - } - bool seen_type_0 = false; - for (std::size_t i = 0; i != hdr.timecnt; ++i) { - transitions_[i].type_index = Decode8(bp++); - if (transitions_[i].type_index >= hdr.typecnt) return false; - if (transitions_[i].type_index == 0) seen_type_0 = true; - } - - // Decode and validate the transition types. - transition_types_.reserve(hdr.typecnt + 2); - transition_types_.resize(hdr.typecnt); - for (std::size_t i = 0; i != hdr.typecnt; ++i) { - transition_types_[i].utc_offset = - static_cast<std::int_least32_t>(Decode32(bp)); - if (transition_types_[i].utc_offset >= kSecsPerDay || - transition_types_[i].utc_offset <= -kSecsPerDay) - return false; - bp += 4; - transition_types_[i].is_dst = (Decode8(bp++) != 0); - transition_types_[i].abbr_index = Decode8(bp++); - if (transition_types_[i].abbr_index >= hdr.charcnt) return false; - } - - // Determine the before-first-transition type. - default_transition_type_ = 0; - if (seen_type_0 && hdr.timecnt != 0) { - std::uint_fast8_t index = 0; - if (transition_types_[0].is_dst) { - index = transitions_[0].type_index; - while (index != 0 && transition_types_[index].is_dst) --index; - } - while (index != hdr.typecnt && transition_types_[index].is_dst) ++index; - if (index != hdr.typecnt) default_transition_type_ = index; - } - - // Copy all the abbreviations. - abbreviations_.reserve(hdr.charcnt + 10); - abbreviations_.assign(bp, hdr.charcnt); - bp += hdr.charcnt; - - // Skip the unused portions. We've already dispensed with leap-second - // 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 += (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(); - if (tzh.tzh_version[0] != '\0') { - // Snarf up the NL-enclosed future POSIX spec. Note - // that version '3' files utilize an extended format. - auto get_char = [](ZoneInfoSource* azip) -> int { - unsigned char ch; // all non-EOF results are positive - return (azip->Read(&ch, 1) == 1) ? ch : EOF; - }; - if (get_char(zip) != '\n') return false; - for (int c = get_char(zip); c != '\n'; c = get_char(zip)) { - if (c == EOF) return false; - future_spec_.push_back(static_cast<char>(c)); - } - } - - // We don't check for EOF so that we're forwards compatible. - - // If we did not find version information during the standard loading - // process (as of tzh_version '3' that is unsupported), then ask the - // ZoneInfoSource for any out-of-bound version string it may be privy to. - if (version_.empty()) { - version_ = zip->Version(); - } - - // Trim redundant transitions. zic may have added these to work around - // differences between the glibc and reference implementations (see - // zic.c:dontmerge) 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)) { - break; - } - hdr.timecnt -= 1; - } - transitions_.resize(hdr.timecnt); - - // Ensure that there is always a transition in the first half of the - // time line (the second half is handled below) so that the signed - // difference between a civil_second and the civil_second of its - // previous transition is always representable, without overflow. - if (transitions_.empty() || transitions_.front().unix_time >= 0) { - Transition& tr(*transitions_.emplace(transitions_.begin())); - tr.unix_time = -(1LL << 59); // -18267312070-10-26T17:01:52+00:00 - tr.type_index = default_transition_type_; - } - - // Extend the transitions using the future specification. - if (!ExtendTransitions()) return false; - - // Ensure that there is always a transition in the second half of the - // time line (the first half is handled above) so that the signed - // difference between a civil_second and the civil_second of its - // previous transition is always representable, without overflow. - const Transition& last(transitions_.back()); - if (last.unix_time < 0) { - const std::uint_fast8_t type_index = last.type_index; - Transition& tr(*transitions_.emplace(transitions_.end())); - tr.unix_time = 2147483647; // 2038-01-19T03:14:07+00:00 - tr.type_index = type_index; - } - - // Compute the local civil time for each transition and the preceding - // second. These will be used for reverse conversions in MakeTime(). - const TransitionType* ttp = &transition_types_[default_transition_type_]; - for (std::size_t i = 0; i != transitions_.size(); ++i) { - Transition& tr(transitions_[i]); - tr.prev_civil_sec = LocalTime(tr.unix_time, *ttp).cs - 1; - ttp = &transition_types_[tr.type_index]; - tr.civil_sec = LocalTime(tr.unix_time, *ttp).cs; - if (i != 0) { - // Check that the transitions are ordered by civil time. Essentially - // this means that an offset change cannot cross another such change. - // No one does this in practice, and we depend on it in MakeTime(). - if (!Transition::ByCivilTime()(transitions_[i - 1], tr)) - return false; // out of order - } - } - - // Compute the maximum/minimum civil times that can be converted to a - // time_point<seconds> for each of the zone's transition types. - for (auto& tt : transition_types_) { - tt.civil_max = LocalTime(seconds::max().count(), tt).cs; - tt.civil_min = LocalTime(seconds::min().count(), tt).cs; - } - - transitions_.shrink_to_fit(); - return true; -} - namespace { using FilePtr = std::unique_ptr<FILE, int (*)(FILE*)>; @@ -693,7 +474,8 @@ std::unique_ptr<ZoneInfoSource> AndroidZoneInfoSource::Open( const std::size_t pos = (name.compare(0, 5, "file:") == 0) ? 5 : 0; // See Android's libc/tzcode/bionic.cpp for additional information. - for (const char* tzdata : {"/data/misc/zoneinfo/current/tzdata", + for (const char* tzdata : {"/apex/com.android.tzdata/etc/tz/tzdata", + "/data/misc/zoneinfo/current/tzdata", "/system/usr/share/zoneinfo/tzdata"}) { auto fp = FOpen(tzdata, "rb"); if (fp == nullptr) continue; @@ -758,9 +540,16 @@ std::unique_ptr<ZoneInfoSource> FuchsiaZoneInfoSource::Open( // Prefixes where a Fuchsia component might find zoneinfo files, // in descending order of preference. const auto kTzdataPrefixes = { + // The tzdata from `config-data`. "/config/data/tzdata/", + // The tzdata bundled in the component's package. "/pkg/data/tzdata/", + // General data storage. "/data/tzdata/", + // The recommended path for routed-in tzdata files. + // See for details: + // https://fuchsia.dev/fuchsia-src/concepts/process/namespaces?hl=en#typical_directory_structure + "/config/tzdata/", }; const auto kEmptyPrefix = {""}; const bool name_absolute = (pos != name.size() && name[pos] == '/'); @@ -795,6 +584,227 @@ std::unique_ptr<ZoneInfoSource> FuchsiaZoneInfoSource::Open( } // namespace +// What (no leap-seconds) UTC+seconds zoneinfo would look like. +bool TimeZoneInfo::ResetToBuiltinUTC(const seconds& offset) { + transition_types_.resize(1); + TransitionType& tt(transition_types_.back()); + tt.utc_offset = static_cast<std::int_least32_t>(offset.count()); + tt.is_dst = false; + tt.abbr_index = 0; + + // We temporarily add some redundant, contemporary (2015 through 2025) + // transitions for performance reasons. See TimeZoneInfo::LocalTime(). + // TODO: Fix the performance issue and remove the extra transitions. + transitions_.clear(); + transitions_.reserve(12); + for (const std::int_fast64_t unix_time : { + -(1LL << 59), // a "first half" transition + 1420070400LL, // 2015-01-01T00:00:00+00:00 + 1451606400LL, // 2016-01-01T00:00:00+00:00 + 1483228800LL, // 2017-01-01T00:00:00+00:00 + 1514764800LL, // 2018-01-01T00:00:00+00:00 + 1546300800LL, // 2019-01-01T00:00:00+00:00 + 1577836800LL, // 2020-01-01T00:00:00+00:00 + 1609459200LL, // 2021-01-01T00:00:00+00:00 + 1640995200LL, // 2022-01-01T00:00:00+00:00 + 1672531200LL, // 2023-01-01T00:00:00+00:00 + 1704067200LL, // 2024-01-01T00:00:00+00:00 + 1735689600LL, // 2025-01-01T00:00:00+00:00 + }) { + Transition& tr(*transitions_.emplace(transitions_.end())); + tr.unix_time = unix_time; + tr.type_index = 0; + tr.civil_sec = LocalTime(tr.unix_time, tt).cs; + tr.prev_civil_sec = tr.civil_sec - 1; + } + + default_transition_type_ = 0; + abbreviations_ = FixedOffsetToAbbr(offset); + abbreviations_.append(1, '\0'); + future_spec_.clear(); // never needed for a fixed-offset zone + extended_ = false; + + tt.civil_max = LocalTime(seconds::max().count(), tt).cs; + tt.civil_min = LocalTime(seconds::min().count(), tt).cs; + + transitions_.shrink_to_fit(); + return true; +} + +bool TimeZoneInfo::Load(ZoneInfoSource* zip) { + // Read and validate the header. + tzhead tzh; + if (zip->Read(&tzh, sizeof(tzh)) != sizeof(tzh)) return false; + if (strncmp(tzh.tzh_magic, TZ_MAGIC, sizeof(tzh.tzh_magic)) != 0) + return false; + Header hdr; + if (!hdr.Build(tzh)) return false; + std::size_t time_len = 4; + if (tzh.tzh_version[0] != '\0') { + // Skip the 4-byte data. + if (zip->Skip(hdr.DataLength(time_len)) != 0) return false; + // Read and validate the header for the 8-byte data. + if (zip->Read(&tzh, sizeof(tzh)) != sizeof(tzh)) return false; + if (strncmp(tzh.tzh_magic, TZ_MAGIC, sizeof(tzh.tzh_magic)) != 0) + return false; + if (tzh.tzh_version[0] == '\0') return false; + if (!hdr.Build(tzh)) return false; + time_len = 8; + } + if (hdr.typecnt == 0) return false; + if (hdr.leapcnt != 0) { + // This code assumes 60-second minutes so we do not want + // the leap-second encoded zoneinfo. We could reverse the + // compensation, but the "right" encoding is rarely used + // so currently we simply reject such data. + return false; + } + if (hdr.ttisstdcnt != 0 && hdr.ttisstdcnt != hdr.typecnt) return false; + if (hdr.ttisutcnt != 0 && hdr.ttisutcnt != hdr.typecnt) return false; + + // Read the data into a local buffer. + std::size_t len = hdr.DataLength(time_len); + std::vector<char> tbuf(len); + if (zip->Read(tbuf.data(), len) != len) return false; + const char* bp = tbuf.data(); + + // Decode and validate the transitions. + transitions_.reserve(hdr.timecnt + 2); + transitions_.resize(hdr.timecnt); + for (std::size_t i = 0; i != hdr.timecnt; ++i) { + transitions_[i].unix_time = (time_len == 4) ? Decode32(bp) : Decode64(bp); + bp += time_len; + if (i != 0) { + // Check that the transitions are ordered by time (as zic guarantees). + if (!Transition::ByUnixTime()(transitions_[i - 1], transitions_[i])) + return false; // out of order + } + } + bool seen_type_0 = false; + for (std::size_t i = 0; i != hdr.timecnt; ++i) { + transitions_[i].type_index = Decode8(bp++); + if (transitions_[i].type_index >= hdr.typecnt) return false; + if (transitions_[i].type_index == 0) seen_type_0 = true; + } + + // Decode and validate the transition types. + transition_types_.reserve(hdr.typecnt + 2); + transition_types_.resize(hdr.typecnt); + for (std::size_t i = 0; i != hdr.typecnt; ++i) { + transition_types_[i].utc_offset = + static_cast<std::int_least32_t>(Decode32(bp)); + if (transition_types_[i].utc_offset >= kSecsPerDay || + transition_types_[i].utc_offset <= -kSecsPerDay) + return false; + bp += 4; + transition_types_[i].is_dst = (Decode8(bp++) != 0); + transition_types_[i].abbr_index = Decode8(bp++); + if (transition_types_[i].abbr_index >= hdr.charcnt) return false; + } + + // Determine the before-first-transition type. + default_transition_type_ = 0; + if (seen_type_0 && hdr.timecnt != 0) { + std::uint_fast8_t index = 0; + if (transition_types_[0].is_dst) { + index = transitions_[0].type_index; + while (index != 0 && transition_types_[index].is_dst) --index; + } + while (index != hdr.typecnt && transition_types_[index].is_dst) ++index; + if (index != hdr.typecnt) default_transition_type_ = index; + } + + // Copy all the abbreviations. + abbreviations_.reserve(hdr.charcnt + 10); + abbreviations_.assign(bp, hdr.charcnt); + bp += hdr.charcnt; + + // Skip the unused portions. We've already dispensed with leap-second + // 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 += (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(); + if (tzh.tzh_version[0] != '\0') { + // Snarf up the NL-enclosed future POSIX spec. Note + // that version '3' files utilize an extended format. + auto get_char = [](ZoneInfoSource* azip) -> int { + unsigned char ch; // all non-EOF results are positive + return (azip->Read(&ch, 1) == 1) ? ch : EOF; + }; + if (get_char(zip) != '\n') return false; + for (int c = get_char(zip); c != '\n'; c = get_char(zip)) { + if (c == EOF) return false; + future_spec_.push_back(static_cast<char>(c)); + } + } + + // We don't check for EOF so that we're forwards compatible. + + // If we did not find version information during the standard loading + // process (as of tzh_version '3' that is unsupported), then ask the + // ZoneInfoSource for any out-of-bound version string it may be privy to. + if (version_.empty()) { + version_ = zip->Version(); + } + + // Ensure that there is always a transition in the first half of the + // time line (the second half is handled below) so that the signed + // difference between a civil_second and the civil_second of its + // previous transition is always representable, without overflow. + if (transitions_.empty() || transitions_.front().unix_time >= 0) { + Transition& tr(*transitions_.emplace(transitions_.begin())); + tr.unix_time = -(1LL << 59); // -18267312070-10-26T17:01:52+00:00 + tr.type_index = default_transition_type_; + } + + // Extend the transitions using the future specification. + if (!ExtendTransitions()) return false; + + // Ensure that there is always a transition in the second half of the + // time line (the first half is handled above) so that the signed + // difference between a civil_second and the civil_second of its + // previous transition is always representable, without overflow. + const Transition& last(transitions_.back()); + if (last.unix_time < 0) { + const std::uint_fast8_t type_index = last.type_index; + Transition& tr(*transitions_.emplace(transitions_.end())); + tr.unix_time = 2147483647; // 2038-01-19T03:14:07+00:00 + tr.type_index = type_index; + } + + // Compute the local civil time for each transition and the preceding + // second. These will be used for reverse conversions in MakeTime(). + const TransitionType* ttp = &transition_types_[default_transition_type_]; + for (std::size_t i = 0; i != transitions_.size(); ++i) { + Transition& tr(transitions_[i]); + tr.prev_civil_sec = LocalTime(tr.unix_time, *ttp).cs - 1; + ttp = &transition_types_[tr.type_index]; + tr.civil_sec = LocalTime(tr.unix_time, *ttp).cs; + if (i != 0) { + // Check that the transitions are ordered by civil time. Essentially + // this means that an offset change cannot cross another such change. + // No one does this in practice, and we depend on it in MakeTime(). + if (!Transition::ByCivilTime()(transitions_[i - 1], tr)) + return false; // out of order + } + } + + // Compute the maximum/minimum civil times that can be converted to a + // time_point<seconds> for each of the zone's transition types. + for (auto& tt : transition_types_) { + tt.civil_max = LocalTime(seconds::max().count(), tt).cs; + tt.civil_min = LocalTime(seconds::min().count(), tt).cs; + } + + transitions_.shrink_to_fit(); + return true; +} + bool TimeZoneInfo::Load(const std::string& name) { // We can ensure that the loading of UTC or any other fixed-offset // zone never fails because the simple, fixed-offset state can be @@ -816,6 +826,18 @@ bool TimeZoneInfo::Load(const std::string& name) { return zip != nullptr && Load(zip.get()); } +std::unique_ptr<TimeZoneInfo> TimeZoneInfo::UTC() { + auto tz = std::unique_ptr<TimeZoneInfo>(new TimeZoneInfo); + tz->ResetToBuiltinUTC(seconds::zero()); + return tz; +} + +std::unique_ptr<TimeZoneInfo> TimeZoneInfo::Make(const std::string& name) { + auto tz = std::unique_ptr<TimeZoneInfo>(new TimeZoneInfo); + if (!tz->Load(name)) tz.reset(); // fallback to UTC + return tz; +} + // BreakTime() translation for a particular transition type. time_zone::absolute_lookup TimeZoneInfo::LocalTime( std::int_fast64_t unix_time, const TransitionType& tt) const { diff --git a/absl/time/internal/cctz/src/time_zone_info.h b/absl/time/internal/cctz/src/time_zone_info.h index 2467ff55..689df6f9 100644 --- a/absl/time/internal/cctz/src/time_zone_info.h +++ b/absl/time/internal/cctz/src/time_zone_info.h @@ -18,6 +18,7 @@ #include <atomic> #include <cstddef> #include <cstdint> +#include <memory> #include <string> #include <vector> @@ -64,12 +65,9 @@ struct TransitionType { // A time zone backed by the IANA Time Zone Database (zoneinfo). class TimeZoneInfo : public TimeZoneIf { public: - TimeZoneInfo() = default; - TimeZoneInfo(const TimeZoneInfo&) = delete; - TimeZoneInfo& operator=(const TimeZoneInfo&) = delete; - - // Loads the zoneinfo for the given name, returning true if successful. - bool Load(const std::string& name); + // Factories. + static std::unique_ptr<TimeZoneInfo> UTC(); // never fails + static std::unique_ptr<TimeZoneInfo> Make(const std::string& name); // TimeZoneIf implementations. time_zone::absolute_lookup BreakTime( @@ -83,17 +81,9 @@ class TimeZoneInfo : public TimeZoneIf { std::string Description() const override; private: - struct Header { // counts of: - std::size_t timecnt; // transition times - std::size_t typecnt; // transition types - std::size_t charcnt; // zone abbreviation characters - std::size_t leapcnt; // leap seconds (we expect none) - std::size_t ttisstdcnt; // UTC/local indicators (unused) - std::size_t ttisutcnt; // standard/wall indicators (unused) - - bool Build(const tzhead& tzh); - std::size_t DataLength(std::size_t time_len) const; - }; + TimeZoneInfo() = default; + TimeZoneInfo(const TimeZoneInfo&) = delete; + TimeZoneInfo& operator=(const TimeZoneInfo&) = delete; bool GetTransitionType(std::int_fast32_t utc_offset, bool is_dst, const std::string& abbr, std::uint_least8_t* index); @@ -102,6 +92,7 @@ class TimeZoneInfo : public TimeZoneIf { bool ExtendTransitions(); bool ResetToBuiltinUTC(const seconds& offset); + bool Load(const std::string& name); bool Load(ZoneInfoSource* zip); // Helpers for BreakTime() and MakeTime(). diff --git a/absl/time/internal/cctz/src/time_zone_libc.cc b/absl/time/internal/cctz/src/time_zone_libc.cc index 887dd097..d0146122 100644 --- a/absl/time/internal/cctz/src/time_zone_libc.cc +++ b/absl/time/internal/cctz/src/time_zone_libc.cc @@ -62,7 +62,7 @@ auto tm_zone(const std::tm& tm) -> decltype(tzname[0]) { } #elif defined(__native_client__) || defined(__myriad2__) || \ defined(__EMSCRIPTEN__) -// Uses the globals: 'timezone' and 'tzname'. +// Uses the globals: '_timezone' and 'tzname'. auto tm_gmtoff(const std::tm& tm) -> decltype(_timezone + 0) { const bool is_dst = tm.tm_isdst > 0; return _timezone + (is_dst ? 60 * 60 : 0); @@ -71,6 +71,16 @@ auto tm_zone(const std::tm& tm) -> decltype(tzname[0]) { const bool is_dst = tm.tm_isdst > 0; return tzname[is_dst]; } +#elif defined(__VXWORKS__) +// Uses the globals: 'timezone' and 'tzname'. +auto tm_gmtoff(const std::tm& tm) -> decltype(timezone + 0) { + const bool is_dst = tm.tm_isdst > 0; + return timezone + (is_dst ? 60 * 60 : 0); +} +auto tm_zone(const std::tm& tm) -> decltype(tzname[0]) { + const bool is_dst = tm.tm_isdst > 0; + return tzname[is_dst]; +} #else // Adapt to different spellings of the struct std::tm extension fields. #if defined(tm_gmtoff) @@ -108,6 +118,7 @@ auto tm_zone(const T& tm) -> decltype(tm.__tm_zone) { } #endif // tm_zone #endif +using tm_gmtoff_t = decltype(tm_gmtoff(std::tm{})); inline std::tm* gm_time(const std::time_t* timep, std::tm* result) { #if defined(_WIN32) || defined(_WIN64) @@ -125,37 +136,36 @@ inline std::tm* local_time(const std::time_t* timep, std::tm* result) { #endif } -// Converts a civil second and "dst" flag into a time_t and UTC offset. +// Converts a civil second and "dst" flag into a time_t and a struct tm. // Returns false if time_t cannot represent the requested civil second. // Caller must have already checked that cs.year() will fit into a tm_year. -bool make_time(const civil_second& cs, int is_dst, std::time_t* t, int* off) { - std::tm tm; - tm.tm_year = static_cast<int>(cs.year() - year_t{1900}); - tm.tm_mon = cs.month() - 1; - tm.tm_mday = cs.day(); - tm.tm_hour = cs.hour(); - tm.tm_min = cs.minute(); - tm.tm_sec = cs.second(); - tm.tm_isdst = is_dst; - *t = std::mktime(&tm); +bool make_time(const civil_second& cs, int is_dst, std::time_t* t, + std::tm* tm) { + tm->tm_year = static_cast<int>(cs.year() - year_t{1900}); + tm->tm_mon = cs.month() - 1; + tm->tm_mday = cs.day(); + tm->tm_hour = cs.hour(); + tm->tm_min = cs.minute(); + tm->tm_sec = cs.second(); + tm->tm_isdst = is_dst; + *t = std::mktime(tm); if (*t == std::time_t{-1}) { std::tm tm2; const std::tm* tmp = local_time(t, &tm2); - if (tmp == nullptr || tmp->tm_year != tm.tm_year || - tmp->tm_mon != tm.tm_mon || tmp->tm_mday != tm.tm_mday || - tmp->tm_hour != tm.tm_hour || tmp->tm_min != tm.tm_min || - tmp->tm_sec != tm.tm_sec) { + if (tmp == nullptr || tmp->tm_year != tm->tm_year || + tmp->tm_mon != tm->tm_mon || tmp->tm_mday != tm->tm_mday || + tmp->tm_hour != tm->tm_hour || tmp->tm_min != tm->tm_min || + tmp->tm_sec != tm->tm_sec) { // A true error (not just one second before the epoch). return false; } } - *off = static_cast<int>(tm_gmtoff(tm)); return true; } // Find the least time_t in [lo:hi] where local time matches offset, given: // (1) lo doesn't match, (2) hi does, and (3) there is only one transition. -std::time_t find_trans(std::time_t lo, std::time_t hi, int offset) { +std::time_t find_trans(std::time_t lo, std::time_t hi, tm_gmtoff_t offset) { std::tm tm; while (lo + 1 != hi) { const std::time_t mid = lo + (hi - lo) / 2; @@ -183,8 +193,9 @@ std::time_t find_trans(std::time_t lo, std::time_t hi, int offset) { } // namespace -TimeZoneLibC::TimeZoneLibC(const std::string& name) - : local_(name == "localtime") {} +std::unique_ptr<TimeZoneLibC> TimeZoneLibC::Make(const std::string& name) { + return std::unique_ptr<TimeZoneLibC>(new TimeZoneLibC(name)); +} time_zone::absolute_lookup TimeZoneLibC::BreakTime( const time_point<seconds>& tp) const { @@ -254,33 +265,37 @@ time_zone::civil_lookup TimeZoneLibC::MakeTime(const civil_second& cs) const { // We probe with "is_dst" values of 0 and 1 to try to distinguish unique // civil seconds from skipped or repeated ones. This is not always possible // however, as the "dst" flag does not change over some offset transitions. - // We are also subject to the vagaries of mktime() implementations. + // We are also subject to the vagaries of mktime() implementations. For + // example, some implementations treat "tm_isdst" as a demand (useless), + // and some as a disambiguator (useful). std::time_t t0, t1; - int offset0, offset1; - if (make_time(cs, 0, &t0, &offset0) && make_time(cs, 1, &t1, &offset1)) { - if (t0 == t1) { + std::tm tm0, tm1; + if (make_time(cs, 0, &t0, &tm0) && make_time(cs, 1, &t1, &tm1)) { + if (tm0.tm_isdst == tm1.tm_isdst) { // The civil time was singular (pre == trans == post). - const time_point<seconds> tp = FromUnixSeconds(t0); + const time_point<seconds> tp = FromUnixSeconds(tm0.tm_isdst ? t1 : t0); return {time_zone::civil_lookup::UNIQUE, tp, tp, tp}; } - if (t0 > t1) { + tm_gmtoff_t offset = tm_gmtoff(tm0); + if (t0 < t1) { // negative DST std::swap(t0, t1); - std::swap(offset0, offset1); + offset = tm_gmtoff(tm1); } - const std::time_t tt = find_trans(t0, t1, offset1); + + const std::time_t tt = find_trans(t1, t0, offset); const time_point<seconds> trans = FromUnixSeconds(tt); - if (offset0 < offset1) { + if (tm0.tm_isdst) { // The civil time did not exist (pre >= trans > post). - const time_point<seconds> pre = FromUnixSeconds(t1); - const time_point<seconds> post = FromUnixSeconds(t0); + const time_point<seconds> pre = FromUnixSeconds(t0); + const time_point<seconds> post = FromUnixSeconds(t1); return {time_zone::civil_lookup::SKIPPED, pre, trans, post}; } // The civil time was ambiguous (pre < trans <= post). - const time_point<seconds> pre = FromUnixSeconds(t0); - const time_point<seconds> post = FromUnixSeconds(t1); + const time_point<seconds> pre = FromUnixSeconds(t1); + const time_point<seconds> post = FromUnixSeconds(t0); return {time_zone::civil_lookup::REPEATED, pre, trans, post}; } @@ -309,6 +324,9 @@ std::string TimeZoneLibC::Description() const { return local_ ? "localtime" : "UTC"; } +TimeZoneLibC::TimeZoneLibC(const std::string& name) + : local_(name == "localtime") {} + } // namespace cctz } // namespace time_internal ABSL_NAMESPACE_END diff --git a/absl/time/internal/cctz/src/time_zone_libc.h b/absl/time/internal/cctz/src/time_zone_libc.h index 1da9039a..ae210737 100644 --- a/absl/time/internal/cctz/src/time_zone_libc.h +++ b/absl/time/internal/cctz/src/time_zone_libc.h @@ -15,6 +15,7 @@ #ifndef ABSL_TIME_INTERNAL_CCTZ_TIME_ZONE_LIBC_H_ #define ABSL_TIME_INTERNAL_CCTZ_TIME_ZONE_LIBC_H_ +#include <memory> #include <string> #include "absl/base/config.h" @@ -27,10 +28,10 @@ namespace cctz { // A time zone backed by gmtime_r(3), localtime_r(3), and mktime(3), // and which therefore only supports UTC and the local time zone. -// TODO: Add support for fixed offsets from UTC. class TimeZoneLibC : public TimeZoneIf { public: - explicit TimeZoneLibC(const std::string& name); + // Factory. + static std::unique_ptr<TimeZoneLibC> Make(const std::string& name); // TimeZoneIf implementations. time_zone::absolute_lookup BreakTime( @@ -44,6 +45,10 @@ class TimeZoneLibC : public TimeZoneIf { std::string Description() const override; private: + explicit TimeZoneLibC(const std::string& name); + TimeZoneLibC(const TimeZoneLibC&) = delete; + TimeZoneLibC& operator=(const TimeZoneLibC&) = delete; + const bool local_; // localtime or UTC }; diff --git a/absl/time/internal/cctz/src/time_zone_lookup.cc b/absl/time/internal/cctz/src/time_zone_lookup.cc index f6983aeb..d22691bd 100644 --- a/absl/time/internal/cctz/src/time_zone_lookup.cc +++ b/absl/time/internal/cctz/src/time_zone_lookup.cc @@ -35,6 +35,24 @@ #include <zircon/types.h> #endif +#if defined(_WIN32) +#include <sdkddkver.h> +// Include only when the SDK is for Windows 10 (and later), and the binary is +// targeted for Windows XP and later. +// Note: The Windows SDK added windows.globalization.h file for Windows 10, but +// MinGW did not add it until NTDDI_WIN10_NI (SDK version 10.0.22621.0). +#if ((defined(_WIN32_WINNT_WIN10) && !defined(__MINGW32__)) || \ + (defined(NTDDI_WIN10_NI) && NTDDI_VERSION >= NTDDI_WIN10_NI)) && \ + (_WIN32_WINNT >= _WIN32_WINNT_WINXP) +#define USE_WIN32_LOCAL_TIME_ZONE +#include <roapi.h> +#include <tchar.h> +#include <wchar.h> +#include <windows.globalization.h> +#include <windows.h> +#endif +#endif + #include <cstdlib> #include <cstring> #include <string> @@ -47,8 +65,8 @@ ABSL_NAMESPACE_BEGIN namespace time_internal { namespace cctz { -#if defined(__ANDROID__) && defined(__ANDROID_API__) && __ANDROID_API__ >= 21 namespace { +#if defined(__ANDROID__) && defined(__ANDROID_API__) && __ANDROID_API__ >= 21 // Android 'L' removes __system_property_get() from the NDK, however // it is still a hidden symbol in libc so we use dlsym() to access it. // See Chromium's base/sys_info_android.cc for a similar example. @@ -72,9 +90,84 @@ int __system_property_get(const char* name, char* value) { static property_get_func system_property_get = LoadSystemPropertyGet(); return system_property_get ? system_property_get(name, value) : -1; } +#endif -} // namespace +#if defined(USE_WIN32_LOCAL_TIME_ZONE) +// Calls the WinRT Calendar.GetTimeZone method to obtain the IANA ID of the +// local time zone. Returns an empty vector in case of an error. +std::string win32_local_time_zone(const HMODULE combase) { + std::string result; + const auto ro_activate_instance = + reinterpret_cast<decltype(&RoActivateInstance)>( + GetProcAddress(combase, "RoActivateInstance")); + if (!ro_activate_instance) { + return result; + } + const auto windows_create_string_reference = + reinterpret_cast<decltype(&WindowsCreateStringReference)>( + GetProcAddress(combase, "WindowsCreateStringReference")); + if (!windows_create_string_reference) { + return result; + } + const auto windows_delete_string = + reinterpret_cast<decltype(&WindowsDeleteString)>( + GetProcAddress(combase, "WindowsDeleteString")); + if (!windows_delete_string) { + return result; + } + const auto windows_get_string_raw_buffer = + reinterpret_cast<decltype(&WindowsGetStringRawBuffer)>( + GetProcAddress(combase, "WindowsGetStringRawBuffer")); + if (!windows_get_string_raw_buffer) { + return result; + } + + // The string returned by WindowsCreateStringReference doesn't need to be + // deleted. + HSTRING calendar_class_id; + HSTRING_HEADER calendar_class_id_header; + HRESULT hr = windows_create_string_reference( + RuntimeClass_Windows_Globalization_Calendar, + sizeof(RuntimeClass_Windows_Globalization_Calendar) / sizeof(wchar_t) - 1, + &calendar_class_id_header, &calendar_class_id); + if (FAILED(hr)) { + return result; + } + + IInspectable* calendar; + hr = ro_activate_instance(calendar_class_id, &calendar); + if (FAILED(hr)) { + return result; + } + + ABI::Windows::Globalization::ITimeZoneOnCalendar* time_zone; + hr = calendar->QueryInterface(IID_PPV_ARGS(&time_zone)); + if (FAILED(hr)) { + calendar->Release(); + return result; + } + + HSTRING tz_hstr; + hr = time_zone->GetTimeZone(&tz_hstr); + if (SUCCEEDED(hr)) { + UINT32 wlen; + const PCWSTR tz_wstr = windows_get_string_raw_buffer(tz_hstr, &wlen); + if (tz_wstr) { + const int size = + WideCharToMultiByte(CP_UTF8, 0, tz_wstr, static_cast<int>(wlen), + nullptr, 0, nullptr, nullptr); + result.resize(static_cast<size_t>(size)); + WideCharToMultiByte(CP_UTF8, 0, tz_wstr, static_cast<int>(wlen), + &result[0], size, nullptr, nullptr); + } + windows_delete_string(tz_hstr); + } + time_zone->Release(); + calendar->Release(); + return result; +} #endif +} // namespace std::string time_zone::name() const { return effective_impl().Name(); } @@ -190,6 +283,39 @@ time_zone local_time_zone() { zone = primary_tz.c_str(); } #endif +#if defined(USE_WIN32_LOCAL_TIME_ZONE) + // Use the WinRT Calendar class to get the local time zone. This feature is + // available on Windows 10 and later. The library is dynamically linked to + // maintain binary compatibility with Windows XP - Windows 7. On Windows 8, + // The combase.dll API functions are available but the RoActivateInstance + // call will fail for the Calendar class. + std::string winrt_tz; + const HMODULE combase = + LoadLibraryEx(_T("combase.dll"), nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); + if (combase) { + const auto ro_initialize = reinterpret_cast<decltype(&::RoInitialize)>( + GetProcAddress(combase, "RoInitialize")); + const auto ro_uninitialize = reinterpret_cast<decltype(&::RoUninitialize)>( + GetProcAddress(combase, "RoUninitialize")); + if (ro_initialize && ro_uninitialize) { + const HRESULT hr = ro_initialize(RO_INIT_MULTITHREADED); + // RPC_E_CHANGED_MODE means that a previous RoInitialize call specified + // a different concurrency model. The WinRT runtime is initialized and + // should work for our purpose here, but we should *not* call + // RoUninitialize because it's a failure. + if (SUCCEEDED(hr) || hr == RPC_E_CHANGED_MODE) { + winrt_tz = win32_local_time_zone(combase); + if (SUCCEEDED(hr)) { + ro_uninitialize(); + } + } + } + FreeLibrary(combase); + } + if (!winrt_tz.empty()) { + zone = winrt_tz.c_str(); + } +#endif // Allow ${TZ} to override to default zone. char* tz_env = nullptr; diff --git a/absl/time/internal/cctz/src/time_zone_lookup_test.cc b/absl/time/internal/cctz/src/time_zone_lookup_test.cc index ab461f04..6f7e5cfe 100644 --- a/absl/time/internal/cctz/src/time_zone_lookup_test.cc +++ b/absl/time/internal/cctz/src/time_zone_lookup_test.cc @@ -45,7 +45,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Africa/Addis_Ababa", "Africa/Algiers", "Africa/Asmara", - "Africa/Asmera", "Africa/Bamako", "Africa/Bangui", "Africa/Banjul", @@ -101,7 +100,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "America/Araguaina", "America/Argentina/Buenos_Aires", "America/Argentina/Catamarca", - "America/Argentina/ComodRivadavia", "America/Argentina/Cordoba", "America/Argentina/Jujuy", "America/Argentina/La_Rioja", @@ -125,18 +123,16 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "America/Boa_Vista", "America/Bogota", "America/Boise", - "America/Buenos_Aires", "America/Cambridge_Bay", "America/Campo_Grande", "America/Cancun", "America/Caracas", - "America/Catamarca", "America/Cayenne", "America/Cayman", "America/Chicago", "America/Chihuahua", + "America/Ciudad_Juarez", "America/Coral_Harbour", - "America/Cordoba", "America/Costa_Rica", "America/Creston", "America/Cuiaba", @@ -152,7 +148,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "America/El_Salvador", "America/Ensenada", "America/Fort_Nelson", - "America/Fort_Wayne", "America/Fortaleza", "America/Glace_Bay", "America/Godthab", @@ -174,20 +169,16 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "America/Indiana/Vevay", "America/Indiana/Vincennes", "America/Indiana/Winamac", - "America/Indianapolis", "America/Inuvik", "America/Iqaluit", "America/Jamaica", - "America/Jujuy", "America/Juneau", "America/Kentucky/Louisville", "America/Kentucky/Monticello", - "America/Knox_IN", "America/Kralendijk", "America/La_Paz", "America/Lima", "America/Los_Angeles", - "America/Louisville", "America/Lower_Princes", "America/Maceio", "America/Managua", @@ -196,7 +187,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "America/Martinique", "America/Matamoros", "America/Mazatlan", - "America/Mendoza", "America/Menominee", "America/Merida", "America/Metlakatla", @@ -233,7 +223,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "America/Regina", "America/Resolute", "America/Rio_Branco", - "America/Rosario", "America/Santa_Isabel", "America/Santarem", "America/Santiago", @@ -269,7 +258,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Antarctica/McMurdo", "Antarctica/Palmer", "Antarctica/Rothera", - "Antarctica/South_Pole", "Antarctica/Syowa", "Antarctica/Troll", "Antarctica/Vostok", @@ -281,7 +269,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Asia/Aqtau", "Asia/Aqtobe", "Asia/Ashgabat", - "Asia/Ashkhabad", "Asia/Atyrau", "Asia/Baghdad", "Asia/Bahrain", @@ -291,13 +278,10 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Asia/Beirut", "Asia/Bishkek", "Asia/Brunei", - "Asia/Calcutta", "Asia/Chita", "Asia/Choibalsan", "Asia/Chongqing", - "Asia/Chungking", "Asia/Colombo", - "Asia/Dacca", "Asia/Damascus", "Asia/Dhaka", "Asia/Dili", @@ -320,14 +304,12 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Asia/Karachi", "Asia/Kashgar", "Asia/Kathmandu", - "Asia/Katmandu", "Asia/Khandyga", "Asia/Kolkata", "Asia/Krasnoyarsk", "Asia/Kuala_Lumpur", "Asia/Kuching", "Asia/Kuwait", - "Asia/Macao", "Asia/Macau", "Asia/Magadan", "Asia/Makassar", @@ -344,9 +326,7 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Asia/Qatar", "Asia/Qostanay", "Asia/Qyzylorda", - "Asia/Rangoon", "Asia/Riyadh", - "Asia/Saigon", "Asia/Sakhalin", "Asia/Samarkand", "Asia/Seoul", @@ -358,13 +338,10 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Asia/Tbilisi", "Asia/Tehran", "Asia/Tel_Aviv", - "Asia/Thimbu", "Asia/Thimphu", "Asia/Tokyo", "Asia/Tomsk", - "Asia/Ujung_Pandang", "Asia/Ulaanbaatar", - "Asia/Ulan_Bator", "Asia/Urumqi", "Asia/Ust-Nera", "Asia/Vientiane", @@ -377,7 +354,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Atlantic/Bermuda", "Atlantic/Canary", "Atlantic/Cape_Verde", - "Atlantic/Faeroe", "Atlantic/Faroe", "Atlantic/Jan_Mayen", "Atlantic/Madeira", @@ -385,7 +361,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Atlantic/South_Georgia", "Atlantic/St_Helena", "Atlantic/Stanley", - "Australia/ACT", "Australia/Adelaide", "Australia/Brisbane", "Australia/Broken_Hill", @@ -394,42 +369,12 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Australia/Darwin", "Australia/Eucla", "Australia/Hobart", - "Australia/LHI", "Australia/Lindeman", "Australia/Lord_Howe", "Australia/Melbourne", - "Australia/NSW", - "Australia/North", "Australia/Perth", - "Australia/Queensland", - "Australia/South", "Australia/Sydney", - "Australia/Tasmania", - "Australia/Victoria", - "Australia/West", "Australia/Yancowinna", - "Brazil/Acre", - "Brazil/DeNoronha", - "Brazil/East", - "Brazil/West", - "CET", - "CST6CDT", - "Canada/Atlantic", - "Canada/Central", - "Canada/Eastern", - "Canada/Mountain", - "Canada/Newfoundland", - "Canada/Pacific", - "Canada/Saskatchewan", - "Canada/Yukon", - "Chile/Continental", - "Chile/EasterIsland", - "Cuba", - "EET", - "EST", - "EST5EDT", - "Egypt", - "Eire", "Etc/GMT", "Etc/GMT+0", "Etc/GMT+1", @@ -487,7 +432,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Europe/Istanbul", "Europe/Jersey", "Europe/Kaliningrad", - "Europe/Kiev", "Europe/Kirov", "Europe/Kyiv", "Europe/Lisbon", @@ -519,7 +463,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Europe/Tirane", "Europe/Tiraspol", "Europe/Ulyanovsk", - "Europe/Uzhgorod", "Europe/Vaduz", "Europe/Vatican", "Europe/Vienna", @@ -527,19 +470,8 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Europe/Volgograd", "Europe/Warsaw", "Europe/Zagreb", - "Europe/Zaporozhye", "Europe/Zurich", "Factory", - "GB", - "GB-Eire", - "GMT", - "GMT+0", - "GMT-0", - "GMT0", - "Greenwich", - "HST", - "Hongkong", - "Iceland", "Indian/Antananarivo", "Indian/Chagos", "Indian/Christmas", @@ -551,23 +483,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Indian/Mauritius", "Indian/Mayotte", "Indian/Reunion", - "Iran", - "Israel", - "Jamaica", - "Japan", - "Kwajalein", - "Libya", - "MET", - "MST", - "MST7MDT", - "Mexico/BajaNorte", - "Mexico/BajaSur", - "Mexico/General", - "NZ", - "NZ-CHAT", - "Navajo", - "PRC", - "PST8PDT", "Pacific/Apia", "Pacific/Auckland", "Pacific/Bougainville", @@ -575,7 +490,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Pacific/Chuuk", "Pacific/Easter", "Pacific/Efate", - "Pacific/Enderbury", "Pacific/Fakaofo", "Pacific/Fiji", "Pacific/Funafuti", @@ -600,7 +514,6 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Pacific/Palau", "Pacific/Pitcairn", "Pacific/Pohnpei", - "Pacific/Ponape", "Pacific/Port_Moresby", "Pacific/Rarotonga", "Pacific/Saipan", @@ -608,34 +521,10 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Pacific/Tahiti", "Pacific/Tarawa", "Pacific/Tongatapu", - "Pacific/Truk", "Pacific/Wake", "Pacific/Wallis", "Pacific/Yap", - "Poland", - "Portugal", - "ROC", - "ROK", - "Singapore", - "Turkey", - "UCT", - "US/Alaska", - "US/Aleutian", - "US/Arizona", - "US/Central", - "US/East-Indiana", - "US/Eastern", - "US/Hawaii", - "US/Indiana-Starke", - "US/Michigan", - "US/Mountain", - "US/Pacific", - "US/Samoa", "UTC", - "Universal", - "W-SU", - "WET", - "Zulu", nullptr}; // Helper to return a loaded time zone by value (UTC on error). @@ -734,6 +623,10 @@ TEST(TimeZone, UTC) { time_zone loaded_utc0; EXPECT_TRUE(load_time_zone("UTC0", &loaded_utc0)); EXPECT_EQ(loaded_utc0, utc); + + time_zone loaded_bad; + EXPECT_FALSE(load_time_zone("Invalid/TimeZone", &loaded_bad)); + EXPECT_EQ(loaded_bad, utc); } TEST(TimeZone, NamedTimeZones) { @@ -911,19 +804,19 @@ TEST(MakeTime, TimePointResolution) { const time_zone utc = utc_time_zone(); const time_point<chrono::nanoseconds> tp_ns = convert(civil_second(2015, 1, 2, 3, 4, 5), utc); - EXPECT_EQ("04:05", format("%M:%E*S", tp_ns, utc)); + EXPECT_EQ("04:05", absl::time_internal::cctz::format("%M:%E*S", tp_ns, utc)); const time_point<chrono::microseconds> tp_us = convert(civil_second(2015, 1, 2, 3, 4, 5), utc); - EXPECT_EQ("04:05", format("%M:%E*S", tp_us, utc)); + EXPECT_EQ("04:05", absl::time_internal::cctz::format("%M:%E*S", tp_us, utc)); const time_point<chrono::milliseconds> tp_ms = convert(civil_second(2015, 1, 2, 3, 4, 5), utc); - EXPECT_EQ("04:05", format("%M:%E*S", tp_ms, utc)); + EXPECT_EQ("04:05", absl::time_internal::cctz::format("%M:%E*S", tp_ms, utc)); const time_point<chrono::seconds> tp_s = convert(civil_second(2015, 1, 2, 3, 4, 5), utc); - EXPECT_EQ("04:05", format("%M:%E*S", tp_s, utc)); + EXPECT_EQ("04:05", absl::time_internal::cctz::format("%M:%E*S", tp_s, utc)); const time_point<absl::time_internal::cctz::seconds> tp_s64 = convert(civil_second(2015, 1, 2, 3, 4, 5), utc); - EXPECT_EQ("04:05", format("%M:%E*S", tp_s64, utc)); + EXPECT_EQ("04:05", absl::time_internal::cctz::format("%M:%E*S", tp_s64, utc)); // These next two require chrono::time_point_cast because the conversion // from a resolution of seconds (the return value of convert()) to a @@ -931,10 +824,10 @@ TEST(MakeTime, TimePointResolution) { const time_point<chrono::minutes> tp_m = chrono::time_point_cast<chrono::minutes>( convert(civil_second(2015, 1, 2, 3, 4, 5), utc)); - EXPECT_EQ("04:00", format("%M:%E*S", tp_m, utc)); + EXPECT_EQ("04:00", absl::time_internal::cctz::format("%M:%E*S", tp_m, utc)); const time_point<chrono::hours> tp_h = chrono::time_point_cast<chrono::hours>( convert(civil_second(2015, 1, 2, 3, 4, 5), utc)); - EXPECT_EQ("00:00", format("%M:%E*S", tp_h, utc)); + EXPECT_EQ("00:00", absl::time_internal::cctz::format("%M:%E*S", tp_h, utc)); } TEST(MakeTime, Normalization) { @@ -960,9 +853,11 @@ TEST(MakeTime, SysSecondsLimits) { // Approach the maximal time_point<cctz::seconds> value from below. tp = convert(civil_second(292277026596, 12, 4, 15, 30, 6), utc); - EXPECT_EQ("292277026596-12-04T15:30:06+00:00", format(RFC3339, tp, utc)); + EXPECT_EQ("292277026596-12-04T15:30:06+00:00", + absl::time_internal::cctz::format(RFC3339, tp, utc)); tp = convert(civil_second(292277026596, 12, 4, 15, 30, 7), utc); - EXPECT_EQ("292277026596-12-04T15:30:07+00:00", format(RFC3339, tp, utc)); + EXPECT_EQ("292277026596-12-04T15:30:07+00:00", + absl::time_internal::cctz::format(RFC3339, tp, utc)); EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp); tp = convert(civil_second(292277026596, 12, 4, 15, 30, 8), utc); EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp); @@ -971,7 +866,8 @@ TEST(MakeTime, SysSecondsLimits) { // Checks that we can also get the maximal value for a far-east zone. tp = convert(civil_second(292277026596, 12, 5, 5, 30, 7), east); - EXPECT_EQ("292277026596-12-05T05:30:07+14:00", format(RFC3339, tp, east)); + EXPECT_EQ("292277026596-12-05T05:30:07+14:00", + absl::time_internal::cctz::format(RFC3339, tp, east)); EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp); tp = convert(civil_second(292277026596, 12, 5, 5, 30, 8), east); EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp); @@ -980,7 +876,8 @@ TEST(MakeTime, SysSecondsLimits) { // Checks that we can also get the maximal value for a far-west zone. tp = convert(civil_second(292277026596, 12, 4, 1, 30, 7), west); - EXPECT_EQ("292277026596-12-04T01:30:07-14:00", format(RFC3339, tp, west)); + EXPECT_EQ("292277026596-12-04T01:30:07-14:00", + absl::time_internal::cctz::format(RFC3339, tp, west)); EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp); tp = convert(civil_second(292277026596, 12, 4, 7, 30, 8), west); EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::max(), tp); @@ -989,9 +886,11 @@ TEST(MakeTime, SysSecondsLimits) { // Approach the minimal time_point<cctz::seconds> value from above. tp = convert(civil_second(-292277022657, 1, 27, 8, 29, 53), utc); - EXPECT_EQ("-292277022657-01-27T08:29:53+00:00", format(RFC3339, tp, utc)); + EXPECT_EQ("-292277022657-01-27T08:29:53+00:00", + absl::time_internal::cctz::format(RFC3339, tp, utc)); tp = convert(civil_second(-292277022657, 1, 27, 8, 29, 52), utc); - EXPECT_EQ("-292277022657-01-27T08:29:52+00:00", format(RFC3339, tp, utc)); + EXPECT_EQ("-292277022657-01-27T08:29:52+00:00", + absl::time_internal::cctz::format(RFC3339, tp, utc)); EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp); tp = convert(civil_second(-292277022657, 1, 27, 8, 29, 51), utc); EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp); @@ -1000,7 +899,8 @@ TEST(MakeTime, SysSecondsLimits) { // Checks that we can also get the minimal value for a far-east zone. tp = convert(civil_second(-292277022657, 1, 27, 22, 29, 52), east); - EXPECT_EQ("-292277022657-01-27T22:29:52+14:00", format(RFC3339, tp, east)); + EXPECT_EQ("-292277022657-01-27T22:29:52+14:00", + absl::time_internal::cctz::format(RFC3339, tp, east)); EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp); tp = convert(civil_second(-292277022657, 1, 27, 22, 29, 51), east); EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp); @@ -1009,7 +909,8 @@ TEST(MakeTime, SysSecondsLimits) { // Checks that we can also get the minimal value for a far-west zone. tp = convert(civil_second(-292277022657, 1, 26, 18, 29, 52), west); - EXPECT_EQ("-292277022657-01-26T18:29:52-14:00", format(RFC3339, tp, west)); + EXPECT_EQ("-292277022657-01-26T18:29:52-14:00", + absl::time_internal::cctz::format(RFC3339, tp, west)); EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp); tp = convert(civil_second(-292277022657, 1, 26, 18, 29, 51), west); EXPECT_EQ(time_point<absl::time_internal::cctz::seconds>::min(), tp); @@ -1026,17 +927,19 @@ TEST(MakeTime, SysSecondsLimits) { const time_zone cut = LoadZone("libc:UTC"); const year_t max_tm_year = year_t{std::numeric_limits<int>::max()} + 1900; tp = convert(civil_second(max_tm_year, 12, 31, 23, 59, 59), cut); -#if defined(__FreeBSD__) || defined(__OpenBSD__) - // The BSD gmtime_r() fails on extreme positive tm_year values. +#if defined(__FreeBSD__) || defined(__OpenBSD__) || defined(__EMSCRIPTEN__) + // Some gmtime_r() impls fail on extreme positive values. #else - EXPECT_EQ("2147485547-12-31T23:59:59+00:00", format(RFC3339, tp, cut)); + EXPECT_EQ("2147485547-12-31T23:59:59+00:00", + absl::time_internal::cctz::format(RFC3339, tp, cut)); #endif const year_t min_tm_year = year_t{std::numeric_limits<int>::min()} + 1900; tp = convert(civil_second(min_tm_year, 1, 1, 0, 0, 0), cut); -#if defined(__Fuchsia__) - // Fuchsia's gmtime_r() fails on extreme negative values (fxbug.dev/78527). +#if defined(__Fuchsia__) || defined(__EMSCRIPTEN__) + // Some gmtime_r() impls fail on extreme negative values (fxbug.dev/78527). #else - EXPECT_EQ("-2147481748-01-01T00:00:00+00:00", format(RFC3339, tp, cut)); + EXPECT_EQ("-2147481748-01-01T00:00:00+00:00", + absl::time_internal::cctz::format(RFC3339, tp, cut)); #endif #endif } @@ -1062,7 +965,7 @@ TEST(MakeTime, LocalTimeLibC) { tp = zi.lookup(transition.to).trans) { const auto fcl = zi.lookup(transition.from); const auto tcl = zi.lookup(transition.to); - civil_second cs; // compare cs in zi and lc + civil_second cs, us; // compare cs and us in zi and lc if (fcl.kind == time_zone::civil_lookup::UNIQUE) { if (tcl.kind == time_zone::civil_lookup::UNIQUE) { // Both unique; must be an is_dst or abbr change. @@ -1078,12 +981,14 @@ TEST(MakeTime, LocalTimeLibC) { } ASSERT_EQ(time_zone::civil_lookup::REPEATED, tcl.kind); cs = transition.to; + us = transition.from; } else { ASSERT_EQ(time_zone::civil_lookup::UNIQUE, tcl.kind); ASSERT_EQ(time_zone::civil_lookup::SKIPPED, fcl.kind); cs = transition.from; + us = transition.to; } - if (cs.year() > 2037) break; // limit test time (and to 32-bit time_t) + if (us.year() > 2037) break; // limit test time (and to 32-bit time_t) const auto cl_zi = zi.lookup(cs); if (zi.lookup(cl_zi.pre).is_dst == zi.lookup(cl_zi.post).is_dst) { // The "libc" implementation cannot correctly classify transitions @@ -1115,6 +1020,13 @@ TEST(MakeTime, LocalTimeLibC) { EXPECT_EQ(cl_zi.pre, cl_lc.pre); EXPECT_EQ(cl_zi.trans, cl_lc.trans); EXPECT_EQ(cl_zi.post, cl_lc.post); + const auto ucl_zi = zi.lookup(us); + const auto ucl_lc = lc.lookup(us); + SCOPED_TRACE(testing::Message() << "For " << us << " in " << *np); + EXPECT_EQ(ucl_zi.kind, ucl_lc.kind); + EXPECT_EQ(ucl_zi.pre, ucl_lc.pre); + EXPECT_EQ(ucl_zi.trans, ucl_lc.trans); + EXPECT_EQ(ucl_zi.post, ucl_lc.post); } } if (ep == nullptr) { diff --git a/absl/time/internal/cctz/src/time_zone_posix.h b/absl/time/internal/cctz/src/time_zone_posix.h index 0cf29055..7fd2b9ec 100644 --- a/absl/time/internal/cctz/src/time_zone_posix.h +++ b/absl/time/internal/cctz/src/time_zone_posix.h @@ -104,7 +104,7 @@ struct PosixTransition { // The entirety of a POSIX-string specified time-zone rule. The standard // abbreviation and offset are always given. If the time zone includes -// daylight saving, then the daylight abbrevation is non-empty and the +// daylight saving, then the daylight abbreviation is non-empty and the // remaining fields are also valid. Note that the start/end transitions // are not ordered---in the southern hemisphere the transition to end // daylight time occurs first in any particular year. diff --git a/absl/time/internal/cctz/src/tzfile.h b/absl/time/internal/cctz/src/tzfile.h index 31e85982..114026d0 100644 --- a/absl/time/internal/cctz/src/tzfile.h +++ b/absl/time/internal/cctz/src/tzfile.h @@ -21,14 +21,6 @@ ** Information about time zone files. */ -#ifndef TZDIR -#define TZDIR "/usr/share/zoneinfo" /* Time zone object file directory */ -#endif /* !defined TZDIR */ - -#ifndef TZDEFAULT -#define TZDEFAULT "/etc/localtime" -#endif /* !defined TZDEFAULT */ - #ifndef TZDEFRULES #define TZDEFRULES "posixrules" #endif /* !defined TZDEFRULES */ @@ -102,20 +94,24 @@ struct tzhead { */ #ifndef TZ_MAX_TIMES +/* This must be at least 242 for Europe/London with 'zic -b fat'. */ #define TZ_MAX_TIMES 2000 #endif /* !defined TZ_MAX_TIMES */ #ifndef TZ_MAX_TYPES -/* This must be at least 17 for Europe/Samara and Europe/Vilnius. */ +/* This must be at least 18 for Europe/Vilnius with 'zic -b fat'. */ #define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */ #endif /* !defined TZ_MAX_TYPES */ #ifndef TZ_MAX_CHARS +/* This must be at least 40 for America/Anchorage. */ #define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */ /* (limited by what unsigned chars can hold) */ #endif /* !defined TZ_MAX_CHARS */ #ifndef TZ_MAX_LEAPS +/* This must be at least 27 for leap seconds from 1972 through mid-2023. + There's a plan to discontinue leap seconds by 2035. */ #define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */ #endif /* !defined TZ_MAX_LEAPS */ diff --git a/absl/time/internal/cctz/src/zone_info_source.cc b/absl/time/internal/cctz/src/zone_info_source.cc index 1b162337..9bc8197b 100644 --- a/absl/time/internal/cctz/src/zone_info_source.cc +++ b/absl/time/internal/cctz/src/zone_info_source.cc @@ -67,41 +67,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/README.zoneinfo b/absl/time/internal/cctz/testdata/README.zoneinfo index 67e9c404..0fe55858 100644 --- a/absl/time/internal/cctz/testdata/README.zoneinfo +++ b/absl/time/internal/cctz/testdata/README.zoneinfo @@ -22,7 +22,7 @@ New versions can be generated using the following shell script. LOCALTIME=Factory \ TZDATA_TEXT= \ PACKRATDATA=backzone PACKRATLIST=zone.tab \ - ZONETABLES=zone1970.tab + ZONETABLES=zone1970.tab\ zonenow.tab tar --create --dereference --hard-dereference --file tzfile.tar \ --directory=tz tzfile.h tar --create --dereference --hard-dereference --file zoneinfo.tar \ diff --git a/absl/time/internal/cctz/testdata/version b/absl/time/internal/cctz/testdata/version index b74fa117..cd9c3f6d 100644 --- a/absl/time/internal/cctz/testdata/version +++ b/absl/time/internal/cctz/testdata/version @@ -1 +1 @@ -2022g +2023d diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Cairo b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Cairo Binary files differindex ea38c970..1e6d48d1 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Cairo +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Cairo diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Casablanca b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Casablanca Binary files differindex 0263c90b..240ebb2b 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Casablanca +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Casablanca diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/El_Aaiun b/absl/time/internal/cctz/testdata/zoneinfo/Africa/El_Aaiun Binary files differindex 772e23c4..909c5f96 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/El_Aaiun +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/El_Aaiun diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Ensenada b/absl/time/internal/cctz/testdata/zoneinfo/America/Ensenada Binary files differindex e8be26b1..42087af4 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/Godthab b/absl/time/internal/cctz/testdata/zoneinfo/America/Godthab Binary files differindex 79d7a454..310774ea 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Godthab +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Godthab diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Goose_Bay b/absl/time/internal/cctz/testdata/zoneinfo/America/Goose_Bay Binary files differindex 820e0dd2..e2cc3eef 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Goose_Bay +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Goose_Bay diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Indiana/Winamac b/absl/time/internal/cctz/testdata/zoneinfo/America/Indiana/Winamac Binary files differindex 8700ed9f..679d321e 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Indiana/Winamac +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Indiana/Winamac diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Matamoros b/absl/time/internal/cctz/testdata/zoneinfo/America/Matamoros Binary files differindex 88cabcd1..993ac475 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/Metlakatla b/absl/time/internal/cctz/testdata/zoneinfo/America/Metlakatla Binary files differindex 9fefee38..71b0eab0 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Metlakatla +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Metlakatla diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Moncton b/absl/time/internal/cctz/testdata/zoneinfo/America/Moncton Binary files differindex ecb69ef2..020e33d9 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Moncton +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Moncton diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Nuuk b/absl/time/internal/cctz/testdata/zoneinfo/America/Nuuk Binary files differindex 79d7a454..310774ea 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Nuuk +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Nuuk diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Ojinaga b/absl/time/internal/cctz/testdata/zoneinfo/America/Ojinaga Binary files differindex 2fc74e94..f7e40c08 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/Santa_Isabel b/absl/time/internal/cctz/testdata/zoneinfo/America/Santa_Isabel Binary files differindex e8be26b1..42087af4 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/Scoresbysund b/absl/time/internal/cctz/testdata/zoneinfo/America/Scoresbysund Binary files differindex 6db49124..fc1b11cb 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Scoresbysund +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Scoresbysund diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/St_Johns b/absl/time/internal/cctz/testdata/zoneinfo/America/St_Johns Binary files differindex e5f2aec2..94d790ba 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/St_Johns +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/St_Johns diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Tijuana b/absl/time/internal/cctz/testdata/zoneinfo/America/Tijuana Binary files differindex e8be26b1..42087af4 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/America/Yellowknife b/absl/time/internal/cctz/testdata/zoneinfo/America/Yellowknife Binary files differindex ff3eb878..645ee945 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Yellowknife +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Yellowknife diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Casey b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Casey Binary files differindex 30315cc0..84f1c61e 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Casey +++ b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Casey diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Macquarie b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Macquarie Binary files differindex 3fc1f231..99a8e60e 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Macquarie +++ b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Macquarie diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Troll b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Troll Binary files differindex 4e31affb..2359c44b 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Troll +++ b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Troll diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Vostok b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Vostok Binary files differindex 6e329071..4ce8f747 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Vostok +++ b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Vostok diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Gaza b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Gaza Binary files differindex bed968e7..6241b4e7 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Gaza +++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Gaza diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Hebron b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Hebron Binary files differindex 3ce1bac6..5267de96 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Hebron +++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Hebron diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Nicosia b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Nicosia Binary files differindex c210d0a5..390347f4 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Nicosia +++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Nicosia diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Canada/Newfoundland b/absl/time/internal/cctz/testdata/zoneinfo/Canada/Newfoundland Binary files differindex e5f2aec2..94d790ba 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Canada/Newfoundland +++ b/absl/time/internal/cctz/testdata/zoneinfo/Canada/Newfoundland diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Egypt b/absl/time/internal/cctz/testdata/zoneinfo/Egypt Binary files differindex ea38c970..1e6d48d1 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Egypt +++ b/absl/time/internal/cctz/testdata/zoneinfo/Egypt diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Belfast b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Belfast Binary files differindex 323cd381..b9e95d92 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Belfast +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Belfast diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Bucharest b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Bucharest Binary files differindex efa689ba..c4a391e7 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Bucharest +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Bucharest diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Chisinau b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Chisinau Binary files differindex 6970b14c..9152e685 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Chisinau +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Chisinau diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Guernsey b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Guernsey Binary files differindex d40bcaa3..f2d87c6b 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Guernsey +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Guernsey diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Isle_of_Man b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Isle_of_Man Binary files differindex b0a37e7e..9f5aaffd 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Isle_of_Man +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Isle_of_Man diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Jersey b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Jersey Binary files differindex 9a10a2ec..c83814dd 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Jersey +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Jersey diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kiev b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kiev Binary files differindex 4e026859..753a6c86 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kiev +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kiev diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kirov b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kirov Binary files differindex d1c93c54..bfac5611 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kirov +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kirov diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kyiv b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kyiv Binary files differindex 4e026859..753a6c86 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kyiv +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kyiv diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/London b/absl/time/internal/cctz/testdata/zoneinfo/Europe/London Binary files differindex 323cd381..b9e95d92 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/London +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/London diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Nicosia b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Nicosia Binary files differindex c210d0a5..390347f4 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Nicosia +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Nicosia diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Riga b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Riga Binary files differindex 26af4c90..d99170b6 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Riga +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Riga diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Sofia b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Sofia Binary files differindex eabc972a..89450685 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Sofia +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Sofia diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Tallinn b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Tallinn Binary files differindex 5321bbd4..fbebdc62 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Tallinn +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Tallinn diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Tiraspol b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Tiraspol Binary files differindex 6970b14c..9152e685 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Tiraspol +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Tiraspol diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Uzhgorod b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Uzhgorod Binary files differindex 4e026859..753a6c86 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Uzhgorod +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Uzhgorod diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Vilnius b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Vilnius Binary files differindex 75b2eebb..43c3d7f1 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Vilnius +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Vilnius diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Volgograd b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Volgograd Binary files differindex c5170026..0715d58b 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Volgograd +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Volgograd diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Zaporozhye b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Zaporozhye Binary files differindex 4e026859..753a6c86 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Zaporozhye +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Zaporozhye diff --git a/absl/time/internal/cctz/testdata/zoneinfo/GB b/absl/time/internal/cctz/testdata/zoneinfo/GB Binary files differindex 323cd381..b9e95d92 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/GB +++ b/absl/time/internal/cctz/testdata/zoneinfo/GB diff --git a/absl/time/internal/cctz/testdata/zoneinfo/GB-Eire b/absl/time/internal/cctz/testdata/zoneinfo/GB-Eire Binary files differindex 323cd381..b9e95d92 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/GB-Eire +++ b/absl/time/internal/cctz/testdata/zoneinfo/GB-Eire diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaNorte b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaNorte Binary files differindex e8be26b1..42087af4 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/Pacific/Norfolk b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Norfolk Binary files differindex 79e2a941..0c0bdbda 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Norfolk +++ b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Norfolk diff --git a/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab b/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab index 911af5e8..402c015e 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab +++ b/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab @@ -3,17 +3,22 @@ # This file is in the public domain, so clarified as of # 2009-05-17 by Arthur David Olson. # -# From Paul Eggert (2022-11-18): +# From Paul Eggert (2023-09-06): # This file contains a table of two-letter country codes. Columns are # separated by a single tab. Lines beginning with '#' are comments. # All text uses UTF-8 encoding. The columns of the table are as follows: # # 1. ISO 3166-1 alpha-2 country code, current as of -# ISO 3166-1 N1087 (2022-09-02). See: Updates on ISO 3166-1 -# https://isotc.iso.org/livelink/livelink/Open/16944257 -# 2. The usual English name for the coded region, -# chosen so that alphabetic sorting of subsets produces helpful lists. -# This is not the same as the English name in the ISO 3166 tables. +# ISO/TC 46 N1108 (2023-04-05). See: ISO/TC 46 Documents +# https://www.iso.org/committee/48750.html?view=documents +# 2. The usual English name for the coded region. This sometimes +# departs from ISO-listed names, sometimes so that sorted subsets +# of names are useful (e.g., "Samoa (American)" and "Samoa +# (western)" rather than "American Samoa" and "Samoa"), +# sometimes to avoid confusion among non-experts (e.g., +# "Czech Republic" and "Turkey" rather than "Czechia" and "Türkiye"), +# and sometimes to omit needless detail or churn (e.g., "Netherlands" +# rather than "Netherlands (the)" or "Netherlands (Kingdom of the)"). # # The table is sorted by country code. # @@ -238,7 +243,7 @@ SY Syria SZ Eswatini (Swaziland) TC Turks & Caicos Is TD Chad -TF French Southern Territories +TF French S. Terr. TG Togo TH Thailand TJ Tajikistan diff --git a/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab b/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab index a9b36d36..abd94897 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab +++ b/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab @@ -18,7 +18,10 @@ # Please see the theory.html file for how these names are chosen. # If multiple timezones overlap a country, each has a row in the # table, with each column 1 containing the country code. -# 4. Comments; present if and only if a country has multiple timezones. +# 4. Comments; present if and only if countries have multiple timezones, +# and useful only for those countries. For example, the comments +# for the row with countries CH,DE,LI and name Europe/Zurich +# are useful only for DE, since CH and LI have no other timezones. # # If a timezone covers multiple countries, the most-populous city is used, # and that country is listed first in column 1; any other countries @@ -34,7 +37,7 @@ #country- #codes coordinates TZ comments AD +4230+00131 Europe/Andorra -AE,OM,RE,SC,TF +2518+05518 Asia/Dubai UAE, Oman, Réunion, Seychelles, Crozet, Scattered Is +AE,OM,RE,SC,TF +2518+05518 Asia/Dubai Crozet AF +3431+06912 Asia/Kabul AL +4120+01950 Europe/Tirane AM +4011+04430 Asia/Yerevan @@ -44,19 +47,20 @@ AQ -6736+06253 Antarctica/Mawson Mawson AQ -6448-06406 Antarctica/Palmer Palmer AQ -6734-06808 Antarctica/Rothera Rothera AQ -720041+0023206 Antarctica/Troll Troll +AQ -7824+10654 Antarctica/Vostok Vostok AR -3436-05827 America/Argentina/Buenos_Aires Buenos Aires (BA, CF) -AR -3124-06411 America/Argentina/Cordoba Argentina (most areas: CB, CC, CN, ER, FM, MN, SE, SF) +AR -3124-06411 America/Argentina/Cordoba most areas: CB, CC, CN, ER, FM, MN, SE, SF AR -2447-06525 America/Argentina/Salta Salta (SA, LP, NQ, RN) AR -2411-06518 America/Argentina/Jujuy Jujuy (JY) AR -2649-06513 America/Argentina/Tucuman Tucumán (TM) -AR -2828-06547 America/Argentina/Catamarca Catamarca (CT); Chubut (CH) +AR -2828-06547 America/Argentina/Catamarca Catamarca (CT), Chubut (CH) AR -2926-06651 America/Argentina/La_Rioja La Rioja (LR) AR -3132-06831 America/Argentina/San_Juan San Juan (SJ) AR -3253-06849 America/Argentina/Mendoza Mendoza (MZ) AR -3319-06621 America/Argentina/San_Luis San Luis (SL) AR -5138-06913 America/Argentina/Rio_Gallegos Santa Cruz (SC) AR -5448-06818 America/Argentina/Ushuaia Tierra del Fuego (TF) -AS,UM -1416-17042 Pacific/Pago_Pago Samoa, Midway +AS,UM -1416-17042 Pacific/Pago_Pago Midway AT +4813+01620 Europe/Vienna AU -3133+15905 Australia/Lord_Howe Lord Howe Island AU -5430+15857 Antarctica/Macquarie Macquarie Island @@ -78,7 +82,7 @@ BG +4241+02319 Europe/Sofia BM +3217-06446 Atlantic/Bermuda BO -1630-06809 America/La_Paz BR -0351-03225 America/Noronha Atlantic islands -BR -0127-04829 America/Belem Pará (east); Amapá +BR -0127-04829 America/Belem Pará (east), Amapá BR -0343-03830 America/Fortaleza Brazil (northeast: MA, PI, CE, RN, PB) BR -0803-03454 America/Recife Pernambuco BR -0712-04812 America/Araguaina Tocantins @@ -96,43 +100,42 @@ BR -0958-06748 America/Rio_Branco Acre BT +2728+08939 Asia/Thimphu BY +5354+02734 Europe/Minsk BZ +1730-08812 America/Belize -CA +4734-05243 America/St_Johns Newfoundland; Labrador (southeast) -CA +4439-06336 America/Halifax Atlantic - NS (most areas); PE +CA +4734-05243 America/St_Johns Newfoundland, Labrador (SE) +CA +4439-06336 America/Halifax Atlantic - NS (most areas), PE 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,BS +4339-07923 America/Toronto Eastern - ON & QC (most areas) CA +6344-06828 America/Iqaluit Eastern - NU (most areas) -CA +4953-09709 America/Winnipeg Central - ON (west); Manitoba +CA +4953-09709 America/Winnipeg Central - ON (west), Manitoba 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) CA +5017-10750 America/Swift_Current CST - SK (midwest) -CA +5333-11328 America/Edmonton Mountain - AB; BC (E); SK (W) +CA +5333-11328 America/Edmonton Mountain - AB, BC(E), NT(E), SK(W) CA +690650-1050310 America/Cambridge_Bay Mountain - NU (west) -CA +6227-11421 America/Yellowknife Mountain - NT (central) CA +682059-1334300 America/Inuvik Mountain - NT (west) CA +5546-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John) CA +5848-12242 America/Fort_Nelson MST - BC (Ft Nelson) CA +6043-13503 America/Whitehorse MST - Yukon (east) CA +6404-13925 America/Dawson MST - Yukon (west) CA +4916-12307 America/Vancouver Pacific - BC (most areas) -CH,DE,LI +4723+00832 Europe/Zurich Swiss time +CH,DE,LI +4723+00832 Europe/Zurich Büsingen CI,BF,GH,GM,GN,IS,ML,MR,SH,SL,SN,TG +0519-00402 Africa/Abidjan CK -2114-15946 Pacific/Rarotonga -CL -3327-07040 America/Santiago Chile (most areas) +CL -3327-07040 America/Santiago most of Chile CL -5309-07055 America/Punta_Arenas Region of Magallanes CL -2709-10926 Pacific/Easter Easter Island CN +3114+12128 Asia/Shanghai Beijing Time -CN,AQ +4348+08735 Asia/Urumqi Xinjiang Time, Vostok +CN +4348+08735 Asia/Urumqi Xinjiang Time CO +0436-07405 America/Bogota CR +0956-08405 America/Costa_Rica CU +2308-08222 America/Havana CV +1455-02331 Atlantic/Cape_Verde -CY +3510+03322 Asia/Nicosia Cyprus (most areas) +CY +3510+03322 Asia/Nicosia most of Cyprus CY +3507+03357 Asia/Famagusta Northern Cyprus CZ,SK +5005+01426 Europe/Prague -DE,DK,NO,SE,SJ +5230+01322 Europe/Berlin Germany (most areas), Scandinavia +DE,DK,NO,SE,SJ +5230+01322 Europe/Berlin most of Germany DO +1828-06954 America/Santo_Domingo DZ +3647+00303 Africa/Algiers EC -0210-07950 America/Guayaquil Ecuador (mainland) @@ -153,7 +156,7 @@ GB,GG,IM,JE +513030-0000731 Europe/London GE +4143+04449 Asia/Tbilisi GF +0456-05220 America/Cayenne GI +3608-00521 Europe/Gibraltar -GL +6411-05144 America/Nuuk Greenland (most areas) +GL +6411-05144 America/Nuuk most of Greenland GL +7646-01840 America/Danmarkshavn National Park (east coast) GL +7029-02158 America/Scoresbysund Scoresbysund/Ittoqqortoormiit GL +7634-06847 America/Thule Thule/Pituffik @@ -169,8 +172,8 @@ HT +1832-07220 America/Port-au-Prince HU +4730+01905 Europe/Budapest ID -0610+10648 Asia/Jakarta Java, Sumatra ID -0002+10920 Asia/Pontianak Borneo (west, central) -ID -0507+11924 Asia/Makassar Borneo (east, south); Sulawesi/Celebes, Bali, Nusa Tengarra; Timor (west) -ID -0232+14042 Asia/Jayapura New Guinea (West Papua / Irian Jaya); Malukus/Moluccas +ID -0507+11924 Asia/Makassar Borneo (east, south), Sulawesi/Celebes, Bali, Nusa Tengarra, Timor (west) +ID -0232+14042 Asia/Jayapura New Guinea (West Papua / Irian Jaya), Malukus/Moluccas IE +5320-00615 Europe/Dublin IL +314650+0351326 Asia/Jerusalem IN +2232+08822 Asia/Kolkata @@ -183,12 +186,12 @@ JO +3157+03556 Asia/Amman JP +353916+1394441 Asia/Tokyo KE,DJ,ER,ET,KM,MG,SO,TZ,UG,YT -0117+03649 Africa/Nairobi KG +4254+07436 Asia/Bishkek -KI,MH,TV,UM,WF +0125+17300 Pacific/Tarawa Gilberts, Marshalls, Tuvalu, Wallis & Futuna, Wake +KI,MH,TV,UM,WF +0125+17300 Pacific/Tarawa Gilberts, Marshalls, Wake KI -0247-17143 Pacific/Kanton Phoenix Islands KI +0152-15720 Pacific/Kiritimati Line Islands KP +3901+12545 Asia/Pyongyang KR +3733+12658 Asia/Seoul -KZ +4315+07657 Asia/Almaty Kazakhstan (most areas) +KZ +4315+07657 Asia/Almaty most of Kazakhstan KZ +4448+06528 Asia/Qyzylorda Qyzylorda/Kyzylorda/Kzyl-Orda KZ +5312+06337 Asia/Qostanay Qostanay/Kostanay/Kustanay KZ +5017+05710 Asia/Aqtobe Aqtöbe/Aktobe @@ -205,14 +208,14 @@ MA +3339-00735 Africa/Casablanca MD +4700+02850 Europe/Chisinau MH +0905+16720 Pacific/Kwajalein Kwajalein MM,CC +1647+09610 Asia/Yangon -MN +4755+10653 Asia/Ulaanbaatar Mongolia (most areas) +MN +4755+10653 Asia/Ulaanbaatar most of Mongolia MN +4801+09139 Asia/Hovd Bayan-Ölgii, Govi-Altai, Hovd, Uvs, Zavkhan MN +4804+11430 Asia/Choibalsan Dornod, Sükhbaatar MO +221150+1133230 Asia/Macau MQ +1436-06105 America/Martinique MT +3554+01431 Europe/Malta MU -2010+05730 Indian/Mauritius -MV,TF +0410+07330 Indian/Maldives Maldives, Kerguelen, St Paul I, Amsterdam I +MV,TF +0410+07330 Indian/Maldives Kerguelen, St Paul I, Amsterdam I MX +1924-09909 America/Mexico_City Central Mexico MX +2105-08646 America/Cancun Quintana Roo MX +2058-08937 America/Merida Campeche, Yucatán @@ -225,7 +228,7 @@ MX +2313-10625 America/Mazatlan Baja California Sur, Nayarit (most areas), Sinal MX +2048-10515 America/Bahia_Banderas BahÃa de Banderas MX +2904-11058 America/Hermosillo Sonora MX +3232-11701 America/Tijuana Baja California -MY,BN +0133+11020 Asia/Kuching Sabah, Sarawak, Brunei +MY,BN +0133+11020 Asia/Kuching Sabah, Sarawak MZ,BI,BW,CD,MW,RW,ZM,ZW -2558+03235 Africa/Maputo Central Africa Time NA -2234+01706 Africa/Windhoek NC -2216+16627 Pacific/Noumea @@ -237,7 +240,7 @@ NR -0031+16655 Pacific/Nauru NU -1901-16955 Pacific/Niue NZ,AQ -3652+17446 Pacific/Auckland New Zealand time NZ -4357-17633 Pacific/Chatham Chatham Islands -PA,CA,KY +0858-07932 America/Panama EST - Panama, Cayman, ON (Atikokan), NU (Coral H) +PA,CA,KY +0858-07932 America/Panama EST - ON (Atikokan), NU (Coral H) PE -1203-07703 America/Lima PF -1732-14934 Pacific/Tahiti Society Islands PF -0900-13930 Pacific/Marquesas Marquesas Islands @@ -249,7 +252,7 @@ PK +2452+06703 Asia/Karachi PL +5215+02100 Europe/Warsaw PM +4703-05620 America/Miquelon PN -2504-13005 Pacific/Pitcairn -PR,AG,CA,AI,AW,BL,BQ,CW,DM,GD,GP,KN,LC,MF,MS,SX,TT,VC,VG,VI +182806-0660622 America/Puerto_Rico AST +PR,AG,CA,AI,AW,BL,BQ,CW,DM,GD,GP,KN,LC,MF,MS,SX,TT,VC,VG,VI +182806-0660622 America/Puerto_Rico AST - QC (Lower North Shore) PS +3130+03428 Asia/Gaza Gaza Strip PS +313200+0350542 Asia/Hebron West Bank PT +3843-00908 Europe/Lisbon Portugal (mainland) @@ -285,13 +288,13 @@ RU +4310+13156 Asia/Vladivostok MSK+07 - Amur River RU +643337+1431336 Asia/Ust-Nera MSK+07 - Oymyakonsky RU +5934+15048 Asia/Magadan MSK+08 - Magadan RU +4658+14242 Asia/Sakhalin MSK+08 - Sakhalin Island -RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E); North Kuril Is +RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E), N Kuril Is RU +5301+15839 Asia/Kamchatka MSK+09 - Kamchatka RU +6445+17729 Asia/Anadyr MSK+09 - Bering Sea -SA,AQ,KW,YE +2438+04643 Asia/Riyadh Arabia, Syowa -SB,FM -0932+16012 Pacific/Guadalcanal Solomons, Pohnpei +SA,AQ,KW,YE +2438+04643 Asia/Riyadh Syowa +SB,FM -0932+16012 Pacific/Guadalcanal Pohnpei SD +1536+03232 Africa/Khartoum -SG,MY +0117+10351 Asia/Singapore Singapore, peninsular Malaysia +SG,MY +0117+10351 Asia/Singapore peninsular Malaysia SR +0550-05510 America/Paramaribo SS +0451+03137 Africa/Juba ST +0020+00644 Africa/Sao_Tome @@ -299,7 +302,7 @@ SV +1342-08912 America/El_Salvador SY +3330+03618 Asia/Damascus TC +2128-07108 America/Grand_Turk TD +1207+01503 Africa/Ndjamena -TH,CX,KH,LA,VN +1345+10031 Asia/Bangkok Indochina (most areas) +TH,CX,KH,LA,VN +1345+10031 Asia/Bangkok north Vietnam TJ +3835+06848 Asia/Dushanbe TK -0922-17114 Pacific/Fakaofo TL -0833+12535 Asia/Dili @@ -308,7 +311,7 @@ TN +3648+01011 Africa/Tunis TO -210800-1751200 Pacific/Tongatapu TR +4101+02858 Europe/Istanbul TW +2503+12130 Asia/Taipei -UA +5026+03031 Europe/Kyiv Ukraine (most areas) +UA +5026+03031 Europe/Kyiv most of Ukraine US +404251-0740023 America/New_York Eastern (most areas) US +421953-0830245 America/Detroit Eastern - MI (most areas) US +381515-0854534 America/Kentucky/Louisville Eastern - KY (Louisville area) @@ -327,8 +330,8 @@ US +470659-1011757 America/North_Dakota/Center Central - ND (Oliver) US +465042-1012439 America/North_Dakota/New_Salem Central - ND (Morton rural) US +471551-1014640 America/North_Dakota/Beulah Central - ND (Mercer) US +394421-1045903 America/Denver Mountain (most areas) -US +433649-1161209 America/Boise Mountain - ID (south); OR (east) -US,CA +332654-1120424 America/Phoenix MST - Arizona (except Navajo), Creston BC +US +433649-1161209 America/Boise Mountain - ID (south), OR (east) +US,CA +332654-1120424 America/Phoenix MST - AZ (most areas), Creston BC US +340308-1181434 America/Los_Angeles Pacific US +611305-1495401 America/Anchorage Alaska (most areas) US +581807-1342511 America/Juneau Alaska - Juneau area @@ -336,13 +339,13 @@ US +571035-1351807 America/Sitka Alaska - Sitka area US +550737-1313435 America/Metlakatla Alaska - Annette Island US +593249-1394338 America/Yakutat Alaska - Yakutat US +643004-1652423 America/Nome Alaska (west) -US +515248-1763929 America/Adak Aleutian Islands -US,UM +211825-1575130 Pacific/Honolulu Hawaii +US +515248-1763929 America/Adak Alaska - western Aleutians +US +211825-1575130 Pacific/Honolulu Hawaii UY -345433-0561245 America/Montevideo UZ +3940+06648 Asia/Samarkand Uzbekistan (west) UZ +4120+06918 Asia/Tashkent Uzbekistan (east) VE +1030-06656 America/Caracas -VN +1045+10640 Asia/Ho_Chi_Minh Vietnam (south) +VN +1045+10640 Asia/Ho_Chi_Minh south Vietnam VU -1740+16825 Pacific/Efate WS -1350-17144 Pacific/Apia ZA,LS,SZ -2615+02800 Africa/Johannesburg diff --git a/absl/time/internal/cctz/testdata/zoneinfo/zonenow.tab b/absl/time/internal/cctz/testdata/zoneinfo/zonenow.tab new file mode 100644 index 00000000..2dbe8f00 --- /dev/null +++ b/absl/time/internal/cctz/testdata/zoneinfo/zonenow.tab @@ -0,0 +1,301 @@ +# tzdb timezone descriptions, for users who do not care about old timestamps +# +# This file is in the public domain. +# +# From Paul Eggert (2023-12-18): +# This file contains a table where each row stands for a timezone +# where civil timestamps are predicted to agree from now on. +# This file is like zone1970.tab (see zone1970.tab's coments), +# but with the following changes: +# +# 1. Each timezone corresponds to a set of clocks that are planned +# to agree from now on. This is a larger set of clocks than in +# zone1970.tab, where each timezone's clocks must agree from 1970 on. +# 2. The first column is irrelevant and ignored. +# 3. The table is sorted in a different way: +# first by standard time UTC offset; +# then, if DST is used, by daylight saving UTC offset; +# then by time zone abbreviation. +# 4. Every timezone has a nonempty comments column, with wording +# distinguishing the timezone only from other timezones with the +# same UTC offset at some point during the year. +# +# The format of this table is experimental, and may change in future versions. +# +# This table is intended as an aid for users, to help them select timezones +# appropriate for their practical needs. It is not intended to take or +# endorse any position on legal or territorial claims. +# +#XX coordinates TZ comments +# +# -11 - SST +XX -1416-17042 Pacific/Pago_Pago Midway; Samoa ("SST") +# +# -11 +XX -1901-16955 Pacific/Niue Niue +# +# -10 - HST +XX +211825-1575130 Pacific/Honolulu Hawaii ("HST") +# +# -10 +XX -1732-14934 Pacific/Tahiti Tahiti; Cook Islands +# +# -10/-09 - HST / HDT (North America DST) +XX +515248-1763929 America/Adak western Aleutians in Alaska ("HST/HDT") +# +# -09:30 +XX -0900-13930 Pacific/Marquesas Marquesas +# +# -09 +XX -2308-13457 Pacific/Gambier Gambier +# +# -09/-08 - AKST/AKDT (North America DST) +XX +611305-1495401 America/Anchorage most of Alaska ("AKST/AKDT") +# +# -08 +XX -2504-13005 Pacific/Pitcairn Pitcairn +# +# -08/-07 - PST/PDT (North America DST) +XX +340308-1181434 America/Los_Angeles Pacific ("PST/PDT") - US & Canada; Mexico near US border +# +# -07 - MST +XX +332654-1120424 America/Phoenix Mountain Standard ("MST") - Arizona; western Mexico; Yukon +# +# -07/-06 - MST/MDT (North America DST) +XX +394421-1045903 America/Denver Mountain ("MST/MDT") - US & Canada; Mexico near US border +# +# -06 +XX -0054-08936 Pacific/Galapagos Galápagos +# +# -06 - CST +XX +1924-09909 America/Mexico_City Central Standard ("CST") - Saskatchewan; central Mexico; Central America +# +# -06/-05 (Chile DST) +XX -2709-10926 Pacific/Easter Easter Island +# +# -06/-05 - CST/CDT (North America DST) +XX +415100-0873900 America/Chicago Central ("CST/CDT") - US & Canada; Mexico near US border +# +# -05 +XX -1203-07703 America/Lima eastern South America +# +# -05 - EST +XX +175805-0764736 America/Jamaica Eastern Standard ("EST") - Caymans; Jamaica; eastern Mexico; Panama +# +# -05/-04 - CST/CDT (Cuba DST) +XX +2308-08222 America/Havana Cuba +# +# -05/-04 - EST/EDT (North America DST) +XX +404251-0740023 America/New_York Eastern ("EST/EDT") - US & Canada +# +# -04 +XX +1030-06656 America/Caracas western South America +# +# -04 - AST +XX +1828-06954 America/Santo_Domingo Atlantic Standard ("AST") - eastern Caribbean +# +# -04/-03 (Chile DST) +XX -3327-07040 America/Santiago most of Chile +# +# -04/-03 (Paraguay DST) +XX -2516-05740 America/Asuncion Paraguay +# +# -04/-03 - AST/ADT (North America DST) +XX +4439-06336 America/Halifax Atlantic ("AST/ADT") - Canada; Bermuda +# +# -03:30/-02:30 - NST/NDT (North America DST) +XX +4734-05243 America/St_Johns Newfoundland ("NST/NDT") +# +# -03 +XX -2332-04637 America/Sao_Paulo eastern South America +# +# -03/-02 (North America DST) +XX +4703-05620 America/Miquelon St Pierre & Miquelon +# +# -02 +XX -0351-03225 America/Noronha Fernando de Noronha; South Georgia +# +# -02/-01 (EU DST) +XX +6411-05144 America/Nuuk most of Greenland +# +# -01 +XX +1455-02331 Atlantic/Cape_Verde Cape Verde +# +# -01/+00 (EU DST) +XX +3744-02540 Atlantic/Azores Azores +# -01/+00 (EU DST) until 2024-03-31; then -02/-01 (EU DST) +XX +7029-02158 America/Scoresbysund Ittoqqortoormiit +# +# +00 - GMT +XX +0519-00402 Africa/Abidjan far western Africa; Iceland ("GMT") +# +# +00/+01 - GMT/BST (EU DST) +XX +513030-0000731 Europe/London United Kingdom ("GMT/BST") +# +# +00/+01 - WET/WEST (EU DST) +XX +3843-00908 Europe/Lisbon western Europe ("WET/WEST") +# +# +00/+02 - Troll DST +XX -720041+0023206 Antarctica/Troll Troll Station in Antarctica +# +# +01 - CET +XX +3647+00303 Africa/Algiers Algeria, Tunisia ("CET") +# +# +01 - WAT +XX +0627+00324 Africa/Lagos western Africa ("WAT") +# +# +01/+00 - IST/GMT (EU DST in reverse) +XX +5320-00615 Europe/Dublin Ireland ("IST/GMT") +# +# +01/+00 - (Morocco DST) +XX +3339-00735 Africa/Casablanca Morocco +# +# +01/+02 - CET/CEST (EU DST) +XX +4852+00220 Europe/Paris central Europe ("CET/CEST") +# +# +02 - CAT +XX -2558+03235 Africa/Maputo central Africa ("CAT") +# +# +02 - EET +XX +3254+01311 Africa/Tripoli Libya; Kaliningrad ("EET") +# +# +02 - SAST +XX -2615+02800 Africa/Johannesburg southern Africa ("SAST") +# +# +02/+03 - EET/EEST (EU DST) +XX +3758+02343 Europe/Athens eastern Europe ("EET/EEST") +# +# +02/+03 - EET/EEST (Egypt DST) +XX +3003+03115 Africa/Cairo Egypt +# +# +02/+03 - EET/EEST (Lebanon DST) +XX +3353+03530 Asia/Beirut Lebanon +# +# +02/+03 - EET/EEST (Moldova DST) +XX +4700+02850 Europe/Chisinau Moldova +# +# +02/+03 - EET/EEST (Palestine DST) +XX +3130+03428 Asia/Gaza Palestine +# +# +02/+03 - IST/IDT (Israel DST) +XX +314650+0351326 Asia/Jerusalem Israel +# +# +03 +XX +4101+02858 Europe/Istanbul Near East; Belarus +# +# +03 - EAT +XX -0117+03649 Africa/Nairobi eastern Africa ("EAT") +# +# +03 - MSK +XX +554521+0373704 Europe/Moscow Moscow ("MSK") +# +# +03:30 +XX +3540+05126 Asia/Tehran Iran +# +# +04 +XX +2518+05518 Asia/Dubai Russia; Caucasus; Persian Gulf; Seychelles; Réunion +# +# +04:30 +XX +3431+06912 Asia/Kabul Afghanistan +# +# +05 +XX +4120+06918 Asia/Tashkent Russia; Tajikistan; Turkmenistan; Uzbekistan; Maldives +# +# +05 - PKT +XX +2452+06703 Asia/Karachi Pakistan ("PKT") +# +# +05:30 +XX +0656+07951 Asia/Colombo Sri Lanka +# +# +05:30 - IST +XX +2232+08822 Asia/Kolkata India ("IST") +# +# +05:45 +XX +2743+08519 Asia/Kathmandu Nepal +# +# +06 +XX +2343+09025 Asia/Dhaka Russia; Kyrgyzstan; Bhutan; Bangladesh; Chagos +# +# +06:30 +XX +1647+09610 Asia/Yangon Myanmar; Cocos +# +# +07 +XX +1345+10031 Asia/Bangkok Russia; Indochina; Christmas Island +# +# +07 - WIB +XX -0610+10648 Asia/Jakarta Indonesia ("WIB") +# +# +08 +XX +0117+10351 Asia/Singapore Russia; Brunei; Malaysia; Singapore +# +# +08 - AWST +XX -3157+11551 Australia/Perth Western Australia ("AWST") +# +# +08 - CST +XX +3114+12128 Asia/Shanghai China ("CST") +# +# +08 - HKT +XX +2217+11409 Asia/Hong_Kong Hong Kong ("HKT") +# +# +08 - PHT +XX +1435+12100 Asia/Manila Philippines ("PHT") +# +# +08 - WITA +XX -0507+11924 Asia/Makassar Indonesia ("WITA") +# +# +08:45 +XX -3143+12852 Australia/Eucla Eucla +# +# +09 +XX +5203+11328 Asia/Chita Russia; Palau; East Timor +# +# +09 - JST +XX +353916+1394441 Asia/Tokyo Japan ("JST") +# +# +09 - KST +XX +3733+12658 Asia/Seoul Korea ("KST") +# +# +09 - WIT +XX -0232+14042 Asia/Jayapura Indonesia ("WIT") +# +# +09:30 - ACST +XX -1228+13050 Australia/Darwin Northern Territory ("ACST") +# +# +09:30/+10:30 - ACST/ACDT (Australia DST) +XX -3455+13835 Australia/Adelaide South Australia ("ACST/ACDT") +# +# +10 +XX +4310+13156 Asia/Vladivostok Russia; Yap; Chuuk; Papua New Guinea; Dumont d'Urville +# +# +10 - AEST +XX -2728+15302 Australia/Brisbane Queensland ("AEST") +# +# +10 - ChST +XX +1328+14445 Pacific/Guam Mariana Islands ("ChST") +# +# +10/+11 - AEST/AEDT (Australia DST) +XX -3352+15113 Australia/Sydney southeast Australia ("AEST/AEDT") +# +# +10:30/+11 +XX -3133+15905 Australia/Lord_Howe Lord Howe Island +# +# +11 +XX -0613+15534 Pacific/Bougainville Russia; Kosrae; Bougainville; Solomons +# +# +11/+12 (Australia DST) +XX -2903+16758 Pacific/Norfolk Norfolk Island +# +# +12 +XX +5301+15839 Asia/Kamchatka Russia; Tuvalu; Fiji; etc. +# +# +12/+13 (New Zealand DST) +XX -3652+17446 Pacific/Auckland New Zealand ("NZST/NZDT") +# +# +12:45/+13:45 (Chatham DST) +XX -4357-17633 Pacific/Chatham Chatham Islands +# +# +13 +XX -210800-1751200 Pacific/Tongatapu Kanton; Tokelau; Samoa (western); Tonga +# +# +14 +XX +0152-15720 Pacific/Kiritimati Kiritimati diff --git a/absl/time/time.cc b/absl/time/time.cc index 7256a699..d983c12b 100644 --- a/absl/time/time.cc +++ b/absl/time/time.cc @@ -66,6 +66,7 @@ inline int64_t FloorToUnit(absl::Duration d, absl::Duration unit) { : q - 1; } +ABSL_INTERNAL_DISABLE_DEPRECATED_DECLARATION_WARNING inline absl::Time::Breakdown InfiniteFutureBreakdown() { absl::Time::Breakdown bd; bd.year = std::numeric_limits<int64_t>::max(); @@ -99,6 +100,7 @@ inline absl::Time::Breakdown InfinitePastBreakdown() { bd.zone_abbr = "-00"; return bd; } +ABSL_INTERNAL_RESTORE_DEPRECATED_DECLARATION_WARNING inline absl::TimeZone::CivilInfo InfiniteFutureCivilInfo() { TimeZone::CivilInfo ci; @@ -120,6 +122,7 @@ inline absl::TimeZone::CivilInfo InfinitePastCivilInfo() { return ci; } +ABSL_INTERNAL_DISABLE_DEPRECATED_DECLARATION_WARNING inline absl::TimeConversion InfiniteFutureTimeConversion() { absl::TimeConversion tc; tc.pre = tc.trans = tc.post = absl::InfiniteFuture(); @@ -135,9 +138,10 @@ inline TimeConversion InfinitePastTimeConversion() { tc.normalized = true; return tc; } +ABSL_INTERNAL_RESTORE_DEPRECATED_DECLARATION_WARNING // Makes a Time from sec, overflowing to InfiniteFuture/InfinitePast as -// necessary. If sec is min/max, then consult cs+tz to check for overlow. +// necessary. If sec is min/max, then consult cs+tz to check for overflow. Time MakeTimeWithOverflow(const cctz::time_point<cctz::seconds>& sec, const cctz::civil_second& cs, const cctz::time_zone& tz, @@ -203,6 +207,7 @@ bool FindTransition(const cctz::time_zone& tz, // Time // +ABSL_INTERNAL_DISABLE_DEPRECATED_DECLARATION_WARNING absl::Time::Breakdown Time::In(absl::TimeZone tz) const { if (*this == absl::InfiniteFuture()) return InfiniteFutureBreakdown(); if (*this == absl::InfinitePast()) return InfinitePastBreakdown(); @@ -227,6 +232,7 @@ absl::Time::Breakdown Time::In(absl::TimeZone tz) const { bd.zone_abbr = al.abbr; return bd; } +ABSL_INTERNAL_RESTORE_DEPRECATED_DECLARATION_WARNING // // Conversions from/to other time types. @@ -398,7 +404,7 @@ bool TimeZone::PrevTransition(Time t, CivilTransition* trans) const { // // Conversions involving time zones. // - +ABSL_INTERNAL_DISABLE_DEPRECATED_DECLARATION_WARNING absl::TimeConversion ConvertDateTime(int64_t year, int mon, int day, int hour, int min, int sec, TimeZone tz) { // Avoids years that are too extreme for CivilSecond to normalize. @@ -430,6 +436,7 @@ absl::TimeConversion ConvertDateTime(int64_t year, int mon, int day, int hour, } return tc; } +ABSL_INTERNAL_RESTORE_DEPRECATED_DECLARATION_WARNING absl::Time FromTM(const struct tm& tm, absl::TimeZone tz) { civil_year_t tm_year = tm.tm_year; diff --git a/absl/time/time.h b/absl/time/time.h index cc390082..37580805 100644 --- a/absl/time/time.h +++ b/absl/time/time.h @@ -84,6 +84,7 @@ struct timeval; #include <type_traits> #include <utility> +#include "absl/base/config.h" #include "absl/base/macros.h" #include "absl/strings/string_view.h" #include "absl/time/civil_time.h" @@ -187,7 +188,12 @@ class Duration { Duration& operator%=(Duration rhs); // Overloads that forward to either the int64_t or double overloads above. - // Integer operands must be representable as int64_t. + // Integer operands must be representable as int64_t. Integer division is + // truncating, so values less than the resolution will be returned as zero. + // Floating-point multiplication and division is rounding (halfway cases + // rounding away from zero), so values less than the resolution may be + // returned as either the resolution or zero. In particular, `d / 2.0` + // can produce `d` when it is the resolution and "even". template <typename T, time_internal::EnableIfIntegral<T> = 0> Duration& operator*=(T r) { int64_t x = r; @@ -214,7 +220,7 @@ class Duration { template <typename H> friend H AbslHashValue(H h, Duration d) { - return H::combine(std::move(h), d.rep_hi_, d.rep_lo_); + return H::combine(std::move(h), d.rep_hi_.Get(), d.rep_lo_); } private: @@ -223,7 +229,79 @@ class Duration { friend constexpr Duration time_internal::MakeDuration(int64_t hi, uint32_t lo); constexpr Duration(int64_t hi, uint32_t lo) : rep_hi_(hi), rep_lo_(lo) {} - int64_t rep_hi_; + + // We store `rep_hi_` 4-byte rather than 8-byte aligned to avoid 4 bytes of + // tail padding. + class HiRep { + public: + // Default constructor default-initializes `hi_`, which has the same + // semantics as default-initializing an `int64_t` (undetermined value). + HiRep() = default; + + HiRep(const HiRep&) = default; + HiRep& operator=(const HiRep&) = default; + + explicit constexpr HiRep(const int64_t value) + : // C++17 forbids default-initialization in constexpr contexts. We can + // remove this in C++20. +#if defined(ABSL_IS_BIG_ENDIAN) && ABSL_IS_BIG_ENDIAN + hi_(0), + lo_(0) +#else + lo_(0), + hi_(0) +#endif + { + *this = value; + } + + constexpr int64_t Get() const { + const uint64_t unsigned_value = + (static_cast<uint64_t>(hi_) << 32) | static_cast<uint64_t>(lo_); + // `static_cast<int64_t>(unsigned_value)` is implementation-defined + // before c++20. On all supported platforms the behaviour is that mandated + // by c++20, i.e. "If the destination type is signed, [...] the result is + // the unique value of the destination type equal to the source value + // modulo 2^n, where n is the number of bits used to represent the + // destination type." + static_assert( + (static_cast<int64_t>((std::numeric_limits<uint64_t>::max)()) == + int64_t{-1}) && + (static_cast<int64_t>(static_cast<uint64_t>( + (std::numeric_limits<int64_t>::max)()) + + 1) == + (std::numeric_limits<int64_t>::min)()), + "static_cast<int64_t>(uint64_t) does not have c++20 semantics"); + return static_cast<int64_t>(unsigned_value); + } + + constexpr HiRep& operator=(const int64_t value) { + // "If the destination type is unsigned, the resulting value is the + // smallest unsigned value equal to the source value modulo 2^n + // where `n` is the number of bits used to represent the destination + // type". + const auto unsigned_value = static_cast<uint64_t>(value); + hi_ = static_cast<uint32_t>(unsigned_value >> 32); + lo_ = static_cast<uint32_t>(unsigned_value); + return *this; + } + + private: + // Notes: + // - Ideally we would use a `char[]` and `std::bitcast`, but the latter + // does not exist (and is not constexpr in `absl`) before c++20. + // - Order is optimized depending on endianness so that the compiler can + // turn `Get()` (resp. `operator=()`) into a single 8-byte load (resp. + // store). +#if defined(ABSL_IS_BIG_ENDIAN) && ABSL_IS_BIG_ENDIAN + uint32_t hi_; + uint32_t lo_; +#else + uint32_t lo_; + uint32_t hi_; +#endif + }; + HiRep rep_hi_; uint32_t rep_lo_; }; @@ -609,6 +687,12 @@ inline std::ostream& operator<<(std::ostream& os, Duration d) { return os << FormatDuration(d); } +// Support for StrFormat(), StrCat() etc. +template <typename Sink> +void AbslStringify(Sink& sink, Duration d) { + sink.Append(FormatDuration(d)); +} + // ParseDuration() // // Parses a duration string consisting of a possibly signed sequence of @@ -718,8 +802,7 @@ class Time { // `absl::TimeZone`. // // Deprecated. Use `absl::TimeZone::CivilInfo`. - struct - Breakdown { + struct ABSL_DEPRECATED("Use `absl::TimeZone::CivilInfo`.") Breakdown { int64_t year; // year (e.g., 2013) int month; // month of year [1:12] int day; // day of month [1:31] @@ -745,7 +828,10 @@ class Time { // Returns the breakdown of this instant in the given TimeZone. // // Deprecated. Use `absl::TimeZone::At(Time)`. + ABSL_INTERNAL_DISABLE_DEPRECATED_DECLARATION_WARNING + ABSL_DEPRECATED("Use `absl::TimeZone::At(Time)`.") Breakdown In(TimeZone tz) const; + ABSL_INTERNAL_RESTORE_DEPRECATED_DECLARATION_WARNING template <typename H> friend H AbslHashValue(H h, Time t) { @@ -839,7 +925,8 @@ ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time InfinitePast() { // FromUDate() // FromUniversal() // -// Creates an `absl::Time` from a variety of other representations. +// Creates an `absl::Time` from a variety of other representations. See +// https://unicode-org.github.io/icu/userguide/datetime/universaltimescale.html ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromUnixNanos(int64_t ns); ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromUnixMicros(int64_t us); ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromUnixMillis(int64_t ms); @@ -856,10 +943,12 @@ ABSL_ATTRIBUTE_CONST_FUNCTION Time FromUniversal(int64_t universal); // ToUDate() // ToUniversal() // -// Converts an `absl::Time` to a variety of other representations. Note that -// these operations round down toward negative infinity where necessary to -// adjust to the resolution of the result type. Beware of possible time_t -// over/underflow in ToTime{T,val,spec}() on 32-bit platforms. +// Converts an `absl::Time` to a variety of other representations. See +// https://unicode-org.github.io/icu/userguide/datetime/universaltimescale.html +// +// Note that these operations round down toward negative infinity where +// necessary to adjust to the resolution of the result type. Beware of +// possible time_t over/underflow in ToTime{T,val,spec}() on 32-bit platforms. ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToUnixNanos(Time t); ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToUnixMicros(Time t); ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToUnixMillis(Time t); @@ -1236,8 +1325,7 @@ ABSL_ATTRIBUTE_PURE_FUNCTION inline Time FromCivil(CivilSecond ct, // `absl::ConvertDateTime()`. Legacy version of `absl::TimeZone::TimeInfo`. // // Deprecated. Use `absl::TimeZone::TimeInfo`. -struct - TimeConversion { +struct ABSL_DEPRECATED("Use `absl::TimeZone::TimeInfo`.") TimeConversion { Time pre; // time calculated using the pre-transition offset Time trans; // when the civil-time discontinuity occurred Time post; // time calculated using the post-transition offset @@ -1271,8 +1359,11 @@ struct // // absl::ToCivilDay(tc.pre, tz).day() == 1 // // Deprecated. Use `absl::TimeZone::At(CivilSecond)`. +ABSL_INTERNAL_DISABLE_DEPRECATED_DECLARATION_WARNING +ABSL_DEPRECATED("Use `absl::TimeZone::At(CivilSecond)`.") TimeConversion ConvertDateTime(int64_t year, int mon, int day, int hour, int min, int sec, TimeZone tz); +ABSL_INTERNAL_RESTORE_DEPRECATED_DECLARATION_WARNING // FromDateTime() // @@ -1289,9 +1380,12 @@ TimeConversion ConvertDateTime(int64_t year, int mon, int day, int hour, // Deprecated. Use `absl::FromCivil(CivilSecond, TimeZone)`. Note that the // behavior of `FromCivil()` differs from `FromDateTime()` for skipped civil // times. If you care about that see `absl::TimeZone::At(absl::CivilSecond)`. -inline Time FromDateTime(int64_t year, int mon, int day, int hour, - int min, int sec, TimeZone tz) { +ABSL_DEPRECATED("Use `absl::FromCivil(CivilSecond, TimeZone)`.") +inline Time FromDateTime(int64_t year, int mon, int day, int hour, int min, + int sec, TimeZone tz) { + ABSL_INTERNAL_DISABLE_DEPRECATED_DECLARATION_WARNING return ConvertDateTime(year, mon, day, hour, min, sec, tz).pre; + ABSL_INTERNAL_RESTORE_DEPRECATED_DECLARATION_WARNING } // FromTM() @@ -1386,6 +1480,12 @@ inline std::ostream& operator<<(std::ostream& os, Time t) { return os << FormatTime(t); } +// Support for StrFormat(), StrCat() etc. +template <typename Sink> +void AbslStringify(Sink& sink, Time t) { + sink.Append(FormatTime(t)); +} + // ParseTime() // // Parses an input string according to the provided format string and @@ -1491,7 +1591,7 @@ ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration MakeNormalizedDuration( // Provide access to the Duration representation. ABSL_ATTRIBUTE_CONST_FUNCTION constexpr int64_t GetRepHi(Duration d) { - return d.rep_hi_; + return d.rep_hi_.Get(); } ABSL_ATTRIBUTE_CONST_FUNCTION constexpr uint32_t GetRepLo(Duration d) { return d.rep_lo_; diff --git a/absl/time/time_test.cc b/absl/time/time_test.cc index d235e9ad..bcf4f2ad 100644 --- a/absl/time/time_test.cc +++ b/absl/time/time_test.cc @@ -28,6 +28,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/numeric/int128.h" +#include "absl/strings/str_format.h" #include "absl/time/clock.h" #include "absl/time/internal/test_util.h" @@ -377,11 +378,6 @@ TEST(Time, FloorConversion) { } TEST(Time, RoundtripConversion) { -#if defined(ABSL_SKIP_TIME_TESTS_BROKEN_ON_MSVC_OPT) && \ - ABSL_SKIP_TIME_TESTS_BROKEN_ON_MSVC_OPT - GTEST_SKIP(); -#endif - #define TEST_CONVERSION_ROUND_TRIP(SOURCE, FROM, TO, MATCHER) \ EXPECT_THAT(TO(FROM(SOURCE)), MATCHER(SOURCE)) @@ -563,11 +559,6 @@ TEST(Time, FromChrono) { } TEST(Time, ToChronoTime) { -#if defined(ABSL_SKIP_TIME_TESTS_BROKEN_ON_MSVC_OPT) && \ - ABSL_SKIP_TIME_TESTS_BROKEN_ON_MSVC_OPT - GTEST_SKIP(); -#endif - EXPECT_EQ(std::chrono::system_clock::from_time_t(-1), absl::ToChronoTime(absl::FromTimeT(-1))); EXPECT_EQ(std::chrono::system_clock::from_time_t(0), @@ -1287,4 +1278,11 @@ TEST(Time, PrevTransitionNYC) { // We have a transition but we don't know which one. } +TEST(Time, AbslStringify) { + // FormatTime is already well tested, so just use one test case here to + // verify that StrFormat("%v", t) works as expected. + absl::Time t = absl::Now(); + EXPECT_EQ(absl::StrFormat("%v", t), absl::FormatTime(t)); +} + } // namespace diff --git a/absl/types/BUILD.bazel b/absl/types/BUILD.bazel index bb801012..ce8f605b 100644 --- a/absl/types/BUILD.bazel +++ b/absl/types/BUILD.bazel @@ -20,7 +20,14 @@ load( "ABSL_TEST_COPTS", ) -package(default_visibility = ["//visibility:public"]) +package( + default_visibility = ["//visibility:public"], + features = [ + "header_modules", + "layering_check", + "parse_headers", + ], +) licenses(["notice"]) @@ -77,8 +84,9 @@ cc_test( ":any", "//absl/base:config", "//absl/base:exception_testing", - "//absl/base:raw_logging_internal", "//absl/container:test_instance_tracker", + "//absl/log", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -92,6 +100,7 @@ cc_test( ":any", "//absl/base:config", "//absl/base:exception_safety_testing", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -109,6 +118,7 @@ cc_library( deps = [ "//absl/algorithm", "//absl/base:core_headers", + "//absl/base:nullability", "//absl/base:throw_delegate", "//absl/meta:type_traits", ], @@ -129,6 +139,7 @@ cc_test( "//absl/container:inlined_vector", "//absl/hash:hash_testing", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -144,6 +155,7 @@ cc_library( "//absl/base:base_internal", "//absl/base:config", "//absl/base:core_headers", + "//absl/base:nullability", "//absl/memory", "//absl/meta:type_traits", "//absl/utility", @@ -185,9 +197,10 @@ cc_test( deps = [ ":optional", "//absl/base:config", - "//absl/base:raw_logging_internal", + "//absl/log", "//absl/meta:type_traits", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -203,44 +216,7 @@ cc_test( ":optional", "//absl/base:config", "//absl/base:exception_safety_testing", - "@com_google_googletest//:gtest_main", - ], -) - -cc_library( - name = "conformance_testing", - testonly = 1, - hdrs = [ - "internal/conformance_aliases.h", - "internal/conformance_archetype.h", - "internal/conformance_profile.h", - "internal/conformance_testing.h", - "internal/conformance_testing_helpers.h", - "internal/parentheses.h", - "internal/transform_args.h", - ], - copts = ABSL_TEST_COPTS, - linkopts = ABSL_DEFAULT_LINKOPTS, - deps = [ - "//absl/algorithm:container", - "//absl/meta:type_traits", - "//absl/strings", - "//absl/utility", "@com_google_googletest//:gtest", - ], -) - -cc_test( - name = "conformance_testing_test", - size = "small", - srcs = [ - "internal/conformance_testing_test.cc", - ], - copts = ABSL_TEST_COPTS, - linkopts = ABSL_DEFAULT_LINKOPTS, - deps = [ - ":conformance_testing", - "//absl/meta:type_traits", "@com_google_googletest//:gtest_main", ], ) @@ -274,6 +250,7 @@ cc_test( "//absl/memory", "//absl/meta:type_traits", "//absl/strings", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -290,6 +267,7 @@ cc_test( ":variant", "//absl/utility", "@com_github_google_benchmark//:benchmark_main", + "@com_google_googletest//:gtest", ], ) @@ -306,6 +284,7 @@ cc_test( "//absl/base:config", "//absl/base:exception_safety_testing", "//absl/memory", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) @@ -316,6 +295,7 @@ cc_library( copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ + "//absl/base:config", "//absl/base:core_headers", "//absl/meta:type_traits", ], @@ -331,6 +311,7 @@ cc_test( deps = [ ":compare", "//absl/base", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/types/CMakeLists.txt b/absl/types/CMakeLists.txt index 830953ae..92b4ae49 100644 --- a/absl/types/CMakeLists.txt +++ b/absl/types/CMakeLists.txt @@ -68,7 +68,7 @@ absl_cc_test( absl::any absl::config absl::exception_testing - absl::raw_logging_internal + absl::log absl::test_instance_tracker GTest::gmock_main ) @@ -115,6 +115,7 @@ absl_cc_library( DEPS absl::algorithm absl::core_headers + absl::nullability absl::throw_delegate absl::type_traits PUBLIC @@ -175,6 +176,7 @@ absl_cc_library( absl::config absl::core_headers absl::memory + absl::nullability absl::type_traits absl::utility PUBLIC @@ -220,7 +222,7 @@ absl_cc_test( DEPS absl::optional absl::config - absl::raw_logging_internal + absl::log absl::strings absl::type_traits GTest::gmock_main @@ -240,59 +242,6 @@ absl_cc_test( GTest::gmock_main ) -# Internal-only target, do not depend on directly. -absl_cc_library( - NAME - conformance_testing - HDRS - "internal/conformance_aliases.h" - "internal/conformance_archetype.h" - "internal/conformance_profile.h" - "internal/conformance_testing.h" - "internal/conformance_testing_helpers.h" - "internal/parentheses.h" - "internal/transform_args.h" - COPTS - ${ABSL_DEFAULT_COPTS} - DEPS - absl::algorithm - absl::debugging - absl::type_traits - absl::strings - absl::utility - GTest::gmock_main - TESTONLY -) - -absl_cc_test( - NAME - conformance_testing_test - SRCS - "internal/conformance_testing_test.cc" - COPTS - ${ABSL_TEST_COPTS} - ${ABSL_EXCEPTIONS_FLAG} - LINKOPTS - ${ABSL_EXCEPTIONS_FLAG_LINKOPTS} - DEPS - absl::conformance_testing - absl::type_traits - GTest::gmock_main -) - -absl_cc_test( - NAME - conformance_testing_test_no_exceptions - SRCS - "internal/conformance_testing_test.cc" - COPTS - ${ABSL_TEST_COPTS} - DEPS - absl::conformance_testing - absl::type_traits - GTest::gmock_main -) - absl_cc_library( NAME variant @@ -337,6 +286,7 @@ absl_cc_library( COPTS ${ABSL_DEFAULT_COPTS} DEPS + absl::config absl::core_headers absl::type_traits PUBLIC diff --git a/absl/types/any.h b/absl/types/any.h index 204da26d..61f071f1 100644 --- a/absl/types/any.h +++ b/absl/types/any.h @@ -53,6 +53,7 @@ #ifndef ABSL_TYPES_ANY_H_ #define ABSL_TYPES_ANY_H_ +#include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/utility/utility.h" @@ -288,7 +289,7 @@ class any { typename T, typename... Args, typename VT = absl::decay_t<T>, absl::enable_if_t<std::is_copy_constructible<VT>::value && std::is_constructible<VT, Args...>::value>* = nullptr> - VT& emplace(Args&&... args) { + VT& emplace(Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND { reset(); // NOTE: reset() is required here even in the world of exceptions. Obj<VT>* const object_ptr = new Obj<VT>(in_place, std::forward<Args>(args)...); @@ -312,7 +313,8 @@ class any { absl::enable_if_t<std::is_copy_constructible<VT>::value && std::is_constructible<VT, std::initializer_list<U>&, Args...>::value>* = nullptr> - VT& emplace(std::initializer_list<U> ilist, Args&&... args) { + VT& emplace(std::initializer_list<U> ilist, + Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND { reset(); // NOTE: reset() is required here even in the world of exceptions. Obj<VT>* const object_ptr = new Obj<VT>(in_place, ilist, std::forward<Args>(args)...); diff --git a/absl/types/any_test.cc b/absl/types/any_test.cc index d382b927..666ea5b6 100644 --- a/absl/types/any_test.cc +++ b/absl/types/any_test.cc @@ -25,8 +25,8 @@ #include "gtest/gtest.h" #include "absl/base/config.h" #include "absl/base/internal/exception_testing.h" -#include "absl/base/internal/raw_logging.h" #include "absl/container/internal/test_instance_tracker.h" +#include "absl/log/log.h" namespace { using absl::test_internal::CopyableOnlyInstance; @@ -704,7 +704,7 @@ struct BadCopyable { #ifdef ABSL_HAVE_EXCEPTIONS throw BadCopy(); #else - ABSL_RAW_LOG(FATAL, "Bad copy"); + LOG(FATAL) << "Bad copy"; #endif } }; diff --git a/absl/types/bad_any_cast.cc b/absl/types/bad_any_cast.cc index b0592cc9..22558b48 100644 --- a/absl/types/bad_any_cast.cc +++ b/absl/types/bad_any_cast.cc @@ -43,4 +43,22 @@ void ThrowBadAnyCast() { ABSL_NAMESPACE_END } // namespace absl +#else + +// https://github.com/abseil/abseil-cpp/issues/1465 +// CMake builds on Apple platforms error when libraries are empty. +// Our CMake configuration can avoid this error on header-only libraries, +// but since this library is conditionally empty, including a single +// variable is an easy workaround. +#ifdef __APPLE__ +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace types_internal { +extern const char kAvoidEmptyBadAnyCastLibraryWarning; +const char kAvoidEmptyBadAnyCastLibraryWarning = 0; +} // namespace types_internal +ABSL_NAMESPACE_END +} // namespace absl +#endif // __APPLE__ + #endif // ABSL_USES_STD_ANY diff --git a/absl/types/bad_optional_access.cc b/absl/types/bad_optional_access.cc index 26aca70d..2552cc85 100644 --- a/absl/types/bad_optional_access.cc +++ b/absl/types/bad_optional_access.cc @@ -45,4 +45,22 @@ void throw_bad_optional_access() { ABSL_NAMESPACE_END } // namespace absl +#else + +// https://github.com/abseil/abseil-cpp/issues/1465 +// CMake builds on Apple platforms error when libraries are empty. +// Our CMake configuration can avoid this error on header-only libraries, +// but since this library is conditionally empty, including a single +// variable is an easy workaround. +#ifdef __APPLE__ +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace types_internal { +extern const char kAvoidEmptyBadOptionalAccessLibraryWarning; +const char kAvoidEmptyBadOptionalAccessLibraryWarning = 0; +} // namespace types_internal +ABSL_NAMESPACE_END +} // namespace absl +#endif // __APPLE__ + #endif // ABSL_USES_STD_OPTIONAL diff --git a/absl/types/bad_variant_access.cc b/absl/types/bad_variant_access.cc index 3dc88cc0..a76aa80d 100644 --- a/absl/types/bad_variant_access.cc +++ b/absl/types/bad_variant_access.cc @@ -61,4 +61,22 @@ void Rethrow() { ABSL_NAMESPACE_END } // namespace absl +#else + +// https://github.com/abseil/abseil-cpp/issues/1465 +// CMake builds on Apple platforms error when libraries are empty. +// Our CMake configuration can avoid this error on header-only libraries, +// but since this library is conditionally empty, including a single +// variable is an easy workaround. +#ifdef __APPLE__ +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace types_internal { +extern const char kAvoidEmptyBadVariantAccessLibraryWarning; +const char kAvoidEmptyBadVariantAccessLibraryWarning = 0; +} // namespace types_internal +ABSL_NAMESPACE_END +} // namespace absl +#endif // __APPLE__ + #endif // ABSL_USES_STD_VARIANT diff --git a/absl/types/compare.h b/absl/types/compare.h index 1a965e97..3cf4a917 100644 --- a/absl/types/compare.h +++ b/absl/types/compare.h @@ -16,43 +16,75 @@ // compare.h // ----------------------------------------------------------------------------- // -// This header file defines the `absl::weak_equality`, `absl::strong_equality`, -// `absl::partial_ordering`, `absl::weak_ordering`, and `absl::strong_ordering` -// types for storing the results of three way comparisons. +// This header file defines the `absl::partial_ordering`, `absl::weak_ordering`, +// and `absl::strong_ordering` types for storing the results of three way +// comparisons. // // Example: // absl::weak_ordering compare(const std::string& a, const std::string& b); // // These are C++11 compatible versions of the C++20 corresponding types -// (`std::weak_equality`, etc.) and are designed to be drop-in replacements +// (`std::partial_ordering`, etc.) and are designed to be drop-in replacements // for code compliant with C++20. #ifndef ABSL_TYPES_COMPARE_H_ #define ABSL_TYPES_COMPARE_H_ +#include "absl/base/config.h" + +#ifdef ABSL_USES_STD_ORDERING + +#include <compare> // IWYU pragma: export +#include <type_traits> + +#include "absl/meta/type_traits.h" + +#else + #include <cstddef> #include <cstdint> #include <cstdlib> #include <type_traits> #include "absl/base/attributes.h" +#include "absl/base/macros.h" #include "absl/meta/type_traits.h" +#endif + namespace absl { ABSL_NAMESPACE_BEGIN + +#ifdef ABSL_USES_STD_ORDERING + +using std::partial_ordering; +using std::strong_ordering; +using std::weak_ordering; + +#else + namespace compare_internal { using value_type = int8_t; class OnlyLiteralZero { - // A private type which cannot be named to explicitly cast to it. - struct MatchLiteralZero; - public: +#if ABSL_HAVE_ATTRIBUTE(enable_if) + // On clang, we can avoid triggering modernize-use-nullptr by only enabling + // this overload when the value is a compile time integer constant equal to 0. + // + // In c++20, this could be a static_assert in a consteval function. + constexpr OnlyLiteralZero(int n) // NOLINT + __attribute__((enable_if(n == 0, "Only literal `0` is allowed."))) {} +#else // ABSL_HAVE_ATTRIBUTE(enable_if) // Accept only literal zero since it can be implicitly converted to a pointer - // type. nullptr constants will be caught by the other constructor which - // accepts a nullptr_t. - constexpr OnlyLiteralZero(MatchLiteralZero *) noexcept {} // NOLINT + // to member type. nullptr constants will be caught by the other constructor + // which accepts a nullptr_t. + // + // This constructor is not used for clang since it triggers + // modernize-use-nullptr. + constexpr OnlyLiteralZero(int OnlyLiteralZero::*) noexcept {} // NOLINT +#endif // Fails compilation when `nullptr` or integral type arguments other than // `int` are passed. This constructor doesn't accept `int` because literal `0` @@ -112,20 +144,6 @@ enum class ncmp : value_type { unordered = -127 }; // in the header file (for performance) without using inline variables (which // aren't available in C++11). template <typename T> -struct weak_equality_base { - ABSL_COMPARE_INLINE_BASECLASS_DECL(equivalent); - ABSL_COMPARE_INLINE_BASECLASS_DECL(nonequivalent); -}; - -template <typename T> -struct strong_equality_base { - ABSL_COMPARE_INLINE_BASECLASS_DECL(equal); - ABSL_COMPARE_INLINE_BASECLASS_DECL(nonequal); - ABSL_COMPARE_INLINE_BASECLASS_DECL(equivalent); - ABSL_COMPARE_INLINE_BASECLASS_DECL(nonequivalent); -}; - -template <typename T> struct partial_ordering_base { ABSL_COMPARE_INLINE_BASECLASS_DECL(less); ABSL_COMPARE_INLINE_BASECLASS_DECL(equivalent); @@ -150,104 +168,6 @@ struct strong_ordering_base { } // namespace compare_internal -class weak_equality - : public compare_internal::weak_equality_base<weak_equality> { - explicit constexpr weak_equality(compare_internal::eq v) noexcept - : value_(static_cast<compare_internal::value_type>(v)) {} - friend struct compare_internal::weak_equality_base<weak_equality>; - - public: - ABSL_COMPARE_INLINE_SUBCLASS_DECL(weak_equality, equivalent); - ABSL_COMPARE_INLINE_SUBCLASS_DECL(weak_equality, nonequivalent); - - // Comparisons - friend constexpr bool operator==( - weak_equality v, compare_internal::OnlyLiteralZero) noexcept { - return v.value_ == 0; - } - friend constexpr bool operator!=( - weak_equality v, compare_internal::OnlyLiteralZero) noexcept { - return v.value_ != 0; - } - friend constexpr bool operator==(compare_internal::OnlyLiteralZero, - weak_equality v) noexcept { - return 0 == v.value_; - } - friend constexpr bool operator!=(compare_internal::OnlyLiteralZero, - weak_equality v) noexcept { - return 0 != v.value_; - } - friend constexpr bool operator==(weak_equality v1, - weak_equality v2) noexcept { - return v1.value_ == v2.value_; - } - friend constexpr bool operator!=(weak_equality v1, - weak_equality v2) noexcept { - return v1.value_ != v2.value_; - } - - private: - compare_internal::value_type value_; -}; -ABSL_COMPARE_INLINE_INIT(weak_equality, equivalent, - compare_internal::eq::equivalent); -ABSL_COMPARE_INLINE_INIT(weak_equality, nonequivalent, - compare_internal::eq::nonequivalent); - -class strong_equality - : public compare_internal::strong_equality_base<strong_equality> { - explicit constexpr strong_equality(compare_internal::eq v) noexcept - : value_(static_cast<compare_internal::value_type>(v)) {} - friend struct compare_internal::strong_equality_base<strong_equality>; - - public: - ABSL_COMPARE_INLINE_SUBCLASS_DECL(strong_equality, equal); - ABSL_COMPARE_INLINE_SUBCLASS_DECL(strong_equality, nonequal); - ABSL_COMPARE_INLINE_SUBCLASS_DECL(strong_equality, equivalent); - ABSL_COMPARE_INLINE_SUBCLASS_DECL(strong_equality, nonequivalent); - - // Conversion - constexpr operator weak_equality() const noexcept { // NOLINT - return value_ == 0 ? weak_equality::equivalent - : weak_equality::nonequivalent; - } - // Comparisons - friend constexpr bool operator==( - strong_equality v, compare_internal::OnlyLiteralZero) noexcept { - return v.value_ == 0; - } - friend constexpr bool operator!=( - strong_equality v, compare_internal::OnlyLiteralZero) noexcept { - return v.value_ != 0; - } - friend constexpr bool operator==(compare_internal::OnlyLiteralZero, - strong_equality v) noexcept { - return 0 == v.value_; - } - friend constexpr bool operator!=(compare_internal::OnlyLiteralZero, - strong_equality v) noexcept { - return 0 != v.value_; - } - friend constexpr bool operator==(strong_equality v1, - strong_equality v2) noexcept { - return v1.value_ == v2.value_; - } - friend constexpr bool operator!=(strong_equality v1, - strong_equality v2) noexcept { - return v1.value_ != v2.value_; - } - - private: - compare_internal::value_type value_; -}; -ABSL_COMPARE_INLINE_INIT(strong_equality, equal, compare_internal::eq::equal); -ABSL_COMPARE_INLINE_INIT(strong_equality, nonequal, - compare_internal::eq::nonequal); -ABSL_COMPARE_INLINE_INIT(strong_equality, equivalent, - compare_internal::eq::equivalent); -ABSL_COMPARE_INLINE_INIT(strong_equality, nonequivalent, - compare_internal::eq::nonequivalent); - class partial_ordering : public compare_internal::partial_ordering_base<partial_ordering> { explicit constexpr partial_ordering(compare_internal::eq v) noexcept @@ -269,11 +189,6 @@ class partial_ordering ABSL_COMPARE_INLINE_SUBCLASS_DECL(partial_ordering, greater); ABSL_COMPARE_INLINE_SUBCLASS_DECL(partial_ordering, unordered); - // Conversion - constexpr operator weak_equality() const noexcept { // NOLINT - return value_ == 0 ? weak_equality::equivalent - : weak_equality::nonequivalent; - } // Comparisons friend constexpr bool operator==( partial_ordering v, compare_internal::OnlyLiteralZero) noexcept { @@ -357,10 +272,6 @@ class weak_ordering ABSL_COMPARE_INLINE_SUBCLASS_DECL(weak_ordering, greater); // Conversions - constexpr operator weak_equality() const noexcept { // NOLINT - return value_ == 0 ? weak_equality::equivalent - : weak_equality::nonequivalent; - } constexpr operator partial_ordering() const noexcept { // NOLINT return value_ == 0 ? partial_ordering::equivalent : (value_ < 0 ? partial_ordering::less @@ -448,13 +359,6 @@ class strong_ordering ABSL_COMPARE_INLINE_SUBCLASS_DECL(strong_ordering, greater); // Conversions - constexpr operator weak_equality() const noexcept { // NOLINT - return value_ == 0 ? weak_equality::equivalent - : weak_equality::nonequivalent; - } - constexpr operator strong_equality() const noexcept { // NOLINT - return value_ == 0 ? strong_equality::equal : strong_equality::nonequal; - } constexpr operator partial_ordering() const noexcept { // NOLINT return value_ == 0 ? partial_ordering::equivalent : (value_ < 0 ? partial_ordering::less @@ -537,6 +441,8 @@ ABSL_COMPARE_INLINE_INIT(strong_ordering, greater, #undef ABSL_COMPARE_INLINE_SUBCLASS_DECL #undef ABSL_COMPARE_INLINE_INIT +#endif // ABSL_USES_STD_ORDERING + namespace compare_internal { // We also provide these comparator adapter functions for internal absl use. diff --git a/absl/types/compare_test.cc b/absl/types/compare_test.cc index 8095baf9..455cdbba 100644 --- a/absl/types/compare_test.cc +++ b/absl/types/compare_test.cc @@ -26,45 +26,6 @@ namespace { // to an int, which can't be converted to the unspecified zero type. bool Identity(bool b) { return b; } -TEST(Compare, WeakEquality) { - EXPECT_TRUE(Identity(weak_equality::equivalent == 0)); - EXPECT_TRUE(Identity(0 == weak_equality::equivalent)); - EXPECT_TRUE(Identity(weak_equality::nonequivalent != 0)); - EXPECT_TRUE(Identity(0 != weak_equality::nonequivalent)); - const weak_equality values[] = {weak_equality::equivalent, - weak_equality::nonequivalent}; - for (const auto& lhs : values) { - for (const auto& rhs : values) { - const bool are_equal = &lhs == &rhs; - EXPECT_EQ(lhs == rhs, are_equal); - EXPECT_EQ(lhs != rhs, !are_equal); - } - } -} - -TEST(Compare, StrongEquality) { - EXPECT_TRUE(Identity(strong_equality::equal == 0)); - EXPECT_TRUE(Identity(0 == strong_equality::equal)); - EXPECT_TRUE(Identity(strong_equality::nonequal != 0)); - EXPECT_TRUE(Identity(0 != strong_equality::nonequal)); - EXPECT_TRUE(Identity(strong_equality::equivalent == 0)); - EXPECT_TRUE(Identity(0 == strong_equality::equivalent)); - EXPECT_TRUE(Identity(strong_equality::nonequivalent != 0)); - EXPECT_TRUE(Identity(0 != strong_equality::nonequivalent)); - const strong_equality values[] = {strong_equality::equal, - strong_equality::nonequal}; - for (const auto& lhs : values) { - for (const auto& rhs : values) { - const bool are_equal = &lhs == &rhs; - EXPECT_EQ(lhs == rhs, are_equal); - EXPECT_EQ(lhs != rhs, !are_equal); - } - } - EXPECT_TRUE(Identity(strong_equality::equivalent == strong_equality::equal)); - EXPECT_TRUE( - Identity(strong_equality::nonequivalent == strong_equality::nonequal)); -} - TEST(Compare, PartialOrdering) { EXPECT_TRUE(Identity(partial_ordering::less < 0)); EXPECT_TRUE(Identity(0 > partial_ordering::less)); @@ -147,30 +108,6 @@ TEST(Compare, StrongOrdering) { TEST(Compare, Conversions) { EXPECT_TRUE( - Identity(implicit_cast<weak_equality>(strong_equality::equal) == 0)); - EXPECT_TRUE( - Identity(implicit_cast<weak_equality>(strong_equality::nonequal) != 0)); - EXPECT_TRUE( - Identity(implicit_cast<weak_equality>(strong_equality::equivalent) == 0)); - EXPECT_TRUE(Identity( - implicit_cast<weak_equality>(strong_equality::nonequivalent) != 0)); - - EXPECT_TRUE( - Identity(implicit_cast<weak_equality>(partial_ordering::less) != 0)); - EXPECT_TRUE(Identity( - implicit_cast<weak_equality>(partial_ordering::equivalent) == 0)); - EXPECT_TRUE( - Identity(implicit_cast<weak_equality>(partial_ordering::greater) != 0)); - EXPECT_TRUE( - Identity(implicit_cast<weak_equality>(partial_ordering::unordered) != 0)); - - EXPECT_TRUE(implicit_cast<weak_equality>(weak_ordering::less) != 0); - EXPECT_TRUE( - Identity(implicit_cast<weak_equality>(weak_ordering::equivalent) == 0)); - EXPECT_TRUE( - Identity(implicit_cast<weak_equality>(weak_ordering::greater) != 0)); - - EXPECT_TRUE( Identity(implicit_cast<partial_ordering>(weak_ordering::less) != 0)); EXPECT_TRUE( Identity(implicit_cast<partial_ordering>(weak_ordering::less) < 0)); @@ -186,24 +123,6 @@ TEST(Compare, Conversions) { Identity(implicit_cast<partial_ordering>(weak_ordering::greater) >= 0)); EXPECT_TRUE( - Identity(implicit_cast<weak_equality>(strong_ordering::less) != 0)); - EXPECT_TRUE( - Identity(implicit_cast<weak_equality>(strong_ordering::equal) == 0)); - EXPECT_TRUE( - Identity(implicit_cast<weak_equality>(strong_ordering::equivalent) == 0)); - EXPECT_TRUE( - Identity(implicit_cast<weak_equality>(strong_ordering::greater) != 0)); - - EXPECT_TRUE( - Identity(implicit_cast<strong_equality>(strong_ordering::less) != 0)); - EXPECT_TRUE( - Identity(implicit_cast<strong_equality>(strong_ordering::equal) == 0)); - EXPECT_TRUE(Identity( - implicit_cast<strong_equality>(strong_ordering::equivalent) == 0)); - EXPECT_TRUE( - Identity(implicit_cast<strong_equality>(strong_ordering::greater) != 0)); - - EXPECT_TRUE( Identity(implicit_cast<partial_ordering>(strong_ordering::less) != 0)); EXPECT_TRUE( Identity(implicit_cast<partial_ordering>(strong_ordering::less) < 0)); @@ -360,14 +279,6 @@ TEST(DoThreeWayComparison, SanityTest) { #ifdef __cpp_inline_variables TEST(Compare, StaticAsserts) { - static_assert(weak_equality::equivalent == 0, ""); - static_assert(weak_equality::nonequivalent != 0, ""); - - static_assert(strong_equality::equal == 0, ""); - static_assert(strong_equality::nonequal != 0, ""); - static_assert(strong_equality::equivalent == 0, ""); - static_assert(strong_equality::nonequivalent != 0, ""); - static_assert(partial_ordering::less < 0, ""); static_assert(partial_ordering::equivalent == 0, ""); static_assert(partial_ordering::greater > 0, ""); diff --git a/absl/types/internal/conformance_aliases.h b/absl/types/internal/conformance_aliases.h deleted file mode 100644 index 0cc6884e..00000000 --- a/absl/types/internal/conformance_aliases.h +++ /dev/null @@ -1,447 +0,0 @@ -// 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. -// -// ----------------------------------------------------------------------------- -// regularity_aliases.h -// ----------------------------------------------------------------------------- -// -// This file contains type aliases of common ConformanceProfiles and Archetypes -// so that they can be directly used by name without creating them from scratch. - -#ifndef ABSL_TYPES_INTERNAL_CONFORMANCE_ALIASES_H_ -#define ABSL_TYPES_INTERNAL_CONFORMANCE_ALIASES_H_ - -#include "absl/types/internal/conformance_archetype.h" -#include "absl/types/internal/conformance_profile.h" - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace types_internal { - -// Creates both a Profile and a corresponding Archetype with root name "name". -#define ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS(name, ...) \ - struct name##Profile : __VA_ARGS__ {}; \ - \ - using name##Archetype = ::absl::types_internal::Archetype<name##Profile>; \ - \ - template <class AbslInternalProfileTag> \ - using name##Archetype##_ = ::absl::types_internal::Archetype< \ - ::absl::types_internal::StrongProfileTypedef<name##Profile, \ - AbslInternalProfileTag>> - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasTrivialDefaultConstructor, - ConformanceProfile<default_constructible::trivial>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasNothrowDefaultConstructor, - ConformanceProfile<default_constructible::nothrow>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasDefaultConstructor, ConformanceProfile<default_constructible::yes>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasTrivialMoveConstructor, ConformanceProfile<default_constructible::maybe, - move_constructible::trivial>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasNothrowMoveConstructor, ConformanceProfile<default_constructible::maybe, - move_constructible::nothrow>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasMoveConstructor, - ConformanceProfile<default_constructible::maybe, move_constructible::yes>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasTrivialCopyConstructor, - ConformanceProfile<default_constructible::maybe, move_constructible::maybe, - copy_constructible::trivial>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasNothrowCopyConstructor, - ConformanceProfile<default_constructible::maybe, move_constructible::maybe, - copy_constructible::nothrow>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasCopyConstructor, - ConformanceProfile<default_constructible::maybe, move_constructible::maybe, - copy_constructible::yes>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasTrivialMoveAssign, - ConformanceProfile<default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::trivial>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasNothrowMoveAssign, - ConformanceProfile<default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::nothrow>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasMoveAssign, - ConformanceProfile<default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::yes>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasTrivialCopyAssign, - ConformanceProfile<default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::maybe, - copy_assignable::trivial>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasNothrowCopyAssign, - ConformanceProfile<default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::maybe, - copy_assignable::nothrow>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasCopyAssign, - ConformanceProfile<default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::maybe, - copy_assignable::yes>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasTrivialDestructor, - ConformanceProfile<default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::maybe, - copy_assignable::maybe, destructible::trivial>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasNothrowDestructor, - ConformanceProfile<default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::maybe, - copy_assignable::maybe, destructible::nothrow>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasDestructor, - ConformanceProfile<default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::maybe, - copy_assignable::maybe, destructible::yes>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasNothrowEquality, - ConformanceProfile<default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::maybe, - copy_assignable::maybe, destructible::maybe, - equality_comparable::nothrow>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasEquality, - ConformanceProfile<default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::maybe, - copy_assignable::maybe, destructible::maybe, - equality_comparable::yes>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasNothrowInequality, - ConformanceProfile<default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::maybe, - copy_assignable::maybe, destructible::maybe, - equality_comparable::maybe, - inequality_comparable::nothrow>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasInequality, - ConformanceProfile<default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::maybe, - copy_assignable::maybe, destructible::maybe, - equality_comparable::maybe, inequality_comparable::yes>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasNothrowLessThan, - ConformanceProfile<default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::maybe, - copy_assignable::maybe, destructible::maybe, - equality_comparable::maybe, inequality_comparable::maybe, - less_than_comparable::nothrow>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasLessThan, - ConformanceProfile<default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::maybe, - copy_assignable::maybe, destructible::maybe, - equality_comparable::maybe, inequality_comparable::maybe, - less_than_comparable::yes>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasNothrowLessEqual, - ConformanceProfile<default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::maybe, - copy_assignable::maybe, destructible::maybe, - equality_comparable::maybe, inequality_comparable::maybe, - less_than_comparable::maybe, - less_equal_comparable::nothrow>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasLessEqual, - ConformanceProfile<default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::maybe, - copy_assignable::maybe, destructible::maybe, - equality_comparable::maybe, inequality_comparable::maybe, - less_than_comparable::maybe, - less_equal_comparable::yes>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasNothrowGreaterEqual, - ConformanceProfile< - default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::maybe, - copy_assignable::maybe, destructible::maybe, equality_comparable::maybe, - inequality_comparable::maybe, less_than_comparable::maybe, - less_equal_comparable::maybe, greater_equal_comparable::nothrow>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasGreaterEqual, - ConformanceProfile< - default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::maybe, - copy_assignable::maybe, destructible::maybe, equality_comparable::maybe, - inequality_comparable::maybe, less_than_comparable::maybe, - less_equal_comparable::maybe, greater_equal_comparable::yes>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasNothrowGreaterThan, - ConformanceProfile< - default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::maybe, - copy_assignable::maybe, destructible::maybe, equality_comparable::maybe, - inequality_comparable::maybe, less_than_comparable::maybe, - less_equal_comparable::maybe, greater_equal_comparable::maybe, - greater_than_comparable::nothrow>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasGreaterThan, - ConformanceProfile< - default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::maybe, - copy_assignable::maybe, destructible::maybe, equality_comparable::maybe, - inequality_comparable::maybe, less_than_comparable::maybe, - less_equal_comparable::maybe, greater_equal_comparable::maybe, - greater_than_comparable::yes>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasNothrowSwap, - ConformanceProfile< - default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::maybe, - copy_assignable::maybe, destructible::maybe, equality_comparable::maybe, - inequality_comparable::maybe, less_than_comparable::maybe, - less_equal_comparable::maybe, greater_equal_comparable::maybe, - greater_than_comparable::maybe, swappable::nothrow>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasSwap, - ConformanceProfile< - default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::maybe, - copy_assignable::maybe, destructible::maybe, equality_comparable::maybe, - inequality_comparable::maybe, less_than_comparable::maybe, - less_equal_comparable::maybe, greater_equal_comparable::maybe, - greater_than_comparable::maybe, swappable::yes>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HasStdHashSpecialization, - ConformanceProfile< - default_constructible::maybe, move_constructible::maybe, - copy_constructible::maybe, move_assignable::maybe, - copy_assignable::maybe, destructible::maybe, equality_comparable::maybe, - inequality_comparable::maybe, less_than_comparable::maybe, - less_equal_comparable::maybe, greater_equal_comparable::maybe, - greater_than_comparable::maybe, swappable::maybe, hashable::yes>); - -//////////////////////////////////////////////////////////////////////////////// -//// The remaining aliases are combinations of the previous aliases. //// -//////////////////////////////////////////////////////////////////////////////// - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - Equatable, CombineProfiles<HasEqualityProfile, HasInequalityProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - Comparable, - CombineProfiles<EquatableProfile, HasLessThanProfile, HasLessEqualProfile, - HasGreaterEqualProfile, HasGreaterThanProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - NothrowEquatable, - CombineProfiles<HasNothrowEqualityProfile, HasNothrowInequalityProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - NothrowComparable, - CombineProfiles<NothrowEquatableProfile, HasNothrowLessThanProfile, - HasNothrowLessEqualProfile, HasNothrowGreaterEqualProfile, - HasNothrowGreaterThanProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - Value, - CombineProfiles<HasNothrowMoveConstructorProfile, HasCopyConstructorProfile, - HasNothrowMoveAssignProfile, HasCopyAssignProfile, - HasNothrowDestructorProfile, HasNothrowSwapProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - EquatableValue, CombineProfiles<EquatableProfile, ValueProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - ComparableValue, CombineProfiles<ComparableProfile, ValueProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - DefaultConstructibleValue, - CombineProfiles<HasDefaultConstructorProfile, ValueProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - NothrowMoveConstructible, CombineProfiles<HasNothrowMoveConstructorProfile, - HasNothrowDestructorProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - EquatableNothrowMoveConstructible, - CombineProfiles<EquatableProfile, NothrowMoveConstructibleProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - ComparableNothrowMoveConstructible, - CombineProfiles<ComparableProfile, NothrowMoveConstructibleProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - DefaultConstructibleNothrowMoveConstructible, - CombineProfiles<HasDefaultConstructorProfile, - NothrowMoveConstructibleProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - CopyConstructible, - CombineProfiles<HasNothrowMoveConstructorProfile, HasCopyConstructorProfile, - HasNothrowDestructorProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - EquatableCopyConstructible, - CombineProfiles<EquatableProfile, CopyConstructibleProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - ComparableCopyConstructible, - CombineProfiles<ComparableProfile, CopyConstructibleProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - DefaultConstructibleCopyConstructible, - CombineProfiles<HasDefaultConstructorProfile, CopyConstructibleProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - NothrowMovable, - CombineProfiles<HasNothrowMoveConstructorProfile, - HasNothrowMoveAssignProfile, HasNothrowDestructorProfile, - HasNothrowSwapProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - EquatableNothrowMovable, - CombineProfiles<EquatableProfile, NothrowMovableProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - ComparableNothrowMovable, - CombineProfiles<ComparableProfile, NothrowMovableProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - DefaultConstructibleNothrowMovable, - CombineProfiles<HasDefaultConstructorProfile, NothrowMovableProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - TrivialSpecialMemberFunctions, - CombineProfiles<HasTrivialDefaultConstructorProfile, - HasTrivialMoveConstructorProfile, - HasTrivialCopyConstructorProfile, - HasTrivialMoveAssignProfile, HasTrivialCopyAssignProfile, - HasTrivialDestructorProfile, HasNothrowSwapProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - TriviallyComplete, - CombineProfiles<TrivialSpecialMemberFunctionsProfile, ComparableProfile, - HasStdHashSpecializationProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HashableNothrowMoveConstructible, - CombineProfiles<HasStdHashSpecializationProfile, - NothrowMoveConstructibleProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HashableCopyConstructible, - CombineProfiles<HasStdHashSpecializationProfile, CopyConstructibleProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HashableNothrowMovable, - CombineProfiles<HasStdHashSpecializationProfile, NothrowMovableProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - HashableValue, - CombineProfiles<HasStdHashSpecializationProfile, ValueProfile>); - -ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS( - ComparableHashableValue, - CombineProfiles<HashableValueProfile, ComparableProfile>); - -// The "preferred" profiles that we support in Abseil. -template <template <class...> class Receiver> -using ExpandBasicProfiles = - Receiver<NothrowMoveConstructibleProfile, CopyConstructibleProfile, - NothrowMovableProfile, ValueProfile>; - -// The basic profiles except that they are also all Equatable. -template <template <class...> class Receiver> -using ExpandBasicEquatableProfiles = - Receiver<EquatableNothrowMoveConstructibleProfile, - EquatableCopyConstructibleProfile, EquatableNothrowMovableProfile, - EquatableValueProfile>; - -// The basic profiles except that they are also all Comparable. -template <template <class...> class Receiver> -using ExpandBasicComparableProfiles = - Receiver<ComparableNothrowMoveConstructibleProfile, - ComparableCopyConstructibleProfile, - ComparableNothrowMovableProfile, ComparableValueProfile>; - -// The basic profiles except that they are also all Hashable. -template <template <class...> class Receiver> -using ExpandBasicHashableProfiles = - Receiver<HashableNothrowMoveConstructibleProfile, - HashableCopyConstructibleProfile, HashableNothrowMovableProfile, - HashableValueProfile>; - -// The basic profiles except that they are also all DefaultConstructible. -template <template <class...> class Receiver> -using ExpandBasicDefaultConstructibleProfiles = - Receiver<DefaultConstructibleNothrowMoveConstructibleProfile, - DefaultConstructibleCopyConstructibleProfile, - DefaultConstructibleNothrowMovableProfile, - DefaultConstructibleValueProfile>; - -// The type profiles that we support in Abseil (all of the previous lists). -template <template <class...> class Receiver> -using ExpandSupportedProfiles = Receiver< - NothrowMoveConstructibleProfile, CopyConstructibleProfile, - NothrowMovableProfile, ValueProfile, - EquatableNothrowMoveConstructibleProfile, EquatableCopyConstructibleProfile, - EquatableNothrowMovableProfile, EquatableValueProfile, - ComparableNothrowMoveConstructibleProfile, - ComparableCopyConstructibleProfile, ComparableNothrowMovableProfile, - ComparableValueProfile, DefaultConstructibleNothrowMoveConstructibleProfile, - DefaultConstructibleCopyConstructibleProfile, - DefaultConstructibleNothrowMovableProfile, DefaultConstructibleValueProfile, - HashableNothrowMoveConstructibleProfile, HashableCopyConstructibleProfile, - HashableNothrowMovableProfile, HashableValueProfile>; - -// TODO(calabrese) Include types that have throwing move constructors, since in -// practice we still need to support them because of standard library types with -// (potentially) non-noexcept moves. - -} // namespace types_internal -ABSL_NAMESPACE_END -} // namespace absl - -#undef ABSL_INTERNAL_PROFILE_AND_ARCHETYPE_ALIAS - -#endif // ABSL_TYPES_INTERNAL_CONFORMANCE_ALIASES_H_ diff --git a/absl/types/internal/conformance_archetype.h b/absl/types/internal/conformance_archetype.h deleted file mode 100644 index 2349e0f7..00000000 --- a/absl/types/internal/conformance_archetype.h +++ /dev/null @@ -1,978 +0,0 @@ -// Copyright 2019 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. -// -// ----------------------------------------------------------------------------- -// conformance_archetype.h -// ----------------------------------------------------------------------------- -// -// This file contains a facility for generating "archetypes" of out of -// "Conformance Profiles" (see "conformance_profiles.h" for more information -// about Conformance Profiles). An archetype is a type that aims to support the -// bare minimum requirements of a given Conformance Profile. For instance, an -// archetype that corresponds to an ImmutableProfile has exactly a nothrow -// move-constructor, a potentially-throwing copy constructor, a nothrow -// destructor, with all other special-member-functions deleted. These archetypes -// are useful for testing to make sure that templates are able to work with the -// kinds of types that they claim to support (i.e. that they do not accidentally -// under-constrain), -// -// The main type template in this file is the Archetype template, which takes -// a Conformance Profile as a template argument and its instantiations are a -// minimum-conforming model of that profile. - -#ifndef ABSL_TYPES_INTERNAL_CONFORMANCE_ARCHETYPE_H_ -#define ABSL_TYPES_INTERNAL_CONFORMANCE_ARCHETYPE_H_ - -#include <cstddef> -#include <functional> -#include <type_traits> -#include <utility> - -#include "absl/meta/type_traits.h" -#include "absl/types/internal/conformance_profile.h" - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace types_internal { - -// A minimum-conforming implementation of a type with properties specified in -// `Prof`, where `Prof` is a valid Conformance Profile. -template <class Prof, class /*Enabler*/ = void> -class Archetype; - -// Given an Archetype, obtain the properties of the profile associated with that -// archetype. -template <class Archetype> -struct PropertiesOfArchetype; - -template <class Prof> -struct PropertiesOfArchetype<Archetype<Prof>> { - using type = PropertiesOfT<Prof>; -}; - -template <class Archetype> -using PropertiesOfArchetypeT = typename PropertiesOfArchetype<Archetype>::type; - -// A metafunction to determine if a type is an `Archetype`. -template <class T> -struct IsArchetype : std::false_type {}; - -template <class Prof> -struct IsArchetype<Archetype<Prof>> : std::true_type {}; - -// A constructor tag type used when creating an Archetype with internal state. -struct MakeArchetypeState {}; - -// Data stored within an archetype that is copied/compared/hashed when the -// corresponding operations are used. -using ArchetypeState = std::size_t; - -//////////////////////////////////////////////////////////////////////////////// -// This section of the file defines a chain of base classes for Archetype, // -// where each base defines a specific special member function with the // -// appropriate properties (deleted, noexcept(false), noexcept, or trivial). // -//////////////////////////////////////////////////////////////////////////////// - -// The bottom-most base, which contains the state and the default constructor. -template <default_constructible DefaultConstructibleValue> -struct ArchetypeStateBase { - static_assert(DefaultConstructibleValue == default_constructible::yes || - DefaultConstructibleValue == default_constructible::nothrow, - ""); - - ArchetypeStateBase() noexcept( - DefaultConstructibleValue == - default_constructible:: - nothrow) /*Vacuous archetype_state initialization*/ {} - explicit ArchetypeStateBase(MakeArchetypeState, ArchetypeState state) noexcept - : archetype_state(state) {} - - ArchetypeState archetype_state; -}; - -template <> -struct ArchetypeStateBase<default_constructible::maybe> { - explicit ArchetypeStateBase() = delete; - explicit ArchetypeStateBase(MakeArchetypeState, ArchetypeState state) noexcept - : archetype_state(state) {} - - ArchetypeState archetype_state; -}; - -template <> -struct ArchetypeStateBase<default_constructible::trivial> { - ArchetypeStateBase() = default; - explicit ArchetypeStateBase(MakeArchetypeState, ArchetypeState state) noexcept - : archetype_state(state) {} - - ArchetypeState archetype_state; -}; - -// The move-constructor base -template <default_constructible DefaultConstructibleValue, - move_constructible MoveConstructibleValue> -struct ArchetypeMoveConstructor - : ArchetypeStateBase<DefaultConstructibleValue> { - static_assert(MoveConstructibleValue == move_constructible::yes || - MoveConstructibleValue == move_constructible::nothrow, - ""); - - explicit ArchetypeMoveConstructor(MakeArchetypeState, - ArchetypeState state) noexcept - : ArchetypeStateBase<DefaultConstructibleValue>(MakeArchetypeState(), - state) {} - - ArchetypeMoveConstructor() = default; - ArchetypeMoveConstructor(ArchetypeMoveConstructor&& other) noexcept( - MoveConstructibleValue == move_constructible::nothrow) - : ArchetypeStateBase<DefaultConstructibleValue>(MakeArchetypeState(), - other.archetype_state) {} - ArchetypeMoveConstructor(const ArchetypeMoveConstructor&) = default; - ArchetypeMoveConstructor& operator=(ArchetypeMoveConstructor&&) = default; - ArchetypeMoveConstructor& operator=(const ArchetypeMoveConstructor&) = - default; -}; - -template <default_constructible DefaultConstructibleValue> -struct ArchetypeMoveConstructor<DefaultConstructibleValue, - move_constructible::trivial> - : ArchetypeStateBase<DefaultConstructibleValue> { - explicit ArchetypeMoveConstructor(MakeArchetypeState, - ArchetypeState state) noexcept - : ArchetypeStateBase<DefaultConstructibleValue>(MakeArchetypeState(), - state) {} - - ArchetypeMoveConstructor() = default; -}; - -// The copy-constructor base -template <default_constructible DefaultConstructibleValue, - move_constructible MoveConstructibleValue, - copy_constructible CopyConstructibleValue> -struct ArchetypeCopyConstructor - : ArchetypeMoveConstructor<DefaultConstructibleValue, - MoveConstructibleValue> { - static_assert(CopyConstructibleValue == copy_constructible::yes || - CopyConstructibleValue == copy_constructible::nothrow, - ""); - explicit ArchetypeCopyConstructor(MakeArchetypeState, - ArchetypeState state) noexcept - : ArchetypeMoveConstructor<DefaultConstructibleValue, - MoveConstructibleValue>(MakeArchetypeState(), - state) {} - - ArchetypeCopyConstructor() = default; - ArchetypeCopyConstructor(ArchetypeCopyConstructor&&) = default; - ArchetypeCopyConstructor(const ArchetypeCopyConstructor& other) noexcept( - CopyConstructibleValue == copy_constructible::nothrow) - : ArchetypeMoveConstructor<DefaultConstructibleValue, - MoveConstructibleValue>( - MakeArchetypeState(), other.archetype_state) {} - ArchetypeCopyConstructor& operator=(ArchetypeCopyConstructor&&) = default; - ArchetypeCopyConstructor& operator=(const ArchetypeCopyConstructor&) = - default; -}; - -template <default_constructible DefaultConstructibleValue, - move_constructible MoveConstructibleValue> -struct ArchetypeCopyConstructor<DefaultConstructibleValue, - MoveConstructibleValue, - copy_constructible::maybe> - : ArchetypeMoveConstructor<DefaultConstructibleValue, - MoveConstructibleValue> { - explicit ArchetypeCopyConstructor(MakeArchetypeState, - ArchetypeState state) noexcept - : ArchetypeMoveConstructor<DefaultConstructibleValue, - MoveConstructibleValue>(MakeArchetypeState(), - state) {} - - ArchetypeCopyConstructor() = default; - ArchetypeCopyConstructor(ArchetypeCopyConstructor&&) = default; - ArchetypeCopyConstructor(const ArchetypeCopyConstructor&) = delete; - ArchetypeCopyConstructor& operator=(ArchetypeCopyConstructor&&) = default; - ArchetypeCopyConstructor& operator=(const ArchetypeCopyConstructor&) = - default; -}; - -template <default_constructible DefaultConstructibleValue, - move_constructible MoveConstructibleValue> -struct ArchetypeCopyConstructor<DefaultConstructibleValue, - MoveConstructibleValue, - copy_constructible::trivial> - : ArchetypeMoveConstructor<DefaultConstructibleValue, - MoveConstructibleValue> { - explicit ArchetypeCopyConstructor(MakeArchetypeState, - ArchetypeState state) noexcept - : ArchetypeMoveConstructor<DefaultConstructibleValue, - MoveConstructibleValue>(MakeArchetypeState(), - state) {} - - ArchetypeCopyConstructor() = default; -}; - -// The move-assign base -template <default_constructible DefaultConstructibleValue, - move_constructible MoveConstructibleValue, - copy_constructible CopyConstructibleValue, - move_assignable MoveAssignableValue> -struct ArchetypeMoveAssign - : ArchetypeCopyConstructor<DefaultConstructibleValue, - MoveConstructibleValue, CopyConstructibleValue> { - static_assert(MoveAssignableValue == move_assignable::yes || - MoveAssignableValue == move_assignable::nothrow, - ""); - explicit ArchetypeMoveAssign(MakeArchetypeState, - ArchetypeState state) noexcept - : ArchetypeCopyConstructor<DefaultConstructibleValue, - MoveConstructibleValue, - CopyConstructibleValue>(MakeArchetypeState(), - state) {} - - ArchetypeMoveAssign() = default; - ArchetypeMoveAssign(ArchetypeMoveAssign&&) = default; - ArchetypeMoveAssign(const ArchetypeMoveAssign&) = default; - ArchetypeMoveAssign& operator=(ArchetypeMoveAssign&& other) noexcept( - MoveAssignableValue == move_assignable::nothrow) { - this->archetype_state = other.archetype_state; - return *this; - } - - ArchetypeMoveAssign& operator=(const ArchetypeMoveAssign&) = default; -}; - -template <default_constructible DefaultConstructibleValue, - move_constructible MoveConstructibleValue, - copy_constructible CopyConstructibleValue> -struct ArchetypeMoveAssign<DefaultConstructibleValue, MoveConstructibleValue, - CopyConstructibleValue, move_assignable::trivial> - : ArchetypeCopyConstructor<DefaultConstructibleValue, - MoveConstructibleValue, CopyConstructibleValue> { - explicit ArchetypeMoveAssign(MakeArchetypeState, - ArchetypeState state) noexcept - : ArchetypeCopyConstructor<DefaultConstructibleValue, - MoveConstructibleValue, - CopyConstructibleValue>(MakeArchetypeState(), - state) {} - - ArchetypeMoveAssign() = default; -}; - -// The copy-assign base -template <default_constructible DefaultConstructibleValue, - move_constructible MoveConstructibleValue, - copy_constructible CopyConstructibleValue, - move_assignable MoveAssignableValue, - copy_assignable CopyAssignableValue> -struct ArchetypeCopyAssign - : ArchetypeMoveAssign<DefaultConstructibleValue, MoveConstructibleValue, - CopyConstructibleValue, MoveAssignableValue> { - static_assert(CopyAssignableValue == copy_assignable::yes || - CopyAssignableValue == copy_assignable::nothrow, - ""); - explicit ArchetypeCopyAssign(MakeArchetypeState, - ArchetypeState state) noexcept - : ArchetypeMoveAssign<DefaultConstructibleValue, MoveConstructibleValue, - CopyConstructibleValue, MoveAssignableValue>( - MakeArchetypeState(), state) {} - - ArchetypeCopyAssign() = default; - ArchetypeCopyAssign(ArchetypeCopyAssign&&) = default; - ArchetypeCopyAssign(const ArchetypeCopyAssign&) = default; - ArchetypeCopyAssign& operator=(ArchetypeCopyAssign&&) = default; - - ArchetypeCopyAssign& operator=(const ArchetypeCopyAssign& other) noexcept( - CopyAssignableValue == copy_assignable::nothrow) { - this->archetype_state = other.archetype_state; - return *this; - } -}; - -template <default_constructible DefaultConstructibleValue, - move_constructible MoveConstructibleValue, - copy_constructible CopyConstructibleValue, - move_assignable MoveAssignableValue> -struct ArchetypeCopyAssign<DefaultConstructibleValue, MoveConstructibleValue, - CopyConstructibleValue, MoveAssignableValue, - copy_assignable::maybe> - : ArchetypeMoveAssign<DefaultConstructibleValue, MoveConstructibleValue, - CopyConstructibleValue, MoveAssignableValue> { - explicit ArchetypeCopyAssign(MakeArchetypeState, - ArchetypeState state) noexcept - : ArchetypeMoveAssign<DefaultConstructibleValue, MoveConstructibleValue, - CopyConstructibleValue, MoveAssignableValue>( - MakeArchetypeState(), state) {} - - ArchetypeCopyAssign() = default; - ArchetypeCopyAssign(ArchetypeCopyAssign&&) = default; - ArchetypeCopyAssign(const ArchetypeCopyAssign&) = default; - ArchetypeCopyAssign& operator=(ArchetypeCopyAssign&&) = default; - ArchetypeCopyAssign& operator=(const ArchetypeCopyAssign&) = delete; -}; - -template <default_constructible DefaultConstructibleValue, - move_constructible MoveConstructibleValue, - copy_constructible CopyConstructibleValue, - move_assignable MoveAssignableValue> -struct ArchetypeCopyAssign<DefaultConstructibleValue, MoveConstructibleValue, - CopyConstructibleValue, MoveAssignableValue, - copy_assignable::trivial> - : ArchetypeMoveAssign<DefaultConstructibleValue, MoveConstructibleValue, - CopyConstructibleValue, MoveAssignableValue> { - explicit ArchetypeCopyAssign(MakeArchetypeState, - ArchetypeState state) noexcept - : ArchetypeMoveAssign<DefaultConstructibleValue, MoveConstructibleValue, - CopyConstructibleValue, MoveAssignableValue>( - MakeArchetypeState(), state) {} - - ArchetypeCopyAssign() = default; -}; - -// The destructor base -template <default_constructible DefaultConstructibleValue, - move_constructible MoveConstructibleValue, - copy_constructible CopyConstructibleValue, - move_assignable MoveAssignableValue, - copy_assignable CopyAssignableValue, destructible DestructibleValue> -struct ArchetypeDestructor - : ArchetypeCopyAssign<DefaultConstructibleValue, MoveConstructibleValue, - CopyConstructibleValue, MoveAssignableValue, - CopyAssignableValue> { - static_assert(DestructibleValue == destructible::yes || - DestructibleValue == destructible::nothrow, - ""); - - explicit ArchetypeDestructor(MakeArchetypeState, - ArchetypeState state) noexcept - : ArchetypeCopyAssign<DefaultConstructibleValue, MoveConstructibleValue, - CopyConstructibleValue, MoveAssignableValue, - CopyAssignableValue>(MakeArchetypeState(), state) {} - - ArchetypeDestructor() = default; - ArchetypeDestructor(ArchetypeDestructor&&) = default; - ArchetypeDestructor(const ArchetypeDestructor&) = default; - ArchetypeDestructor& operator=(ArchetypeDestructor&&) = default; - ArchetypeDestructor& operator=(const ArchetypeDestructor&) = default; - ~ArchetypeDestructor() noexcept(DestructibleValue == destructible::nothrow) {} -}; - -template <default_constructible DefaultConstructibleValue, - move_constructible MoveConstructibleValue, - copy_constructible CopyConstructibleValue, - move_assignable MoveAssignableValue, - copy_assignable CopyAssignableValue> -struct ArchetypeDestructor<DefaultConstructibleValue, MoveConstructibleValue, - CopyConstructibleValue, MoveAssignableValue, - CopyAssignableValue, destructible::trivial> - : ArchetypeCopyAssign<DefaultConstructibleValue, MoveConstructibleValue, - CopyConstructibleValue, MoveAssignableValue, - CopyAssignableValue> { - explicit ArchetypeDestructor(MakeArchetypeState, - ArchetypeState state) noexcept - : ArchetypeCopyAssign<DefaultConstructibleValue, MoveConstructibleValue, - CopyConstructibleValue, MoveAssignableValue, - CopyAssignableValue>(MakeArchetypeState(), state) {} - - ArchetypeDestructor() = default; -}; - -// An alias to the top of the chain of bases for special-member functions. -// NOTE: move_constructible::maybe, move_assignable::maybe, and -// destructible::maybe are handled in the top-level type by way of SFINAE. -// Because of this, we never instantiate the base classes with -// move_constructible::maybe, move_assignable::maybe, or destructible::maybe so -// that we minimize the number of different possible type-template -// instantiations. -template <default_constructible DefaultConstructibleValue, - move_constructible MoveConstructibleValue, - copy_constructible CopyConstructibleValue, - move_assignable MoveAssignableValue, - copy_assignable CopyAssignableValue, destructible DestructibleValue> -using ArchetypeSpecialMembersBase = ArchetypeDestructor< - DefaultConstructibleValue, - MoveConstructibleValue != move_constructible::maybe - ? MoveConstructibleValue - : move_constructible::nothrow, - CopyConstructibleValue, - MoveAssignableValue != move_assignable::maybe ? MoveAssignableValue - : move_assignable::nothrow, - CopyAssignableValue, - DestructibleValue != destructible::maybe ? DestructibleValue - : destructible::nothrow>; - -// A function that is used to create an archetype with some associated state. -template <class Arch> -Arch MakeArchetype(ArchetypeState state) noexcept { - static_assert(IsArchetype<Arch>::value, - "The explicit template argument to MakeArchetype is required " - "to be an Archetype."); - return Arch(MakeArchetypeState(), state); -} - -// This is used to conditionally delete "copy" and "move" constructors in a way -// that is consistent with what the ConformanceProfile requires and that also -// strictly enforces the arguments to the copy/move to not come from implicit -// conversions when dealing with the Archetype. -template <class Prof, class T> -constexpr bool ShouldDeleteConstructor() { - return !((PropertiesOfT<Prof>::move_constructible_support != - move_constructible::maybe && - std::is_same<T, Archetype<Prof>>::value) || - (PropertiesOfT<Prof>::copy_constructible_support != - copy_constructible::maybe && - (std::is_same<T, const Archetype<Prof>&>::value || - std::is_same<T, Archetype<Prof>&>::value || - std::is_same<T, const Archetype<Prof>>::value))); -} - -// This is used to conditionally delete "copy" and "move" assigns in a way -// that is consistent with what the ConformanceProfile requires and that also -// strictly enforces the arguments to the copy/move to not come from implicit -// conversions when dealing with the Archetype. -template <class Prof, class T> -constexpr bool ShouldDeleteAssign() { - return !( - (PropertiesOfT<Prof>::move_assignable_support != move_assignable::maybe && - std::is_same<T, Archetype<Prof>>::value) || - (PropertiesOfT<Prof>::copy_assignable_support != copy_assignable::maybe && - (std::is_same<T, const Archetype<Prof>&>::value || - std::is_same<T, Archetype<Prof>&>::value || - std::is_same<T, const Archetype<Prof>>::value))); -} - -// TODO(calabrese) Inherit from a chain of secondary bases to pull in the -// associated functions of other concepts. -template <class Prof, class Enabler> -class Archetype : ArchetypeSpecialMembersBase< - PropertiesOfT<Prof>::default_constructible_support, - PropertiesOfT<Prof>::move_constructible_support, - PropertiesOfT<Prof>::copy_constructible_support, - PropertiesOfT<Prof>::move_assignable_support, - PropertiesOfT<Prof>::copy_assignable_support, - PropertiesOfT<Prof>::destructible_support> { - static_assert(std::is_same<Enabler, void>::value, - "An explicit type must not be passed as the second template " - "argument to 'Archetype`."); - - // The cases mentioned in these static_asserts are expected to be handled in - // the partial template specializations of Archetype that follow this - // definition. - static_assert(PropertiesOfT<Prof>::destructible_support != - destructible::maybe, - ""); - static_assert(PropertiesOfT<Prof>::move_constructible_support != - move_constructible::maybe || - PropertiesOfT<Prof>::copy_constructible_support == - copy_constructible::maybe, - ""); - static_assert(PropertiesOfT<Prof>::move_assignable_support != - move_assignable::maybe || - PropertiesOfT<Prof>::copy_assignable_support == - copy_assignable::maybe, - ""); - - public: - Archetype() = default; - - // Disallow moves when requested, and disallow implicit conversions. - template <class T, typename std::enable_if< - ShouldDeleteConstructor<Prof, T>()>::type* = nullptr> - Archetype(T&&) = delete; - - // Disallow moves when requested, and disallow implicit conversions. - template <class T, typename std::enable_if< - ShouldDeleteAssign<Prof, T>()>::type* = nullptr> - Archetype& operator=(T&&) = delete; - - using ArchetypeSpecialMembersBase< - PropertiesOfT<Prof>::default_constructible_support, - PropertiesOfT<Prof>::move_constructible_support, - PropertiesOfT<Prof>::copy_constructible_support, - PropertiesOfT<Prof>::move_assignable_support, - PropertiesOfT<Prof>::copy_assignable_support, - PropertiesOfT<Prof>::destructible_support>::archetype_state; - - private: - explicit Archetype(MakeArchetypeState, ArchetypeState state) noexcept - : ArchetypeSpecialMembersBase< - PropertiesOfT<Prof>::default_constructible_support, - PropertiesOfT<Prof>::move_constructible_support, - PropertiesOfT<Prof>::copy_constructible_support, - PropertiesOfT<Prof>::move_assignable_support, - PropertiesOfT<Prof>::copy_assignable_support, - PropertiesOfT<Prof>::destructible_support>(MakeArchetypeState(), - state) {} - - friend Archetype MakeArchetype<Archetype>(ArchetypeState) noexcept; -}; - -template <class Prof> -class Archetype<Prof, typename std::enable_if< - PropertiesOfT<Prof>::move_constructible_support != - move_constructible::maybe && - PropertiesOfT<Prof>::move_assignable_support == - move_assignable::maybe && - PropertiesOfT<Prof>::destructible_support != - destructible::maybe>::type> - : ArchetypeSpecialMembersBase< - PropertiesOfT<Prof>::default_constructible_support, - PropertiesOfT<Prof>::move_constructible_support, - PropertiesOfT<Prof>::copy_constructible_support, - PropertiesOfT<Prof>::move_assignable_support, - PropertiesOfT<Prof>::copy_assignable_support, - PropertiesOfT<Prof>::destructible_support> { - public: - Archetype() = default; - Archetype(Archetype&&) = default; - Archetype(const Archetype&) = default; - Archetype& operator=(Archetype&&) = delete; - Archetype& operator=(const Archetype&) = default; - - // Disallow moves when requested, and disallow implicit conversions. - template <class T, typename std::enable_if< - ShouldDeleteConstructor<Prof, T>()>::type* = nullptr> - Archetype(T&&) = delete; - - // Disallow moves when requested, and disallow implicit conversions. - template <class T, typename std::enable_if< - ShouldDeleteAssign<Prof, T>()>::type* = nullptr> - Archetype& operator=(T&&) = delete; - - using ArchetypeSpecialMembersBase< - PropertiesOfT<Prof>::default_constructible_support, - PropertiesOfT<Prof>::move_constructible_support, - PropertiesOfT<Prof>::copy_constructible_support, - PropertiesOfT<Prof>::move_assignable_support, - PropertiesOfT<Prof>::copy_assignable_support, - PropertiesOfT<Prof>::destructible_support>::archetype_state; - - private: - explicit Archetype(MakeArchetypeState, ArchetypeState state) noexcept - : ArchetypeSpecialMembersBase< - PropertiesOfT<Prof>::default_constructible_support, - PropertiesOfT<Prof>::move_constructible_support, - PropertiesOfT<Prof>::copy_constructible_support, - PropertiesOfT<Prof>::move_assignable_support, - PropertiesOfT<Prof>::copy_assignable_support, - PropertiesOfT<Prof>::destructible_support>(MakeArchetypeState(), - state) {} - - friend Archetype MakeArchetype<Archetype>(ArchetypeState) noexcept; -}; - -template <class Prof> -class Archetype<Prof, typename std::enable_if< - PropertiesOfT<Prof>::move_constructible_support == - move_constructible::maybe && - PropertiesOfT<Prof>::move_assignable_support == - move_assignable::maybe && - PropertiesOfT<Prof>::destructible_support != - destructible::maybe>::type> - : ArchetypeSpecialMembersBase< - PropertiesOfT<Prof>::default_constructible_support, - PropertiesOfT<Prof>::move_constructible_support, - PropertiesOfT<Prof>::copy_constructible_support, - PropertiesOfT<Prof>::move_assignable_support, - PropertiesOfT<Prof>::copy_assignable_support, - PropertiesOfT<Prof>::destructible_support> { - public: - Archetype() = default; - Archetype(Archetype&&) = delete; - Archetype(const Archetype&) = default; - Archetype& operator=(Archetype&&) = delete; - Archetype& operator=(const Archetype&) = default; - - // Disallow moves when requested, and disallow implicit conversions. - template <class T, typename std::enable_if< - ShouldDeleteConstructor<Prof, T>()>::type* = nullptr> - Archetype(T&&) = delete; - - // Disallow moves when requested, and disallow implicit conversions. - template <class T, typename std::enable_if< - ShouldDeleteAssign<Prof, T>()>::type* = nullptr> - Archetype& operator=(T&&) = delete; - - using ArchetypeSpecialMembersBase< - PropertiesOfT<Prof>::default_constructible_support, - PropertiesOfT<Prof>::move_constructible_support, - PropertiesOfT<Prof>::copy_constructible_support, - PropertiesOfT<Prof>::move_assignable_support, - PropertiesOfT<Prof>::copy_assignable_support, - PropertiesOfT<Prof>::destructible_support>::archetype_state; - - private: - explicit Archetype(MakeArchetypeState, ArchetypeState state) noexcept - : ArchetypeSpecialMembersBase< - PropertiesOfT<Prof>::default_constructible_support, - PropertiesOfT<Prof>::move_constructible_support, - PropertiesOfT<Prof>::copy_constructible_support, - PropertiesOfT<Prof>::move_assignable_support, - PropertiesOfT<Prof>::copy_assignable_support, - PropertiesOfT<Prof>::destructible_support>(MakeArchetypeState(), - state) {} - - friend Archetype MakeArchetype<Archetype>(ArchetypeState) noexcept; -}; - -template <class Prof> -class Archetype<Prof, typename std::enable_if< - PropertiesOfT<Prof>::move_constructible_support == - move_constructible::maybe && - PropertiesOfT<Prof>::move_assignable_support != - move_assignable::maybe && - PropertiesOfT<Prof>::destructible_support != - destructible::maybe>::type> - : ArchetypeSpecialMembersBase< - PropertiesOfT<Prof>::default_constructible_support, - PropertiesOfT<Prof>::move_constructible_support, - PropertiesOfT<Prof>::copy_constructible_support, - PropertiesOfT<Prof>::move_assignable_support, - PropertiesOfT<Prof>::copy_assignable_support, - PropertiesOfT<Prof>::destructible_support> { - public: - Archetype() = default; - Archetype(Archetype&&) = delete; - Archetype(const Archetype&) = default; - Archetype& operator=(Archetype&&) = default; - Archetype& operator=(const Archetype&) = default; - - // Disallow moves when requested, and disallow implicit conversions. - template <class T, typename std::enable_if< - ShouldDeleteConstructor<Prof, T>()>::type* = nullptr> - Archetype(T&&) = delete; - - // Disallow moves when requested, and disallow implicit conversions. - template <class T, typename std::enable_if< - ShouldDeleteAssign<Prof, T>()>::type* = nullptr> - Archetype& operator=(T&&) = delete; - - using ArchetypeSpecialMembersBase< - PropertiesOfT<Prof>::default_constructible_support, - PropertiesOfT<Prof>::move_constructible_support, - PropertiesOfT<Prof>::copy_constructible_support, - PropertiesOfT<Prof>::move_assignable_support, - PropertiesOfT<Prof>::copy_assignable_support, - PropertiesOfT<Prof>::destructible_support>::archetype_state; - - private: - explicit Archetype(MakeArchetypeState, ArchetypeState state) noexcept - : ArchetypeSpecialMembersBase< - PropertiesOfT<Prof>::default_constructible_support, - PropertiesOfT<Prof>::move_constructible_support, - PropertiesOfT<Prof>::copy_constructible_support, - PropertiesOfT<Prof>::move_assignable_support, - PropertiesOfT<Prof>::copy_assignable_support, - PropertiesOfT<Prof>::destructible_support>(MakeArchetypeState(), - state) {} - - friend Archetype MakeArchetype<Archetype>(ArchetypeState) noexcept; -}; - -template <class Prof> -class Archetype<Prof, typename std::enable_if< - PropertiesOfT<Prof>::move_constructible_support != - move_constructible::maybe && - PropertiesOfT<Prof>::move_assignable_support == - move_assignable::maybe && - PropertiesOfT<Prof>::destructible_support == - destructible::maybe>::type> - : ArchetypeSpecialMembersBase< - PropertiesOfT<Prof>::default_constructible_support, - PropertiesOfT<Prof>::move_constructible_support, - PropertiesOfT<Prof>::copy_constructible_support, - PropertiesOfT<Prof>::move_assignable_support, - PropertiesOfT<Prof>::copy_assignable_support, - PropertiesOfT<Prof>::destructible_support> { - public: - Archetype() = default; - Archetype(Archetype&&) = default; - Archetype(const Archetype&) = default; - Archetype& operator=(Archetype&&) = delete; - Archetype& operator=(const Archetype&) = default; - ~Archetype() = delete; - - // Disallow moves when requested, and disallow implicit conversions. - template <class T, typename std::enable_if< - ShouldDeleteConstructor<Prof, T>()>::type* = nullptr> - Archetype(T&&) = delete; - - // Disallow moves when requested, and disallow implicit conversions. - template <class T, typename std::enable_if< - ShouldDeleteAssign<Prof, T>()>::type* = nullptr> - Archetype& operator=(T&&) = delete; - - using ArchetypeSpecialMembersBase< - PropertiesOfT<Prof>::default_constructible_support, - PropertiesOfT<Prof>::move_constructible_support, - PropertiesOfT<Prof>::copy_constructible_support, - PropertiesOfT<Prof>::move_assignable_support, - PropertiesOfT<Prof>::copy_assignable_support, - PropertiesOfT<Prof>::destructible_support>::archetype_state; - - private: - explicit Archetype(MakeArchetypeState, ArchetypeState state) noexcept - : ArchetypeSpecialMembersBase< - PropertiesOfT<Prof>::default_constructible_support, - PropertiesOfT<Prof>::move_constructible_support, - PropertiesOfT<Prof>::copy_constructible_support, - PropertiesOfT<Prof>::move_assignable_support, - PropertiesOfT<Prof>::copy_assignable_support, - PropertiesOfT<Prof>::destructible_support>(MakeArchetypeState(), - state) {} - - friend Archetype MakeArchetype<Archetype>(ArchetypeState) noexcept; -}; - -template <class Prof> -class Archetype<Prof, typename std::enable_if< - PropertiesOfT<Prof>::move_constructible_support == - move_constructible::maybe && - PropertiesOfT<Prof>::move_assignable_support == - move_assignable::maybe && - PropertiesOfT<Prof>::destructible_support == - destructible::maybe>::type> - : ArchetypeSpecialMembersBase< - PropertiesOfT<Prof>::default_constructible_support, - PropertiesOfT<Prof>::move_constructible_support, - PropertiesOfT<Prof>::copy_constructible_support, - PropertiesOfT<Prof>::move_assignable_support, - PropertiesOfT<Prof>::copy_assignable_support, - PropertiesOfT<Prof>::destructible_support> { - public: - Archetype() = default; - Archetype(Archetype&&) = delete; - Archetype(const Archetype&) = default; - Archetype& operator=(Archetype&&) = delete; - Archetype& operator=(const Archetype&) = default; - ~Archetype() = delete; - - // Disallow moves when requested, and disallow implicit conversions. - template <class T, typename std::enable_if< - ShouldDeleteConstructor<Prof, T>()>::type* = nullptr> - Archetype(T&&) = delete; - - // Disallow moves when requested, and disallow implicit conversions. - template <class T, typename std::enable_if< - ShouldDeleteAssign<Prof, T>()>::type* = nullptr> - Archetype& operator=(T&&) = delete; - - using ArchetypeSpecialMembersBase< - PropertiesOfT<Prof>::default_constructible_support, - PropertiesOfT<Prof>::move_constructible_support, - PropertiesOfT<Prof>::copy_constructible_support, - PropertiesOfT<Prof>::move_assignable_support, - PropertiesOfT<Prof>::copy_assignable_support, - PropertiesOfT<Prof>::destructible_support>::archetype_state; - - private: - explicit Archetype(MakeArchetypeState, ArchetypeState state) noexcept - : ArchetypeSpecialMembersBase< - PropertiesOfT<Prof>::default_constructible_support, - PropertiesOfT<Prof>::move_constructible_support, - PropertiesOfT<Prof>::copy_constructible_support, - PropertiesOfT<Prof>::move_assignable_support, - PropertiesOfT<Prof>::copy_assignable_support, - PropertiesOfT<Prof>::destructible_support>(MakeArchetypeState(), - state) {} - - friend Archetype MakeArchetype<Archetype>(ArchetypeState) noexcept; -}; - -template <class Prof> -class Archetype<Prof, typename std::enable_if< - PropertiesOfT<Prof>::move_constructible_support == - move_constructible::maybe && - PropertiesOfT<Prof>::move_assignable_support != - move_assignable::maybe && - PropertiesOfT<Prof>::destructible_support == - destructible::maybe>::type> - : ArchetypeSpecialMembersBase< - PropertiesOfT<Prof>::default_constructible_support, - PropertiesOfT<Prof>::move_constructible_support, - PropertiesOfT<Prof>::copy_constructible_support, - PropertiesOfT<Prof>::move_assignable_support, - PropertiesOfT<Prof>::copy_assignable_support, - PropertiesOfT<Prof>::destructible_support> { - public: - Archetype() = default; - Archetype(Archetype&&) = delete; - Archetype(const Archetype&) = default; - Archetype& operator=(Archetype&&) = default; - Archetype& operator=(const Archetype&) = default; - ~Archetype() = delete; - - // Disallow moves when requested, and disallow implicit conversions. - template <class T, typename std::enable_if< - ShouldDeleteConstructor<Prof, T>()>::type* = nullptr> - Archetype(T&&) = delete; - - // Disallow moves when requested, and disallow implicit conversions. - template <class T, typename std::enable_if< - ShouldDeleteAssign<Prof, T>()>::type* = nullptr> - Archetype& operator=(T&&) = delete; - - using ArchetypeSpecialMembersBase< - PropertiesOfT<Prof>::default_constructible_support, - PropertiesOfT<Prof>::move_constructible_support, - PropertiesOfT<Prof>::copy_constructible_support, - PropertiesOfT<Prof>::move_assignable_support, - PropertiesOfT<Prof>::copy_assignable_support, - PropertiesOfT<Prof>::destructible_support>::archetype_state; - - private: - explicit Archetype(MakeArchetypeState, ArchetypeState state) noexcept - : ArchetypeSpecialMembersBase< - PropertiesOfT<Prof>::default_constructible_support, - PropertiesOfT<Prof>::move_constructible_support, - PropertiesOfT<Prof>::copy_constructible_support, - PropertiesOfT<Prof>::move_assignable_support, - PropertiesOfT<Prof>::copy_assignable_support, - PropertiesOfT<Prof>::destructible_support>(MakeArchetypeState(), - state) {} - - friend Archetype MakeArchetype<Archetype>(ArchetypeState) noexcept; -}; - -// Explicitly deleted swap for Archetype if the profile does not require swap. -// It is important to delete it rather than simply leave it out so that the -// "using std::swap;" idiom will result in this deleted overload being picked. -template <class Prof, - absl::enable_if_t<!PropertiesOfT<Prof>::is_swappable, int> = 0> -void swap(Archetype<Prof>&, Archetype<Prof>&) = delete; // NOLINT - -// A conditionally-noexcept swap implementation for Archetype when the profile -// supports swap. -template <class Prof, - absl::enable_if_t<PropertiesOfT<Prof>::is_swappable, int> = 0> -void swap(Archetype<Prof>& lhs, Archetype<Prof>& rhs) // NOLINT - noexcept(PropertiesOfT<Prof>::swappable_support != swappable::yes) { - std::swap(lhs.archetype_state, rhs.archetype_state); -} - -// A convertible-to-bool type that is used as the return type of comparison -// operators since the standard doesn't always require exactly bool. -struct NothrowBool { - explicit NothrowBool() = delete; - ~NothrowBool() = default; - - // TODO(calabrese) Delete the copy constructor in C++17 mode since guaranteed - // elision makes it not required when returning from a function. - // NothrowBool(NothrowBool const&) = delete; - - NothrowBool& operator=(NothrowBool const&) = delete; - - explicit operator bool() const noexcept { return value; } - - static NothrowBool make(bool const value) noexcept { - return NothrowBool(value); - } - - private: - explicit NothrowBool(bool const value) noexcept : value(value) {} - - bool value; -}; - -// A convertible-to-bool type that is used as the return type of comparison -// operators since the standard doesn't always require exactly bool. -// Note: ExceptionalBool has a conversion operator that is not noexcept, so -// that even when a comparison operator is noexcept, that operation may still -// potentially throw when converted to bool. -struct ExceptionalBool { - explicit ExceptionalBool() = delete; - ~ExceptionalBool() = default; - - // TODO(calabrese) Delete the copy constructor in C++17 mode since guaranteed - // elision makes it not required when returning from a function. - // ExceptionalBool(ExceptionalBool const&) = delete; - - ExceptionalBool& operator=(ExceptionalBool const&) = delete; - - explicit operator bool() const { return value; } // NOLINT - - static ExceptionalBool make(bool const value) noexcept { - return ExceptionalBool(value); - } - - private: - explicit ExceptionalBool(bool const value) noexcept : value(value) {} - - bool value; -}; - -// The following macro is only used as a helper in this file to stamp out -// comparison operator definitions. It is undefined after usage. -// -// NOTE: Non-nothrow operators throw via their result's conversion to bool even -// though the operation itself is noexcept. -#define ABSL_TYPES_INTERNAL_OP(enum_name, op) \ - template <class Prof> \ - absl::enable_if_t<!PropertiesOfT<Prof>::is_##enum_name, bool> operator op( \ - const Archetype<Prof>&, const Archetype<Prof>&) = delete; \ - \ - template <class Prof> \ - typename absl::enable_if_t< \ - PropertiesOfT<Prof>::is_##enum_name, \ - std::conditional<PropertiesOfT<Prof>::enum_name##_support == \ - enum_name::nothrow, \ - NothrowBool, ExceptionalBool>>::type \ - operator op(const Archetype<Prof>& lhs, \ - const Archetype<Prof>& rhs) noexcept { \ - return absl::conditional_t< \ - PropertiesOfT<Prof>::enum_name##_support == enum_name::nothrow, \ - NothrowBool, ExceptionalBool>::make(lhs.archetype_state op \ - rhs.archetype_state); \ - } - -ABSL_TYPES_INTERNAL_OP(equality_comparable, ==); -ABSL_TYPES_INTERNAL_OP(inequality_comparable, !=); -ABSL_TYPES_INTERNAL_OP(less_than_comparable, <); -ABSL_TYPES_INTERNAL_OP(less_equal_comparable, <=); -ABSL_TYPES_INTERNAL_OP(greater_equal_comparable, >=); -ABSL_TYPES_INTERNAL_OP(greater_than_comparable, >); - -#undef ABSL_TYPES_INTERNAL_OP - -// Base class for std::hash specializations when an Archetype doesn't support -// hashing. -struct PoisonedHash { - PoisonedHash() = delete; - PoisonedHash(const PoisonedHash&) = delete; - PoisonedHash& operator=(const PoisonedHash&) = delete; -}; - -// Base class for std::hash specializations when an Archetype supports hashing. -template <class Prof> -struct EnabledHash { - using argument_type = Archetype<Prof>; - using result_type = std::size_t; - result_type operator()(const argument_type& arg) const { - return std::hash<ArchetypeState>()(arg.archetype_state); - } -}; - -} // namespace types_internal -ABSL_NAMESPACE_END -} // namespace absl - -namespace std { - -template <class Prof> // NOLINT -struct hash<::absl::types_internal::Archetype<Prof>> - : conditional<::absl::types_internal::PropertiesOfT<Prof>::is_hashable, - ::absl::types_internal::EnabledHash<Prof>, - ::absl::types_internal::PoisonedHash>::type {}; - -} // namespace std - -#endif // ABSL_TYPES_INTERNAL_CONFORMANCE_ARCHETYPE_H_ diff --git a/absl/types/internal/conformance_profile.h b/absl/types/internal/conformance_profile.h deleted file mode 100644 index 37b017db..00000000 --- a/absl/types/internal/conformance_profile.h +++ /dev/null @@ -1,933 +0,0 @@ -// Copyright 2019 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. -// -// ----------------------------------------------------------------------------- -// conformance_profiles.h -// ----------------------------------------------------------------------------- -// -// This file contains templates for representing "Regularity Profiles" and -// concisely-named versions of commonly used Regularity Profiles. -// -// A Regularity Profile is a compile-time description of the types of operations -// that a given type supports, along with properties of those operations when -// they do exist. For instance, a Regularity Profile may describe a type that -// has a move-constructor that is noexcept and a copy constructor that is not -// noexcept. This description can then be examined and passed around to other -// templates for the purposes of asserting expectations on user-defined types -// via a series trait checks, or for determining what kinds of run-time tests -// are able to be performed. -// -// Regularity Profiles are also used when creating "archetypes," which are -// minimum-conforming types that meet all of the requirements of a given -// Regularity Profile. For more information regarding archetypes, see -// "conformance_archetypes.h". - -#ifndef ABSL_TYPES_INTERNAL_CONFORMANCE_PROFILE_H_ -#define ABSL_TYPES_INTERNAL_CONFORMANCE_PROFILE_H_ - -#include <set> -#include <type_traits> -#include <utility> -#include <vector> - -#include "gtest/gtest.h" -#include "absl/algorithm/container.h" -#include "absl/meta/type_traits.h" -#include "absl/strings/ascii.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/string_view.h" -#include "absl/types/internal/conformance_testing_helpers.h" -#include "absl/utility/utility.h" - -// TODO(calabrese) Add support for extending profiles. - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace types_internal { - -// Converts an enum to its underlying integral value. -template <typename Enum> -constexpr absl::underlying_type_t<Enum> UnderlyingValue(Enum value) { - return static_cast<absl::underlying_type_t<Enum>>(value); -} - -// A tag type used in place of a matcher when checking that an assertion result -// does not actually contain any errors. -struct NoError {}; - -// ----------------------------------------------------------------------------- -// ConformanceErrors -// ----------------------------------------------------------------------------- -class ConformanceErrors { - public: - // Setup the error reporting mechanism by seeding it with the name of the type - // that is being tested. - explicit ConformanceErrors(std::string type_name) - : assertion_result_(false), type_name_(std::move(type_name)) { - assertion_result_ << "\n\n" - "Assuming the following type alias:\n" - "\n" - " using _T = " - << type_name_ << ";\n\n"; - outputDivider(); - } - - // Adds the test name to the list of successfully run tests iff it was not - // previously reported as failing. This behavior is useful for tests that - // have multiple parts, where failures and successes are reported individually - // with the same test name. - void addTestSuccess(absl::string_view test_name) { - auto normalized_test_name = absl::AsciiStrToLower(test_name); - - // If the test is already reported as failing, do not add it to the list of - // successes. - if (test_failures_.find(normalized_test_name) == test_failures_.end()) { - test_successes_.insert(std::move(normalized_test_name)); - } - } - - // Streams a single error description into the internal buffer (a visual - // divider is automatically inserted after the error so that multiple errors - // are visibly distinct). - // - // This function increases the error count by 1. - // - // TODO(calabrese) Determine desired behavior when if this function throws. - template <class... P> - void addTestFailure(absl::string_view test_name, const P&... args) { - // Output a message related to the test failure. - assertion_result_ << "\n\n" - "Failed test: " - << test_name << "\n\n"; - addTestFailureImpl(args...); - assertion_result_ << "\n\n"; - outputDivider(); - - auto normalized_test_name = absl::AsciiStrToLower(test_name); - - // If previous parts of this test succeeded, remove it from that set. - test_successes_.erase(normalized_test_name); - - // Add the test name to the list of failed tests. - test_failures_.insert(std::move(normalized_test_name)); - - has_error_ = true; - } - - // Convert this object into a testing::AssertionResult instance such that it - // can be used with gtest. - ::testing::AssertionResult assertionResult() const { - return has_error_ ? assertion_result_ : ::testing::AssertionSuccess(); - } - - // Convert this object into a testing::AssertionResult instance such that it - // can be used with gtest. This overload expects errors, using the specified - // matcher. - ::testing::AssertionResult expectFailedTests( - const std::set<std::string>& test_names) const { - // Since we are expecting nonconformance, output an error message when the - // type actually conformed to the specified profile. - if (!has_error_) { - return ::testing::AssertionFailure() - << "Unexpected conformance of type:\n" - " " - << type_name_ << "\n\n"; - } - - // Get a list of all expected failures that did not actually fail - // (or that were not run). - std::vector<std::string> nonfailing_tests; - absl::c_set_difference(test_names, test_failures_, - std::back_inserter(nonfailing_tests)); - - // Get a list of all "expected failures" that were never actually run. - std::vector<std::string> unrun_tests; - absl::c_set_difference(nonfailing_tests, test_successes_, - std::back_inserter(unrun_tests)); - - // Report when the user specified tests that were not run. - if (!unrun_tests.empty()) { - const bool tests_were_run = - !(test_failures_.empty() && test_successes_.empty()); - - // Prepare an assertion result used in the case that tests pass that were - // expected to fail. - ::testing::AssertionResult result = ::testing::AssertionFailure(); - result << "When testing type:\n " << type_name_ - << "\n\nThe following tests were expected to fail but were not " - "run"; - - if (tests_were_run) result << " (was the test name spelled correctly?)"; - - result << ":\n\n"; - - // List all of the tests that unexpectedly passed. - for (const auto& test_name : unrun_tests) { - result << " " << test_name << "\n"; - } - - if (!tests_were_run) result << "\nNo tests were run."; - - if (!test_failures_.empty()) { - // List test failures - result << "\nThe tests that were run and failed are:\n\n"; - for (const auto& test_name : test_failures_) { - result << " " << test_name << "\n"; - } - } - - if (!test_successes_.empty()) { - // List test successes - result << "\nThe tests that were run and succeeded are:\n\n"; - for (const auto& test_name : test_successes_) { - result << " " << test_name << "\n"; - } - } - - return result; - } - - // If some tests passed when they were expected to fail, alert the caller. - if (nonfailing_tests.empty()) return ::testing::AssertionSuccess(); - - // Prepare an assertion result used in the case that tests pass that were - // expected to fail. - ::testing::AssertionResult unexpected_successes = - ::testing::AssertionFailure(); - unexpected_successes << "When testing type:\n " << type_name_ - << "\n\nThe following tests passed when they were " - "expected to fail:\n\n"; - - // List all of the tests that unexpectedly passed. - for (const auto& test_name : nonfailing_tests) { - unexpected_successes << " " << test_name << "\n"; - } - - return unexpected_successes; - } - - private: - void outputDivider() { - assertion_result_ << "========================================"; - } - - void addTestFailureImpl() {} - - template <class H, class... T> - void addTestFailureImpl(const H& head, const T&... tail) { - assertion_result_ << head; - addTestFailureImpl(tail...); - } - - ::testing::AssertionResult assertion_result_; - std::set<std::string> test_failures_; - std::set<std::string> test_successes_; - std::string type_name_; - bool has_error_ = false; -}; - -template <class T, class /*Enabler*/ = void> -struct PropertiesOfImpl {}; - -template <class T> -struct PropertiesOfImpl<T, absl::void_t<typename T::properties>> { - using type = typename T::properties; -}; - -template <class T> -struct PropertiesOfImpl<T, absl::void_t<typename T::profile_alias_of>> { - using type = typename PropertiesOfImpl<typename T::profile_alias_of>::type; -}; - -template <class T> -struct PropertiesOf : PropertiesOfImpl<T> {}; - -template <class T> -using PropertiesOfT = typename PropertiesOf<T>::type; - -// NOTE: These enums use this naming convention to be consistent with the -// standard trait names, which is useful since it allows us to match up each -// enum name with a corresponding trait name in macro definitions. - -// An enum that describes the various expectations on an operations existence. -enum class function_support { maybe, yes, nothrow, trivial }; - -constexpr const char* PessimisticPropertyDescription(function_support v) { - return v == function_support::maybe - ? "no" - : v == function_support::yes - ? "yes, potentially throwing" - : v == function_support::nothrow ? "yes, nothrow" - : "yes, trivial"; -} - -// Return a string that describes the kind of property support that was -// expected. -inline std::string ExpectedFunctionKindList(function_support min, - function_support max) { - if (min == max) { - std::string result = - absl::StrCat("Expected:\n ", - PessimisticPropertyDescription( - static_cast<function_support>(UnderlyingValue(min))), - "\n"); - return result; - } - - std::string result = "Expected one of:\n"; - for (auto curr_support = UnderlyingValue(min); - curr_support <= UnderlyingValue(max); ++curr_support) { - absl::StrAppend(&result, " ", - PessimisticPropertyDescription( - static_cast<function_support>(curr_support)), - "\n"); - } - - return result; -} - -template <class Enum> -void ExpectModelOfImpl(ConformanceErrors* errors, Enum min_support, - Enum max_support, Enum kind) { - const auto kind_value = UnderlyingValue(kind); - const auto min_support_value = UnderlyingValue(min_support); - const auto max_support_value = UnderlyingValue(max_support); - - if (!(kind_value >= min_support_value && kind_value <= max_support_value)) { - errors->addTestFailure( - PropertyName(kind), "**Failed property expectation**\n\n", - ExpectedFunctionKindList( - static_cast<function_support>(min_support_value), - static_cast<function_support>(max_support_value)), - '\n', "Actual:\n ", - PessimisticPropertyDescription( - static_cast<function_support>(kind_value))); - } else { - errors->addTestSuccess(PropertyName(kind)); - } -} - -#define ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM(description, name) \ - enum class name { maybe, yes, nothrow, trivial }; \ - \ - constexpr const char* PropertyName(name v) { return description; } \ - static_assert(true, "") // Force a semicolon when using this macro. - -ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for default construction", - default_constructible); -ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for move construction", - move_constructible); -ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for copy construction", - copy_constructible); -ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for move assignment", - move_assignable); -ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for copy assignment", - copy_assignable); -ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM("support for destruction", - destructible); - -#undef ABSL_INTERNAL_SPECIAL_MEMBER_FUNCTION_ENUM - -#define ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM(description, name) \ - enum class name { maybe, yes, nothrow }; \ - \ - constexpr const char* PropertyName(name v) { return description; } \ - static_assert(true, "") // Force a semicolon when using this macro. - -ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for ==", equality_comparable); -ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for !=", inequality_comparable); -ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for <", less_than_comparable); -ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for <=", less_equal_comparable); -ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for >=", - greater_equal_comparable); -ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for >", greater_than_comparable); - -ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM("support for swap", swappable); - -#undef ABSL_INTERNAL_INTRINSIC_FUNCTION_ENUM - -enum class hashable { maybe, yes }; - -constexpr const char* PropertyName(hashable v) { - return "support for std::hash"; -} - -template <class T> -using AlwaysFalse = std::false_type; - -#define ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(name, property) \ - template <class T> \ - constexpr property property##_support_of() { \ - return std::is_##property<T>::value \ - ? std::is_nothrow_##property<T>::value \ - ? absl::is_trivially_##property<T>::value \ - ? property::trivial \ - : property::nothrow \ - : property::yes \ - : property::maybe; \ - } \ - \ - template <class T, class MinProf, class MaxProf> \ - void ExpectModelOf##name(ConformanceErrors* errors) { \ - (ExpectModelOfImpl)(errors, PropertiesOfT<MinProf>::property##_support, \ - PropertiesOfT<MaxProf>::property##_support, \ - property##_support_of<T>()); \ - } - -ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(DefaultConstructible, - default_constructible); - -ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(MoveConstructible, - move_constructible); - -ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(CopyConstructible, - copy_constructible); - -ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(MoveAssignable, - move_assignable); - -ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(CopyAssignable, - copy_assignable); - -ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER(Destructible, destructible); - -#undef ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_SPECIAL_MEMBER - -void BoolFunction(bool) noexcept; - -//////////////////////////////////////////////////////////////////////////////// -// -// A metafunction for checking if an operation exists through SFINAE. -// -// `T` is the type to test and Op is an alias containing the expression to test. -template <class T, template <class...> class Op, class = void> -struct IsOpableImpl : std::false_type {}; - -template <class T, template <class...> class Op> -struct IsOpableImpl<T, Op, absl::void_t<Op<T>>> : std::true_type {}; - -template <template <class...> class Op> -struct IsOpable { - template <class T> - using apply = typename IsOpableImpl<T, Op>::type; -}; -// -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// -// A metafunction for checking if an operation exists and is also noexcept -// through SFINAE and the noexcept operator. -/// -// `T` is the type to test and Op is an alias containing the expression to test. -template <class T, template <class...> class Op, class = void> -struct IsNothrowOpableImpl : std::false_type {}; - -template <class T, template <class...> class Op> -struct IsNothrowOpableImpl<T, Op, absl::enable_if_t<Op<T>::value>> - : std::true_type {}; - -template <template <class...> class Op> -struct IsNothrowOpable { - template <class T> - using apply = typename IsNothrowOpableImpl<T, Op>::type; -}; -// -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// -// A macro that produces the necessary function for reporting what kind of -// support a specific comparison operation has and a function for reporting an -// error if a given type's support for that operation does not meet the expected -// requirements. -#define ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON(name, property, op) \ - template <class T, \ - class Result = std::integral_constant< \ - bool, noexcept((BoolFunction)(std::declval<const T&>() op \ - std::declval<const T&>()))>> \ - using name = Result; \ - \ - template <class T> \ - constexpr property property##_support_of() { \ - return IsOpable<name>::apply<T>::value \ - ? IsNothrowOpable<name>::apply<T>::value ? property::nothrow \ - : property::yes \ - : property::maybe; \ - } \ - \ - template <class T, class MinProf, class MaxProf> \ - void ExpectModelOf##name(ConformanceErrors* errors) { \ - (ExpectModelOfImpl)(errors, PropertiesOfT<MinProf>::property##_support, \ - PropertiesOfT<MaxProf>::property##_support, \ - property##_support_of<T>()); \ - } -// -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// -// Generate the necessary support-checking and error reporting functions for -// each of the comparison operators. -ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON(EqualityComparable, - equality_comparable, ==); - -ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON(InequalityComparable, - inequality_comparable, !=); - -ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON(LessThanComparable, - less_than_comparable, <); - -ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON(LessEqualComparable, - less_equal_comparable, <=); - -ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON(GreaterEqualComparable, - greater_equal_comparable, >=); - -ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON(GreaterThanComparable, - greater_than_comparable, >); - -#undef ABSL_INTERNAL_PESSIMISTIC_MODEL_OF_COMPARISON -// -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// -// The necessary support-checking and error-reporting functions for swap. -template <class T> -constexpr swappable swappable_support_of() { - return type_traits_internal::IsSwappable<T>::value - ? type_traits_internal::IsNothrowSwappable<T>::value - ? swappable::nothrow - : swappable::yes - : swappable::maybe; -} - -template <class T, class MinProf, class MaxProf> -void ExpectModelOfSwappable(ConformanceErrors* errors) { - (ExpectModelOfImpl)(errors, PropertiesOfT<MinProf>::swappable_support, - PropertiesOfT<MaxProf>::swappable_support, - swappable_support_of<T>()); -} -// -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// -// The necessary support-checking and error-reporting functions for std::hash. -template <class T> -constexpr hashable hashable_support_of() { - return type_traits_internal::IsHashable<T>::value ? hashable::yes - : hashable::maybe; -} - -template <class T, class MinProf, class MaxProf> -void ExpectModelOfHashable(ConformanceErrors* errors) { - (ExpectModelOfImpl)(errors, PropertiesOfT<MinProf>::hashable_support, - PropertiesOfT<MaxProf>::hashable_support, - hashable_support_of<T>()); -} -// -//////////////////////////////////////////////////////////////////////////////// - -template < - default_constructible DefaultConstructibleValue = - default_constructible::maybe, - move_constructible MoveConstructibleValue = move_constructible::maybe, - copy_constructible CopyConstructibleValue = copy_constructible::maybe, - move_assignable MoveAssignableValue = move_assignable::maybe, - copy_assignable CopyAssignableValue = copy_assignable::maybe, - destructible DestructibleValue = destructible::maybe, - equality_comparable EqualityComparableValue = equality_comparable::maybe, - inequality_comparable InequalityComparableValue = - inequality_comparable::maybe, - less_than_comparable LessThanComparableValue = less_than_comparable::maybe, - less_equal_comparable LessEqualComparableValue = - less_equal_comparable::maybe, - greater_equal_comparable GreaterEqualComparableValue = - greater_equal_comparable::maybe, - greater_than_comparable GreaterThanComparableValue = - greater_than_comparable::maybe, - swappable SwappableValue = swappable::maybe, - hashable HashableValue = hashable::maybe> -struct ConformanceProfile { - using properties = ConformanceProfile; - - static constexpr default_constructible - default_constructible_support = // NOLINT - DefaultConstructibleValue; - - static constexpr move_constructible move_constructible_support = // NOLINT - MoveConstructibleValue; - - static constexpr copy_constructible copy_constructible_support = // NOLINT - CopyConstructibleValue; - - static constexpr move_assignable move_assignable_support = // NOLINT - MoveAssignableValue; - - static constexpr copy_assignable copy_assignable_support = // NOLINT - CopyAssignableValue; - - static constexpr destructible destructible_support = // NOLINT - DestructibleValue; - - static constexpr equality_comparable equality_comparable_support = // NOLINT - EqualityComparableValue; - - static constexpr inequality_comparable - inequality_comparable_support = // NOLINT - InequalityComparableValue; - - static constexpr less_than_comparable - less_than_comparable_support = // NOLINT - LessThanComparableValue; - - static constexpr less_equal_comparable - less_equal_comparable_support = // NOLINT - LessEqualComparableValue; - - static constexpr greater_equal_comparable - greater_equal_comparable_support = // NOLINT - GreaterEqualComparableValue; - - static constexpr greater_than_comparable - greater_than_comparable_support = // NOLINT - GreaterThanComparableValue; - - static constexpr swappable swappable_support = SwappableValue; // NOLINT - - static constexpr hashable hashable_support = HashableValue; // NOLINT - - static constexpr bool is_default_constructible = // NOLINT - DefaultConstructibleValue != default_constructible::maybe; - - static constexpr bool is_move_constructible = // NOLINT - MoveConstructibleValue != move_constructible::maybe; - - static constexpr bool is_copy_constructible = // NOLINT - CopyConstructibleValue != copy_constructible::maybe; - - static constexpr bool is_move_assignable = // NOLINT - MoveAssignableValue != move_assignable::maybe; - - static constexpr bool is_copy_assignable = // NOLINT - CopyAssignableValue != copy_assignable::maybe; - - static constexpr bool is_destructible = // NOLINT - DestructibleValue != destructible::maybe; - - static constexpr bool is_equality_comparable = // NOLINT - EqualityComparableValue != equality_comparable::maybe; - - static constexpr bool is_inequality_comparable = // NOLINT - InequalityComparableValue != inequality_comparable::maybe; - - static constexpr bool is_less_than_comparable = // NOLINT - LessThanComparableValue != less_than_comparable::maybe; - - static constexpr bool is_less_equal_comparable = // NOLINT - LessEqualComparableValue != less_equal_comparable::maybe; - - static constexpr bool is_greater_equal_comparable = // NOLINT - GreaterEqualComparableValue != greater_equal_comparable::maybe; - - static constexpr bool is_greater_than_comparable = // NOLINT - GreaterThanComparableValue != greater_than_comparable::maybe; - - static constexpr bool is_swappable = // NOLINT - SwappableValue != swappable::maybe; - - static constexpr bool is_hashable = // NOLINT - HashableValue != hashable::maybe; -}; - -//////////////////////////////////////////////////////////////////////////////// -// -// Compliant SFINAE-friendliness is not always present on the standard library -// implementations that we support. This helper-struct (and associated enum) is -// used as a means to conditionally check the hashability support of a type. -enum class CheckHashability { no, yes }; - -template <class T, CheckHashability ShouldCheckHashability> -struct conservative_hashable_support_of; - -template <class T> -struct conservative_hashable_support_of<T, CheckHashability::no> { - static constexpr hashable Invoke() { return hashable::maybe; } -}; - -template <class T> -struct conservative_hashable_support_of<T, CheckHashability::yes> { - static constexpr hashable Invoke() { return hashable_support_of<T>(); } -}; -// -//////////////////////////////////////////////////////////////////////////////// - -// The ConformanceProfile that is expected based on introspection into the type -// by way of trait checks. -template <class T, CheckHashability ShouldCheckHashability> -struct SyntacticConformanceProfileOf { - using properties = ConformanceProfile< - default_constructible_support_of<T>(), move_constructible_support_of<T>(), - copy_constructible_support_of<T>(), move_assignable_support_of<T>(), - copy_assignable_support_of<T>(), destructible_support_of<T>(), - equality_comparable_support_of<T>(), - inequality_comparable_support_of<T>(), - less_than_comparable_support_of<T>(), - less_equal_comparable_support_of<T>(), - greater_equal_comparable_support_of<T>(), - greater_than_comparable_support_of<T>(), swappable_support_of<T>(), - conservative_hashable_support_of<T, ShouldCheckHashability>::Invoke()>; -}; - -#define ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF_IMPL(type, name) \ - template <default_constructible DefaultConstructibleValue, \ - move_constructible MoveConstructibleValue, \ - copy_constructible CopyConstructibleValue, \ - move_assignable MoveAssignableValue, \ - copy_assignable CopyAssignableValue, \ - destructible DestructibleValue, \ - equality_comparable EqualityComparableValue, \ - inequality_comparable InequalityComparableValue, \ - less_than_comparable LessThanComparableValue, \ - less_equal_comparable LessEqualComparableValue, \ - greater_equal_comparable GreaterEqualComparableValue, \ - greater_than_comparable GreaterThanComparableValue, \ - swappable SwappableValue, hashable HashableValue> \ - constexpr type ConformanceProfile< \ - DefaultConstructibleValue, MoveConstructibleValue, \ - CopyConstructibleValue, MoveAssignableValue, CopyAssignableValue, \ - DestructibleValue, EqualityComparableValue, InequalityComparableValue, \ - LessThanComparableValue, LessEqualComparableValue, \ - GreaterEqualComparableValue, GreaterThanComparableValue, SwappableValue, \ - HashableValue>::name - -#define ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(type) \ - ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF_IMPL(type, \ - type##_support); \ - ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF_IMPL(bool, is_##type) - -#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL -ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(default_constructible); -ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(move_constructible); -ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(copy_constructible); -ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(move_assignable); -ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(copy_assignable); -ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(destructible); -ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(equality_comparable); -ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(inequality_comparable); -ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(less_than_comparable); -ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(less_equal_comparable); -ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(greater_equal_comparable); -ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(greater_than_comparable); -ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(swappable); -ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF(hashable); -#endif - -#undef ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF -#undef ABSL_INTERNAL_CONFORMANCE_TESTING_DATA_MEMBER_DEF_IMPL - -// Retrieve the enum with the minimum underlying value. -// Note: std::min is not constexpr in C++11, which is why this is necessary. -template <class H> -constexpr H MinEnum(H head) { - return head; -} - -template <class H, class N, class... T> -constexpr H MinEnum(H head, N next, T... tail) { - return (UnderlyingValue)(head) < (UnderlyingValue)(next) - ? (MinEnum)(head, tail...) - : (MinEnum)(next, tail...); -} - -template <class... Profs> -struct MinimalProfiles { - static constexpr default_constructible - default_constructible_support = // NOLINT - (MinEnum)(PropertiesOfT<Profs>::default_constructible_support...); - - static constexpr move_constructible move_constructible_support = // NOLINT - (MinEnum)(PropertiesOfT<Profs>::move_constructible_support...); - - static constexpr copy_constructible copy_constructible_support = // NOLINT - (MinEnum)(PropertiesOfT<Profs>::copy_constructible_support...); - - static constexpr move_assignable move_assignable_support = // NOLINT - (MinEnum)(PropertiesOfT<Profs>::move_assignable_support...); - - static constexpr copy_assignable copy_assignable_support = // NOLINT - (MinEnum)(PropertiesOfT<Profs>::copy_assignable_support...); - - static constexpr destructible destructible_support = // NOLINT - (MinEnum)(PropertiesOfT<Profs>::destructible_support...); - - static constexpr equality_comparable equality_comparable_support = // NOLINT - (MinEnum)(PropertiesOfT<Profs>::equality_comparable_support...); - - static constexpr inequality_comparable - inequality_comparable_support = // NOLINT - (MinEnum)(PropertiesOfT<Profs>::inequality_comparable_support...); - - static constexpr less_than_comparable - less_than_comparable_support = // NOLINT - (MinEnum)(PropertiesOfT<Profs>::less_than_comparable_support...); - - static constexpr less_equal_comparable - less_equal_comparable_support = // NOLINT - (MinEnum)(PropertiesOfT<Profs>::less_equal_comparable_support...); - - static constexpr greater_equal_comparable - greater_equal_comparable_support = // NOLINT - (MinEnum)(PropertiesOfT<Profs>::greater_equal_comparable_support...); - - static constexpr greater_than_comparable - greater_than_comparable_support = // NOLINT - (MinEnum)(PropertiesOfT<Profs>::greater_than_comparable_support...); - - static constexpr swappable swappable_support = // NOLINT - (MinEnum)(PropertiesOfT<Profs>::swappable_support...); - - static constexpr hashable hashable_support = // NOLINT - (MinEnum)(PropertiesOfT<Profs>::hashable_support...); - - using properties = ConformanceProfile< - default_constructible_support, move_constructible_support, - copy_constructible_support, move_assignable_support, - copy_assignable_support, destructible_support, - equality_comparable_support, inequality_comparable_support, - less_than_comparable_support, less_equal_comparable_support, - greater_equal_comparable_support, greater_than_comparable_support, - swappable_support, hashable_support>; -}; - -// Retrieve the enum with the greatest underlying value. -// Note: std::max is not constexpr in C++11, which is why this is necessary. -template <class H> -constexpr H MaxEnum(H head) { - return head; -} - -template <class H, class N, class... T> -constexpr H MaxEnum(H head, N next, T... tail) { - return (UnderlyingValue)(next) < (UnderlyingValue)(head) - ? (MaxEnum)(head, tail...) - : (MaxEnum)(next, tail...); -} - -template <class... Profs> -struct CombineProfilesImpl { - static constexpr default_constructible - default_constructible_support = // NOLINT - (MaxEnum)(PropertiesOfT<Profs>::default_constructible_support...); - - static constexpr move_constructible move_constructible_support = // NOLINT - (MaxEnum)(PropertiesOfT<Profs>::move_constructible_support...); - - static constexpr copy_constructible copy_constructible_support = // NOLINT - (MaxEnum)(PropertiesOfT<Profs>::copy_constructible_support...); - - static constexpr move_assignable move_assignable_support = // NOLINT - (MaxEnum)(PropertiesOfT<Profs>::move_assignable_support...); - - static constexpr copy_assignable copy_assignable_support = // NOLINT - (MaxEnum)(PropertiesOfT<Profs>::copy_assignable_support...); - - static constexpr destructible destructible_support = // NOLINT - (MaxEnum)(PropertiesOfT<Profs>::destructible_support...); - - static constexpr equality_comparable equality_comparable_support = // NOLINT - (MaxEnum)(PropertiesOfT<Profs>::equality_comparable_support...); - - static constexpr inequality_comparable - inequality_comparable_support = // NOLINT - (MaxEnum)(PropertiesOfT<Profs>::inequality_comparable_support...); - - static constexpr less_than_comparable - less_than_comparable_support = // NOLINT - (MaxEnum)(PropertiesOfT<Profs>::less_than_comparable_support...); - - static constexpr less_equal_comparable - less_equal_comparable_support = // NOLINT - (MaxEnum)(PropertiesOfT<Profs>::less_equal_comparable_support...); - - static constexpr greater_equal_comparable - greater_equal_comparable_support = // NOLINT - (MaxEnum)(PropertiesOfT<Profs>::greater_equal_comparable_support...); - - static constexpr greater_than_comparable - greater_than_comparable_support = // NOLINT - (MaxEnum)(PropertiesOfT<Profs>::greater_than_comparable_support...); - - static constexpr swappable swappable_support = // NOLINT - (MaxEnum)(PropertiesOfT<Profs>::swappable_support...); - - static constexpr hashable hashable_support = // NOLINT - (MaxEnum)(PropertiesOfT<Profs>::hashable_support...); - - using properties = ConformanceProfile< - default_constructible_support, move_constructible_support, - copy_constructible_support, move_assignable_support, - copy_assignable_support, destructible_support, - equality_comparable_support, inequality_comparable_support, - less_than_comparable_support, less_equal_comparable_support, - greater_equal_comparable_support, greater_than_comparable_support, - swappable_support, hashable_support>; -}; - -// NOTE: We use this as opposed to a direct alias of CombineProfilesImpl so that -// when named aliases of CombineProfiles are created (such as in -// conformance_aliases.h), we only pay for the combination algorithm on the -// profiles that are actually used. -template <class... Profs> -struct CombineProfiles { - using profile_alias_of = CombineProfilesImpl<Profs...>; -}; - -template <> -struct CombineProfiles<> { - using properties = ConformanceProfile<>; -}; - -template <class Profile, class Tag> -struct StrongProfileTypedef { - using properties = PropertiesOfT<Profile>; -}; - -template <class T, class /*Enabler*/ = void> -struct IsProfileImpl : std::false_type {}; - -template <class T> -struct IsProfileImpl<T, absl::void_t<PropertiesOfT<T>>> : std::true_type {}; - -template <class T> -struct IsProfile : IsProfileImpl<T>::type {}; - -// A tag that describes which set of properties we will check when the user -// requires a strict match in conformance (as opposed to a loose match which -// allows more-refined support of any given operation). -// -// Currently only the RegularityDomain exists and it includes all operations -// that the conformance testing suite knows about. The intent is that if the -// suite is expanded to support extension, such as for checking conformance of -// concepts like Iterators or Containers, additional corresponding domains can -// be created. -struct RegularityDomain {}; - -} // namespace types_internal -ABSL_NAMESPACE_END -} // namespace absl - -#endif // ABSL_TYPES_INTERNAL_CONFORMANCE_PROFILE_H_ diff --git a/absl/types/internal/conformance_testing.h b/absl/types/internal/conformance_testing.h deleted file mode 100644 index 487b0f78..00000000 --- a/absl/types/internal/conformance_testing.h +++ /dev/null @@ -1,1386 +0,0 @@ -// Copyright 2019 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. -// -// ----------------------------------------------------------------------------- -// conformance_testing.h -// ----------------------------------------------------------------------------- -// - -#ifndef ABSL_TYPES_INTERNAL_CONFORMANCE_TESTING_H_ -#define ABSL_TYPES_INTERNAL_CONFORMANCE_TESTING_H_ - -//////////////////////////////////////////////////////////////////////////////// -// // -// Many templates in this file take a `T` and a `Prof` type as explicit // -// template arguments. These are a type to be checked and a // -// "Regularity Profile" that describes what operations that type `T` is // -// expected to support. See "regularity_profiles.h" for more details // -// regarding Regularity Profiles. // -// // -//////////////////////////////////////////////////////////////////////////////// - -#include <cstddef> -#include <set> -#include <tuple> -#include <type_traits> -#include <utility> - -#include "gtest/gtest.h" -#include "absl/meta/type_traits.h" -#include "absl/strings/ascii.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/string_view.h" -#include "absl/types/internal/conformance_aliases.h" -#include "absl/types/internal/conformance_archetype.h" -#include "absl/types/internal/conformance_profile.h" -#include "absl/types/internal/conformance_testing_helpers.h" -#include "absl/types/internal/parentheses.h" -#include "absl/types/internal/transform_args.h" -#include "absl/utility/utility.h" - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace types_internal { - -// Returns true if the compiler incorrectly greedily instantiates constexpr -// templates in any unevaluated context. -constexpr bool constexpr_instantiation_when_unevaluated() { -#if defined(__apple_build_version__) // TODO(calabrese) Make more specific - return true; -#elif defined(__clang__) - return __clang_major__ < 4; -#elif defined(__GNUC__) - // TODO(calabrese) Figure out why gcc 7 fails (seems like a different bug) - return __GNUC__ < 5 || (__GNUC__ == 5 && __GNUC_MINOR__ < 2) || __GNUC__ >= 7; -#else - return false; -#endif -} - -// Returns true if the standard library being used incorrectly produces an error -// when instantiating the definition of a poisoned std::hash specialization. -constexpr bool poisoned_hash_fails_instantiation() { -#if defined(_MSC_VER) && !defined(_LIBCPP_VERSION) - return _MSC_VER < 1914; -#else - return false; -#endif -} - -template <class Fun> -struct GeneratorType { - decltype(std::declval<const Fun&>()()) operator()() const - noexcept(noexcept(std::declval<const Fun&>()())) { - return fun(); - } - - Fun fun; - const char* description; -}; - -// A "make" function for the GeneratorType template that deduces the function -// object type. -template <class Fun, - absl::enable_if_t<IsNullaryCallable<Fun>::value>** = nullptr> -GeneratorType<Fun> Generator(Fun fun, const char* description) { - return GeneratorType<Fun>{absl::move(fun), description}; -} - -// A type that contains a set of nullary function objects that each return an -// instance of the same type and value (though possibly different -// representations, such as +0 and -0 or two vectors with the same elements but -// with different capacities). -template <class... Funs> -struct EquivalenceClassType { - std::tuple<GeneratorType<Funs>...> generators; -}; - -// A "make" function for the EquivalenceClassType template that deduces the -// function object types and is constrained such that a user can only pass in -// function objects that all have the same return type. -template <class... Funs, absl::enable_if_t<AreGeneratorsWithTheSameReturnType< - Funs...>::value>** = nullptr> -EquivalenceClassType<Funs...> EquivalenceClass(GeneratorType<Funs>... funs) { - return {std::make_tuple(absl::move(funs)...)}; -} - -// A type that contains an ordered series of EquivalenceClassTypes, from -// smallest value to largest value. -template <class... EqClasses> -struct OrderedEquivalenceClasses { - std::tuple<EqClasses...> eq_classes; -}; - -// An object containing the parts of a given (name, initialization expression), -// and is capable of generating a string that describes the given. -struct GivenDeclaration { - std::string outputDeclaration(std::size_t width) const { - const std::size_t indent_size = 2; - std::string result = absl::StrCat(" ", name); - - if (!expression.empty()) { - // Indent - result.resize(indent_size + width, ' '); - absl::StrAppend(&result, " = ", expression, ";\n"); - } else { - absl::StrAppend(&result, ";\n"); - } - - return result; - } - - std::string name; - std::string expression; -}; - -// Produce a string that contains all of the givens of an error report. -template <class... Decls> -std::string PrepareGivenContext(const Decls&... decls) { - const std::size_t width = (std::max)({decls.name.size()...}); - return absl::StrCat("Given:\n", decls.outputDeclaration(width)..., "\n"); -} - -//////////////////////////////////////////////////////////////////////////////// -// Function objects that perform a check for each comparison operator // -//////////////////////////////////////////////////////////////////////////////// - -#define ABSL_INTERNAL_EXPECT_OP(name, op) \ - struct Expect##name { \ - template <class T> \ - void operator()(absl::string_view test_name, absl::string_view context, \ - const T& lhs, const T& rhs, absl::string_view lhs_name, \ - absl::string_view rhs_name) const { \ - if (!static_cast<bool>(lhs op rhs)) { \ - errors->addTestFailure( \ - test_name, absl::StrCat(context, \ - "**Unexpected comparison result**\n" \ - "\n" \ - "Expression:\n" \ - " ", \ - lhs_name, " " #op " ", rhs_name, \ - "\n" \ - "\n" \ - "Expected: true\n" \ - " Actual: false")); \ - } else { \ - errors->addTestSuccess(test_name); \ - } \ - } \ - \ - ConformanceErrors* errors; \ - }; \ - \ - struct ExpectNot##name { \ - template <class T> \ - void operator()(absl::string_view test_name, absl::string_view context, \ - const T& lhs, const T& rhs, absl::string_view lhs_name, \ - absl::string_view rhs_name) const { \ - if (lhs op rhs) { \ - errors->addTestFailure( \ - test_name, absl::StrCat(context, \ - "**Unexpected comparison result**\n" \ - "\n" \ - "Expression:\n" \ - " ", \ - lhs_name, " " #op " ", rhs_name, \ - "\n" \ - "\n" \ - "Expected: false\n" \ - " Actual: true")); \ - } else { \ - errors->addTestSuccess(test_name); \ - } \ - } \ - \ - ConformanceErrors* errors; \ - } - -ABSL_INTERNAL_EXPECT_OP(Eq, ==); -ABSL_INTERNAL_EXPECT_OP(Ne, !=); -ABSL_INTERNAL_EXPECT_OP(Lt, <); -ABSL_INTERNAL_EXPECT_OP(Le, <=); -ABSL_INTERNAL_EXPECT_OP(Ge, >=); -ABSL_INTERNAL_EXPECT_OP(Gt, >); - -#undef ABSL_INTERNAL_EXPECT_OP - -// A function object that verifies that two objects hash to the same value by -// way of the std::hash specialization. -struct ExpectSameHash { - template <class T> - void operator()(absl::string_view test_name, absl::string_view context, - const T& lhs, const T& rhs, absl::string_view lhs_name, - absl::string_view rhs_name) const { - if (std::hash<T>()(lhs) != std::hash<T>()(rhs)) { - errors->addTestFailure( - test_name, absl::StrCat(context, - "**Unexpected hash result**\n" - "\n" - "Expression:\n" - " std::hash<T>()(", - lhs_name, ") == std::hash<T>()(", rhs_name, - ")\n" - "\n" - "Expected: true\n" - " Actual: false")); - } else { - errors->addTestSuccess(test_name); - } - } - - ConformanceErrors* errors; -}; - -// A function template that takes two objects and verifies that each comparison -// operator behaves in a way that is consistent with equality. It has "OneWay" -// in the name because the first argument will always be the left-hand operand -// of the corresponding comparison operator and the second argument will -// always be the right-hand operand. It will never switch that order. -// At a higher level in the test suite, the one-way form is called once for each -// of the two possible orders whenever lhs and rhs are not the same initializer. -template <class T, class Prof> -void ExpectOneWayEquality(ConformanceErrors* errors, - absl::string_view test_name, - absl::string_view context, const T& lhs, const T& rhs, - absl::string_view lhs_name, - absl::string_view rhs_name) { - If<PropertiesOfT<Prof>::is_equality_comparable>::Invoke( - ExpectEq{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name); - - If<PropertiesOfT<Prof>::is_inequality_comparable>::Invoke( - ExpectNotNe{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name); - - If<PropertiesOfT<Prof>::is_less_than_comparable>::Invoke( - ExpectNotLt{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name); - - If<PropertiesOfT<Prof>::is_less_equal_comparable>::Invoke( - ExpectLe{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name); - - If<PropertiesOfT<Prof>::is_greater_equal_comparable>::Invoke( - ExpectGe{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name); - - If<PropertiesOfT<Prof>::is_greater_than_comparable>::Invoke( - ExpectNotGt{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name); - - If<PropertiesOfT<Prof>::is_hashable>::Invoke( - ExpectSameHash{errors}, test_name, context, lhs, rhs, lhs_name, rhs_name); -} - -// A function template that takes two objects and verifies that each comparison -// operator behaves in a way that is consistent with equality. This function -// differs from ExpectOneWayEquality in that this will do checks with argument -// order reversed in addition to in-order. -template <class T, class Prof> -void ExpectEquality(ConformanceErrors* errors, absl::string_view test_name, - absl::string_view context, const T& lhs, const T& rhs, - absl::string_view lhs_name, absl::string_view rhs_name) { - (ExpectOneWayEquality<T, Prof>)(errors, test_name, context, lhs, rhs, - lhs_name, rhs_name); - (ExpectOneWayEquality<T, Prof>)(errors, test_name, context, rhs, lhs, - rhs_name, lhs_name); -} - -// Given a generator, makes sure that a generated value and a moved-from -// generated value are equal. -template <class T, class Prof> -struct ExpectMoveConstructOneGenerator { - template <class Fun> - void operator()(const Fun& generator) const { - const T object = generator(); - const T moved_object = absl::move(generator()); // Force no elision. - - (ExpectEquality<T, Prof>)(errors, "Move construction", - PrepareGivenContext( - GivenDeclaration{"const _T object", - generator.description}, - GivenDeclaration{"const _T moved_object", - std::string("std::move(") + - generator.description + - ")"}), - object, moved_object, "object", "moved_object"); - } - - ConformanceErrors* errors; -}; - -// Given a generator, makes sure that a generated value and a copied-from -// generated value are equal. -template <class T, class Prof> -struct ExpectCopyConstructOneGenerator { - template <class Fun> - void operator()(const Fun& generator) const { - const T object = generator(); - const T copied_object = static_cast<const T&>(generator()); - - (ExpectEquality<T, Prof>)(errors, "Copy construction", - PrepareGivenContext( - GivenDeclaration{"const _T object", - generator.description}, - GivenDeclaration{ - "const _T copied_object", - std::string("static_cast<const _T&>(") + - generator.description + ")"}), - object, copied_object, "object", "copied_object"); - } - - ConformanceErrors* errors; -}; - -// Default-construct and do nothing before destruction. -// -// This is useful in exercising the codepath of default construction followed by -// destruction, but does not explicitly test anything. An example of where this -// might fail is a default destructor that default-initializes a scalar and a -// destructor reads the value of that member. Sanitizers can catch this as long -// as our test attempts to execute such a case. -template <class T> -struct ExpectDefaultConstructWithDestruct { - void operator()() const { - // Scoped so that destructor gets called before reporting success. - { - T object; - static_cast<void>(object); - } - - errors->addTestSuccess("Default construction"); - } - - ConformanceErrors* errors; -}; - -// Check move-assign into a default-constructed object. -template <class T, class Prof> -struct ExpectDefaultConstructWithMoveAssign { - template <class Fun> - void operator()(const Fun& generator) const { - const T source_of_truth = generator(); - T object; - object = generator(); - - (ExpectEquality<T, Prof>)(errors, "Move assignment", - PrepareGivenContext( - GivenDeclaration{"const _T object", - generator.description}, - GivenDeclaration{"_T object", ""}, - GivenDeclaration{"object", - generator.description}), - object, source_of_truth, "std::as_const(object)", - "source_of_truth"); - } - - ConformanceErrors* errors; -}; - -// Check copy-assign into a default-constructed object. -template <class T, class Prof> -struct ExpectDefaultConstructWithCopyAssign { - template <class Fun> - void operator()(const Fun& generator) const { - const T source_of_truth = generator(); - T object; - object = static_cast<const T&>(generator()); - - (ExpectEquality<T, Prof>)(errors, "Copy assignment", - PrepareGivenContext( - GivenDeclaration{"const _T source_of_truth", - generator.description}, - GivenDeclaration{"_T object", ""}, - GivenDeclaration{ - "object", - std::string("static_cast<const _T&>(") + - generator.description + ")"}), - object, source_of_truth, "std::as_const(object)", - "source_of_truth"); - } - - ConformanceErrors* errors; -}; - -// Perform a self move-assign. -template <class T, class Prof> -struct ExpectSelfMoveAssign { - template <class Fun> - void operator()(const Fun& generator) const { - T object = generator(); - object = absl::move(object); - - // NOTE: Self move-assign results in a valid-but-unspecified state. - - (ExpectEquality<T, Prof>)(errors, "Move assignment", - PrepareGivenContext( - GivenDeclaration{"_T object", - generator.description}, - GivenDeclaration{"object", - "std::move(object)"}), - object, object, "object", "object"); - } - - ConformanceErrors* errors; -}; - -// Perform a self copy-assign. -template <class T, class Prof> -struct ExpectSelfCopyAssign { - template <class Fun> - void operator()(const Fun& generator) const { - const T source_of_truth = generator(); - T object = generator(); - const T& const_object = object; - object = const_object; - - (ExpectEquality<T, Prof>)(errors, "Copy assignment", - PrepareGivenContext( - GivenDeclaration{"const _T source_of_truth", - generator.description}, - GivenDeclaration{"_T object", - generator.description}, - GivenDeclaration{"object", - "std::as_const(object)"}), - const_object, source_of_truth, - "std::as_const(object)", "source_of_truth"); - } - - ConformanceErrors* errors; -}; - -// Perform a self-swap. -template <class T, class Prof> -struct ExpectSelfSwap { - template <class Fun> - void operator()(const Fun& generator) const { - const T source_of_truth = generator(); - T object = generator(); - - type_traits_internal::Swap(object, object); - - std::string preliminary_info = absl::StrCat( - PrepareGivenContext( - GivenDeclaration{"const _T source_of_truth", generator.description}, - GivenDeclaration{"_T object", generator.description}), - "After performing a self-swap:\n" - " using std::swap;\n" - " swap(object, object);\n" - "\n"); - - (ExpectEquality<T, Prof>)(errors, "Swap", std::move(preliminary_info), - object, source_of_truth, "std::as_const(object)", - "source_of_truth"); - } - - ConformanceErrors* errors; -}; - -// Perform each of the single-generator checks when necessary operations are -// supported. -template <class T, class Prof> -struct ExpectSelfComparison { - template <class Fun> - void operator()(const Fun& generator) const { - const T object = generator(); - (ExpectOneWayEquality<T, Prof>)(errors, "Comparison", - PrepareGivenContext(GivenDeclaration{ - "const _T object", - generator.description}), - object, object, "object", "object"); - } - - ConformanceErrors* errors; -}; - -// Perform each of the single-generator checks when necessary operations are -// supported. -template <class T, class Prof> -struct ExpectConsistency { - template <class Fun> - void operator()(const Fun& generator) const { - If<PropertiesOfT<Prof>::is_move_constructible>::Invoke( - ExpectMoveConstructOneGenerator<T, Prof>{errors}, generator); - - If<PropertiesOfT<Prof>::is_copy_constructible>::Invoke( - ExpectCopyConstructOneGenerator<T, Prof>{errors}, generator); - - If<PropertiesOfT<Prof>::is_default_constructible && - PropertiesOfT<Prof>::is_move_assignable>:: - Invoke(ExpectDefaultConstructWithMoveAssign<T, Prof>{errors}, - generator); - - If<PropertiesOfT<Prof>::is_default_constructible && - PropertiesOfT<Prof>::is_copy_assignable>:: - Invoke(ExpectDefaultConstructWithCopyAssign<T, Prof>{errors}, - generator); - - If<PropertiesOfT<Prof>::is_move_assignable>::Invoke( - ExpectSelfMoveAssign<T, Prof>{errors}, generator); - - If<PropertiesOfT<Prof>::is_copy_assignable>::Invoke( - ExpectSelfCopyAssign<T, Prof>{errors}, generator); - - If<PropertiesOfT<Prof>::is_swappable>::Invoke( - ExpectSelfSwap<T, Prof>{errors}, generator); - } - - ConformanceErrors* errors; -}; - -// Check move-assign with two different values. -template <class T, class Prof> -struct ExpectMoveAssign { - template <class Fun0, class Fun1> - void operator()(const Fun0& generator0, const Fun1& generator1) const { - const T source_of_truth1 = generator1(); - T object = generator0(); - object = generator1(); - - (ExpectEquality<T, Prof>)(errors, "Move assignment", - PrepareGivenContext( - GivenDeclaration{"const _T source_of_truth1", - generator1.description}, - GivenDeclaration{"_T object", - generator0.description}, - GivenDeclaration{"object", - generator1.description}), - object, source_of_truth1, "std::as_const(object)", - "source_of_truth1"); - } - - ConformanceErrors* errors; -}; - -// Check copy-assign with two different values. -template <class T, class Prof> -struct ExpectCopyAssign { - template <class Fun0, class Fun1> - void operator()(const Fun0& generator0, const Fun1& generator1) const { - const T source_of_truth1 = generator1(); - T object = generator0(); - object = static_cast<const T&>(generator1()); - - (ExpectEquality<T, Prof>)(errors, "Copy assignment", - PrepareGivenContext( - GivenDeclaration{"const _T source_of_truth1", - generator1.description}, - GivenDeclaration{"_T object", - generator0.description}, - GivenDeclaration{ - "object", - std::string("static_cast<const _T&>(") + - generator1.description + ")"}), - object, source_of_truth1, "std::as_const(object)", - "source_of_truth1"); - } - - ConformanceErrors* errors; -}; - -// Check swap with two different values. -template <class T, class Prof> -struct ExpectSwap { - template <class Fun0, class Fun1> - void operator()(const Fun0& generator0, const Fun1& generator1) const { - const T source_of_truth0 = generator0(); - const T source_of_truth1 = generator1(); - T object0 = generator0(); - T object1 = generator1(); - - type_traits_internal::Swap(object0, object1); - - const std::string context = - PrepareGivenContext( - GivenDeclaration{"const _T source_of_truth0", - generator0.description}, - GivenDeclaration{"const _T source_of_truth1", - generator1.description}, - GivenDeclaration{"_T object0", generator0.description}, - GivenDeclaration{"_T object1", generator1.description}) + - "After performing a swap:\n" - " using std::swap;\n" - " swap(object0, object1);\n" - "\n"; - - (ExpectEquality<T, Prof>)(errors, "Swap", context, object0, - source_of_truth1, "std::as_const(object0)", - "source_of_truth1"); - (ExpectEquality<T, Prof>)(errors, "Swap", context, object1, - source_of_truth0, "std::as_const(object1)", - "source_of_truth0"); - } - - ConformanceErrors* errors; -}; - -// Validate that `generator0` and `generator1` produce values that are equal. -template <class T, class Prof> -struct ExpectEquivalenceClassComparison { - template <class Fun0, class Fun1> - void operator()(const Fun0& generator0, const Fun1& generator1) const { - const T object0 = generator0(); - const T object1 = generator1(); - - (ExpectEquality<T, Prof>)(errors, "Comparison", - PrepareGivenContext( - GivenDeclaration{"const _T object0", - generator0.description}, - GivenDeclaration{"const _T object1", - generator1.description}), - object0, object1, "object0", "object1"); - } - - ConformanceErrors* errors; -}; - -// Validate that all objects in the same equivalence-class have the same value. -template <class T, class Prof> -struct ExpectEquivalenceClassConsistency { - template <class Fun0, class Fun1> - void operator()(const Fun0& generator0, const Fun1& generator1) const { - If<PropertiesOfT<Prof>::is_move_assignable>::Invoke( - ExpectMoveAssign<T, Prof>{errors}, generator0, generator1); - - If<PropertiesOfT<Prof>::is_copy_assignable>::Invoke( - ExpectCopyAssign<T, Prof>{errors}, generator0, generator1); - - If<PropertiesOfT<Prof>::is_swappable>::Invoke(ExpectSwap<T, Prof>{errors}, - generator0, generator1); - } - - ConformanceErrors* errors; -}; - -// Given a "lesser" object and a "greater" object, perform every combination of -// comparison operators supported for the type, expecting consistent results. -template <class T, class Prof> -void ExpectOrdered(ConformanceErrors* errors, absl::string_view context, - const T& small, const T& big, absl::string_view small_name, - absl::string_view big_name) { - const absl::string_view test_name = "Comparison"; - - If<PropertiesOfT<Prof>::is_equality_comparable>::Invoke( - ExpectNotEq{errors}, test_name, context, small, big, small_name, - big_name); - If<PropertiesOfT<Prof>::is_equality_comparable>::Invoke( - ExpectNotEq{errors}, test_name, context, big, small, big_name, - small_name); - - If<PropertiesOfT<Prof>::is_inequality_comparable>::Invoke( - ExpectNe{errors}, test_name, context, small, big, small_name, big_name); - If<PropertiesOfT<Prof>::is_inequality_comparable>::Invoke( - ExpectNe{errors}, test_name, context, big, small, big_name, small_name); - - If<PropertiesOfT<Prof>::is_less_than_comparable>::Invoke( - ExpectLt{errors}, test_name, context, small, big, small_name, big_name); - If<PropertiesOfT<Prof>::is_less_than_comparable>::Invoke( - ExpectNotLt{errors}, test_name, context, big, small, big_name, - small_name); - - If<PropertiesOfT<Prof>::is_less_equal_comparable>::Invoke( - ExpectLe{errors}, test_name, context, small, big, small_name, big_name); - If<PropertiesOfT<Prof>::is_less_equal_comparable>::Invoke( - ExpectNotLe{errors}, test_name, context, big, small, big_name, - small_name); - - If<PropertiesOfT<Prof>::is_greater_equal_comparable>::Invoke( - ExpectNotGe{errors}, test_name, context, small, big, small_name, - big_name); - If<PropertiesOfT<Prof>::is_greater_equal_comparable>::Invoke( - ExpectGe{errors}, test_name, context, big, small, big_name, small_name); - - If<PropertiesOfT<Prof>::is_greater_than_comparable>::Invoke( - ExpectNotGt{errors}, test_name, context, small, big, small_name, - big_name); - If<PropertiesOfT<Prof>::is_greater_than_comparable>::Invoke( - ExpectGt{errors}, test_name, context, big, small, big_name, small_name); -} - -// For every two elements of an equivalence class, makes sure that those two -// elements compare equal, including checks with the same argument passed as -// both operands. -template <class T, class Prof> -struct ExpectEquivalenceClassComparisons { - template <class... Funs> - void operator()(EquivalenceClassType<Funs...> eq_class) const { - (ForEachTupleElement)(ExpectSelfComparison<T, Prof>{errors}, - eq_class.generators); - - (ForEveryTwo)(ExpectEquivalenceClassComparison<T, Prof>{errors}, - eq_class.generators); - } - - ConformanceErrors* errors; -}; - -// For every element of an equivalence class, makes sure that the element is -// self-consistent (in other words, if any of move/copy/swap are defined, -// perform those operations and make such that results and operands still -// compare equal to known values whenever it is required for that operation. -template <class T, class Prof> -struct ExpectEquivalenceClass { - template <class... Funs> - void operator()(EquivalenceClassType<Funs...> eq_class) const { - (ForEachTupleElement)(ExpectConsistency<T, Prof>{errors}, - eq_class.generators); - - (ForEveryTwo)(ExpectEquivalenceClassConsistency<T, Prof>{errors}, - eq_class.generators); - } - - ConformanceErrors* errors; -}; - -// Validate that the passed-in argument is a generator of a greater value than -// the one produced by the "small_gen" datamember with respect to all of the -// comparison operators that Prof requires, with both argument orders to test. -template <class T, class Prof, class SmallGenerator> -struct ExpectBiggerGeneratorThanComparisons { - template <class BigGenerator> - void operator()(BigGenerator big_gen) const { - const T small = small_gen(); - const T big = big_gen(); - - (ExpectOrdered<T, Prof>)(errors, - PrepareGivenContext( - GivenDeclaration{"const _T small", - small_gen.description}, - GivenDeclaration{"const _T big", - big_gen.description}), - small, big, "small", "big"); - } - - SmallGenerator small_gen; - ConformanceErrors* errors; -}; - -// Perform all of the move, copy, and swap checks on the value generated by -// `small_gen` and the value generated by `big_gen`. -template <class T, class Prof, class SmallGenerator> -struct ExpectBiggerGeneratorThan { - template <class BigGenerator> - void operator()(BigGenerator big_gen) const { - If<PropertiesOfT<Prof>::is_move_assignable>::Invoke( - ExpectMoveAssign<T, Prof>{errors}, small_gen, big_gen); - If<PropertiesOfT<Prof>::is_move_assignable>::Invoke( - ExpectMoveAssign<T, Prof>{errors}, big_gen, small_gen); - - If<PropertiesOfT<Prof>::is_copy_assignable>::Invoke( - ExpectCopyAssign<T, Prof>{errors}, small_gen, big_gen); - If<PropertiesOfT<Prof>::is_copy_assignable>::Invoke( - ExpectCopyAssign<T, Prof>{errors}, big_gen, small_gen); - - If<PropertiesOfT<Prof>::is_swappable>::Invoke(ExpectSwap<T, Prof>{errors}, - small_gen, big_gen); - } - - SmallGenerator small_gen; - ConformanceErrors* errors; -}; - -// Validate that the result of a generator is greater than the results of all -// generators in an equivalence class with respect to comparisons. -template <class T, class Prof, class SmallGenerator> -struct ExpectBiggerGeneratorThanEqClassesComparisons { - template <class BigEqClass> - void operator()(BigEqClass big_eq_class) const { - (ForEachTupleElement)( - ExpectBiggerGeneratorThanComparisons<T, Prof, SmallGenerator>{small_gen, - errors}, - big_eq_class.generators); - } - - SmallGenerator small_gen; - ConformanceErrors* errors; -}; - -// Validate that the non-comparison binary operations required by Prof are -// correct for the result of each generator of big_eq_class and a generator of -// the logically smaller value returned by small_gen. -template <class T, class Prof, class SmallGenerator> -struct ExpectBiggerGeneratorThanEqClasses { - template <class BigEqClass> - void operator()(BigEqClass big_eq_class) const { - (ForEachTupleElement)( - ExpectBiggerGeneratorThan<T, Prof, SmallGenerator>{small_gen, errors}, - big_eq_class.generators); - } - - SmallGenerator small_gen; - ConformanceErrors* errors; -}; - -// Validate that each equivalence class that is passed is logically less than -// the equivalence classes that comes later on in the argument list. -template <class T, class Prof> -struct ExpectOrderedEquivalenceClassesComparisons { - template <class... BigEqClasses> - struct Impl { - // Validate that the value produced by `small_gen` is less than all of the - // values generated by those of the logically larger equivalence classes. - template <class SmallGenerator> - void operator()(SmallGenerator small_gen) const { - (ForEachTupleElement)(ExpectBiggerGeneratorThanEqClassesComparisons< - T, Prof, SmallGenerator>{small_gen, errors}, - big_eq_classes); - } - - std::tuple<BigEqClasses...> big_eq_classes; - ConformanceErrors* errors; - }; - - // When given no equivalence classes, no validation is necessary. - void operator()() const {} - - template <class SmallEqClass, class... BigEqClasses> - void operator()(SmallEqClass small_eq_class, - BigEqClasses... big_eq_classes) const { - // For each generator in the first equivalence class, make sure that it is - // less than each of those in the logically greater equivalence classes. - (ForEachTupleElement)( - Impl<BigEqClasses...>{std::make_tuple(absl::move(big_eq_classes)...), - errors}, - small_eq_class.generators); - - // Recurse so that all equivalence class combinations are checked. - (*this)(absl::move(big_eq_classes)...); - } - - ConformanceErrors* errors; -}; - -// Validate that the non-comparison binary operations required by Prof are -// correct for the result of each generator of big_eq_classes and a generator of -// the logically smaller value returned by small_gen. -template <class T, class Prof> -struct ExpectOrderedEquivalenceClasses { - template <class... BigEqClasses> - struct Impl { - template <class SmallGenerator> - void operator()(SmallGenerator small_gen) const { - (ForEachTupleElement)( - ExpectBiggerGeneratorThanEqClasses<T, Prof, SmallGenerator>{small_gen, - errors}, - big_eq_classes); - } - - std::tuple<BigEqClasses...> big_eq_classes; - ConformanceErrors* errors; - }; - - // Check that small_eq_class is logically consistent and also is logically - // less than all values in big_eq_classes. - template <class SmallEqClass, class... BigEqClasses> - void operator()(SmallEqClass small_eq_class, - BigEqClasses... big_eq_classes) const { - (ForEachTupleElement)( - Impl<BigEqClasses...>{std::make_tuple(absl::move(big_eq_classes)...), - errors}, - small_eq_class.generators); - - (*this)(absl::move(big_eq_classes)...); - } - - // Terminating case of operator(). - void operator()() const {} - - ConformanceErrors* errors; -}; - -// Validate that a type meets the syntactic requirements of std::hash if the -// range of profiles requires it. -template <class T, class MinProf, class MaxProf> -struct ExpectHashable { - void operator()() const { - ExpectModelOfHashable<T, MinProf, MaxProf>(errors); - } - - ConformanceErrors* errors; -}; - -// Validate that the type `T` meets all of the requirements associated with -// `MinProf` and without going beyond the syntactic properties of `MaxProf`. -template <class T, class MinProf, class MaxProf> -struct ExpectModels { - void operator()(ConformanceErrors* errors) const { - ExpectModelOfDefaultConstructible<T, MinProf, MaxProf>(errors); - ExpectModelOfMoveConstructible<T, MinProf, MaxProf>(errors); - ExpectModelOfCopyConstructible<T, MinProf, MaxProf>(errors); - ExpectModelOfMoveAssignable<T, MinProf, MaxProf>(errors); - ExpectModelOfCopyAssignable<T, MinProf, MaxProf>(errors); - ExpectModelOfDestructible<T, MinProf, MaxProf>(errors); - ExpectModelOfEqualityComparable<T, MinProf, MaxProf>(errors); - ExpectModelOfInequalityComparable<T, MinProf, MaxProf>(errors); - ExpectModelOfLessThanComparable<T, MinProf, MaxProf>(errors); - ExpectModelOfLessEqualComparable<T, MinProf, MaxProf>(errors); - ExpectModelOfGreaterEqualComparable<T, MinProf, MaxProf>(errors); - ExpectModelOfGreaterThanComparable<T, MinProf, MaxProf>(errors); - ExpectModelOfSwappable<T, MinProf, MaxProf>(errors); - - // Only check hashability on compilers that have a compliant default-hash. - If<!poisoned_hash_fails_instantiation()>::Invoke( - ExpectHashable<T, MinProf, MaxProf>{errors}); - } -}; - -// A metafunction that yields a Profile matching the set of properties that are -// safe to be checked (lack-of-hashability is only checked on standard library -// implementations that are standards compliant in that they provide a std::hash -// primary template that is SFINAE-friendly) -template <class LogicalProf, class T> -struct MinimalCheckableProfile { - using type = - MinimalProfiles<PropertiesOfT<LogicalProf>, - PropertiesOfT<SyntacticConformanceProfileOf< - T, !PropertiesOfT<LogicalProf>::is_hashable && - poisoned_hash_fails_instantiation() - ? CheckHashability::no - : CheckHashability::yes>>>; -}; - -// An identity metafunction -template <class T> -struct Always { - using type = T; -}; - -// Validate the T meets all of the necessary requirements of LogicalProf, with -// syntactic requirements defined by the profile range [MinProf, MaxProf]. -template <class T, class LogicalProf, class MinProf, class MaxProf, - class... EqClasses> -ConformanceErrors ExpectRegularityImpl( - OrderedEquivalenceClasses<EqClasses...> vals) { - ConformanceErrors errors((NameOf<T>())); - - If<!constexpr_instantiation_when_unevaluated()>::Invoke( - ExpectModels<T, MinProf, MaxProf>(), &errors); - - using minimal_profile = typename absl::conditional_t< - constexpr_instantiation_when_unevaluated(), Always<LogicalProf>, - MinimalCheckableProfile<LogicalProf, T>>::type; - - If<PropertiesOfT<minimal_profile>::is_default_constructible>::Invoke( - ExpectDefaultConstructWithDestruct<T>{&errors}); - - ////////////////////////////////////////////////////////////////////////////// - // Perform all comparison checks first, since later checks depend on their - // correctness. - // - // Check all of the comparisons for all values in the same equivalence - // class (equal with respect to comparison operators and hash the same). - (ForEachTupleElement)( - ExpectEquivalenceClassComparisons<T, minimal_profile>{&errors}, - vals.eq_classes); - - // Check all of the comparisons for each combination of values that are in - // different equivalence classes (not equal with respect to comparison - // operators). - absl::apply( - ExpectOrderedEquivalenceClassesComparisons<T, minimal_profile>{&errors}, - vals.eq_classes); - // - ////////////////////////////////////////////////////////////////////////////// - - // Perform remaining checks, relying on comparisons. - // TODO(calabrese) short circuit if any comparisons above failed. - (ForEachTupleElement)(ExpectEquivalenceClass<T, minimal_profile>{&errors}, - vals.eq_classes); - - absl::apply(ExpectOrderedEquivalenceClasses<T, minimal_profile>{&errors}, - vals.eq_classes); - - return errors; -} - -// A type that represents a range of profiles that are acceptable to be matched. -// -// `MinProf` is the minimum set of syntactic requirements that must be met. -// -// `MaxProf` is the maximum set of syntactic requirements that must be met. -// This maximum is particularly useful for certain "strictness" checking. Some -// examples for when this is useful: -// -// * Making sure that a type is move-only (rather than simply movable) -// -// * Making sure that a member function is *not* noexcept in cases where it -// cannot be noexcept, such as if a dependent datamember has certain -// operations that are not noexcept. -// -// * Making sure that a type tightly matches a spec, such as the standard. -// -// `LogicalProf` is the Profile for which run-time testing is to take place. -// -// Note: The reason for `LogicalProf` is because it is often the case, when -// dealing with templates, that a declaration of a given operation is specified, -// but whose body would fail to instantiate. Examples include the -// copy-constructor of a standard container when the element-type is move-only, -// or the comparison operators of a standard container when the element-type -// does not have the necessary comparison operations defined. The `LogicalProf` -// parameter allows us to capture the intent of what should be tested at -// run-time, even in the cases where syntactically it might otherwise appear as -// though the type undergoing testing supports more than it actually does. -template <class LogicalProf, class MinProf = LogicalProf, - class MaxProf = MinProf> -struct ProfileRange { - using logical_profile = LogicalProf; - using min_profile = MinProf; - using max_profile = MaxProf; -}; - -// Similar to ProfileRange except that it creates a profile range that is -// coupled with a Domain and is used when testing that a type matches exactly -// the "minimum" requirements of LogicalProf. -template <class StrictnessDomain, class LogicalProf, - class MinProf = LogicalProf, class MaxProf = MinProf> -struct StrictProfileRange { - // We do not yet support extension. - static_assert( - std::is_same<StrictnessDomain, RegularityDomain>::value, - "Currently, the only valid StrictnessDomain is RegularityDomain."); - using strictness_domain = StrictnessDomain; - using logical_profile = LogicalProf; - using min_profile = MinProf; - using max_profile = MaxProf; -}; - -//////////////////////////////////////////////////////////////////////////////// -// -// A metafunction that creates a StrictProfileRange from a Domain and either a -// Profile or ProfileRange. -template <class StrictnessDomain, class ProfOrRange> -struct MakeStrictProfileRange; - -template <class StrictnessDomain, class LogicalProf> -struct MakeStrictProfileRange { - using type = StrictProfileRange<StrictnessDomain, LogicalProf>; -}; - -template <class StrictnessDomain, class LogicalProf, class MinProf, - class MaxProf> -struct MakeStrictProfileRange<StrictnessDomain, - ProfileRange<LogicalProf, MinProf, MaxProf>> { - using type = - StrictProfileRange<StrictnessDomain, LogicalProf, MinProf, MaxProf>; -}; - -template <class StrictnessDomain, class ProfOrRange> -using MakeStrictProfileRangeT = - typename MakeStrictProfileRange<StrictnessDomain, ProfOrRange>::type; -// -//////////////////////////////////////////////////////////////////////////////// - -// A profile in the RegularityDomain with the strongest possible requirements. -using MostStrictProfile = - CombineProfiles<TriviallyCompleteProfile, NothrowComparableProfile>; - -// Forms a ProfileRange that treats the Profile as the bare minimum requirements -// of a type. -template <class LogicalProf, class MinProf = LogicalProf> -using LooseProfileRange = StrictProfileRange<RegularityDomain, LogicalProf, - MinProf, MostStrictProfile>; - -template <class Prof> -using MakeLooseProfileRangeT = Prof; - -//////////////////////////////////////////////////////////////////////////////// -// -// The following classes implement the metafunction ProfileRangeOfT<T> that -// takes either a Profile or ProfileRange and yields the ProfileRange to be -// used during testing. -// -template <class T, class /*Enabler*/ = void> -struct ProfileRangeOfImpl; - -template <class T> -struct ProfileRangeOfImpl<T, absl::void_t<PropertiesOfT<T>>> { - using type = LooseProfileRange<T>; -}; - -template <class T> -struct ProfileRangeOf : ProfileRangeOfImpl<T> {}; - -template <class StrictnessDomain, class LogicalProf, class MinProf, - class MaxProf> -struct ProfileRangeOf< - StrictProfileRange<StrictnessDomain, LogicalProf, MinProf, MaxProf>> { - using type = - StrictProfileRange<StrictnessDomain, LogicalProf, MinProf, MaxProf>; -}; - -template <class T> -using ProfileRangeOfT = typename ProfileRangeOf<T>::type; -// -//////////////////////////////////////////////////////////////////////////////// - -// Extract the logical profile of a range (what will be runtime tested). -template <class T> -using LogicalProfileOfT = typename ProfileRangeOfT<T>::logical_profile; - -// Extract the minimal syntactic profile of a range (error if not at least). -template <class T> -using MinProfileOfT = typename ProfileRangeOfT<T>::min_profile; - -// Extract the maximum syntactic profile of a range (error if more than). -template <class T> -using MaxProfileOfT = typename ProfileRangeOfT<T>::max_profile; - -//////////////////////////////////////////////////////////////////////////////// -// -template <class T> -struct IsProfileOrProfileRange : IsProfile<T>::type {}; - -template <class StrictnessDomain, class LogicalProf, class MinProf, - class MaxProf> -struct IsProfileOrProfileRange< - StrictProfileRange<StrictnessDomain, LogicalProf, MinProf, MaxProf>> - : std::true_type {}; -// -//////////////////////////////////////////////////////////////////////////////// - -// TODO(calabrese): Consider naming the functions in this class the same as -// the macros (defined later on) so that auto-complete leads to the correct name -// and so that a user cannot accidentally call a function rather than the macro -// form. -template <bool ExpectSuccess, class T, class... EqClasses> -struct ExpectConformanceOf { - // Add a value to be tested. Subsequent calls to this function on the same - // object must specify logically "larger" values with respect to the - // comparison operators of the type, if any. - // - // NOTE: This function should not be called directly. A stateless lambda is - // implicitly formed and passed when using the INITIALIZER macro at the bottom - // of this file. - template <class Fun, - absl::enable_if_t<std::is_same< - ResultOfGeneratorT<GeneratorType<Fun>>, T>::value>** = nullptr> - ABSL_MUST_USE_RESULT ExpectConformanceOf<ExpectSuccess, T, EqClasses..., - EquivalenceClassType<Fun>> - initializer(GeneratorType<Fun> fun) && { - return { - {std::tuple_cat(absl::move(ordered_vals.eq_classes), - std::make_tuple((EquivalenceClass)(absl::move(fun))))}, - std::move(expected_failed_tests)}; - } - - template <class... TestNames, - absl::enable_if_t<!ExpectSuccess && sizeof...(EqClasses) == 0 && - absl::conjunction<std::is_convertible< - TestNames, absl::string_view>...>::value>** = - nullptr> - ABSL_MUST_USE_RESULT ExpectConformanceOf<ExpectSuccess, T, EqClasses...> - due_to(TestNames&&... test_names) && { - (InsertEach)(&expected_failed_tests, - absl::AsciiStrToLower(absl::string_view(test_names))...); - - return {absl::move(ordered_vals), std::move(expected_failed_tests)}; - } - - template <class... TestNames, int = 0, // MSVC disambiguator - absl::enable_if_t<ExpectSuccess && sizeof...(EqClasses) == 0 && - absl::conjunction<std::is_convertible< - TestNames, absl::string_view>...>::value>** = - nullptr> - ABSL_MUST_USE_RESULT ExpectConformanceOf<ExpectSuccess, T, EqClasses...> - due_to(TestNames&&... test_names) && { - // TODO(calabrese) Instead have DUE_TO only exist via a CRTP base. - // This would produce better errors messages than the static_assert. - static_assert(!ExpectSuccess, - "DUE_TO cannot be called when conformance is expected -- did " - "you mean to use ASSERT_NONCONFORMANCE_OF?"); - } - - // Add a value to be tested. Subsequent calls to this function on the same - // object must specify logically "larger" values with respect to the - // comparison operators of the type, if any. - // - // NOTE: This function should not be called directly. A stateful lambda is - // implicitly formed and passed when using the INITIALIZER macro at the bottom - // of this file. - template <class Fun, - absl::enable_if_t<std::is_same< - ResultOfGeneratorT<GeneratorType<Fun>>, T>::value>** = nullptr> - ABSL_MUST_USE_RESULT ExpectConformanceOf<ExpectSuccess, T, EqClasses..., - EquivalenceClassType<Fun>> - dont_class_directly_stateful_initializer(GeneratorType<Fun> fun) && { - return { - {std::tuple_cat(absl::move(ordered_vals.eq_classes), - std::make_tuple((EquivalenceClass)(absl::move(fun))))}, - std::move(expected_failed_tests)}; - } - - // Add a set of value to be tested, where each value is equal with respect to - // the comparison operators and std::hash specialization, if defined. - template < - class... Funs, - absl::void_t<absl::enable_if_t<std::is_same< - ResultOfGeneratorT<GeneratorType<Funs>>, T>::value>...>** = nullptr> - ABSL_MUST_USE_RESULT ExpectConformanceOf<ExpectSuccess, T, EqClasses..., - EquivalenceClassType<Funs...>> - equivalence_class(GeneratorType<Funs>... funs) && { - return {{std::tuple_cat( - absl::move(ordered_vals.eq_classes), - std::make_tuple((EquivalenceClass)(absl::move(funs)...)))}, - std::move(expected_failed_tests)}; - } - - // Execute the tests for the captured set of values, strictly matching a range - // of expected profiles in a given domain. - template < - class ProfRange, - absl::enable_if_t<IsProfileOrProfileRange<ProfRange>::value>** = nullptr> - ABSL_MUST_USE_RESULT ::testing::AssertionResult with_strict_profile( - ProfRange /*profile*/) { - ConformanceErrors test_result = - (ExpectRegularityImpl< - T, LogicalProfileOfT<ProfRange>, MinProfileOfT<ProfRange>, - MaxProfileOfT<ProfRange>>)(absl::move(ordered_vals)); - - return ExpectSuccess ? test_result.assertionResult() - : test_result.expectFailedTests(expected_failed_tests); - } - - // Execute the tests for the captured set of values, loosely matching a range - // of expected profiles (loose in that an interface is allowed to be more - // refined that a profile suggests, such as a type having a noexcept copy - // constructor when all that is required is that the copy constructor exists). - template <class Prof, absl::enable_if_t<IsProfile<Prof>::value>** = nullptr> - ABSL_MUST_USE_RESULT ::testing::AssertionResult with_loose_profile( - Prof /*profile*/) { - ConformanceErrors test_result = - (ExpectRegularityImpl< - T, Prof, Prof, - CombineProfiles<TriviallyCompleteProfile, - NothrowComparableProfile>>)(absl:: - move(ordered_vals)); - - return ExpectSuccess ? test_result.assertionResult() - : test_result.expectFailedTests(expected_failed_tests); - } - - OrderedEquivalenceClasses<EqClasses...> ordered_vals; - std::set<std::string> expected_failed_tests; -}; - -template <class T> -using ExpectConformanceOfType = ExpectConformanceOf</*ExpectSuccess=*/true, T>; - -template <class T> -using ExpectNonconformanceOfType = - ExpectConformanceOf</*ExpectSuccess=*/false, T>; - -struct EquivalenceClassMaker { - // TODO(calabrese) Constrain to callable - template <class Fun> - static GeneratorType<Fun> initializer(GeneratorType<Fun> fun) { - return fun; - } -}; - -// A top-level macro that begins the builder pattern. -// -// The argument here takes the datatype to be tested. -#define ABSL_INTERNAL_ASSERT_CONFORMANCE_OF(...) \ - GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ - if ABSL_INTERNAL_LPAREN \ - const ::testing::AssertionResult gtest_ar = \ - ABSL_INTERNAL_LPAREN ::absl::types_internal::ExpectConformanceOfType< \ - __VA_ARGS__>() - -// Akin to ASSERT_CONFORMANCE_OF except that it expects failure and tries to -// match text. -#define ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(...) \ - GTEST_AMBIGUOUS_ELSE_BLOCKER_ \ - if ABSL_INTERNAL_LPAREN \ - const ::testing::AssertionResult gtest_ar = \ - ABSL_INTERNAL_LPAREN ::absl::types_internal::ExpectNonconformanceOfType< \ - __VA_ARGS__>() - -//////////////////////////////////////////////////////////////////////////////// -// NOTE: The following macros look like they are recursive, but are not (macros -// cannot recurse). These actually refer to member functions of the same name. -// This is done intentionally so that a user cannot accidentally invoke a -// member function of the conformance-testing suite without going through the -// macro. -//////////////////////////////////////////////////////////////////////////////// - -// Specify expected test failures as comma-separated strings. -#define DUE_TO(...) due_to(__VA_ARGS__) - -// Specify a value to be tested. -// -// Note: Internally, this takes an expression and turns it into the return value -// of lambda that captures no data. The expression is stringized during -// preprocessing so that it can be used in error reports. -#define INITIALIZER(...) \ - initializer(::absl::types_internal::Generator( \ - [] { return __VA_ARGS__; }, ABSL_INTERNAL_STRINGIZE(__VA_ARGS__))) - -// Specify a value to be tested. -// -// Note: Internally, this takes an expression and turns it into the return value -// of lambda that captures data by reference. The expression is stringized -// during preprocessing so that it can be used in error reports. -#define STATEFUL_INITIALIZER(...) \ - stateful_initializer(::absl::types_internal::Generator( \ - [&] { return __VA_ARGS__; }, ABSL_INTERNAL_STRINGIZE(__VA_ARGS__))) - -// Used in the builder-pattern. -// -// Takes a series of INITIALIZER and/or STATEFUL_INITIALIZER invocations and -// forwards them along to be tested, grouping them such that the testing suite -// knows that they are supposed to represent the same logical value (the values -// compare the same, hash the same, etc.). -#define EQUIVALENCE_CLASS(...) \ - equivalence_class(ABSL_INTERNAL_TRANSFORM_ARGS( \ - ABSL_INTERNAL_PREPEND_EQ_MAKER, __VA_ARGS__)) - -// An invocation of this or WITH_STRICT_PROFILE must end the builder-pattern. -// It takes a Profile as its argument. -// -// This executes the tests and allows types that are "more referined" than the -// profile specifies, but not less. For instance, if the Profile specifies -// noexcept copy-constructiblity, the test will fail if the copy-constructor is -// not noexcept, however, it will succeed if the copy constructor is trivial. -// -// This is useful for testing that a type meets some minimum set of -// requirements. -#define WITH_LOOSE_PROFILE(...) \ - with_loose_profile( \ - ::absl::types_internal::MakeLooseProfileRangeT<__VA_ARGS__>()) \ - ABSL_INTERNAL_RPAREN ABSL_INTERNAL_RPAREN; \ - else GTEST_FATAL_FAILURE_(gtest_ar.failure_message()) // NOLINT - -// An invocation of this or WITH_STRICT_PROFILE must end the builder-pattern. -// It takes a Domain and a Profile as its arguments. -// -// This executes the tests and disallows types that differ at all from the -// properties of the Profile. For instance, if the Profile specifies noexcept -// copy-constructiblity, the test will fail if the copy constructor is trivial. -// -// This is useful for testing that a type does not do anything more than a -// specification requires, such as to minimize things like Hyrum's Law, or more -// commonly, to prevent a type from being "accidentally" copy-constructible in -// a way that may produce incorrect results, simply because the user forget to -// delete that operation. -#define WITH_STRICT_PROFILE(...) \ - with_strict_profile( \ - ::absl::types_internal::MakeStrictProfileRangeT<__VA_ARGS__>()) \ - ABSL_INTERNAL_RPAREN ABSL_INTERNAL_RPAREN; \ - else GTEST_FATAL_FAILURE_(gtest_ar.failure_message()) // NOLINT - -// Internal macro that is used in the internals of the EDSL when forming -// equivalence classes. -#define ABSL_INTERNAL_PREPEND_EQ_MAKER(arg) \ - ::absl::types_internal::EquivalenceClassMaker().arg - -} // namespace types_internal -ABSL_NAMESPACE_END -} // namespace absl - -#endif // ABSL_TYPES_INTERNAL_CONFORMANCE_TESTING_H_ diff --git a/absl/types/internal/conformance_testing_helpers.h b/absl/types/internal/conformance_testing_helpers.h deleted file mode 100644 index 00775f96..00000000 --- a/absl/types/internal/conformance_testing_helpers.h +++ /dev/null @@ -1,391 +0,0 @@ -// Copyright 2019 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_TYPES_INTERNAL_CONFORMANCE_TESTING_HELPERS_H_ -#define ABSL_TYPES_INTERNAL_CONFORMANCE_TESTING_HELPERS_H_ - -// Checks to determine whether or not we can use abi::__cxa_demangle -#if (defined(__ANDROID__) || defined(ANDROID)) && !defined(OS_ANDROID) -#define ABSL_INTERNAL_OS_ANDROID -#endif - -// We support certain compilers only. See demangle.h for details. -#if defined(OS_ANDROID) && (defined(__i386__) || defined(__x86_64__)) -#define ABSL_TYPES_INTERNAL_HAS_CXA_DEMANGLE 0 -#elif (__GNUC__ >= 4 || (__GNUC__ >= 3 && __GNUC_MINOR__ >= 4)) && \ - !defined(__mips__) -#define ABSL_TYPES_INTERNAL_HAS_CXA_DEMANGLE 1 -#elif defined(__clang__) && !defined(_MSC_VER) -#define ABSL_TYPES_INTERNAL_HAS_CXA_DEMANGLE 1 -#else -#define ABSL_TYPES_INTERNAL_HAS_CXA_DEMANGLE 0 -#endif - -#include <tuple> -#include <type_traits> -#include <utility> - -#include "absl/meta/type_traits.h" -#include "absl/strings/string_view.h" -#include "absl/utility/utility.h" - -#if ABSL_TYPES_INTERNAL_HAS_CXA_DEMANGLE -#include <cxxabi.h> - -#include <cstdlib> -#endif - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace types_internal { - -// Return a readable name for type T. -template <class T> -absl::string_view NameOfImpl() { -// TODO(calabrese) Investigate using debugging:internal_demangle as a fallback. -#if ABSL_TYPES_INTERNAL_HAS_CXA_DEMANGLE - int status = 0; - char* demangled_name = nullptr; - - demangled_name = - abi::__cxa_demangle(typeid(T).name(), nullptr, nullptr, &status); - - if (status == 0 && demangled_name != nullptr) { - return demangled_name; - } else { - return typeid(T).name(); - } -#else - return typeid(T).name(); -#endif - // NOTE: We intentionally leak demangled_name so that it remains valid - // throughout the remainder of the program. -} - -// Given a type, returns as nice of a type name as we can produce (demangled). -// -// Note: This currently strips cv-qualifiers and references, but that is okay -// because we only use this internally with unqualified object types. -template <class T> -std::string NameOf() { - static const absl::string_view result = NameOfImpl<T>(); - return std::string(result); -} - -//////////////////////////////////////////////////////////////////////////////// -// -// Metafunction to check if a type is callable with no explicit arguments -template <class Fun, class /*Enabler*/ = void> -struct IsNullaryCallableImpl : std::false_type {}; - -template <class Fun> -struct IsNullaryCallableImpl< - Fun, absl::void_t<decltype(std::declval<const Fun&>()())>> - : std::true_type { - using result_type = decltype(std::declval<const Fun&>()()); - - template <class ValueType> - using for_type = std::is_same<ValueType, result_type>; - - using void_if_true = void; -}; - -template <class Fun> -struct IsNullaryCallable : IsNullaryCallableImpl<Fun> {}; -// -//////////////////////////////////////////////////////////////////////////////// - -// A type that contains a function object that returns an instance of a type -// that is undergoing conformance testing. This function is required to always -// return the same value upon invocation. -template <class Fun> -struct GeneratorType; - -// A type that contains a tuple of GeneratorType<Fun> where each Fun has the -// same return type. The result of each of the different generators should all -// be equal values, though the underlying object representation may differ (such -// as if one returns 0.0 and another return -0.0, or if one returns an empty -// vector and another returns an empty vector with a different capacity. -template <class... Funs> -struct EquivalenceClassType; - -//////////////////////////////////////////////////////////////////////////////// -// -// A metafunction to check if a type is a specialization of EquivalenceClassType -template <class T> -struct IsEquivalenceClass : std::false_type {}; - -template <> -struct IsEquivalenceClass<EquivalenceClassType<>> : std::true_type { - using self = IsEquivalenceClass; - - // A metafunction to check if this EquivalenceClassType is a valid - // EquivalenceClassType for a type `ValueType` that is undergoing testing - template <class ValueType> - using for_type = std::true_type; -}; - -template <class Head, class... Tail> -struct IsEquivalenceClass<EquivalenceClassType<Head, Tail...>> - : std::true_type { - using self = IsEquivalenceClass; - - // The type undergoing conformance testing that this EquivalenceClass - // corresponds to - using result_type = typename IsNullaryCallable<Head>::result_type; - - // A metafunction to check if this EquivalenceClassType is a valid - // EquivalenceClassType for a type `ValueType` that is undergoing testing - template <class ValueType> - using for_type = std::is_same<ValueType, result_type>; -}; -// -//////////////////////////////////////////////////////////////////////////////// - -// A type that contains an ordered series of EquivalenceClassTypes, where the -// the function object of each underlying GeneratorType has the same return type -// -// These equivalence classes are required to be in a logical ascending order -// that is consistent with comparison operators that are defined for the return -// type of each GeneratorType, if any. -template <class... EqClasses> -struct OrderedEquivalenceClasses; - -//////////////////////////////////////////////////////////////////////////////// -// -// A metafunction to determine the return type of the function object contained -// in a GeneratorType specialization. -template <class T> -struct ResultOfGenerator {}; - -template <class Fun> -struct ResultOfGenerator<GeneratorType<Fun>> { - using type = decltype(std::declval<const Fun&>()()); -}; - -template <class Fun> -using ResultOfGeneratorT = typename ResultOfGenerator<GeneratorType<Fun>>::type; -// -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// -// A metafunction that yields true iff each of Funs is a GeneratorType -// specialization and they all contain functions with the same return type -template <class /*Enabler*/, class... Funs> -struct AreGeneratorsWithTheSameReturnTypeImpl : std::false_type {}; - -template <> -struct AreGeneratorsWithTheSameReturnTypeImpl<void> : std::true_type {}; - -template <class Head, class... Tail> -struct AreGeneratorsWithTheSameReturnTypeImpl< - typename std::enable_if<absl::conjunction<std::is_same< - ResultOfGeneratorT<Head>, ResultOfGeneratorT<Tail>>...>::value>::type, - Head, Tail...> : std::true_type {}; - -template <class... Funs> -struct AreGeneratorsWithTheSameReturnType - : AreGeneratorsWithTheSameReturnTypeImpl<void, Funs...>::type {}; -// -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// -// A metafunction that yields true iff each of Funs is an EquivalenceClassType -// specialization and they all contain GeneratorType specializations that have -// the same return type -template <class... EqClasses> -struct AreEquivalenceClassesOfTheSameType { - static_assert(sizeof...(EqClasses) != sizeof...(EqClasses), ""); -}; - -template <> -struct AreEquivalenceClassesOfTheSameType<> : std::true_type { - using self = AreEquivalenceClassesOfTheSameType; - - // Metafunction to check that a type is the same as all of the equivalence - // classes, if any. - // Note: In this specialization there are no equivalence classes, so the - // value type is always compatible. - template <class /*ValueType*/> - using for_type = std::true_type; -}; - -template <class... Funs> -struct AreEquivalenceClassesOfTheSameType<EquivalenceClassType<Funs...>> - : std::true_type { - using self = AreEquivalenceClassesOfTheSameType; - - // Metafunction to check that a type is the same as all of the equivalence - // classes, if any. - template <class ValueType> - using for_type = typename IsEquivalenceClass< - EquivalenceClassType<Funs...>>::template for_type<ValueType>; -}; - -template <class... TailEqClasses> -struct AreEquivalenceClassesOfTheSameType< - EquivalenceClassType<>, EquivalenceClassType<>, TailEqClasses...> - : AreEquivalenceClassesOfTheSameType<TailEqClasses...>::self {}; - -template <class HeadNextFun, class... TailNextFuns, class... TailEqClasses> -struct AreEquivalenceClassesOfTheSameType< - EquivalenceClassType<>, EquivalenceClassType<HeadNextFun, TailNextFuns...>, - TailEqClasses...> - : AreEquivalenceClassesOfTheSameType< - EquivalenceClassType<HeadNextFun, TailNextFuns...>, - TailEqClasses...>::self {}; - -template <class HeadHeadFun, class... TailHeadFuns, class... TailEqClasses> -struct AreEquivalenceClassesOfTheSameType< - EquivalenceClassType<HeadHeadFun, TailHeadFuns...>, EquivalenceClassType<>, - TailEqClasses...> - : AreEquivalenceClassesOfTheSameType< - EquivalenceClassType<HeadHeadFun, TailHeadFuns...>, - TailEqClasses...>::self {}; - -template <class HeadHeadFun, class... TailHeadFuns, class HeadNextFun, - class... TailNextFuns, class... TailEqClasses> -struct AreEquivalenceClassesOfTheSameType< - EquivalenceClassType<HeadHeadFun, TailHeadFuns...>, - EquivalenceClassType<HeadNextFun, TailNextFuns...>, TailEqClasses...> - : absl::conditional_t< - IsNullaryCallable<HeadNextFun>::template for_type< - typename IsNullaryCallable<HeadHeadFun>::result_type>::value, - AreEquivalenceClassesOfTheSameType< - EquivalenceClassType<HeadHeadFun, TailHeadFuns...>, - TailEqClasses...>, - std::false_type> {}; -// -//////////////////////////////////////////////////////////////////////////////// - -// Execute a function for each passed-in parameter. -template <class Fun, class... Cases> -void ForEachParameter(const Fun& fun, const Cases&... cases) { - const std::initializer_list<bool> results = { - (static_cast<void>(fun(cases)), true)...}; - - (void)results; -} - -// Execute a function on each passed-in parameter (using a bound function). -template <class Fun> -struct ForEachParameterFun { - template <class... T> - void operator()(const T&... cases) const { - (ForEachParameter)(fun, cases...); - } - - Fun fun; -}; - -// Execute a function on each element of a tuple. -template <class Fun, class Tup> -void ForEachTupleElement(const Fun& fun, const Tup& tup) { - absl::apply(ForEachParameterFun<Fun>{fun}, tup); -} - -//////////////////////////////////////////////////////////////////////////////// -// -// Execute a function for each combination of two elements of a tuple, including -// combinations of an element with itself. -template <class Fun, class... T> -struct ForEveryTwoImpl { - template <class Lhs> - struct WithBoundLhs { - template <class Rhs> - void operator()(const Rhs& rhs) const { - fun(lhs, rhs); - } - - Fun fun; - Lhs lhs; - }; - - template <class Lhs> - void operator()(const Lhs& lhs) const { - (ForEachTupleElement)(WithBoundLhs<Lhs>{fun, lhs}, args); - } - - Fun fun; - std::tuple<T...> args; -}; - -template <class Fun, class... T> -void ForEveryTwo(const Fun& fun, std::tuple<T...> args) { - (ForEachTupleElement)(ForEveryTwoImpl<Fun, T...>{fun, args}, args); -} -// -//////////////////////////////////////////////////////////////////////////////// - -//////////////////////////////////////////////////////////////////////////////// -// -// Insert all values into an associative container -template<class Container> -void InsertEach(Container* cont) { -} - -template<class Container, class H, class... T> -void InsertEach(Container* cont, H&& head, T&&... tail) { - cont->insert(head); - (InsertEach)(cont, tail...); -} -// -//////////////////////////////////////////////////////////////////////////////// -// A template with a nested "Invoke" static-member-function that executes a -// passed-in Callable when `Condition` is true, otherwise it ignores the -// Callable. This is useful for executing a function object with a condition -// that corresponds to whether or not the Callable can be safely instantiated. -// It has some overlapping uses with C++17 `if constexpr`. -template <bool Condition> -struct If; - -template <> -struct If</*Condition =*/false> { - template <class Fun, class... P> - static void Invoke(const Fun& /*fun*/, P&&... /*args*/) {} -}; - -template <> -struct If</*Condition =*/true> { - template <class Fun, class... P> - static void Invoke(const Fun& fun, P&&... args) { - // TODO(calabrese) Use std::invoke equivalent instead of function-call. - fun(absl::forward<P>(args)...); - } -}; - -// -// ABSL_INTERNAL_STRINGIZE(...) -// -// This variadic macro transforms its arguments into a c-string literal after -// expansion. -// -// Example: -// -// ABSL_INTERNAL_STRINGIZE(std::array<int, 10>) -// -// Results in: -// -// "std::array<int, 10>" -#define ABSL_INTERNAL_STRINGIZE(...) ABSL_INTERNAL_STRINGIZE_IMPL((__VA_ARGS__)) -#define ABSL_INTERNAL_STRINGIZE_IMPL(arg) ABSL_INTERNAL_STRINGIZE_IMPL2 arg -#define ABSL_INTERNAL_STRINGIZE_IMPL2(...) #__VA_ARGS__ - -} // namespace types_internal -ABSL_NAMESPACE_END -} // namespace absl - -#endif // ABSL_TYPES_INTERNAL_CONFORMANCE_TESTING_HELPERS_H_ diff --git a/absl/types/internal/conformance_testing_test.cc b/absl/types/internal/conformance_testing_test.cc deleted file mode 100644 index cf262fa6..00000000 --- a/absl/types/internal/conformance_testing_test.cc +++ /dev/null @@ -1,1556 +0,0 @@ -// Copyright 2019 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/types/internal/conformance_testing.h" - -#include <new> -#include <type_traits> -#include <utility> - -#include "gtest/gtest.h" -#include "absl/meta/type_traits.h" -#include "absl/types/internal/conformance_aliases.h" -#include "absl/types/internal/conformance_profile.h" - -namespace { - -namespace ti = absl::types_internal; - -template <class T> -using DefaultConstructibleWithNewImpl = decltype(::new (std::nothrow) T); - -template <class T> -using DefaultConstructibleWithNew = - absl::type_traits_internal::is_detected<DefaultConstructibleWithNewImpl, T>; - -template <class T> -using MoveConstructibleWithNewImpl = - decltype(::new (std::nothrow) T(std::declval<T>())); - -template <class T> -using MoveConstructibleWithNew = - absl::type_traits_internal::is_detected<MoveConstructibleWithNewImpl, T>; - -template <class T> -using CopyConstructibleWithNewImpl = - decltype(::new (std::nothrow) T(std::declval<const T&>())); - -template <class T> -using CopyConstructibleWithNew = - absl::type_traits_internal::is_detected<CopyConstructibleWithNewImpl, T>; - -template <class T, - class Result = - std::integral_constant<bool, noexcept(::new (std::nothrow) T)>> -using NothrowDefaultConstructibleWithNewImpl = - typename std::enable_if<Result::value>::type; - -template <class T> -using NothrowDefaultConstructibleWithNew = - absl::type_traits_internal::is_detected< - NothrowDefaultConstructibleWithNewImpl, T>; - -template <class T, - class Result = std::integral_constant< - bool, noexcept(::new (std::nothrow) T(std::declval<T>()))>> -using NothrowMoveConstructibleWithNewImpl = - typename std::enable_if<Result::value>::type; - -template <class T> -using NothrowMoveConstructibleWithNew = - absl::type_traits_internal::is_detected<NothrowMoveConstructibleWithNewImpl, - T>; - -template <class T, - class Result = std::integral_constant< - bool, noexcept(::new (std::nothrow) T(std::declval<const T&>()))>> -using NothrowCopyConstructibleWithNewImpl = - typename std::enable_if<Result::value>::type; - -template <class T> -using NothrowCopyConstructibleWithNew = - absl::type_traits_internal::is_detected<NothrowCopyConstructibleWithNewImpl, - T>; - -// NOTE: ?: is used to verify contextually-convertible to bool and not simply -// implicit or explicit convertibility. -#define ABSL_INTERNAL_COMPARISON_OP_EXPR(op) \ - ((std::declval<const T&>() op std::declval<const T&>()) ? true : true) - -#define ABSL_INTERNAL_COMPARISON_OP_TRAIT(name, op) \ - template <class T> \ - using name##Impl = decltype(ABSL_INTERNAL_COMPARISON_OP_EXPR(op)); \ - \ - template <class T> \ - using name = absl::type_traits_internal::is_detected<name##Impl, T>; \ - \ - template <class T, \ - class Result = std::integral_constant< \ - bool, noexcept(ABSL_INTERNAL_COMPARISON_OP_EXPR(op))>> \ - using Nothrow##name##Impl = typename std::enable_if<Result::value>::type; \ - \ - template <class T> \ - using Nothrow##name = \ - absl::type_traits_internal::is_detected<Nothrow##name##Impl, T> - -ABSL_INTERNAL_COMPARISON_OP_TRAIT(EqualityComparable, ==); -ABSL_INTERNAL_COMPARISON_OP_TRAIT(InequalityComparable, !=); -ABSL_INTERNAL_COMPARISON_OP_TRAIT(LessThanComparable, <); -ABSL_INTERNAL_COMPARISON_OP_TRAIT(LessEqualComparable, <=); -ABSL_INTERNAL_COMPARISON_OP_TRAIT(GreaterEqualComparable, >=); -ABSL_INTERNAL_COMPARISON_OP_TRAIT(GreaterThanComparable, >); - -#undef ABSL_INTERNAL_COMPARISON_OP_TRAIT - -template <class T> -class ProfileTest : public ::testing::Test {}; - -TYPED_TEST_SUITE_P(ProfileTest); - -TYPED_TEST_P(ProfileTest, HasAppropriateConstructionProperties) { - using profile = typename TypeParam::profile; - using arch = typename TypeParam::arch; - using expected_profile = typename TypeParam::expected_profile; - - using props = ti::PropertiesOfT<profile>; - using arch_props = ti::PropertiesOfArchetypeT<arch>; - using expected_props = ti::PropertiesOfT<expected_profile>; - - // Make sure all of the properties are as expected. - // There are seemingly redundant tests here to make it easier to diagnose - // the specifics of the failure if something were to go wrong. - EXPECT_TRUE((std::is_same<props, arch_props>::value)); - EXPECT_TRUE((std::is_same<props, expected_props>::value)); - EXPECT_TRUE((std::is_same<arch_props, expected_props>::value)); - - EXPECT_EQ(props::default_constructible_support, - expected_props::default_constructible_support); - - EXPECT_EQ(props::move_constructible_support, - expected_props::move_constructible_support); - - EXPECT_EQ(props::copy_constructible_support, - expected_props::copy_constructible_support); - - EXPECT_EQ(props::destructible_support, expected_props::destructible_support); - - // Avoid additional error message noise when profile and archetype match with - // each other but were not what was expected. - if (!std::is_same<props, arch_props>::value) { - EXPECT_EQ(arch_props::default_constructible_support, - expected_props::default_constructible_support); - - EXPECT_EQ(arch_props::move_constructible_support, - expected_props::move_constructible_support); - - EXPECT_EQ(arch_props::copy_constructible_support, - expected_props::copy_constructible_support); - - EXPECT_EQ(arch_props::destructible_support, - expected_props::destructible_support); - } - - ////////////////////////////////////////////////////////////////////////////// - // Default constructor checks // - ////////////////////////////////////////////////////////////////////////////// - EXPECT_EQ(props::default_constructible_support, - expected_props::default_constructible_support); - - switch (expected_props::default_constructible_support) { - case ti::default_constructible::maybe: - EXPECT_FALSE(DefaultConstructibleWithNew<arch>::value); - EXPECT_FALSE(NothrowDefaultConstructibleWithNew<arch>::value); - - // Standard constructible traits depend on the destructor. - if (std::is_destructible<arch>::value) { - EXPECT_FALSE(std::is_default_constructible<arch>::value); - EXPECT_FALSE(std::is_nothrow_default_constructible<arch>::value); - EXPECT_FALSE(absl::is_trivially_default_constructible<arch>::value); - } - break; - case ti::default_constructible::yes: - EXPECT_TRUE(DefaultConstructibleWithNew<arch>::value); - EXPECT_FALSE(NothrowDefaultConstructibleWithNew<arch>::value); - - // Standard constructible traits depend on the destructor. - if (std::is_destructible<arch>::value) { - EXPECT_TRUE(std::is_default_constructible<arch>::value); - EXPECT_FALSE(std::is_nothrow_default_constructible<arch>::value); - EXPECT_FALSE(absl::is_trivially_default_constructible<arch>::value); - } - break; - case ti::default_constructible::nothrow: - EXPECT_TRUE(DefaultConstructibleWithNew<arch>::value); - EXPECT_TRUE(NothrowDefaultConstructibleWithNew<arch>::value); - - // Standard constructible traits depend on the destructor. - if (std::is_destructible<arch>::value) { - EXPECT_TRUE(std::is_default_constructible<arch>::value); - EXPECT_TRUE(std::is_nothrow_default_constructible<arch>::value); - EXPECT_FALSE(absl::is_trivially_default_constructible<arch>::value); - - // Constructor traits also check the destructor. - if (std::is_nothrow_destructible<arch>::value) { - EXPECT_TRUE(std::is_nothrow_default_constructible<arch>::value); - } - } - break; - case ti::default_constructible::trivial: - EXPECT_TRUE(DefaultConstructibleWithNew<arch>::value); - EXPECT_TRUE(NothrowDefaultConstructibleWithNew<arch>::value); - - // Standard constructible traits depend on the destructor. - if (std::is_destructible<arch>::value) { - EXPECT_TRUE(std::is_default_constructible<arch>::value); - EXPECT_TRUE(std::is_nothrow_default_constructible<arch>::value); - - // Constructor triviality traits require trivially destructible types. - if (absl::is_trivially_destructible<arch>::value) { - EXPECT_TRUE(absl::is_trivially_default_constructible<arch>::value); - } - } - break; - } - - ////////////////////////////////////////////////////////////////////////////// - // Move constructor checks // - ////////////////////////////////////////////////////////////////////////////// - EXPECT_EQ(props::move_constructible_support, - expected_props::move_constructible_support); - - switch (expected_props::move_constructible_support) { - case ti::move_constructible::maybe: - EXPECT_FALSE(MoveConstructibleWithNew<arch>::value); - EXPECT_FALSE(NothrowMoveConstructibleWithNew<arch>::value); - - // Standard constructible traits depend on the destructor. - if (std::is_destructible<arch>::value) { - EXPECT_FALSE(std::is_move_constructible<arch>::value); - EXPECT_FALSE(std::is_nothrow_move_constructible<arch>::value); - EXPECT_FALSE(absl::is_trivially_move_constructible<arch>::value); - } - break; - case ti::move_constructible::yes: - EXPECT_TRUE(MoveConstructibleWithNew<arch>::value); - EXPECT_FALSE(NothrowMoveConstructibleWithNew<arch>::value); - - // Standard constructible traits depend on the destructor. - if (std::is_destructible<arch>::value) { - EXPECT_TRUE(std::is_move_constructible<arch>::value); - EXPECT_FALSE(std::is_nothrow_move_constructible<arch>::value); - EXPECT_FALSE(absl::is_trivially_move_constructible<arch>::value); - } - break; - case ti::move_constructible::nothrow: - EXPECT_TRUE(MoveConstructibleWithNew<arch>::value); - EXPECT_TRUE(NothrowMoveConstructibleWithNew<arch>::value); - - // Standard constructible traits depend on the destructor. - if (std::is_destructible<arch>::value) { - EXPECT_TRUE(std::is_move_constructible<arch>::value); - EXPECT_TRUE(std::is_nothrow_move_constructible<arch>::value); - EXPECT_FALSE(absl::is_trivially_move_constructible<arch>::value); - - // Constructor traits also check the destructor. - if (std::is_nothrow_destructible<arch>::value) { - EXPECT_TRUE(std::is_nothrow_move_constructible<arch>::value); - } - } - break; - case ti::move_constructible::trivial: - EXPECT_TRUE(MoveConstructibleWithNew<arch>::value); - EXPECT_TRUE(NothrowMoveConstructibleWithNew<arch>::value); - - // Standard constructible traits depend on the destructor. - if (std::is_destructible<arch>::value) { - EXPECT_TRUE(std::is_move_constructible<arch>::value); - EXPECT_TRUE(std::is_nothrow_move_constructible<arch>::value); - - // Constructor triviality traits require trivially destructible types. - if (absl::is_trivially_destructible<arch>::value) { - EXPECT_TRUE(absl::is_trivially_move_constructible<arch>::value); - } - } - break; - } - - ////////////////////////////////////////////////////////////////////////////// - // Copy constructor checks // - ////////////////////////////////////////////////////////////////////////////// - EXPECT_EQ(props::copy_constructible_support, - expected_props::copy_constructible_support); - - switch (expected_props::copy_constructible_support) { - case ti::copy_constructible::maybe: - EXPECT_FALSE(CopyConstructibleWithNew<arch>::value); - EXPECT_FALSE(NothrowCopyConstructibleWithNew<arch>::value); - - // Standard constructible traits depend on the destructor. - if (std::is_destructible<arch>::value) { - EXPECT_FALSE(std::is_copy_constructible<arch>::value); - EXPECT_FALSE(std::is_nothrow_copy_constructible<arch>::value); - EXPECT_FALSE(absl::is_trivially_copy_constructible<arch>::value); - } - break; - case ti::copy_constructible::yes: - EXPECT_TRUE(CopyConstructibleWithNew<arch>::value); - EXPECT_FALSE(NothrowCopyConstructibleWithNew<arch>::value); - - // Standard constructible traits depend on the destructor. - if (std::is_destructible<arch>::value) { - EXPECT_TRUE(std::is_copy_constructible<arch>::value); - EXPECT_FALSE(std::is_nothrow_copy_constructible<arch>::value); - EXPECT_FALSE(absl::is_trivially_copy_constructible<arch>::value); - } - break; - case ti::copy_constructible::nothrow: - EXPECT_TRUE(CopyConstructibleWithNew<arch>::value); - EXPECT_TRUE(NothrowCopyConstructibleWithNew<arch>::value); - - // Standard constructible traits depend on the destructor. - if (std::is_destructible<arch>::value) { - EXPECT_TRUE(std::is_copy_constructible<arch>::value); - EXPECT_TRUE(std::is_nothrow_copy_constructible<arch>::value); - EXPECT_FALSE(absl::is_trivially_copy_constructible<arch>::value); - - // Constructor traits also check the destructor. - if (std::is_nothrow_destructible<arch>::value) { - EXPECT_TRUE(std::is_nothrow_copy_constructible<arch>::value); - } - } - break; - case ti::copy_constructible::trivial: - EXPECT_TRUE(CopyConstructibleWithNew<arch>::value); - EXPECT_TRUE(NothrowCopyConstructibleWithNew<arch>::value); - - // Standard constructible traits depend on the destructor. - if (std::is_destructible<arch>::value) { - EXPECT_TRUE(std::is_copy_constructible<arch>::value); - EXPECT_TRUE(std::is_nothrow_copy_constructible<arch>::value); - - // Constructor triviality traits require trivially destructible types. - if (absl::is_trivially_destructible<arch>::value) { - EXPECT_TRUE(absl::is_trivially_copy_constructible<arch>::value); - } - } - break; - } - - ////////////////////////////////////////////////////////////////////////////// - // Destructible checks // - ////////////////////////////////////////////////////////////////////////////// - EXPECT_EQ(props::destructible_support, expected_props::destructible_support); - - switch (expected_props::destructible_support) { - case ti::destructible::maybe: - EXPECT_FALSE(std::is_destructible<arch>::value); - EXPECT_FALSE(std::is_nothrow_destructible<arch>::value); - EXPECT_FALSE(absl::is_trivially_destructible<arch>::value); - break; - case ti::destructible::yes: - EXPECT_TRUE(std::is_destructible<arch>::value); - EXPECT_FALSE(std::is_nothrow_destructible<arch>::value); - EXPECT_FALSE(absl::is_trivially_destructible<arch>::value); - break; - case ti::destructible::nothrow: - EXPECT_TRUE(std::is_destructible<arch>::value); - EXPECT_TRUE(std::is_nothrow_destructible<arch>::value); - EXPECT_FALSE(absl::is_trivially_destructible<arch>::value); - break; - case ti::destructible::trivial: - EXPECT_TRUE(std::is_destructible<arch>::value); - EXPECT_TRUE(std::is_nothrow_destructible<arch>::value); - EXPECT_TRUE(absl::is_trivially_destructible<arch>::value); - break; - } -} - -TYPED_TEST_P(ProfileTest, HasAppropriateAssignmentProperties) { - using profile = typename TypeParam::profile; - using arch = typename TypeParam::arch; - using expected_profile = typename TypeParam::expected_profile; - - using props = ti::PropertiesOfT<profile>; - using arch_props = ti::PropertiesOfArchetypeT<arch>; - using expected_props = ti::PropertiesOfT<expected_profile>; - - // Make sure all of the properties are as expected. - // There are seemingly redundant tests here to make it easier to diagnose - // the specifics of the failure if something were to go wrong. - EXPECT_TRUE((std::is_same<props, arch_props>::value)); - EXPECT_TRUE((std::is_same<props, expected_props>::value)); - EXPECT_TRUE((std::is_same<arch_props, expected_props>::value)); - - EXPECT_EQ(props::move_assignable_support, - expected_props::move_assignable_support); - - EXPECT_EQ(props::copy_assignable_support, - expected_props::copy_assignable_support); - - // Avoid additional error message noise when profile and archetype match with - // each other but were not what was expected. - if (!std::is_same<props, arch_props>::value) { - EXPECT_EQ(arch_props::move_assignable_support, - expected_props::move_assignable_support); - - EXPECT_EQ(arch_props::copy_assignable_support, - expected_props::copy_assignable_support); - } - - ////////////////////////////////////////////////////////////////////////////// - // Move assignment checks // - ////////////////////////////////////////////////////////////////////////////// - EXPECT_EQ(props::move_assignable_support, - expected_props::move_assignable_support); - - switch (expected_props::move_assignable_support) { - case ti::move_assignable::maybe: - EXPECT_FALSE(std::is_move_assignable<arch>::value); - EXPECT_FALSE(std::is_nothrow_move_assignable<arch>::value); - EXPECT_FALSE(absl::is_trivially_move_assignable<arch>::value); - break; - case ti::move_assignable::yes: - EXPECT_TRUE(std::is_move_assignable<arch>::value); - EXPECT_FALSE(std::is_nothrow_move_assignable<arch>::value); - EXPECT_FALSE(absl::is_trivially_move_assignable<arch>::value); - break; - case ti::move_assignable::nothrow: - EXPECT_TRUE(std::is_move_assignable<arch>::value); - EXPECT_TRUE(std::is_nothrow_move_assignable<arch>::value); - EXPECT_FALSE(absl::is_trivially_move_assignable<arch>::value); - break; - case ti::move_assignable::trivial: - EXPECT_TRUE(std::is_move_assignable<arch>::value); - EXPECT_TRUE(std::is_nothrow_move_assignable<arch>::value); - EXPECT_TRUE(absl::is_trivially_move_assignable<arch>::value); - break; - } - - ////////////////////////////////////////////////////////////////////////////// - // Copy assignment checks // - ////////////////////////////////////////////////////////////////////////////// - EXPECT_EQ(props::copy_assignable_support, - expected_props::copy_assignable_support); - - switch (expected_props::copy_assignable_support) { - case ti::copy_assignable::maybe: - EXPECT_FALSE(std::is_copy_assignable<arch>::value); - EXPECT_FALSE(std::is_nothrow_copy_assignable<arch>::value); - EXPECT_FALSE(absl::is_trivially_copy_assignable<arch>::value); - break; - case ti::copy_assignable::yes: - EXPECT_TRUE(std::is_copy_assignable<arch>::value); - EXPECT_FALSE(std::is_nothrow_copy_assignable<arch>::value); - EXPECT_FALSE(absl::is_trivially_copy_assignable<arch>::value); - break; - case ti::copy_assignable::nothrow: - EXPECT_TRUE(std::is_copy_assignable<arch>::value); - EXPECT_TRUE(std::is_nothrow_copy_assignable<arch>::value); - EXPECT_FALSE(absl::is_trivially_copy_assignable<arch>::value); - break; - case ti::copy_assignable::trivial: - EXPECT_TRUE(std::is_copy_assignable<arch>::value); - EXPECT_TRUE(std::is_nothrow_copy_assignable<arch>::value); - EXPECT_TRUE(absl::is_trivially_copy_assignable<arch>::value); - break; - } -} - -TYPED_TEST_P(ProfileTest, HasAppropriateComparisonProperties) { - using profile = typename TypeParam::profile; - using arch = typename TypeParam::arch; - using expected_profile = typename TypeParam::expected_profile; - - using props = ti::PropertiesOfT<profile>; - using arch_props = ti::PropertiesOfArchetypeT<arch>; - using expected_props = ti::PropertiesOfT<expected_profile>; - - // Make sure all of the properties are as expected. - // There are seemingly redundant tests here to make it easier to diagnose - // the specifics of the failure if something were to go wrong. - EXPECT_TRUE((std::is_same<props, arch_props>::value)); - EXPECT_TRUE((std::is_same<props, expected_props>::value)); - EXPECT_TRUE((std::is_same<arch_props, expected_props>::value)); - - EXPECT_EQ(props::equality_comparable_support, - expected_props::equality_comparable_support); - - EXPECT_EQ(props::inequality_comparable_support, - expected_props::inequality_comparable_support); - - EXPECT_EQ(props::less_than_comparable_support, - expected_props::less_than_comparable_support); - - EXPECT_EQ(props::less_equal_comparable_support, - expected_props::less_equal_comparable_support); - - EXPECT_EQ(props::greater_equal_comparable_support, - expected_props::greater_equal_comparable_support); - - EXPECT_EQ(props::greater_than_comparable_support, - expected_props::greater_than_comparable_support); - - // Avoid additional error message noise when profile and archetype match with - // each other but were not what was expected. - if (!std::is_same<props, arch_props>::value) { - EXPECT_EQ(arch_props::equality_comparable_support, - expected_props::equality_comparable_support); - - EXPECT_EQ(arch_props::inequality_comparable_support, - expected_props::inequality_comparable_support); - - EXPECT_EQ(arch_props::less_than_comparable_support, - expected_props::less_than_comparable_support); - - EXPECT_EQ(arch_props::less_equal_comparable_support, - expected_props::less_equal_comparable_support); - - EXPECT_EQ(arch_props::greater_equal_comparable_support, - expected_props::greater_equal_comparable_support); - - EXPECT_EQ(arch_props::greater_than_comparable_support, - expected_props::greater_than_comparable_support); - } - - ////////////////////////////////////////////////////////////////////////////// - // Equality comparable checks // - ////////////////////////////////////////////////////////////////////////////// - switch (expected_props::equality_comparable_support) { - case ti::equality_comparable::maybe: - EXPECT_FALSE(EqualityComparable<arch>::value); - EXPECT_FALSE(NothrowEqualityComparable<arch>::value); - break; - case ti::equality_comparable::yes: - EXPECT_TRUE(EqualityComparable<arch>::value); - EXPECT_FALSE(NothrowEqualityComparable<arch>::value); - break; - case ti::equality_comparable::nothrow: - EXPECT_TRUE(EqualityComparable<arch>::value); - EXPECT_TRUE(NothrowEqualityComparable<arch>::value); - break; - } - - ////////////////////////////////////////////////////////////////////////////// - // Inequality comparable checks // - ////////////////////////////////////////////////////////////////////////////// - switch (expected_props::inequality_comparable_support) { - case ti::inequality_comparable::maybe: - EXPECT_FALSE(InequalityComparable<arch>::value); - EXPECT_FALSE(NothrowInequalityComparable<arch>::value); - break; - case ti::inequality_comparable::yes: - EXPECT_TRUE(InequalityComparable<arch>::value); - EXPECT_FALSE(NothrowInequalityComparable<arch>::value); - break; - case ti::inequality_comparable::nothrow: - EXPECT_TRUE(InequalityComparable<arch>::value); - EXPECT_TRUE(NothrowInequalityComparable<arch>::value); - break; - } - - ////////////////////////////////////////////////////////////////////////////// - // Less than comparable checks // - ////////////////////////////////////////////////////////////////////////////// - switch (expected_props::less_than_comparable_support) { - case ti::less_than_comparable::maybe: - EXPECT_FALSE(LessThanComparable<arch>::value); - EXPECT_FALSE(NothrowLessThanComparable<arch>::value); - break; - case ti::less_than_comparable::yes: - EXPECT_TRUE(LessThanComparable<arch>::value); - EXPECT_FALSE(NothrowLessThanComparable<arch>::value); - break; - case ti::less_than_comparable::nothrow: - EXPECT_TRUE(LessThanComparable<arch>::value); - EXPECT_TRUE(NothrowLessThanComparable<arch>::value); - break; - } - - ////////////////////////////////////////////////////////////////////////////// - // Less equal comparable checks // - ////////////////////////////////////////////////////////////////////////////// - switch (expected_props::less_equal_comparable_support) { - case ti::less_equal_comparable::maybe: - EXPECT_FALSE(LessEqualComparable<arch>::value); - EXPECT_FALSE(NothrowLessEqualComparable<arch>::value); - break; - case ti::less_equal_comparable::yes: - EXPECT_TRUE(LessEqualComparable<arch>::value); - EXPECT_FALSE(NothrowLessEqualComparable<arch>::value); - break; - case ti::less_equal_comparable::nothrow: - EXPECT_TRUE(LessEqualComparable<arch>::value); - EXPECT_TRUE(NothrowLessEqualComparable<arch>::value); - break; - } - - ////////////////////////////////////////////////////////////////////////////// - // Greater equal comparable checks // - ////////////////////////////////////////////////////////////////////////////// - switch (expected_props::greater_equal_comparable_support) { - case ti::greater_equal_comparable::maybe: - EXPECT_FALSE(GreaterEqualComparable<arch>::value); - EXPECT_FALSE(NothrowGreaterEqualComparable<arch>::value); - break; - case ti::greater_equal_comparable::yes: - EXPECT_TRUE(GreaterEqualComparable<arch>::value); - EXPECT_FALSE(NothrowGreaterEqualComparable<arch>::value); - break; - case ti::greater_equal_comparable::nothrow: - EXPECT_TRUE(GreaterEqualComparable<arch>::value); - EXPECT_TRUE(NothrowGreaterEqualComparable<arch>::value); - break; - } - - ////////////////////////////////////////////////////////////////////////////// - // Greater than comparable checks // - ////////////////////////////////////////////////////////////////////////////// - switch (expected_props::greater_than_comparable_support) { - case ti::greater_than_comparable::maybe: - EXPECT_FALSE(GreaterThanComparable<arch>::value); - EXPECT_FALSE(NothrowGreaterThanComparable<arch>::value); - break; - case ti::greater_than_comparable::yes: - EXPECT_TRUE(GreaterThanComparable<arch>::value); - EXPECT_FALSE(NothrowGreaterThanComparable<arch>::value); - break; - case ti::greater_than_comparable::nothrow: - EXPECT_TRUE(GreaterThanComparable<arch>::value); - EXPECT_TRUE(NothrowGreaterThanComparable<arch>::value); - break; - } -} - -TYPED_TEST_P(ProfileTest, HasAppropriateAuxilliaryProperties) { - using profile = typename TypeParam::profile; - using arch = typename TypeParam::arch; - using expected_profile = typename TypeParam::expected_profile; - - using props = ti::PropertiesOfT<profile>; - using arch_props = ti::PropertiesOfArchetypeT<arch>; - using expected_props = ti::PropertiesOfT<expected_profile>; - - // Make sure all of the properties are as expected. - // There are seemingly redundant tests here to make it easier to diagnose - // the specifics of the failure if something were to go wrong. - EXPECT_TRUE((std::is_same<props, arch_props>::value)); - EXPECT_TRUE((std::is_same<props, expected_props>::value)); - EXPECT_TRUE((std::is_same<arch_props, expected_props>::value)); - - EXPECT_EQ(props::swappable_support, expected_props::swappable_support); - - EXPECT_EQ(props::hashable_support, expected_props::hashable_support); - - // Avoid additional error message noise when profile and archetype match with - // each other but were not what was expected. - if (!std::is_same<props, arch_props>::value) { - EXPECT_EQ(arch_props::swappable_support, expected_props::swappable_support); - - EXPECT_EQ(arch_props::hashable_support, expected_props::hashable_support); - } - - ////////////////////////////////////////////////////////////////////////////// - // Swappable checks // - ////////////////////////////////////////////////////////////////////////////// - switch (expected_props::swappable_support) { - case ti::swappable::maybe: - EXPECT_FALSE(absl::type_traits_internal::IsSwappable<arch>::value); - EXPECT_FALSE(absl::type_traits_internal::IsNothrowSwappable<arch>::value); - break; - case ti::swappable::yes: - EXPECT_TRUE(absl::type_traits_internal::IsSwappable<arch>::value); - EXPECT_FALSE(absl::type_traits_internal::IsNothrowSwappable<arch>::value); - break; - case ti::swappable::nothrow: - EXPECT_TRUE(absl::type_traits_internal::IsSwappable<arch>::value); - EXPECT_TRUE(absl::type_traits_internal::IsNothrowSwappable<arch>::value); - break; - } - - ////////////////////////////////////////////////////////////////////////////// - // Hashable checks // - ////////////////////////////////////////////////////////////////////////////// - switch (expected_props::hashable_support) { - case ti::hashable::maybe: -#if ABSL_META_INTERNAL_STD_HASH_SFINAE_FRIENDLY_ - EXPECT_FALSE(absl::type_traits_internal::IsHashable<arch>::value); -#endif // ABSL_META_INTERNAL_STD_HASH_SFINAE_FRIENDLY_ - break; - case ti::hashable::yes: - EXPECT_TRUE(absl::type_traits_internal::IsHashable<arch>::value); - break; - } -} - -REGISTER_TYPED_TEST_SUITE_P(ProfileTest, HasAppropriateConstructionProperties, - HasAppropriateAssignmentProperties, - HasAppropriateComparisonProperties, - HasAppropriateAuxilliaryProperties); - -template <class Profile, class Arch, class ExpectedProfile> -struct ProfileAndExpectation { - using profile = Profile; - using arch = Arch; - using expected_profile = ExpectedProfile; -}; - -using CoreProfilesToTest = ::testing::Types< - // The terminating case of combine (all properties are "maybe"). - ProfileAndExpectation<ti::CombineProfiles<>, - ti::Archetype<ti::CombineProfiles<>>, - ti::ConformanceProfile<>>, - - // Core default constructor profiles - ProfileAndExpectation< - ti::HasDefaultConstructorProfile, ti::HasDefaultConstructorArchetype, - ti::ConformanceProfile<ti::default_constructible::yes>>, - ProfileAndExpectation< - ti::HasNothrowDefaultConstructorProfile, - ti::HasNothrowDefaultConstructorArchetype, - ti::ConformanceProfile<ti::default_constructible::nothrow>>, - ProfileAndExpectation< - ti::HasTrivialDefaultConstructorProfile, - ti::HasTrivialDefaultConstructorArchetype, - ti::ConformanceProfile<ti::default_constructible::trivial>>, - - // Core move constructor profiles - ProfileAndExpectation< - ti::HasMoveConstructorProfile, ti::HasMoveConstructorArchetype, - ti::ConformanceProfile<ti::default_constructible::maybe, - ti::move_constructible::yes>>, - ProfileAndExpectation< - ti::HasNothrowMoveConstructorProfile, - ti::HasNothrowMoveConstructorArchetype, - ti::ConformanceProfile<ti::default_constructible::maybe, - ti::move_constructible::nothrow>>, - ProfileAndExpectation< - ti::HasTrivialMoveConstructorProfile, - ti::HasTrivialMoveConstructorArchetype, - ti::ConformanceProfile<ti::default_constructible::maybe, - ti::move_constructible::trivial>>, - - // Core copy constructor profiles - ProfileAndExpectation< - ti::HasCopyConstructorProfile, ti::HasCopyConstructorArchetype, - ti::ConformanceProfile<ti::default_constructible::maybe, - ti::move_constructible::maybe, - ti::copy_constructible::yes>>, - ProfileAndExpectation< - ti::HasNothrowCopyConstructorProfile, - ti::HasNothrowCopyConstructorArchetype, - ti::ConformanceProfile<ti::default_constructible::maybe, - ti::move_constructible::maybe, - ti::copy_constructible::nothrow>>, - ProfileAndExpectation< - ti::HasTrivialCopyConstructorProfile, - ti::HasTrivialCopyConstructorArchetype, - ti::ConformanceProfile<ti::default_constructible::maybe, - ti::move_constructible::maybe, - ti::copy_constructible::trivial>>, - - // Core move assignment profiles - ProfileAndExpectation< - ti::HasMoveAssignProfile, ti::HasMoveAssignArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::yes>>, - ProfileAndExpectation< - ti::HasNothrowMoveAssignProfile, ti::HasNothrowMoveAssignArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::nothrow>>, - ProfileAndExpectation< - ti::HasTrivialMoveAssignProfile, ti::HasTrivialMoveAssignArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::trivial>>, - - // Core copy assignment profiles - ProfileAndExpectation< - ti::HasCopyAssignProfile, ti::HasCopyAssignArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::yes>>, - ProfileAndExpectation< - ti::HasNothrowCopyAssignProfile, ti::HasNothrowCopyAssignArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::nothrow>>, - ProfileAndExpectation< - ti::HasTrivialCopyAssignProfile, ti::HasTrivialCopyAssignArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::trivial>>, - - // Core destructor profiles - ProfileAndExpectation< - ti::HasDestructorProfile, ti::HasDestructorArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::yes>>, - ProfileAndExpectation< - ti::HasNothrowDestructorProfile, ti::HasNothrowDestructorArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::nothrow>>, - ProfileAndExpectation< - ti::HasTrivialDestructorProfile, ti::HasTrivialDestructorArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::trivial>>, - - // Core equality comparable profiles - ProfileAndExpectation< - ti::HasEqualityProfile, ti::HasEqualityArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::maybe, - ti::equality_comparable::yes>>, - ProfileAndExpectation< - ti::HasNothrowEqualityProfile, ti::HasNothrowEqualityArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::maybe, - ti::equality_comparable::nothrow>>, - - // Core inequality comparable profiles - ProfileAndExpectation< - ti::HasInequalityProfile, ti::HasInequalityArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::maybe, - ti::equality_comparable::maybe, ti::inequality_comparable::yes>>, - ProfileAndExpectation< - ti::HasNothrowInequalityProfile, ti::HasNothrowInequalityArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::maybe, - ti::equality_comparable::maybe, - ti::inequality_comparable::nothrow>>, - - // Core less than comparable profiles - ProfileAndExpectation< - ti::HasLessThanProfile, ti::HasLessThanArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::maybe, - ti::equality_comparable::maybe, ti::inequality_comparable::maybe, - ti::less_than_comparable::yes>>, - ProfileAndExpectation< - ti::HasNothrowLessThanProfile, ti::HasNothrowLessThanArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::maybe, - ti::equality_comparable::maybe, ti::inequality_comparable::maybe, - ti::less_than_comparable::nothrow>>, - - // Core less equal comparable profiles - ProfileAndExpectation< - ti::HasLessEqualProfile, ti::HasLessEqualArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::maybe, - ti::equality_comparable::maybe, ti::inequality_comparable::maybe, - ti::less_than_comparable::maybe, ti::less_equal_comparable::yes>>, - ProfileAndExpectation< - ti::HasNothrowLessEqualProfile, ti::HasNothrowLessEqualArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::maybe, - ti::equality_comparable::maybe, ti::inequality_comparable::maybe, - ti::less_than_comparable::maybe, - ti::less_equal_comparable::nothrow>>, - - // Core greater equal comparable profiles - ProfileAndExpectation< - ti::HasGreaterEqualProfile, ti::HasGreaterEqualArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::maybe, - ti::equality_comparable::maybe, ti::inequality_comparable::maybe, - ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe, - ti::greater_equal_comparable::yes>>, - ProfileAndExpectation< - ti::HasNothrowGreaterEqualProfile, ti::HasNothrowGreaterEqualArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::maybe, - ti::equality_comparable::maybe, ti::inequality_comparable::maybe, - ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe, - ti::greater_equal_comparable::nothrow>>, - - // Core greater than comparable profiles - ProfileAndExpectation< - ti::HasGreaterThanProfile, ti::HasGreaterThanArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::maybe, - ti::equality_comparable::maybe, ti::inequality_comparable::maybe, - ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe, - ti::greater_equal_comparable::maybe, - ti::greater_than_comparable::yes>>, - ProfileAndExpectation< - ti::HasNothrowGreaterThanProfile, ti::HasNothrowGreaterThanArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::maybe, - ti::equality_comparable::maybe, ti::inequality_comparable::maybe, - ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe, - ti::greater_equal_comparable::maybe, - ti::greater_than_comparable::nothrow>>, - - // Core swappable profiles - ProfileAndExpectation< - ti::HasSwapProfile, ti::HasSwapArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::maybe, - ti::equality_comparable::maybe, ti::inequality_comparable::maybe, - ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe, - ti::greater_equal_comparable::maybe, - ti::greater_than_comparable::maybe, ti::swappable::yes>>, - ProfileAndExpectation< - ti::HasNothrowSwapProfile, ti::HasNothrowSwapArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::maybe, - ti::equality_comparable::maybe, ti::inequality_comparable::maybe, - ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe, - ti::greater_equal_comparable::maybe, - ti::greater_than_comparable::maybe, ti::swappable::nothrow>>, - - // Core hashable profiles - ProfileAndExpectation< - ti::HasStdHashSpecializationProfile, - ti::HasStdHashSpecializationArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::maybe, - ti::equality_comparable::maybe, ti::inequality_comparable::maybe, - ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe, - ti::greater_equal_comparable::maybe, - ti::greater_than_comparable::maybe, ti::swappable::maybe, - ti::hashable::yes>>>; - -using CommonProfilesToTest = ::testing::Types< - // NothrowMoveConstructible - ProfileAndExpectation< - ti::NothrowMoveConstructibleProfile, - ti::NothrowMoveConstructibleArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::nothrow, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::nothrow>>, - - // CopyConstructible - ProfileAndExpectation< - ti::CopyConstructibleProfile, ti::CopyConstructibleArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::nothrow, - ti::copy_constructible::yes, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::nothrow>>, - - // NothrowMovable - ProfileAndExpectation< - ti::NothrowMovableProfile, ti::NothrowMovableArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::nothrow, - ti::copy_constructible::maybe, ti::move_assignable::nothrow, - ti::copy_assignable::maybe, ti::destructible::nothrow, - ti::equality_comparable::maybe, ti::inequality_comparable::maybe, - ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe, - ti::greater_equal_comparable::maybe, - ti::greater_than_comparable::maybe, ti::swappable::nothrow>>, - - // Value - ProfileAndExpectation< - ti::ValueProfile, ti::ValueArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::nothrow, - ti::copy_constructible::yes, ti::move_assignable::nothrow, - ti::copy_assignable::yes, ti::destructible::nothrow, - ti::equality_comparable::maybe, ti::inequality_comparable::maybe, - ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe, - ti::greater_equal_comparable::maybe, - ti::greater_than_comparable::maybe, ti::swappable::nothrow>>, - - //////////////////////////////////////////////////////////////////////////// - // Common but also DefaultConstructible // - //////////////////////////////////////////////////////////////////////////// - - // DefaultConstructibleNothrowMoveConstructible - ProfileAndExpectation< - ti::DefaultConstructibleNothrowMoveConstructibleProfile, - ti::DefaultConstructibleNothrowMoveConstructibleArchetype, - ti::ConformanceProfile< - ti::default_constructible::yes, ti::move_constructible::nothrow, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::nothrow>>, - - // DefaultConstructibleCopyConstructible - ProfileAndExpectation< - ti::DefaultConstructibleCopyConstructibleProfile, - ti::DefaultConstructibleCopyConstructibleArchetype, - ti::ConformanceProfile< - ti::default_constructible::yes, ti::move_constructible::nothrow, - ti::copy_constructible::yes, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::nothrow>>, - - // DefaultConstructibleNothrowMovable - ProfileAndExpectation< - ti::DefaultConstructibleNothrowMovableProfile, - ti::DefaultConstructibleNothrowMovableArchetype, - ti::ConformanceProfile< - ti::default_constructible::yes, ti::move_constructible::nothrow, - ti::copy_constructible::maybe, ti::move_assignable::nothrow, - ti::copy_assignable::maybe, ti::destructible::nothrow, - ti::equality_comparable::maybe, ti::inequality_comparable::maybe, - ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe, - ti::greater_equal_comparable::maybe, - ti::greater_than_comparable::maybe, ti::swappable::nothrow>>, - - // DefaultConstructibleValue - ProfileAndExpectation< - ti::DefaultConstructibleValueProfile, - ti::DefaultConstructibleValueArchetype, - ti::ConformanceProfile< - ti::default_constructible::yes, ti::move_constructible::nothrow, - ti::copy_constructible::yes, ti::move_assignable::nothrow, - ti::copy_assignable::yes, ti::destructible::nothrow, - ti::equality_comparable::maybe, ti::inequality_comparable::maybe, - ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe, - ti::greater_equal_comparable::maybe, - ti::greater_than_comparable::maybe, ti::swappable::nothrow>>>; - -using ComparableHelpersProfilesToTest = ::testing::Types< - // Equatable - ProfileAndExpectation< - ti::EquatableProfile, ti::EquatableArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::maybe, - ti::equality_comparable::yes, ti::inequality_comparable::yes>>, - - // Comparable - ProfileAndExpectation< - ti::ComparableProfile, ti::ComparableArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::maybe, - ti::equality_comparable::yes, ti::inequality_comparable::yes, - ti::less_than_comparable::yes, ti::less_equal_comparable::yes, - ti::greater_equal_comparable::yes, - ti::greater_than_comparable::yes>>, - - // NothrowEquatable - ProfileAndExpectation< - ti::NothrowEquatableProfile, ti::NothrowEquatableArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::maybe, - ti::equality_comparable::nothrow, - ti::inequality_comparable::nothrow>>, - - // NothrowComparable - ProfileAndExpectation< - ti::NothrowComparableProfile, ti::NothrowComparableArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::maybe, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::maybe, - ti::equality_comparable::nothrow, - ti::inequality_comparable::nothrow, - ti::less_than_comparable::nothrow, - ti::less_equal_comparable::nothrow, - ti::greater_equal_comparable::nothrow, - ti::greater_than_comparable::nothrow>>>; - -using CommonComparableProfilesToTest = ::testing::Types< - // ComparableNothrowMoveConstructible - ProfileAndExpectation< - ti::ComparableNothrowMoveConstructibleProfile, - ti::ComparableNothrowMoveConstructibleArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::nothrow, - ti::copy_constructible::maybe, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::nothrow, - ti::equality_comparable::yes, ti::inequality_comparable::yes, - ti::less_than_comparable::yes, ti::less_equal_comparable::yes, - ti::greater_equal_comparable::yes, - ti::greater_than_comparable::yes>>, - - // ComparableCopyConstructible - ProfileAndExpectation< - ti::ComparableCopyConstructibleProfile, - ti::ComparableCopyConstructibleArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::nothrow, - ti::copy_constructible::yes, ti::move_assignable::maybe, - ti::copy_assignable::maybe, ti::destructible::nothrow, - ti::equality_comparable::yes, ti::inequality_comparable::yes, - ti::less_than_comparable::yes, ti::less_equal_comparable::yes, - ti::greater_equal_comparable::yes, - ti::greater_than_comparable::yes>>, - - // ComparableNothrowMovable - ProfileAndExpectation< - ti::ComparableNothrowMovableProfile, - ti::ComparableNothrowMovableArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::nothrow, - ti::copy_constructible::maybe, ti::move_assignable::nothrow, - ti::copy_assignable::maybe, ti::destructible::nothrow, - ti::equality_comparable::yes, ti::inequality_comparable::yes, - ti::less_than_comparable::yes, ti::less_equal_comparable::yes, - ti::greater_equal_comparable::yes, ti::greater_than_comparable::yes, - ti::swappable::nothrow>>, - - // ComparableValue - ProfileAndExpectation< - ti::ComparableValueProfile, ti::ComparableValueArchetype, - ti::ConformanceProfile< - ti::default_constructible::maybe, ti::move_constructible::nothrow, - ti::copy_constructible::yes, ti::move_assignable::nothrow, - ti::copy_assignable::yes, ti::destructible::nothrow, - ti::equality_comparable::yes, ti::inequality_comparable::yes, - ti::less_than_comparable::yes, ti::less_equal_comparable::yes, - ti::greater_equal_comparable::yes, ti::greater_than_comparable::yes, - ti::swappable::nothrow>>>; - -using TrivialProfilesToTest = ::testing::Types< - ProfileAndExpectation< - ti::TrivialSpecialMemberFunctionsProfile, - ti::TrivialSpecialMemberFunctionsArchetype, - ti::ConformanceProfile< - ti::default_constructible::trivial, ti::move_constructible::trivial, - ti::copy_constructible::trivial, ti::move_assignable::trivial, - ti::copy_assignable::trivial, ti::destructible::trivial, - ti::equality_comparable::maybe, ti::inequality_comparable::maybe, - ti::less_than_comparable::maybe, ti::less_equal_comparable::maybe, - ti::greater_equal_comparable::maybe, - ti::greater_than_comparable::maybe, ti::swappable::nothrow>>, - - ProfileAndExpectation< - ti::TriviallyCompleteProfile, ti::TriviallyCompleteArchetype, - ti::ConformanceProfile< - ti::default_constructible::trivial, ti::move_constructible::trivial, - ti::copy_constructible::trivial, ti::move_assignable::trivial, - ti::copy_assignable::trivial, ti::destructible::trivial, - ti::equality_comparable::yes, ti::inequality_comparable::yes, - ti::less_than_comparable::yes, ti::less_equal_comparable::yes, - ti::greater_equal_comparable::yes, ti::greater_than_comparable::yes, - ti::swappable::nothrow, ti::hashable::yes>>>; - -INSTANTIATE_TYPED_TEST_SUITE_P(Core, ProfileTest, CoreProfilesToTest); -INSTANTIATE_TYPED_TEST_SUITE_P(Common, ProfileTest, CommonProfilesToTest); -INSTANTIATE_TYPED_TEST_SUITE_P(ComparableHelpers, ProfileTest, - ComparableHelpersProfilesToTest); -INSTANTIATE_TYPED_TEST_SUITE_P(CommonComparable, ProfileTest, - CommonComparableProfilesToTest); -INSTANTIATE_TYPED_TEST_SUITE_P(Trivial, ProfileTest, TrivialProfilesToTest); - -TEST(ConformanceTestingTest, Basic) { - using profile = ti::CombineProfiles<ti::TriviallyCompleteProfile, - ti::NothrowComparableProfile>; - - using lim = std::numeric_limits<float>; - - ABSL_INTERNAL_ASSERT_CONFORMANCE_OF(float) - .INITIALIZER(-lim::infinity()) - .INITIALIZER(lim::lowest()) - .INITIALIZER(-1.f) - .INITIALIZER(-lim::min()) - .EQUIVALENCE_CLASS(INITIALIZER(-0.f), INITIALIZER(0.f)) - .INITIALIZER(lim::min()) - .INITIALIZER(1.f) - .INITIALIZER(lim::max()) - .INITIALIZER(lim::infinity()) - .WITH_STRICT_PROFILE(absl::types_internal::RegularityDomain, profile); -} - -struct BadMoveConstruct { - BadMoveConstruct() = default; - BadMoveConstruct(BadMoveConstruct&& other) noexcept - : value(other.value + 1) {} - BadMoveConstruct& operator=(BadMoveConstruct&& other) noexcept = default; - int value = 0; - - friend bool operator==(BadMoveConstruct const& lhs, - BadMoveConstruct const& rhs) { - return lhs.value == rhs.value; - } - friend bool operator!=(BadMoveConstruct const& lhs, - BadMoveConstruct const& rhs) { - return lhs.value != rhs.value; - } -}; - -struct BadMoveAssign { - BadMoveAssign() = default; - BadMoveAssign(BadMoveAssign&& other) noexcept = default; - BadMoveAssign& operator=(BadMoveAssign&& other) noexcept { - int new_value = other.value + 1; - value = new_value; - return *this; - } - int value = 0; - - friend bool operator==(BadMoveAssign const& lhs, BadMoveAssign const& rhs) { - return lhs.value == rhs.value; - } - friend bool operator!=(BadMoveAssign const& lhs, BadMoveAssign const& rhs) { - return lhs.value != rhs.value; - } -}; - -enum class WhichCompIsBad { eq, ne, lt, le, ge, gt }; - -template <WhichCompIsBad Which> -struct BadCompare { - int value; - - friend bool operator==(BadCompare const& lhs, BadCompare const& rhs) { - return Which == WhichCompIsBad::eq ? lhs.value != rhs.value - : lhs.value == rhs.value; - } - - friend bool operator!=(BadCompare const& lhs, BadCompare const& rhs) { - return Which == WhichCompIsBad::ne ? lhs.value == rhs.value - : lhs.value != rhs.value; - } - - friend bool operator<(BadCompare const& lhs, BadCompare const& rhs) { - return Which == WhichCompIsBad::lt ? lhs.value >= rhs.value - : lhs.value < rhs.value; - } - - friend bool operator<=(BadCompare const& lhs, BadCompare const& rhs) { - return Which == WhichCompIsBad::le ? lhs.value > rhs.value - : lhs.value <= rhs.value; - } - - friend bool operator>=(BadCompare const& lhs, BadCompare const& rhs) { - return Which == WhichCompIsBad::ge ? lhs.value < rhs.value - : lhs.value >= rhs.value; - } - - friend bool operator>(BadCompare const& lhs, BadCompare const& rhs) { - return Which == WhichCompIsBad::gt ? lhs.value <= rhs.value - : lhs.value > rhs.value; - } -}; - -TEST(ConformanceTestingDeathTest, Failures) { - { - using profile = ti::CombineProfiles<ti::TriviallyCompleteProfile, - ti::NothrowComparableProfile>; - - // Note: The initializers are intentionally in the wrong order. - ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(float) - .INITIALIZER(1.f) - .INITIALIZER(0.f) - .WITH_LOOSE_PROFILE(profile); - } - - { - using profile = - ti::CombineProfiles<ti::NothrowMovableProfile, ti::EquatableProfile>; - - ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadMoveConstruct) - .DUE_TO("Move construction") - .INITIALIZER(BadMoveConstruct()) - .WITH_LOOSE_PROFILE(profile); - } - - { - using profile = - ti::CombineProfiles<ti::NothrowMovableProfile, ti::EquatableProfile>; - - ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadMoveAssign) - .DUE_TO("Move assignment") - .INITIALIZER(BadMoveAssign()) - .WITH_LOOSE_PROFILE(profile); - } -} - -TEST(ConformanceTestingDeathTest, CompFailures) { - using profile = ti::ComparableProfile; - - { - using BadComp = BadCompare<WhichCompIsBad::eq>; - - ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadComp) - .DUE_TO("Comparison") - .INITIALIZER(BadComp{0}) - .INITIALIZER(BadComp{1}) - .WITH_LOOSE_PROFILE(profile); - } - - { - using BadComp = BadCompare<WhichCompIsBad::ne>; - - ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadComp) - .DUE_TO("Comparison") - .INITIALIZER(BadComp{0}) - .INITIALIZER(BadComp{1}) - .WITH_LOOSE_PROFILE(profile); - } - - { - using BadComp = BadCompare<WhichCompIsBad::lt>; - - ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadComp) - .DUE_TO("Comparison") - .INITIALIZER(BadComp{0}) - .INITIALIZER(BadComp{1}) - .WITH_LOOSE_PROFILE(profile); - } - - { - using BadComp = BadCompare<WhichCompIsBad::le>; - - ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadComp) - .DUE_TO("Comparison") - .INITIALIZER(BadComp{0}) - .INITIALIZER(BadComp{1}) - .WITH_LOOSE_PROFILE(profile); - } - - { - using BadComp = BadCompare<WhichCompIsBad::ge>; - - ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadComp) - .DUE_TO("Comparison") - .INITIALIZER(BadComp{0}) - .INITIALIZER(BadComp{1}) - .WITH_LOOSE_PROFILE(profile); - } - - { - using BadComp = BadCompare<WhichCompIsBad::gt>; - - ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadComp) - .DUE_TO("Comparison") - .INITIALIZER(BadComp{0}) - .INITIALIZER(BadComp{1}) - .WITH_LOOSE_PROFILE(profile); - } -} - -struct BadSelfMove { - BadSelfMove() = default; - BadSelfMove(BadSelfMove&&) = default; - BadSelfMove& operator=(BadSelfMove&& other) noexcept { - if (this == &other) { - broken_state = true; - } - return *this; - } - - friend bool operator==(const BadSelfMove& lhs, const BadSelfMove& rhs) { - return !(lhs.broken_state || rhs.broken_state); - } - - friend bool operator!=(const BadSelfMove& lhs, const BadSelfMove& rhs) { - return lhs.broken_state || rhs.broken_state; - } - - bool broken_state = false; -}; - -TEST(ConformanceTestingDeathTest, SelfMoveFailure) { - using profile = ti::EquatableNothrowMovableProfile; - - { - ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadSelfMove) - .DUE_TO("Move assignment") - .INITIALIZER(BadSelfMove()) - .WITH_LOOSE_PROFILE(profile); - } -} - -struct BadSelfCopy { - BadSelfCopy() = default; - BadSelfCopy(BadSelfCopy&&) = default; - BadSelfCopy(const BadSelfCopy&) = default; - BadSelfCopy& operator=(BadSelfCopy&&) = default; - BadSelfCopy& operator=(BadSelfCopy const& other) { - if (this == &other) { - broken_state = true; - } - return *this; - } - - friend bool operator==(const BadSelfCopy& lhs, const BadSelfCopy& rhs) { - return !(lhs.broken_state || rhs.broken_state); - } - - friend bool operator!=(const BadSelfCopy& lhs, const BadSelfCopy& rhs) { - return lhs.broken_state || rhs.broken_state; - } - - bool broken_state = false; -}; - -TEST(ConformanceTestingDeathTest, SelfCopyFailure) { - using profile = ti::EquatableValueProfile; - - { - ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadSelfCopy) - .DUE_TO("Copy assignment") - .INITIALIZER(BadSelfCopy()) - .WITH_LOOSE_PROFILE(profile); - } -} - -struct BadSelfSwap { - friend void swap(BadSelfSwap& lhs, BadSelfSwap& rhs) noexcept { - if (&lhs == &rhs) lhs.broken_state = true; - } - - friend bool operator==(const BadSelfSwap& lhs, const BadSelfSwap& rhs) { - return !(lhs.broken_state || rhs.broken_state); - } - - friend bool operator!=(const BadSelfSwap& lhs, const BadSelfSwap& rhs) { - return lhs.broken_state || rhs.broken_state; - } - - bool broken_state = false; -}; - -TEST(ConformanceTestingDeathTest, SelfSwapFailure) { - using profile = ti::EquatableNothrowMovableProfile; - - { - ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadSelfSwap) - .DUE_TO("Swap") - .INITIALIZER(BadSelfSwap()) - .WITH_LOOSE_PROFILE(profile); - } -} - -struct BadDefaultInitializedMoveAssign { - BadDefaultInitializedMoveAssign() : default_initialized(true) {} - explicit BadDefaultInitializedMoveAssign(int v) : value(v) {} - BadDefaultInitializedMoveAssign( - BadDefaultInitializedMoveAssign&& other) noexcept - : value(other.value) {} - BadDefaultInitializedMoveAssign& operator=( - BadDefaultInitializedMoveAssign&& other) noexcept { - value = other.value; - if (default_initialized) ++value; // Bad move if lhs is default initialized - return *this; - } - - friend bool operator==(const BadDefaultInitializedMoveAssign& lhs, - const BadDefaultInitializedMoveAssign& rhs) { - return lhs.value == rhs.value; - } - - friend bool operator!=(const BadDefaultInitializedMoveAssign& lhs, - const BadDefaultInitializedMoveAssign& rhs) { - return lhs.value != rhs.value; - } - - bool default_initialized = false; - int value = 0; -}; - -TEST(ConformanceTestingDeathTest, DefaultInitializedMoveAssignFailure) { - using profile = - ti::CombineProfiles<ti::DefaultConstructibleNothrowMovableProfile, - ti::EquatableProfile>; - - { - ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadDefaultInitializedMoveAssign) - .DUE_TO("move assignment") - .INITIALIZER(BadDefaultInitializedMoveAssign(0)) - .WITH_LOOSE_PROFILE(profile); - } -} - -struct BadDefaultInitializedCopyAssign { - BadDefaultInitializedCopyAssign() : default_initialized(true) {} - explicit BadDefaultInitializedCopyAssign(int v) : value(v) {} - BadDefaultInitializedCopyAssign( - BadDefaultInitializedCopyAssign&& other) noexcept - : value(other.value) {} - BadDefaultInitializedCopyAssign(const BadDefaultInitializedCopyAssign& other) - : value(other.value) {} - - BadDefaultInitializedCopyAssign& operator=( - BadDefaultInitializedCopyAssign&& other) noexcept { - value = other.value; - return *this; - } - - BadDefaultInitializedCopyAssign& operator=( - const BadDefaultInitializedCopyAssign& other) { - value = other.value; - if (default_initialized) ++value; // Bad move if lhs is default initialized - return *this; - } - - friend bool operator==(const BadDefaultInitializedCopyAssign& lhs, - const BadDefaultInitializedCopyAssign& rhs) { - return lhs.value == rhs.value; - } - - friend bool operator!=(const BadDefaultInitializedCopyAssign& lhs, - const BadDefaultInitializedCopyAssign& rhs) { - return lhs.value != rhs.value; - } - - bool default_initialized = false; - int value = 0; -}; - -TEST(ConformanceTestingDeathTest, DefaultInitializedAssignFailure) { - using profile = ti::CombineProfiles<ti::DefaultConstructibleValueProfile, - ti::EquatableProfile>; - - { - ABSL_INTERNAL_ASSERT_NONCONFORMANCE_OF(BadDefaultInitializedCopyAssign) - .DUE_TO("copy assignment") - .INITIALIZER(BadDefaultInitializedCopyAssign(0)) - .WITH_LOOSE_PROFILE(profile); - } -} - -} // namespace diff --git a/absl/types/internal/optional.h b/absl/types/internal/optional.h index 6ed0c669..a96d260a 100644 --- a/absl/types/internal/optional.h +++ b/absl/types/internal/optional.h @@ -25,34 +25,6 @@ #include "absl/meta/type_traits.h" #include "absl/utility/utility.h" -// ABSL_OPTIONAL_USE_INHERITING_CONSTRUCTORS -// -// Inheriting constructors is supported in GCC 4.8+, Clang 3.3+ and MSVC 2015. -// __cpp_inheriting_constructors is a predefined macro and a recommended way to -// check for this language feature, but GCC doesn't support it until 5.0 and -// Clang doesn't support it until 3.6. -// Also, MSVC 2015 has a bug: it doesn't inherit the constexpr template -// constructor. For example, the following code won't work on MSVC 2015 Update3: -// struct Base { -// int t; -// template <typename T> -// constexpr Base(T t_) : t(t_) {} -// }; -// struct Foo : Base { -// using Base::Base; -// } -// constexpr Foo foo(0); // doesn't work on MSVC 2015 -#if defined(__clang__) -#if __has_feature(cxx_inheriting_constructors) -#define ABSL_OPTIONAL_USE_INHERITING_CONSTRUCTORS 1 -#endif -#elif (defined(__GNUC__) && \ - (__GNUC__ > 4 || __GNUC__ == 4 && __GNUC_MINOR__ >= 8)) || \ - (__cpp_inheriting_constructors >= 200802) || \ - (defined(_MSC_VER) && _MSC_VER >= 1910) -#define ABSL_OPTIONAL_USE_INHERITING_CONSTRUCTORS 1 -#endif - namespace absl { ABSL_NAMESPACE_BEGIN @@ -145,15 +117,7 @@ template <typename T> class optional_data_base : public optional_data_dtor_base<T> { protected: using base = optional_data_dtor_base<T>; -#ifdef ABSL_OPTIONAL_USE_INHERITING_CONSTRUCTORS using base::base; -#else - optional_data_base() = default; - - template <typename... Args> - constexpr explicit optional_data_base(in_place_t t, Args&&... args) - : base(t, absl::forward<Args>(args)...) {} -#endif template <typename... Args> void construct(Args&&... args) { @@ -188,27 +152,13 @@ class optional_data; template <typename T> class optional_data<T, true> : public optional_data_base<T> { protected: -#ifdef ABSL_OPTIONAL_USE_INHERITING_CONSTRUCTORS using optional_data_base<T>::optional_data_base; -#else - optional_data() = default; - - template <typename... Args> - constexpr explicit optional_data(in_place_t t, Args&&... args) - : optional_data_base<T>(t, absl::forward<Args>(args)...) {} -#endif }; template <typename T> class optional_data<T, false> : public optional_data_base<T> { protected: -#ifdef ABSL_OPTIONAL_USE_INHERITING_CONSTRUCTORS using optional_data_base<T>::optional_data_base; -#else - template <typename... Args> - constexpr explicit optional_data(in_place_t t, Args&&... args) - : optional_data_base<T>(t, absl::forward<Args>(args)...) {} -#endif optional_data() = default; @@ -399,6 +349,4 @@ struct optional_hash_base<T, decltype(std::hash<absl::remove_const_t<T> >()( ABSL_NAMESPACE_END } // namespace absl -#undef ABSL_OPTIONAL_USE_INHERITING_CONSTRUCTORS - #endif // ABSL_TYPES_INTERNAL_OPTIONAL_H_ diff --git a/absl/types/internal/parentheses.h b/absl/types/internal/parentheses.h deleted file mode 100644 index 5aebee8f..00000000 --- a/absl/types/internal/parentheses.h +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2019 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. -// -// ----------------------------------------------------------------------------- -// parentheses.h -// ----------------------------------------------------------------------------- -// -// This file contains macros that expand to a left parenthesis and a right -// parenthesis. These are in their own file and are generated from macros -// because otherwise clang-format gets confused and clang-format off directives -// do not help. -// -// The parentheses macros are used when wanting to require a rescan before -// expansion of parenthesized text appearing after a function-style macro name. - -#ifndef ABSL_TYPES_INTERNAL_PARENTHESES_H_ -#define ABSL_TYPES_INTERNAL_PARENTHESES_H_ - -#define ABSL_INTERNAL_LPAREN ( - -#define ABSL_INTERNAL_RPAREN ) - -#endif // ABSL_TYPES_INTERNAL_PARENTHESES_H_ diff --git a/absl/types/internal/span.h b/absl/types/internal/span.h index 344ad4db..ab89ba3c 100644 --- a/absl/types/internal/span.h +++ b/absl/types/internal/span.h @@ -88,7 +88,7 @@ using EnableIfMutable = template <template <typename> class SpanT, typename T> bool EqualImpl(SpanT<T> a, SpanT<T> b) { static_assert(std::is_const<T>::value, ""); - return absl::equal(a.begin(), a.end(), b.begin(), b.end()); + return std::equal(a.begin(), a.end(), b.begin(), b.end()); } template <template <typename> class SpanT, typename T> @@ -125,7 +125,7 @@ struct IsView< }; // These enablers result in 'int' so they can be used as typenames or defaults -// in template paramters lists. +// in template parameters lists. template <typename T> using EnableIfIsView = std::enable_if_t<IsView<T>::value, int>; diff --git a/absl/types/internal/transform_args.h b/absl/types/internal/transform_args.h deleted file mode 100644 index 4a0ab42a..00000000 --- a/absl/types/internal/transform_args.h +++ /dev/null @@ -1,246 +0,0 @@ -// Copyright 2019 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. -// -// ----------------------------------------------------------------------------- -// transform_args.h -// ----------------------------------------------------------------------------- -// -// This file contains a higher-order macro that "transforms" each element of a -// a variadic argument by a provided secondary macro. - -#ifndef ABSL_TYPES_INTERNAL_TRANSFORM_ARGS_H_ -#define ABSL_TYPES_INTERNAL_TRANSFORM_ARGS_H_ - -// -// ABSL_INTERNAL_CAT(a, b) -// -// This macro takes two arguments and concatenates them together via ## after -// expansion. -// -// Example: -// -// ABSL_INTERNAL_CAT(foo_, bar) -// -// Results in: -// -// foo_bar -#define ABSL_INTERNAL_CAT(a, b) ABSL_INTERNAL_CAT_IMPL(a, b) -#define ABSL_INTERNAL_CAT_IMPL(a, b) a##b - -// -// ABSL_INTERNAL_TRANSFORM_ARGS(m, ...) -// -// This macro takes another macro as an argument followed by a trailing series -// of additional parameters (up to 32 additional arguments). It invokes the -// passed-in macro once for each of the additional arguments, with the -// expansions separated by commas. -// -// Example: -// -// ABSL_INTERNAL_TRANSFORM_ARGS(MY_MACRO, a, b, c) -// -// Results in: -// -// MY_MACRO(a), MY_MACRO(b), MY_MACRO(c) -// -// TODO(calabrese) Handle no arguments as a special case. -#define ABSL_INTERNAL_TRANSFORM_ARGS(m, ...) \ - ABSL_INTERNAL_CAT(ABSL_INTERNAL_TRANSFORM_ARGS, \ - ABSL_INTERNAL_NUM_ARGS(__VA_ARGS__)) \ - (m, __VA_ARGS__) - -#define ABSL_INTERNAL_TRANSFORM_ARGS1(m, a0) m(a0) - -#define ABSL_INTERNAL_TRANSFORM_ARGS2(m, a0, a1) m(a0), m(a1) - -#define ABSL_INTERNAL_TRANSFORM_ARGS3(m, a0, a1, a2) m(a0), m(a1), m(a2) - -#define ABSL_INTERNAL_TRANSFORM_ARGS4(m, a0, a1, a2, a3) \ - m(a0), m(a1), m(a2), m(a3) - -#define ABSL_INTERNAL_TRANSFORM_ARGS5(m, a0, a1, a2, a3, a4) \ - m(a0), m(a1), m(a2), m(a3), m(a4) - -#define ABSL_INTERNAL_TRANSFORM_ARGS6(m, a0, a1, a2, a3, a4, a5) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5) - -#define ABSL_INTERNAL_TRANSFORM_ARGS7(m, a0, a1, a2, a3, a4, a5, a6) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6) - -#define ABSL_INTERNAL_TRANSFORM_ARGS8(m, a0, a1, a2, a3, a4, a5, a6, a7) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7) - -#define ABSL_INTERNAL_TRANSFORM_ARGS9(m, a0, a1, a2, a3, a4, a5, a6, a7, a8) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8) - -#define ABSL_INTERNAL_TRANSFORM_ARGS10(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ - a9) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9) - -#define ABSL_INTERNAL_TRANSFORM_ARGS11(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ - a9, a10) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), m(a10) - -#define ABSL_INTERNAL_TRANSFORM_ARGS12(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ - a9, a10, a11) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ - m(a10), m(a11) - -#define ABSL_INTERNAL_TRANSFORM_ARGS13(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ - a9, a10, a11, a12) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ - m(a10), m(a11), m(a12) - -#define ABSL_INTERNAL_TRANSFORM_ARGS14(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ - a9, a10, a11, a12, a13) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ - m(a10), m(a11), m(a12), m(a13) - -#define ABSL_INTERNAL_TRANSFORM_ARGS15(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ - a9, a10, a11, a12, a13, a14) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ - m(a10), m(a11), m(a12), m(a13), m(a14) - -#define ABSL_INTERNAL_TRANSFORM_ARGS16(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ - a9, a10, a11, a12, a13, a14, a15) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ - m(a10), m(a11), m(a12), m(a13), m(a14), m(a15) - -#define ABSL_INTERNAL_TRANSFORM_ARGS17(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ - a9, a10, a11, a12, a13, a14, a15, a16) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ - m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16) - -#define ABSL_INTERNAL_TRANSFORM_ARGS18(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ - a9, a10, a11, a12, a13, a14, a15, a16, \ - a17) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ - m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17) - -#define ABSL_INTERNAL_TRANSFORM_ARGS19(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ - a9, a10, a11, a12, a13, a14, a15, a16, \ - a17, a18) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ - m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18) - -#define ABSL_INTERNAL_TRANSFORM_ARGS20(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ - a9, a10, a11, a12, a13, a14, a15, a16, \ - a17, a18, a19) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ - m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ - m(a19) - -#define ABSL_INTERNAL_TRANSFORM_ARGS21(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ - a9, a10, a11, a12, a13, a14, a15, a16, \ - a17, a18, a19, a20) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ - m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ - m(a19), m(a20) - -#define ABSL_INTERNAL_TRANSFORM_ARGS22(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ - a9, a10, a11, a12, a13, a14, a15, a16, \ - a17, a18, a19, a20, a21) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ - m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ - m(a19), m(a20), m(a21) - -#define ABSL_INTERNAL_TRANSFORM_ARGS23(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ - a9, a10, a11, a12, a13, a14, a15, a16, \ - a17, a18, a19, a20, a21, a22) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ - m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ - m(a19), m(a20), m(a21), m(a22) - -#define ABSL_INTERNAL_TRANSFORM_ARGS24(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ - a9, a10, a11, a12, a13, a14, a15, a16, \ - a17, a18, a19, a20, a21, a22, a23) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ - m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ - m(a19), m(a20), m(a21), m(a22), m(a23) - -#define ABSL_INTERNAL_TRANSFORM_ARGS25(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ - a9, a10, a11, a12, a13, a14, a15, a16, \ - a17, a18, a19, a20, a21, a22, a23, a24) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ - m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ - m(a19), m(a20), m(a21), m(a22), m(a23), m(a24) - -#define ABSL_INTERNAL_TRANSFORM_ARGS26( \ - m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, \ - a16, a17, a18, a19, a20, a21, a22, a23, a24, a25) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ - m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ - m(a19), m(a20), m(a21), m(a22), m(a23), m(a24), m(a25) - -#define ABSL_INTERNAL_TRANSFORM_ARGS27( \ - m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, \ - a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ - m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ - m(a19), m(a20), m(a21), m(a22), m(a23), m(a24), m(a25), m(a26) - -#define ABSL_INTERNAL_TRANSFORM_ARGS28( \ - m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, \ - a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ - m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ - m(a19), m(a20), m(a21), m(a22), m(a23), m(a24), m(a25), m(a26), m(a27) - -#define ABSL_INTERNAL_TRANSFORM_ARGS29( \ - m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, \ - a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ - m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ - m(a19), m(a20), m(a21), m(a22), m(a23), m(a24), m(a25), m(a26), m(a27), \ - m(a28) - -#define ABSL_INTERNAL_TRANSFORM_ARGS30( \ - m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, \ - a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ - m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ - m(a19), m(a20), m(a21), m(a22), m(a23), m(a24), m(a25), m(a26), m(a27), \ - m(a28), m(a29) - -#define ABSL_INTERNAL_TRANSFORM_ARGS31( \ - m, a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, \ - a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ - m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ - m(a19), m(a20), m(a21), m(a22), m(a23), m(a24), m(a25), m(a26), m(a27), \ - m(a28), m(a29), m(a30) - -#define ABSL_INTERNAL_TRANSFORM_ARGS32(m, a0, a1, a2, a3, a4, a5, a6, a7, a8, \ - a9, a10, a11, a12, a13, a14, a15, a16, \ - a17, a18, a19, a20, a21, a22, a23, a24, \ - a25, a26, a27, a28, a29, a30, a31) \ - m(a0), m(a1), m(a2), m(a3), m(a4), m(a5), m(a6), m(a7), m(a8), m(a9), \ - m(a10), m(a11), m(a12), m(a13), m(a14), m(a15), m(a16), m(a17), m(a18), \ - m(a19), m(a20), m(a21), m(a22), m(a23), m(a24), m(a25), m(a26), m(a27), \ - m(a28), m(a29), m(a30), m(a31) - -#define ABSL_INTERNAL_NUM_ARGS_IMPL(a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, \ - a10, a11, a12, a13, a14, a15, a16, a17, \ - a18, a19, a20, a21, a22, a23, a24, a25, \ - a26, a27, a28, a29, a30, a31, result, ...) \ - result - -#define ABSL_INTERNAL_FORCE_EXPANSION(...) __VA_ARGS__ - -#define ABSL_INTERNAL_NUM_ARGS(...) \ - ABSL_INTERNAL_FORCE_EXPANSION(ABSL_INTERNAL_NUM_ARGS_IMPL( \ - __VA_ARGS__, 32, 31, 30, 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, 19, 18, \ - 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, )) - -#endif // ABSL_TYPES_INTERNAL_TRANSFORM_ARGS_H_ diff --git a/absl/types/internal/variant.h b/absl/types/internal/variant.h index c82ded44..263d7b09 100644 --- a/absl/types/internal/variant.h +++ b/absl/types/internal/variant.h @@ -877,8 +877,8 @@ struct IndexOfConstructedType< template <std::size_t... Is> struct ContainsVariantNPos : absl::negation<std::is_same< // NOLINT - absl::integer_sequence<bool, 0 <= Is...>, - absl::integer_sequence<bool, Is != absl::variant_npos...>>> {}; + std::integer_sequence<bool, 0 <= Is...>, + std::integer_sequence<bool, Is != absl::variant_npos...>>> {}; template <class Op, class... QualifiedVariants> using RawVisitResult = @@ -1047,11 +1047,11 @@ class VariantStateBase { std::size_t index_; }; -using absl::internal::identity; +using absl::internal::type_identity; // OverloadSet::Overload() is a unary function which is overloaded to // take any of the element types of the variant, by reference-to-const. -// The return type of the overload on T is identity<T>, so that you +// The return type of the overload on T is type_identity<T>, so that you // can statically determine which overload was called. // // Overload() is not defined, so it can only be called in unevaluated @@ -1062,7 +1062,7 @@ struct OverloadSet; template <typename T, typename... Ts> struct OverloadSet<T, Ts...> : OverloadSet<Ts...> { using Base = OverloadSet<Ts...>; - static identity<T> Overload(const T&); + static type_identity<T> Overload(const T&); using Base::Overload; }; diff --git a/absl/types/optional.h b/absl/types/optional.h index 134b2aff..395fe62f 100644 --- a/absl/types/optional.h +++ b/absl/types/optional.h @@ -61,6 +61,7 @@ ABSL_NAMESPACE_END #include <utility> #include "absl/base/attributes.h" +#include "absl/base/nullability.h" #include "absl/base/internal/inline_variable.h" #include "absl/meta/type_traits.h" #include "absl/types/bad_optional_access.h" @@ -130,7 +131,7 @@ class optional : private optional_internal::optional_data<T>, // Constructs an `optional` holding an empty value, NOT a default constructed // `T`. - constexpr optional() noexcept {} + constexpr optional() noexcept = default; // Constructs an `optional` initialized with `nullopt` to hold an empty value. constexpr optional(nullopt_t) noexcept {} // NOLINT(runtime/explicit) @@ -357,7 +358,7 @@ class optional : private optional_internal::optional_data<T>, template <typename... Args, typename = typename std::enable_if< std::is_constructible<T, Args&&...>::value>::type> - T& emplace(Args&&... args) { + T& emplace(Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND { this->destruct(); this->construct(std::forward<Args>(args)...); return reference(); @@ -377,7 +378,8 @@ class optional : private optional_internal::optional_data<T>, template <typename U, typename... Args, typename = typename std::enable_if<std::is_constructible< T, std::initializer_list<U>&, Args&&...>::value>::type> - T& emplace(std::initializer_list<U> il, Args&&... args) { + T& emplace(std::initializer_list<U> il, + Args&&... args) ABSL_ATTRIBUTE_LIFETIME_BOUND { this->destruct(); this->construct(il, std::forward<Args>(args)...); return reference(); @@ -414,11 +416,11 @@ class optional : private optional_internal::optional_data<T>, // `optional` is empty, behavior is undefined. // // If you need myOpt->foo in constexpr, use (*myOpt).foo instead. - const T* operator->() const { + absl::Nonnull<const T*> operator->() const ABSL_ATTRIBUTE_LIFETIME_BOUND { ABSL_HARDENING_ASSERT(this->engaged_); return std::addressof(this->data_); } - T* operator->() { + absl::Nonnull<T*> operator->() ABSL_ATTRIBUTE_LIFETIME_BOUND { ABSL_HARDENING_ASSERT(this->engaged_); return std::addressof(this->data_); } @@ -427,17 +429,17 @@ class optional : private optional_internal::optional_data<T>, // // Accesses the underlying `T` value of an `optional`. If the `optional` is // empty, behavior is undefined. - constexpr const T& operator*() const& { + constexpr const T& operator*() const& ABSL_ATTRIBUTE_LIFETIME_BOUND { return ABSL_HARDENING_ASSERT(this->engaged_), reference(); } - T& operator*() & { + T& operator*() & ABSL_ATTRIBUTE_LIFETIME_BOUND { ABSL_HARDENING_ASSERT(this->engaged_); return reference(); } - constexpr const T&& operator*() const && { + constexpr const T&& operator*() const&& ABSL_ATTRIBUTE_LIFETIME_BOUND { return ABSL_HARDENING_ASSERT(this->engaged_), absl::move(reference()); } - T&& operator*() && { + T&& operator*() && ABSL_ATTRIBUTE_LIFETIME_BOUND { ABSL_HARDENING_ASSERT(this->engaged_); return std::move(reference()); } @@ -472,23 +474,24 @@ class optional : private optional_internal::optional_data<T>, // and lvalue/rvalue-ness of the `optional` is preserved to the view of // the `T` sub-object. Throws `absl::bad_optional_access` when the `optional` // is empty. - constexpr const T& value() const & { + constexpr const T& value() const& ABSL_ATTRIBUTE_LIFETIME_BOUND { return static_cast<bool>(*this) ? reference() : (optional_internal::throw_bad_optional_access(), reference()); } - T& value() & { + T& value() & ABSL_ATTRIBUTE_LIFETIME_BOUND { return static_cast<bool>(*this) ? reference() : (optional_internal::throw_bad_optional_access(), reference()); } - T&& value() && { // NOLINT(build/c++11) + T&& value() && ABSL_ATTRIBUTE_LIFETIME_BOUND { // NOLINT(build/c++11) return std::move( static_cast<bool>(*this) ? reference() : (optional_internal::throw_bad_optional_access(), reference())); } - constexpr const T&& value() const && { // NOLINT(build/c++11) + constexpr const T&& value() + const&& ABSL_ATTRIBUTE_LIFETIME_BOUND { // NOLINT(build/c++11) return absl::move( static_cast<bool>(*this) ? reference() diff --git a/absl/types/optional_test.cc b/absl/types/optional_test.cc index 21653a90..5da297b0 100644 --- a/absl/types/optional_test.cc +++ b/absl/types/optional_test.cc @@ -23,7 +23,7 @@ #include "gtest/gtest.h" #include "absl/base/config.h" -#include "absl/base/internal/raw_logging.h" +#include "absl/log/log.h" #include "absl/meta/type_traits.h" #include "absl/strings/string_view.h" @@ -97,9 +97,9 @@ struct StructorListener { // 4522: multiple assignment operators specified // We wrote multiple of them to test that the correct overloads are selected. #ifdef _MSC_VER -#pragma warning( push ) -#pragma warning( disable : 4521) -#pragma warning( disable : 4522) +#pragma warning(push) +#pragma warning(disable : 4521) +#pragma warning(disable : 4522) #endif struct Listenable { static StructorListener* listener; @@ -133,20 +133,11 @@ struct Listenable { ~Listenable() { ++listener->destruct; } }; #ifdef _MSC_VER -#pragma warning( pop ) +#pragma warning(pop) #endif StructorListener* Listenable::listener = nullptr; -// ABSL_HAVE_NO_CONSTEXPR_INITIALIZER_LIST is defined to 1 when the standard -// library implementation doesn't marked initializer_list's default constructor -// constexpr. The C++11 standard doesn't specify constexpr on it, but C++14 -// added it. However, libstdc++ 4.7 marked it constexpr. -#if defined(_LIBCPP_VERSION) && \ - (_LIBCPP_STD_VER <= 11 || defined(_LIBCPP_HAS_NO_CXX14_CONSTEXPR)) -#define ABSL_HAVE_NO_CONSTEXPR_INITIALIZER_LIST 1 -#endif - struct ConstexprType { enum CtorTypes { kCtorDefault, @@ -156,10 +147,8 @@ struct ConstexprType { }; constexpr ConstexprType() : x(kCtorDefault) {} constexpr explicit ConstexprType(int i) : x(kCtorInt) {} -#ifndef ABSL_HAVE_NO_CONSTEXPR_INITIALIZER_LIST constexpr ConstexprType(std::initializer_list<int> il) : x(kCtorInitializerList) {} -#endif constexpr ConstexprType(const char*) // NOLINT(runtime/explicit) : x(kCtorConstChar) {} int x; @@ -352,11 +341,9 @@ TEST(optionalTest, InPlaceConstructor) { constexpr absl::optional<ConstexprType> opt1{absl::in_place_t(), 1}; static_assert(opt1, ""); static_assert((*opt1).x == ConstexprType::kCtorInt, ""); -#ifndef ABSL_HAVE_NO_CONSTEXPR_INITIALIZER_LIST constexpr absl::optional<ConstexprType> opt2{absl::in_place_t(), {1, 2}}; static_assert(opt2, ""); static_assert((*opt2).x == ConstexprType::kCtorInitializerList, ""); -#endif EXPECT_FALSE((std::is_constructible<absl::optional<ConvertsFromInPlaceT>, absl::in_place_t>::value)); @@ -1000,9 +987,8 @@ TEST(optionalTest, PointerStuff) { // Skip that test to make the build green again when using the old compiler. // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=59296 is fixed in 4.9.1. #if defined(__GNUC__) && !defined(__clang__) -#define GCC_VERSION (__GNUC__ * 10000 \ - + __GNUC_MINOR__ * 100 \ - + __GNUC_PATCHLEVEL__) +#define GCC_VERSION \ + (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) #if GCC_VERSION < 40901 #define ABSL_SKIP_OVERLOAD_TEST_DUE_TO_GCC_BUG #endif @@ -1010,7 +996,7 @@ TEST(optionalTest, PointerStuff) { // MSVC has a bug with "cv-qualifiers in class construction", fixed in 2017. See // https://docs.microsoft.com/en-us/cpp/cpp-conformance-improvements-2017#bug-fixes -// The compiler some incorrectly ingores the cv-qualifier when generating a +// The compiler some incorrectly ignores the cv-qualifier when generating a // class object via a constructor call. For example: // // class optional { @@ -1214,7 +1200,6 @@ void optionalTest_Comparisons_EXPECT_GREATER(T x, U y) { EXPECT_TRUE(x >= y); } - template <typename T, typename U, typename V> void TestComparisons() { absl::optional<T> ae, a2{2}, a4{4}; @@ -1307,7 +1292,6 @@ TEST(optionalTest, Comparisons) { EXPECT_TRUE(e1 == e2); } - TEST(optionalTest, SwapRegression) { StructorListener listener; Listenable::listener = &listener; @@ -1558,8 +1542,7 @@ TEST(optionalTest, Hash) { struct MoveMeNoThrow { MoveMeNoThrow() : x(0) {} [[noreturn]] MoveMeNoThrow(const MoveMeNoThrow& other) : x(other.x) { - ABSL_RAW_LOG(FATAL, "Should not be called."); - abort(); + LOG(FATAL) << "Should not be called."; } MoveMeNoThrow(MoveMeNoThrow&& other) noexcept : x(other.x) {} int x; diff --git a/absl/types/span.h b/absl/types/span.h index d7bdbb1f..88cd7595 100644 --- a/absl/types/span.h +++ b/absl/types/span.h @@ -63,6 +63,7 @@ #include "absl/base/attributes.h" #include "absl/base/internal/throw_delegate.h" #include "absl/base/macros.h" +#include "absl/base/nullability.h" #include "absl/base/optimization.h" #include "absl/base/port.h" // TODO(strel): remove this include #include "absl/meta/type_traits.h" @@ -172,6 +173,8 @@ class Span { public: using element_type = T; using value_type = absl::remove_cv_t<T>; + // TODO(b/316099902) - pointer should be Nullable<T*>, but this makes it hard + // to recognize foreach loops as safe. using pointer = T*; using const_pointer = const T*; using reference = T&; @@ -296,8 +299,7 @@ class Span { // // Returns a reference to the i'th element of this span. constexpr reference operator[](size_type i) const noexcept { - // MSVC 2015 accepts this as constexpr, but not ptr_[i] - return ABSL_HARDENING_ASSERT(i < size()), *(data() + i); + return ABSL_HARDENING_ASSERT(i < size()), ptr_[i]; } // Span::at() @@ -680,12 +682,12 @@ bool operator>=(Span<T> a, const U& b) { // } // template <int&... ExplicitArgumentBarrier, typename T> -constexpr Span<T> MakeSpan(T* ptr, size_t size) noexcept { +constexpr Span<T> MakeSpan(absl::Nullable<T*> ptr, size_t size) noexcept { return Span<T>(ptr, size); } template <int&... ExplicitArgumentBarrier, typename T> -Span<T> MakeSpan(T* begin, T* end) noexcept { +Span<T> MakeSpan(absl::Nullable<T*> begin, absl::Nullable<T*> end) noexcept { return ABSL_HARDENING_ASSERT(begin <= end), Span<T>(begin, static_cast<size_t>(end - begin)); } @@ -726,12 +728,14 @@ constexpr Span<T> MakeSpan(T (&array)[N]) noexcept { // ProcessInts(absl::MakeConstSpan(std::vector<int>{ 0, 0, 0 })); // template <int&... ExplicitArgumentBarrier, typename T> -constexpr Span<const T> MakeConstSpan(T* ptr, size_t size) noexcept { +constexpr Span<const T> MakeConstSpan(absl::Nullable<T*> ptr, + size_t size) noexcept { return Span<const T>(ptr, size); } template <int&... ExplicitArgumentBarrier, typename T> -Span<const T> MakeConstSpan(T* begin, T* end) noexcept { +Span<const T> MakeConstSpan(absl::Nullable<T*> begin, + absl::Nullable<T*> end) noexcept { return ABSL_HARDENING_ASSERT(begin <= end), Span<const T>(begin, end - begin); } diff --git a/absl/types/span_test.cc b/absl/types/span_test.cc index 13264aae..29e8681f 100644 --- a/absl/types/span_test.cc +++ b/absl/types/span_test.cc @@ -191,7 +191,7 @@ TEST(IntSpan, SpanOfDerived) { } void TestInitializerList(absl::Span<const int> s, const std::vector<int>& v) { - EXPECT_TRUE(absl::equal(s.begin(), s.end(), v.begin(), v.end())); + EXPECT_TRUE(std::equal(s.begin(), s.end(), v.begin(), v.end())); } TEST(ConstIntSpan, InitializerListConversion) { diff --git a/absl/utility/BUILD.bazel b/absl/utility/BUILD.bazel index ca4bc0a6..1c01fc12 100644 --- a/absl/utility/BUILD.bazel +++ b/absl/utility/BUILD.bazel @@ -21,7 +21,14 @@ load( "ABSL_TEST_COPTS", ) -package(default_visibility = ["//visibility:public"]) +package( + default_visibility = ["//visibility:public"], + features = [ + "header_modules", + "layering_check", + "parse_headers", + ], +) licenses(["notice"]) @@ -49,6 +56,31 @@ cc_test( "//absl/base:core_headers", "//absl/memory", "//absl/strings", + "@com_google_googletest//:gtest", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "if_constexpr", + hdrs = [ + "internal/if_constexpr.h", + ], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base:config", + ], +) + +cc_test( + name = "if_constexpr_test", + srcs = ["internal/if_constexpr_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":if_constexpr", + "@com_google_googletest//:gtest", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/utility/CMakeLists.txt b/absl/utility/CMakeLists.txt index 865b758f..27ee0de5 100644 --- a/absl/utility/CMakeLists.txt +++ b/absl/utility/CMakeLists.txt @@ -42,3 +42,27 @@ absl_cc_test( absl::strings GTest::gmock_main ) + +absl_cc_library( + NAME + if_constexpr + HDRS + "internal/if_constexpr.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::config + PUBLIC +) + +absl_cc_test( + NAME + if_constexpr_test + SRCS + "internal/if_constexpr_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::if_constexpr + GTest::gmock_main +) diff --git a/absl/utility/internal/if_constexpr.h b/absl/utility/internal/if_constexpr.h new file mode 100644 index 00000000..7a26311d --- /dev/null +++ b/absl/utility/internal/if_constexpr.h @@ -0,0 +1,70 @@ +// Copyright 2023 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. + +// The IfConstexpr and IfConstexprElse utilities in this file are meant to be +// used to emulate `if constexpr` in pre-C++17 mode in library implementation. +// The motivation is to allow for avoiding complex SFINAE. +// +// The functions passed in must depend on the type(s) of the object(s) that +// require SFINAE. For example: +// template<typename T> +// int MaybeFoo(T& t) { +// if constexpr (HasFoo<T>::value) return t.foo(); +// return 0; +// } +// +// can be written in pre-C++17 as: +// +// template<typename T> +// int MaybeFoo(T& t) { +// int i = 0; +// absl::utility_internal::IfConstexpr<HasFoo<T>::value>( +// [&](const auto& fooer) { i = fooer.foo(); }, t); +// return i; +// } + +#ifndef ABSL_UTILITY_INTERNAL_IF_CONSTEXPR_H_ +#define ABSL_UTILITY_INTERNAL_IF_CONSTEXPR_H_ + +#include <tuple> +#include <utility> + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +namespace utility_internal { + +template <bool condition, typename TrueFunc, typename FalseFunc, + typename... Args> +auto IfConstexprElse(TrueFunc&& true_func, FalseFunc&& false_func, + Args&&... args) { + return std::get<condition>(std::forward_as_tuple( + std::forward<FalseFunc>(false_func), std::forward<TrueFunc>(true_func)))( + std::forward<Args>(args)...); +} + +template <bool condition, typename Func, typename... Args> +void IfConstexpr(Func&& func, Args&&... args) { + IfConstexprElse<condition>(std::forward<Func>(func), [](auto&&...){}, + std::forward<Args>(args)...); +} + +} // namespace utility_internal + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_UTILITY_INTERNAL_IF_CONSTEXPR_H_ diff --git a/absl/utility/internal/if_constexpr_test.cc b/absl/utility/internal/if_constexpr_test.cc new file mode 100644 index 00000000..d1ee7236 --- /dev/null +++ b/absl/utility/internal/if_constexpr_test.cc @@ -0,0 +1,79 @@ +// Copyright 2023 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/utility/internal/if_constexpr.h" + +#include <utility> + +#include "gtest/gtest.h" + +namespace { + +struct Empty {}; +struct HasFoo { + int foo() const { return 1; } +}; + +TEST(IfConstexpr, Basic) { + int i = 0; + absl::utility_internal::IfConstexpr<false>( + [&](const auto& t) { i = t.foo(); }, Empty{}); + EXPECT_EQ(i, 0); + + absl::utility_internal::IfConstexpr<false>( + [&](const auto& t) { i = t.foo(); }, HasFoo{}); + EXPECT_EQ(i, 0); + + absl::utility_internal::IfConstexpr<true>( + [&](const auto& t) { i = t.foo(); }, HasFoo{}); + EXPECT_EQ(i, 1); +} + +TEST(IfConstexprElse, Basic) { + EXPECT_EQ(absl::utility_internal::IfConstexprElse<false>( + [&](const auto& t) { return t.foo(); }, [&](const auto&) { return 2; }, + Empty{}), 2); + + EXPECT_EQ(absl::utility_internal::IfConstexprElse<false>( + [&](const auto& t) { return t.foo(); }, [&](const auto&) { return 2; }, + HasFoo{}), 2); + + EXPECT_EQ(absl::utility_internal::IfConstexprElse<true>( + [&](const auto& t) { return t.foo(); }, [&](const auto&) { return 2; }, + HasFoo{}), 1); +} + +struct HasFooRValue { + int foo() && { return 1; } +}; +struct RValueFunc { + void operator()(HasFooRValue&& t) && { *i = std::move(t).foo(); } + + int* i = nullptr; +}; + +TEST(IfConstexpr, RValues) { + int i = 0; + RValueFunc func = {&i}; + absl::utility_internal::IfConstexpr<false>( + std::move(func), HasFooRValue{}); + EXPECT_EQ(i, 0); + + func = RValueFunc{&i}; + absl::utility_internal::IfConstexpr<true>( + std::move(func), HasFooRValue{}); + EXPECT_EQ(i, 1); +} + +} // namespace diff --git a/absl/utility/utility.h b/absl/utility/utility.h index bf923220..fc0d1f65 100644 --- a/absl/utility/utility.h +++ b/absl/utility/utility.h @@ -12,17 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. // -// This header file contains C++11 versions of standard <utility> header -// abstractions available within C++14 and C++17, and are designed to be drop-in +// This header file contains C++14 versions of standard <utility> header +// abstractions available within C++17, and are designed to be drop-in // replacement for code compliant with C++14 and C++17. // // The following abstractions are defined: // -// * integer_sequence<T, Ints...> == std::integer_sequence<T, Ints...> -// * index_sequence<Ints...> == std::index_sequence<Ints...> -// * make_integer_sequence<T, N> == std::make_integer_sequence<T, N> -// * make_index_sequence<N> == std::make_index_sequence<N> -// * index_sequence_for<Ts...> == std::index_sequence_for<Ts...> // * apply<Functor, Tuple> == std::apply<Functor, Tuple> // * exchange<T> == std::exchange<T> // * make_from_tuple<T> == std::make_from_tuple<T> @@ -33,7 +28,6 @@ // // References: // -// https://en.cppreference.com/w/cpp/utility/integer_sequence // https://en.cppreference.com/w/cpp/utility/apply // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3658.html @@ -53,68 +47,18 @@ namespace absl { ABSL_NAMESPACE_BEGIN -// integer_sequence -// -// Class template representing a compile-time integer sequence. An instantiation -// of `integer_sequence<T, Ints...>` has a sequence of integers encoded in its -// type through its template arguments (which is a common need when -// working with C++11 variadic templates). `absl::integer_sequence` is designed -// to be a drop-in replacement for C++14's `std::integer_sequence`. -// -// Example: -// -// template< class T, T... Ints > -// void user_function(integer_sequence<T, Ints...>); -// -// int main() -// { -// // user_function's `T` will be deduced to `int` and `Ints...` -// // will be deduced to `0, 1, 2, 3, 4`. -// user_function(make_integer_sequence<int, 5>()); -// } -template <typename T, T... Ints> -struct integer_sequence { - using value_type = T; - static constexpr size_t size() noexcept { return sizeof...(Ints); } -}; - -// index_sequence -// -// A helper template for an `integer_sequence` of `size_t`, -// `absl::index_sequence` is designed to be a drop-in replacement for C++14's -// `std::index_sequence`. -template <size_t... Ints> -using index_sequence = integer_sequence<size_t, Ints...>; +// Historical note: Abseil once provided implementations of these +// abstractions for platforms that had not yet provided them. Those +// platforms are no longer supported. New code should simply use the +// the ones from std directly. +using std::index_sequence; +using std::index_sequence_for; +using std::integer_sequence; +using std::make_index_sequence; +using std::make_integer_sequence; namespace utility_internal { -template <typename Seq, size_t SeqSize, size_t Rem> -struct Extend; - -// Note that SeqSize == sizeof...(Ints). It's passed explicitly for efficiency. -template <typename T, T... Ints, size_t SeqSize> -struct Extend<integer_sequence<T, Ints...>, SeqSize, 0> { - using type = integer_sequence<T, Ints..., (Ints + SeqSize)...>; -}; - -template <typename T, T... Ints, size_t SeqSize> -struct Extend<integer_sequence<T, Ints...>, SeqSize, 1> { - using type = integer_sequence<T, Ints..., (Ints + SeqSize)..., 2 * SeqSize>; -}; - -// Recursion helper for 'make_integer_sequence<T, N>'. -// 'Gen<T, N>::type' is an alias for 'integer_sequence<T, 0, 1, ... N-1>'. -template <typename T, size_t N> -struct Gen { - using type = - typename Extend<typename Gen<T, N / 2>::type, N / 2, N % 2>::type; -}; - -template <typename T> -struct Gen<T, 0> { - using type = integer_sequence<T>; -}; - template <typename T> struct InPlaceTypeTag { explicit InPlaceTypeTag() = delete; @@ -131,32 +75,6 @@ struct InPlaceIndexTag { } // namespace utility_internal -// Compile-time sequences of integers - -// make_integer_sequence -// -// This template alias is equivalent to -// `integer_sequence<int, 0, 1, ..., N-1>`, and is designed to be a drop-in -// replacement for C++14's `std::make_integer_sequence`. -template <typename T, T N> -using make_integer_sequence = typename utility_internal::Gen<T, N>::type; - -// make_index_sequence -// -// This template alias is equivalent to `index_sequence<0, 1, ..., N-1>`, -// and is designed to be a drop-in replacement for C++14's -// `std::make_index_sequence`. -template <size_t N> -using make_index_sequence = make_integer_sequence<size_t, N>; - -// index_sequence_for -// -// Converts a typename pack into an index sequence of the same length, and -// is designed to be a drop-in replacement for C++14's -// `std::index_sequence_for()` -template <typename... Ts> -using index_sequence_for = make_index_sequence<sizeof...(Ts)>; - // Tag types #ifdef ABSL_USES_STD_OPTIONAL diff --git a/absl/utility/utility_test.cc b/absl/utility/utility_test.cc index 2f0509aa..1af68136 100644 --- a/absl/utility/utility_test.cc +++ b/absl/utility/utility_test.cc @@ -30,139 +30,10 @@ namespace { -#ifdef _MSC_VER -// Warnings for unused variables in this test are false positives. On other -// platforms, they are suppressed by ABSL_ATTRIBUTE_UNUSED, but that doesn't -// work on MSVC. -// Both the unused variables and the name length warnings are due to calls -// to absl::make_index_sequence with very large values, creating very long type -// names. The resulting warnings are so long they make build output unreadable. -#pragma warning(push) -#pragma warning(disable : 4503) // decorated name length exceeded -#pragma warning(disable : 4101) // unreferenced local variable -#endif // _MSC_VER - using ::testing::ElementsAre; using ::testing::Pointee; using ::testing::StaticAssertTypeEq; -TEST(IntegerSequenceTest, ValueType) { - StaticAssertTypeEq<int, absl::integer_sequence<int>::value_type>(); - StaticAssertTypeEq<char, absl::integer_sequence<char>::value_type>(); -} - -TEST(IntegerSequenceTest, Size) { - EXPECT_EQ(0, (absl::integer_sequence<int>::size())); - EXPECT_EQ(1, (absl::integer_sequence<int, 0>::size())); - EXPECT_EQ(1, (absl::integer_sequence<int, 1>::size())); - EXPECT_EQ(2, (absl::integer_sequence<int, 1, 2>::size())); - EXPECT_EQ(3, (absl::integer_sequence<int, 0, 1, 2>::size())); - EXPECT_EQ(3, (absl::integer_sequence<int, -123, 123, 456>::size())); - constexpr size_t sz = absl::integer_sequence<int, 0, 1>::size(); - EXPECT_EQ(2, sz); -} - -TEST(IntegerSequenceTest, MakeIndexSequence) { - StaticAssertTypeEq<absl::index_sequence<>, absl::make_index_sequence<0>>(); - StaticAssertTypeEq<absl::index_sequence<0>, absl::make_index_sequence<1>>(); - StaticAssertTypeEq<absl::index_sequence<0, 1>, - absl::make_index_sequence<2>>(); - StaticAssertTypeEq<absl::index_sequence<0, 1, 2>, - absl::make_index_sequence<3>>(); -} - -TEST(IntegerSequenceTest, MakeIntegerSequence) { - StaticAssertTypeEq<absl::integer_sequence<int>, - absl::make_integer_sequence<int, 0>>(); - StaticAssertTypeEq<absl::integer_sequence<int, 0>, - absl::make_integer_sequence<int, 1>>(); - StaticAssertTypeEq<absl::integer_sequence<int, 0, 1>, - absl::make_integer_sequence<int, 2>>(); - StaticAssertTypeEq<absl::integer_sequence<int, 0, 1, 2>, - absl::make_integer_sequence<int, 3>>(); -} - -template <typename... Ts> -class Counter {}; - -template <size_t... Is> -void CountAll(absl::index_sequence<Is...>) { - // We only need an alias here, but instantiate a variable to silence warnings - // for unused typedefs in some compilers. - ABSL_ATTRIBUTE_UNUSED Counter<absl::make_index_sequence<Is>...> seq; -} - -// This test verifies that absl::make_index_sequence can handle large arguments -// without blowing up template instantiation stack, going OOM or taking forever -// to compile (there is hard 15 minutes limit imposed by forge). -TEST(IntegerSequenceTest, MakeIndexSequencePerformance) { - // O(log N) template instantiations. - // We only need an alias here, but instantiate a variable to silence warnings - // for unused typedefs in some compilers. - ABSL_ATTRIBUTE_UNUSED absl::make_index_sequence<(1 << 16) - 1> seq; - // O(N) template instantiations. - CountAll(absl::make_index_sequence<(1 << 8) - 1>()); -} - -template <typename F, typename Tup, size_t... Is> -auto ApplyFromTupleImpl(F f, const Tup& tup, absl::index_sequence<Is...>) - -> decltype(f(std::get<Is>(tup)...)) { - return f(std::get<Is>(tup)...); -} - -template <typename Tup> -using TupIdxSeq = absl::make_index_sequence<std::tuple_size<Tup>::value>; - -template <typename F, typename Tup> -auto ApplyFromTuple(F f, const Tup& tup) - -> decltype(ApplyFromTupleImpl(f, tup, TupIdxSeq<Tup>{})) { - return ApplyFromTupleImpl(f, tup, TupIdxSeq<Tup>{}); -} - -template <typename T> -std::string Fmt(const T& x) { - std::ostringstream os; - os << x; - return os.str(); -} - -struct PoorStrCat { - template <typename... Args> - std::string operator()(const Args&... args) const { - std::string r; - for (const auto& e : {Fmt(args)...}) r += e; - return r; - } -}; - -template <typename Tup, size_t... Is> -std::vector<std::string> TupStringVecImpl(const Tup& tup, - absl::index_sequence<Is...>) { - return {Fmt(std::get<Is>(tup))...}; -} - -template <typename... Ts> -std::vector<std::string> TupStringVec(const std::tuple<Ts...>& tup) { - return TupStringVecImpl(tup, absl::index_sequence_for<Ts...>()); -} - -TEST(MakeIndexSequenceTest, ApplyFromTupleExample) { - PoorStrCat f{}; - EXPECT_EQ("12abc3.14", f(12, "abc", 3.14)); - EXPECT_EQ("12abc3.14", ApplyFromTuple(f, std::make_tuple(12, "abc", 3.14))); -} - -TEST(IndexSequenceForTest, Basic) { - StaticAssertTypeEq<absl::index_sequence<>, absl::index_sequence_for<>>(); - StaticAssertTypeEq<absl::index_sequence<0>, absl::index_sequence_for<int>>(); - StaticAssertTypeEq<absl::index_sequence<0, 1, 2, 3>, - absl::index_sequence_for<int, void, char, int>>(); -} - -TEST(IndexSequenceForTest, Example) { - EXPECT_THAT(TupStringVec(std::make_tuple(12, "abc", 3.14)), - ElementsAre("12", "abc", "3.14")); -} int Function(int a, int b) { return a - b; } diff --git a/ci/absl_alternate_options.h b/ci/absl_alternate_options.h index 82d2ecf8..a5638591 100644 --- a/ci/absl_alternate_options.h +++ b/ci/absl_alternate_options.h @@ -22,6 +22,7 @@ #define ABSL_OPTION_USE_STD_OPTIONAL 0 #define ABSL_OPTION_USE_STD_STRING_VIEW 0 #define ABSL_OPTION_USE_STD_VARIANT 0 +#define ABSL_OPTION_USE_STD_ORDERING 0 #define ABSL_OPTION_USE_INLINE_NAMESPACE 1 #define ABSL_OPTION_INLINE_NAMESPACE_NAME ns #define ABSL_OPTION_HARDENED 1 diff --git a/ci/cmake_common.sh b/ci/cmake_common.sh index 373aaa9c..784b3815 100644 --- a/ci/cmake_common.sh +++ b/ci/cmake_common.sh @@ -14,7 +14,11 @@ # The commit of GoogleTest to be used in the CMake tests in this directory. # Keep this in sync with the commit in the WORKSPACE file. -readonly ABSL_GOOGLETEST_COMMIT="b796f7d44681514f58a683a3a71ff17c94edb0c1" # v1.13.0 +# TODO(dmauro): After the next GoogleTest release, use the stable file required +# by Bzlmod. This means downloading a copy of the file and reuploading it to +# avoid changing checksums if the compression is changed by GitHub. It also +# means stop referring to it as a commit and instead use the uploaded filename. +readonly ABSL_GOOGLETEST_COMMIT="f8d7d77c06936315286eb55f8de22cd23c188571" # Avoid depending on GitHub by looking for a cached copy of the commit first. if [[ -r "${KOKORO_GFILE_DIR:-}/distdir/${ABSL_GOOGLETEST_COMMIT}.zip" ]]; then diff --git a/ci/linux_arm_clang-latest_libcxx_bazel.sh b/ci/linux_arm_clang-latest_libcxx_bazel.sh new file mode 100755 index 00000000..13f4ad13 --- /dev/null +++ b/ci/linux_arm_clang-latest_libcxx_bazel.sh @@ -0,0 +1,100 @@ +#!/bin/bash +# +# Copyright 2019 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. + +# This script that can be invoked to test abseil-cpp in a hermetic environment +# using a Docker image on Linux. You must have Docker installed to use this +# script. + +set -euox pipefail + +if [[ -z ${ABSEIL_ROOT:-} ]]; then + ABSEIL_ROOT="$(realpath $(dirname ${0})/..)" +fi + +if [[ -z ${STD:-} ]]; then + STD="c++14 c++17 c++20" +fi + +if [[ -z ${COMPILATION_MODE:-} ]]; then + COMPILATION_MODE="fastbuild opt" +fi + +if [[ -z ${EXCEPTIONS_MODE:-} ]]; then + EXCEPTIONS_MODE="-fno-exceptions -fexceptions" +fi + +source "${ABSEIL_ROOT}/ci/linux_docker_containers.sh" +readonly DOCKER_CONTAINER=${LINUX_ARM_CLANG_LATEST_CONTAINER} + +# USE_BAZEL_CACHE=1 only works on Kokoro. +# Without access to the credentials this won't work. +if [[ ${USE_BAZEL_CACHE:-0} -ne 0 ]]; then + DOCKER_EXTRA_ARGS="--mount type=bind,source=${KOKORO_KEYSTORE_DIR},target=/keystore,readonly ${DOCKER_EXTRA_ARGS:-}" + # Bazel doesn't track changes to tools outside of the workspace + # (e.g. /usr/bin/gcc), so by appending the docker container to the + # remote_http_cache url, we make changes to the container part of + # the cache key. Hashing the key is to make it shorter and url-safe. + container_key=$(echo ${DOCKER_CONTAINER} | sha256sum | head -c 16) + BAZEL_EXTRA_ARGS="--remote_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}" +fi + +# Avoid depending on external sites like GitHub by checking --distdir for +# external dependencies first. +# https://docs.bazel.build/versions/master/guide.html#distdir +if [[ ${KOKORO_GFILE_DIR:-} ]] && [[ -d "${KOKORO_GFILE_DIR}/distdir" ]]; then + DOCKER_EXTRA_ARGS="--mount type=bind,source=${KOKORO_GFILE_DIR}/distdir,target=/distdir,readonly ${DOCKER_EXTRA_ARGS:-}" + BAZEL_EXTRA_ARGS="--distdir=/distdir ${BAZEL_EXTRA_ARGS:-}" +fi + +for std in ${STD}; do + for compilation_mode in ${COMPILATION_MODE}; do + for exceptions_mode in ${EXCEPTIONS_MODE}; do + echo "--------------------------------------------------------------------" + time docker run \ + --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp-ro,readonly \ + --tmpfs=/abseil-cpp \ + --workdir=/abseil-cpp \ + --cap-add=SYS_PTRACE \ + --rm \ + -e CC="/opt/llvm/clang/bin/clang" \ + -e BAZEL_CXXOPTS="-std=${std}:-nostdinc++" \ + -e BAZEL_LINKOPTS="-L/opt/llvm/clang/lib/aarch64-unknown-linux-gnu:-lc++:-lc++abi:-lm:-Wl,-rpath=/opt/llvm/clang/lib/aarch64-unknown-linux-gnu" \ + -e CPLUS_INCLUDE_PATH="/opt/llvm/clang/include/aarch64-unknown-linux-gnu/c++/v1:/opt/llvm/clang/include/c++/v1" \ + ${DOCKER_EXTRA_ARGS:-} \ + ${DOCKER_CONTAINER} \ + /bin/sh -c " + cp -r /abseil-cpp-ro/* /abseil-cpp/ + if [ -n \"${ALTERNATE_OPTIONS:-}\" ]; then + cp ${ALTERNATE_OPTIONS:-} absl/base/options.h || exit 1 + fi + /usr/local/bin/bazel test ... \ + --compilation_mode=\"${compilation_mode}\" \ + --copt=\"${exceptions_mode}\" \ + --copt=\"-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1\" \ + --copt=-Werror \ + --define=\"absl=1\" \ + --enable_bzlmod=true \ + --features=external_include_paths \ + --keep_going \ + --show_timestamps \ + --test_env=\"GTEST_INSTALL_FAILURE_SIGNAL_HANDLER=1\" \ + --test_env=\"TZDIR=/abseil-cpp/absl/time/internal/cctz/testdata/zoneinfo\" \ + --test_output=errors \ + --test_tag_filters=-benchmark \ + ${BAZEL_EXTRA_ARGS:-}" + done + done +done diff --git a/ci/linux_clang-latest_libcxx_asan_bazel.sh b/ci/linux_clang-latest_libcxx_asan_bazel.sh index f9c146b0..3153fae4 100755 --- a/ci/linux_clang-latest_libcxx_asan_bazel.sh +++ b/ci/linux_clang-latest_libcxx_asan_bazel.sh @@ -48,7 +48,7 @@ if [[ ${USE_BAZEL_CACHE:-0} -ne 0 ]]; then # remote_http_cache url, we make changes to the container part of # the cache key. Hashing the key is to make it shorter and url-safe. container_key=$(echo ${DOCKER_CONTAINER} | sha256sum | head -c 16) - BAZEL_EXTRA_ARGS="--remote_http_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}" + BAZEL_EXTRA_ARGS="--remote_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}" fi # Avoid depending on external sites like GitHub by checking --distdir for @@ -70,8 +70,8 @@ for std in ${STD}; do --rm \ -e CC="/opt/llvm/clang/bin/clang" \ -e BAZEL_CXXOPTS="-std=${std}:-nostdinc++" \ - -e BAZEL_LINKOPTS="-L/opt/llvm/libcxx/lib/x86_64-unknown-linux-gnu:-lc++:-lc++abi:-lm:-Wl,-rpath=/opt/llvm/libcxx/lib/x86_64-unknown-linux-gnu" \ - -e CPLUS_INCLUDE_PATH="/opt/llvm/libcxx/include/x86_64-unknown-linux-gnu/c++/v1:/opt/llvm/libcxx/include/c++/v1" \ + -e BAZEL_LINKOPTS="-L/opt/llvm/libcxx/lib:-lc++:-lc++abi:-lm:-Wl,-rpath=/opt/llvm/libcxx/lib" \ + -e CPLUS_INCLUDE_PATH="/opt/llvm/libcxx/include/c++/v1" \ ${DOCKER_EXTRA_ARGS:-} \ ${DOCKER_CONTAINER} \ /usr/local/bin/bazel test ... \ @@ -84,7 +84,7 @@ for std in ${STD}; do --copt="-fsanitize=undefined" \ --copt="-fno-sanitize-blacklist" \ --copt=-Werror \ - --distdir="/bazel-distdir" \ + --enable_bzlmod=true \ --features=external_include_paths \ --keep_going \ --linkopt="-fsanitize=address" \ diff --git a/ci/linux_clang-latest_libcxx_bazel.sh b/ci/linux_clang-latest_libcxx_bazel.sh index 38b2d744..4f3eba49 100755 --- a/ci/linux_clang-latest_libcxx_bazel.sh +++ b/ci/linux_clang-latest_libcxx_bazel.sh @@ -48,7 +48,7 @@ if [[ ${USE_BAZEL_CACHE:-0} -ne 0 ]]; then # remote_http_cache url, we make changes to the container part of # the cache key. Hashing the key is to make it shorter and url-safe. container_key=$(echo ${DOCKER_CONTAINER} | sha256sum | head -c 16) - BAZEL_EXTRA_ARGS="--remote_http_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}" + BAZEL_EXTRA_ARGS="--remote_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}" fi # Avoid depending on external sites like GitHub by checking --distdir for @@ -71,8 +71,8 @@ for std in ${STD}; do --rm \ -e CC="/opt/llvm/clang/bin/clang" \ -e BAZEL_CXXOPTS="-std=${std}:-nostdinc++" \ - -e BAZEL_LINKOPTS="-L/opt/llvm/libcxx/lib/x86_64-unknown-linux-gnu:-lc++:-lc++abi:-lm:-Wl,-rpath=/opt/llvm/libcxx/lib/x86_64-unknown-linux-gnu" \ - -e CPLUS_INCLUDE_PATH="/opt/llvm/libcxx/include/x86_64-unknown-linux-gnu/c++/v1:/opt/llvm/libcxx/include/c++/v1" \ + -e BAZEL_LINKOPTS="-L/opt/llvm/libcxx/lib:-lc++:-lc++abi:-lm:-Wl,-rpath=/opt/llvm/libcxx/lib" \ + -e CPLUS_INCLUDE_PATH="/opt/llvm/libcxx/include/c++/v1" \ ${DOCKER_EXTRA_ARGS:-} \ ${DOCKER_CONTAINER} \ /bin/sh -c " @@ -86,7 +86,7 @@ for std in ${STD}; do --copt=\"-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1\" \ --copt=-Werror \ --define=\"absl=1\" \ - --distdir=\"/bazel-distdir\" \ + --enable_bzlmod=true \ --features=external_include_paths \ --keep_going \ --show_timestamps \ diff --git a/ci/linux_clang-latest_libcxx_tsan_bazel.sh b/ci/linux_clang-latest_libcxx_tsan_bazel.sh index 34d7940e..06f4c2ec 100755 --- a/ci/linux_clang-latest_libcxx_tsan_bazel.sh +++ b/ci/linux_clang-latest_libcxx_tsan_bazel.sh @@ -48,7 +48,7 @@ if [[ ${USE_BAZEL_CACHE:-0} -ne 0 ]]; then # remote_http_cache url, we make changes to the container part of # the cache key. Hashing the key is to make it shorter and url-safe. container_key=$(echo ${DOCKER_CONTAINER} | sha256sum | head -c 16) - BAZEL_EXTRA_ARGS="--remote_http_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}" + BAZEL_EXTRA_ARGS="--remote_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}" fi # Avoid depending on external sites like GitHub by checking --distdir for @@ -70,8 +70,8 @@ for std in ${STD}; do --rm \ -e CC="/opt/llvm/clang/bin/clang" \ -e BAZEL_CXXOPTS="-std=${std}:-nostdinc++" \ - -e BAZEL_LINKOPTS="-L/opt/llvm/libcxx-tsan/lib/x86_64-unknown-linux-gnu:-lc++:-lc++abi:-lm:-Wl,-rpath=/opt/llvm/libcxx-tsan/lib/x86_64-unknown-linux-gnu" \ - -e CPLUS_INCLUDE_PATH="/opt/llvm/libcxx-tsan/include/x86_64-unknown-linux-gnu/c++/v1:/opt/llvm/libcxx-tsan/include/c++/v1" \ + -e BAZEL_LINKOPTS="-L/opt/llvm/libcxx-tsan/lib:-lc++:-lc++abi:-lm:-Wl,-rpath=/opt/llvm/libcxx-tsan/lib" \ + -e CPLUS_INCLUDE_PATH="/opt/llvm/libcxx-tsan/include/c++/v1" \ ${DOCKER_EXTRA_ARGS:-} \ ${DOCKER_CONTAINER} \ /usr/local/bin/bazel test ... \ @@ -82,7 +82,7 @@ for std in ${STD}; do --copt="-fsanitize=thread" \ --copt="-fno-sanitize-blacklist" \ --copt=-Werror \ - --distdir="/bazel-distdir" \ + --enable_bzlmod=true \ --features=external_include_paths \ --keep_going \ --linkopt="-fsanitize=thread" \ diff --git a/ci/linux_clang-latest_libstdcxx_bazel.sh b/ci/linux_clang-latest_libstdcxx_bazel.sh index 13d56fc9..d499e13c 100755 --- a/ci/linux_clang-latest_libstdcxx_bazel.sh +++ b/ci/linux_clang-latest_libstdcxx_bazel.sh @@ -48,7 +48,7 @@ if [[ ${USE_BAZEL_CACHE:-0} -ne 0 ]]; then # remote_http_cache url, we make changes to the container part of # the cache key. Hashing the key is to make it shorter and url-safe. container_key=$(echo ${DOCKER_CONTAINER} | sha256sum | head -c 16) - BAZEL_EXTRA_ARGS="--remote_http_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}" + BAZEL_EXTRA_ARGS="--remote_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}" fi # Avoid depending on external sites like GitHub by checking --distdir for @@ -80,7 +80,7 @@ for std in ${STD}; do --copt="-march=haswell" \ --copt=-Werror \ --define="absl=1" \ - --distdir="/bazel-distdir" \ + --enable_bzlmod=true \ --features=external_include_paths \ --keep_going \ --linkopt="--gcc-toolchain=/usr/local" \ diff --git a/ci/linux_docker_containers.sh b/ci/linux_docker_containers.sh index dcaf18b9..232233d1 100644 --- a/ci/linux_docker_containers.sh +++ b/ci/linux_docker_containers.sh @@ -15,7 +15,8 @@ # The file contains Docker container identifiers currently used by test scripts. # Test scripts should source this file to get the identifiers. -readonly LINUX_ALPINE_CONTAINER="gcr.io/google.com/absl-177019/alpine:20201026" -readonly LINUX_CLANG_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20230217" -readonly LINUX_GCC_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20230217" +readonly LINUX_ALPINE_CONTAINER="gcr.io/google.com/absl-177019/alpine:20230612" +readonly LINUX_CLANG_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20231218" +readonly LINUX_ARM_CLANG_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_arm_hybrid-latest:20231219" +readonly LINUX_GCC_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20231218" readonly LINUX_GCC_FLOOR_CONTAINER="gcr.io/google.com/absl-177019/linux_gcc-floor:20230120" diff --git a/ci/linux_gcc-floor_libstdcxx_bazel.sh b/ci/linux_gcc-floor_libstdcxx_bazel.sh index 68b39994..5bd1dbf8 100755 --- a/ci/linux_gcc-floor_libstdcxx_bazel.sh +++ b/ci/linux_gcc-floor_libstdcxx_bazel.sh @@ -59,6 +59,9 @@ if [[ ${KOKORO_GFILE_DIR:-} ]] && [[ -d "${KOKORO_GFILE_DIR}/distdir" ]]; then BAZEL_EXTRA_ARGS="--distdir=/distdir ${BAZEL_EXTRA_ARGS:-}" fi +# TODO(absl-team): This currently uses Bazel 5. When upgrading to a version +# of Bazel that supports Bzlmod, add --enable_bzlmod=false to keep test +# coverage for the old WORKSPACE dependency management. for std in ${STD}; do for compilation_mode in ${COMPILATION_MODE}; do for exceptions_mode in ${EXCEPTIONS_MODE}; do diff --git a/ci/linux_gcc-latest_libstdcxx_bazel.sh b/ci/linux_gcc-latest_libstdcxx_bazel.sh index 091acb33..8f773466 100755 --- a/ci/linux_gcc-latest_libstdcxx_bazel.sh +++ b/ci/linux_gcc-latest_libstdcxx_bazel.sh @@ -48,7 +48,7 @@ if [[ ${USE_BAZEL_CACHE:-0} -ne 0 ]]; then # remote_http_cache url, we make changes to the container part of # the cache key. Hashing the key is to make it shorter and url-safe. container_key=$(echo ${DOCKER_CONTAINER} | sha256sum | head -c 16) - BAZEL_EXTRA_ARGS="--remote_http_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}" + BAZEL_EXTRA_ARGS="--remote_cache=https://storage.googleapis.com/absl-bazel-remote-cache/${container_key} --google_credentials=/keystore/73103_absl-bazel-remote-cache ${BAZEL_EXTRA_ARGS:-}" fi # Avoid depending on external sites like GitHub by checking --distdir for @@ -84,7 +84,7 @@ for std in ${STD}; do --copt=\"-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1\" \ --copt=-Werror \ --define=\"absl=1\" \ - --distdir=\"/bazel-distdir\" \ + --enable_bzlmod=true \ --features=external_include_paths \ --keep_going \ --show_timestamps \ diff --git a/ci/macos_xcode_bazel.sh b/ci/macos_xcode_bazel.sh index 04c9a1a2..bb8fb4bd 100755 --- a/ci/macos_xcode_bazel.sh +++ b/ci/macos_xcode_bazel.sh @@ -24,7 +24,7 @@ if [[ -z ${ABSEIL_ROOT:-} ]]; then fi # If we are running on Kokoro, check for a versioned Bazel binary. -KOKORO_GFILE_BAZEL_BIN="bazel-5.1.1-darwin-x86_64" +KOKORO_GFILE_BAZEL_BIN="bazel-7.0.0-darwin-x86_64" if [[ ${KOKORO_GFILE_DIR:-} ]] && [[ -f ${KOKORO_GFILE_DIR}/${KOKORO_GFILE_BAZEL_BIN} ]]; then BAZEL_BIN="${KOKORO_GFILE_DIR}/${KOKORO_GFILE_BAZEL_BIN}" chmod +x ${BAZEL_BIN} @@ -56,6 +56,7 @@ ${BAZEL_BIN} test ... \ --copt="-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1" \ --copt="-Werror" \ --cxxopt="-std=c++14" \ + --enable_bzlmod=true \ --features=external_include_paths \ --keep_going \ --show_timestamps \ diff --git a/ci/windows_clangcl_bazel.bat b/ci/windows_clangcl_bazel.bat new file mode 100755 index 00000000..5162628b --- /dev/null +++ b/ci/windows_clangcl_bazel.bat @@ -0,0 +1,61 @@ +:: Copyright 2023 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. + +SETLOCAL ENABLEDELAYEDEXPANSION + +:: Set LLVM directory. +SET BAZEL_LLVM=C:\Program Files\LLVM + +:: Change directory to the root of the project. +CD %~dp0\.. +if %errorlevel% neq 0 EXIT /B 1 + +:: Set the standard version, [c++14|c++17|c++20|c++latest] +:: https://msdn.microsoft.com/en-us/library/mt490614.aspx +:: The default is c++14 if not set on command line. +IF "%STD%"=="" SET STD=c++14 + +:: Set the compilation_mode (fastbuild|opt|dbg) +:: https://docs.bazel.build/versions/master/user-manual.html#flag--compilation_mode +:: The default is fastbuild +IF "%COMPILATION_MODE%"=="" SET COMPILATION_MODE=fastbuild + +:: Copy the alternate option file, if specified. +IF NOT "%ALTERNATE_OPTIONS%"=="" copy %ALTERNATE_OPTIONS% absl\base\options.h + +:: To upgrade Bazel, first download a new binary from +:: https://github.com/bazelbuild/bazel/releases and copy it to +:: /google/data/rw/teams/absl/kokoro/windows. +:: +:: TODO(absl-team): Remove -Wno-microsoft-cast +%KOKORO_GFILE_DIR%\bazel-7.0.0-windows-x86_64.exe ^ + test ... ^ + --compilation_mode=%COMPILATION_MODE% ^ + --compiler=clang-cl ^ + --copt=/std:%STD% ^ + --copt=/WX ^ + --copt=-Wno-microsoft-cast ^ + --define=absl=1 ^ + --distdir=%KOKORO_GFILE_DIR%\distdir ^ + --enable_bzlmod=true ^ + --extra_execution_platforms=//absl:x64_windows-clang-cl ^ + --extra_toolchains=@local_config_cc//:cc-toolchain-x64_windows-clang-cl ^ + --keep_going ^ + --test_env="GTEST_INSTALL_FAILURE_SIGNAL_HANDLER=1" ^ + --test_env=TZDIR="%CD%\absl\time\internal\cctz\testdata\zoneinfo" ^ + --test_output=errors ^ + --test_tag_filters=-benchmark + +if %errorlevel% neq 0 EXIT /B 1 +EXIT /B 0 diff --git a/ci/windows_msvc_bazel.bat b/ci/windows_msvc_bazel.bat new file mode 100755 index 00000000..e2acf5fd --- /dev/null +++ b/ci/windows_msvc_bazel.bat @@ -0,0 +1,52 @@ +:: Copyright 2023 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. + +SETLOCAL ENABLEDELAYEDEXPANSION + +:: Change directory to the root of the project. +CD %~dp0\.. +if %errorlevel% neq 0 EXIT /B 1 + +:: Set the standard version, [c++14|c++latest] +:: https://msdn.microsoft.com/en-us/library/mt490614.aspx +:: The default is c++14 if not set on command line. +IF "%STD%"=="" SET STD=c++14 + +:: Set the compilation_mode (fastbuild|opt|dbg) +:: https://docs.bazel.build/versions/master/user-manual.html#flag--compilation_mode +:: The default is fastbuild +IF "%COMPILATION_MODE%"=="" SET COMPILATION_MODE=fastbuild + +:: Copy the alternate option file, if specified. +IF NOT "%ALTERNATE_OPTIONS%"=="" copy %ALTERNATE_OPTIONS% absl\base\options.h + +:: To upgrade Bazel, first download a new binary from +:: https://github.com/bazelbuild/bazel/releases and copy it to +:: /google/data/rw/teams/absl/kokoro/windows. +%KOKORO_GFILE_DIR%\bazel-7.0.0-windows-x86_64.exe ^ + test ... ^ + --compilation_mode=%COMPILATION_MODE% ^ + --copt=/WX ^ + --copt=/std:%STD% ^ + --define=absl=1 ^ + --distdir=%KOKORO_GFILE_DIR%\distdir ^ + --enable_bzlmod=true ^ + --keep_going ^ + --test_env="GTEST_INSTALL_FAILURE_SIGNAL_HANDLER=1" ^ + --test_env=TZDIR="%CD%\absl\time\internal\cctz\testdata\zoneinfo" ^ + --test_output=errors ^ + --test_tag_filters=-benchmark + +if %errorlevel% neq 0 EXIT /B 1 +EXIT /B 0 diff --git a/ci/windows_msvc_cmake.bat b/ci/windows_msvc_cmake.bat new file mode 100755 index 00000000..c0f1ac94 --- /dev/null +++ b/ci/windows_msvc_cmake.bat @@ -0,0 +1,74 @@ +:: Copyright 2023 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. + +SETLOCAL ENABLEDELAYEDEXPANSION + +:: The commit of GoogleTest to be used in the CMake tests in this directory. +:: Keep this in sync with the commit in the WORKSPACE file. +:: TODO(dmauro): After the next GoogleTest release, use the stable file required +:: by Bzlmod. This means downloading a copy of the file and reuploading it to +:: avoid changing checksums if the compression is changed by GitHub. It also +:: means stop referring to it as a commit and instead use the uploaded filename. +SET ABSL_GOOGLETEST_COMMIT=f8d7d77c06936315286eb55f8de22cd23c188571 + +IF EXIST %KOKORO_GFILE_DIR%\distdir\%ABSL_GOOGLETEST_COMMIT%.zip ( + SET ABSL_GOOGLETEST_DOWNLOAD_URL=file://%KOKORO_GFILE_DIR%\distdir\%ABSL_GOOGLETEST_COMMIT%.zip +) ELSE ( + SET ABSL_GOOGLETEST_DOWNLOAD_URL=https://github.com/google/googletest/archive/%ABSL_GOOGLETEST_COMMIT%.zip +) + +:: Replace '\' with '/' in Windows paths for CMake. +:: Note that this cannot go inside the IF block above, because BAT files are weird. +SET ABSL_GOOGLETEST_DOWNLOAD_URL=%ABSL_GOOGLETEST_DOWNLOAD_URL:\=/% + +IF EXIST "C:\Program Files\CMake\bin\" ( + SET CMAKE_BIN="C:\Program Files\CMake\bin\cmake.exe" + SET CTEST_BIN="C:\Program Files\CMake\bin\ctest.exe" +) ELSE ( + SET CMAKE_BIN="cmake.exe" + SET CTEST_BIN="ctest.exe" +) + +SET CTEST_OUTPUT_ON_FAILURE=1 +SET CMAKE_BUILD_PARALLEL_LEVEL=16 +SET CTEST_PARALLEL_LEVEL=16 + +:: Change directory to the root of the project. +CD %~dp0\.. +if %errorlevel% neq 0 EXIT /B 1 + +SET TZDIR=%CD%\absl\time\internal\cctz\testdata\zoneinfo + +MKDIR "build" +CD "build" + +SET CXXFLAGS="/WX" + +%CMAKE_BIN% ^ + -DABSL_BUILD_TEST_HELPERS=ON ^ + -DABSL_BUILD_TESTING=ON ^ + -DABSL_GOOGLETEST_DOWNLOAD_URL=%ABSL_GOOGLETEST_DOWNLOAD_URL% ^ + -DBUILD_SHARED_LIBS=%ABSL_CMAKE_BUILD_SHARED% ^ + -DCMAKE_CXX_STANDARD=%ABSL_CMAKE_CXX_STANDARD% ^ + -G "%ABSL_CMAKE_GENERATOR%" ^ + .. +IF %errorlevel% neq 0 EXIT /B 1 + +%CMAKE_BIN% --build . --target ALL_BUILD --config %ABSL_CMAKE_BUILD_TYPE% +IF %errorlevel% neq 0 EXIT /B 1 + +%CTEST_BIN% -C %ABSL_CMAKE_BUILD_TYPE% -E "absl_lifetime_test|absl_symbolize_test" +IF %errorlevel% neq 0 EXIT /B 1 + +EXIT /B 0 diff --git a/create_lts.py b/create_lts.py index 56170806..7e5368e1 100755 --- a/create_lts.py +++ b/create_lts.py @@ -33,7 +33,7 @@ def ReplaceStringsInFile(filename, replacement_dict): values Raises: - Exception: A failure occured + Exception: A failure occurred """ f = open(filename, 'r') content = f.read() @@ -62,7 +62,7 @@ def StripContentBetweenTags(filename, strip_begin_tag, strip_end_tag): strip_end_tag: the end of the content to be removed Raises: - Exception: A failure occured + Exception: A failure occurred """ f = open(filename, 'r') content = f.read() @@ -96,6 +96,11 @@ def main(argv): # Replacement directives go here. ReplaceStringsInFile( + 'MODULE.bazel', { + 'version = "head"': + 'version = "{}.0"'.format(datestamp) + }) + ReplaceStringsInFile( 'absl/base/config.h', { '#undef ABSL_LTS_RELEASE_VERSION': '#define ABSL_LTS_RELEASE_VERSION {}'.format(datestamp), |