diff options
author | Benjamin Barenblat <bbaren@google.com> | 2023-05-08 12:51:08 -0400 |
---|---|---|
committer | Benjamin Barenblat <bbaren@google.com> | 2023-05-08 12:51:08 -0400 |
commit | c15cec707b1c7d847e59d2db4d0b82b711a7ee6d (patch) | |
tree | 79f448d5bbc8cf52917b0b091f0e1ab60a419c85 | |
parent | f4f2c1da90c4e6a0683c4e66c0268baa1b79cdf3 (diff) | |
parent | c2435f8342c2d0ed8101cb43adfd605fdc52dca2 (diff) | |
download | abseil-c15cec707b1c7d847e59d2db4d0b82b711a7ee6d.tar.gz abseil-c15cec707b1c7d847e59d2db4d0b82b711a7ee6d.tar.bz2 abseil-c15cec707b1c7d847e59d2db4d0b82b711a7ee6d.zip |
Merge new upstream LTS 20230125.3
527 files changed, 31109 insertions, 6720 deletions
diff --git a/.github/ISSUE_TEMPLATE/00-bug_report.md b/.github/ISSUE_TEMPLATE/00-bug_report.md deleted file mode 100644 index 1edf3de0..00000000 --- a/.github/ISSUE_TEMPLATE/00-bug_report.md +++ /dev/null @@ -1,41 +0,0 @@ ---- -name: Bug report -about: Create a report to help us improve -title: '' -labels: 'bug' -assignees: '' ---- - -**Describe the bug** - -Include a clear and concise description of what the problem is, including what -you expected to happen, and what actually happened. - -**Steps to reproduce the bug** - -It's important that we are able to reproduce the problem that you are -experiencing. Please provide all code and relevant steps to reproduce the -problem, including your `BUILD`/`CMakeLists.txt` file and build commands. Links -to a GitHub branch or [godbolt.org](https://godbolt.org/) that demonstrate the -problem are also helpful. - -**What version of Abseil are you using?** - -**What operating system and version are you using** - -If you are using a Linux distribution please include the name and version of the -distribution as well. - -**What compiler and version are you using?** - -Please include the output of `gcc -v` or `clang -v`, or the equivalent for your -compiler. - -**What build system are you using?** - -Please include the output of `bazel --version` or `cmake --version`, or the -equivalent for your build system. - -**Additional context** - -Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/00-bug_report.yml b/.github/ISSUE_TEMPLATE/00-bug_report.yml new file mode 100644 index 00000000..32cebe15 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/00-bug_report.yml @@ -0,0 +1,53 @@ +name: Bug Report +description: Let us know that something does not work as expected. +title: "[Bug]: Please title this bug report" +body: + - type: textarea + id: what-happened + attributes: + label: Describe the issue + description: What happened, and what did you expect to happen? + validations: + required: true + - type: textarea + id: steps + attributes: + label: Steps to reproduce the problem + description: It is important that we are able to reproduce the problem that you are experiencing. Please provide all code and relevant steps to reproduce the problem, including your `BUILD`/`CMakeLists.txt` file and build commands. Links to a GitHub branch or [godbolt.org](https://godbolt.org/) that demonstrate the problem are also helpful. + validations: + required: true + - type: textarea + id: version + attributes: + label: What version of Abseil are you using? + description: Please include the output of `git rev-parse HEAD` or the name of the LTS release that you are using. + validations: + required: true + - type: textarea + id: os + attributes: + label: What operating system and version are you using? + description: If you are using a Linux distribution please include the name and version of the distribution as well. + validations: + required: true + - type: textarea + id: compiler + attributes: + label: What compiler and version are you using? + description: Please include the output of `gcc -v` or `clang -v`, or the equivalent for your compiler. + validations: + required: true + - type: textarea + id: buildsystem + attributes: + label: What build system are you using? + description: Please include the output of `bazel --version` or `cmake --version`, or the equivalent for your build system. + validations: + required: true + - type: textarea + id: additional + attributes: + label: Additional context + description: Add any other context about the problem here. + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/90-question.md b/.github/ISSUE_TEMPLATE/90-question.md deleted file mode 100644 index 84cf3491..00000000 --- a/.github/ISSUE_TEMPLATE/90-question.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -name: Question -about: Have a question? Ask us anything! :-) -title: '' -labels: 'question' -assignees: '' ---- diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 0086358d..c690fd9a 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1 +1,5 @@ -blank_issues_enabled: true +blank_issues_enabled: false +contact_links: + - name: Question + url: https://github.com/abseil/abseil-cpp/discussions + about: Have a question? Ask us anything! :-) diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index 00cddb84..c4a41e6d 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake @@ -13,6 +13,7 @@ set(ABSL_INTERNAL_DLL_FILES "base/internal/atomic_hook.h" "base/internal/cycleclock.cc" "base/internal/cycleclock.h" + "base/internal/cycleclock_config.h" "base/internal/direct_mmap.h" "base/internal/dynamic_annotations.h" "base/internal/endian.h" @@ -50,6 +51,7 @@ set(ABSL_INTERNAL_DLL_FILES "base/internal/unaligned_access.h" "base/internal/unscaledcycleclock.cc" "base/internal/unscaledcycleclock.h" + "base/internal/unscaledcycleclock_config.h" "base/log_severity.cc" "base/log_severity.h" "base/macros.h" @@ -69,6 +71,7 @@ set(ABSL_INTERNAL_DLL_FILES "container/internal/btree.h" "container/internal/btree_container.h" "container/internal/common.h" + "container/internal/common_policy_traits.h" "container/internal/compressed_tuple.h" "container/internal/container_memory.h" "container/internal/counting_allocator.h" @@ -88,6 +91,26 @@ set(ABSL_INTERNAL_DLL_FILES "container/internal/tracked.h" "container/node_hash_map.h" "container/node_hash_set.h" + "crc/crc32c.cc" + "crc/crc32c.h" + "crc/internal/cpu_detect.cc" + "crc/internal/cpu_detect.h" + "crc/internal/crc32c.h" + "crc/internal/crc32c_inline.h" + "crc/internal/crc32_x86_arm_combined_simd.h" + "crc/internal/crc.cc" + "crc/internal/crc.h" + "crc/internal/crc_cord_state.cc" + "crc/internal/crc_cord_state.h" + "crc/internal/crc_internal.h" + "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_non_temporal_memcpy.cc" + "crc/internal/crc_x86_arm_combined.cc" + "crc/internal/non_temporal_arm_intrinsics.h" + "crc/internal/non_temporal_memcpy.h" "debugging/failure_signal_handler.cc" "debugging/failure_signal_handler.h" "debugging/leak_check.h" @@ -123,6 +146,47 @@ set(ABSL_INTERNAL_DLL_FILES "hash/internal/spy_hash_state.h" "hash/internal/low_level_hash.h" "hash/internal/low_level_hash.cc" + "log/absl_check.h" + "log/absl_log.h" + "log/check.h" + "log/die_if_null.cc" + "log/die_if_null.h" + "log/globals.cc" + "log/globals.h" + "log/internal/append_truncated.h" + "log/internal/check_impl.h" + "log/internal/check_op.cc" + "log/internal/check_op.h" + "log/internal/conditions.cc" + "log/internal/conditions.h" + "log/internal/config.h" + "log/internal/globals.cc" + "log/internal/globals.h" + "log/internal/log_format.cc" + "log/internal/log_format.h" + "log/internal/log_impl.h" + "log/internal/log_message.cc" + "log/internal/log_message.h" + "log/internal/log_sink_set.cc" + "log/internal/log_sink_set.h" + "log/internal/nullguard.cc" + "log/internal/nullguard.h" + "log/internal/nullstream.h" + "log/internal/proto.h" + "log/internal/proto.cc" + "log/internal/strip.h" + "log/internal/structured.h" + "log/internal/voidify.h" + "log/initialize.cc" + "log/initialize.h" + "log/log.h" + "log/log_entry.cc" + "log/log_entry.h" + "log/log_sink.cc" + "log/log_sink.h" + "log/log_sink_registry.h" + "log/log_streamer.h" + "log/structured.h" "memory/memory.h" "meta/type_traits.h" "numeric/bits.h" @@ -149,7 +213,6 @@ set(ABSL_INTERNAL_DLL_FILES "random/internal/fast_uniform_bits.h" "random/internal/generate_real.h" "random/internal/iostream_state_saver.h" - "random/internal/mock_helpers.h" "random/internal/nonsecure_base.h" "random/internal/pcg_engine.h" "random/internal/platform.h" @@ -235,8 +298,13 @@ set(ABSL_INTERNAL_DLL_FILES "strings/internal/cordz_statistics.h" "strings/internal/cordz_update_scope.h" "strings/internal/cordz_update_tracker.h" + "strings/internal/damerau_levenshtein_distance.h" + "strings/internal/damerau_levenshtein_distance.cc" "strings/internal/stl_type_traits.h" "strings/internal/string_constant.h" + "strings/internal/stringify_sink.h" + "strings/internal/stringify_sink.cc" + "strings/internal/has_absl_stringify.h" "strings/match.cc" "strings/match.h" "strings/numbers.cc" @@ -269,6 +337,7 @@ set(ABSL_INTERNAL_DLL_FILES "strings/internal/str_format/bind.cc" "strings/internal/str_format/bind.h" "strings/internal/str_format/checker.h" + "strings/internal/str_format/constexpr_parser.h" "strings/internal/str_format/extension.cc" "strings/internal/str_format/extension.h" "strings/internal/str_format/float_conversion.cc" @@ -353,122 +422,205 @@ set(ABSL_INTERNAL_DLL_FILES ) set(ABSL_INTERNAL_DLL_TARGETS - "stacktrace" - "symbolize" - "examine_stack" - "failure_signal_handler" - "debugging_internal" - "demangle_internal" - "leak_check" - "stack_consumption" - "debugging" - "hash" - "spy_hash_state" - "city" - "memory" - "strings" - "strings_internal" - "cord" - "str_format" - "str_format_internal" - "pow10_helper" - "int128" - "numeric" - "utility" + "absl_check" + "absl_log" + "algorithm" + "algorithm_container" "any" + "any_invocable" + "atomic_hook" "bad_any_cast" "bad_any_cast_impl" - "span" - "optional" "bad_optional_access" "bad_variant_access" - "variant" - "compare" - "algorithm" - "algorithm_container" - "graphcycles_internal" - "kernel_timeout_internal" - "synchronization" - "thread_pool" - "any_invocable" + "base" + "base_internal" "bind_front" - "function_ref" - "atomic_hook" - "log_severity" - "raw_logging_internal" - "spinlock_wait" + "bits" + "btree" + "check" + "city" + "civil_time" + "compare" + "compressed_tuple" "config" - "dynamic_annotations" + "container" + "container_common" + "container_memory" + "cord" "core_headers" - "malloc_internal" - "base_internal" - "base" - "throw_delegate" - "pretty_function" + "counting_allocator" + "crc_cpu_detect" + "crc_internal" + "crc32c" + "debugging" + "debugging_internal" + "demangle_internal" + "die_if_null" + "dynamic_annotations" "endian" - "bits" + "examine_stack" "exponential_biased" - "periodic_sampler" - "scoped_set_env" - "type_traits" + "failure_signal_handler" + "fixed_array" + "flat_hash_map" + "flat_hash_set" + "function_ref" + "graphcycles_internal" + "hash" + "hash_function_defaults" + "hash_policy_traits" + "hashtable_debug" + "hashtable_debug_hooks" + "hashtablez_sampler" + "inlined_vector" + "inlined_vector_internal" + "int128" + "kernel_timeout_internal" + "layout" + "leak_check" + "log_internal_check_impl" + "log_internal_check_op" + "log_internal_conditions" + "log_internal_config" + "log_internal_format" + "log_internal_globals" + "log_internal_log_impl" + "log_internal_proto" + "log_internal_message" + "log_internal_log_sink_set" + "log_internal_nullguard" + "log_internal_nullstream" + "log_internal_strip" + "log_internal_voidify" + "log_internal_append_truncated" + "log_globals" + "log_initialize" + "log" + "log_entry" + "log_sink" + "log_sink_registry" + "log_streamer" + "log_internal_structured" + "log_severity" + "log_structured" + "malloc_internal" + "memory" "meta" - "random_random" + "node_hash_map" + "node_hash_set" + "node_slot_policy" + "non_temporal_arm_intrinsics" + "non_temporal_memcpy" + "numeric" + "optional" + "periodic_sampler" + "pow10_helper" + "pretty_function" "random_bit_gen_ref" "random_distributions" - "random_seed_gen_exception" - "random_seed_sequences" - "random_internal_traits" "random_internal_distribution_caller" "random_internal_distributions" - "random_internal_fast_uniform_bits" - "random_internal_seed_material" - "random_internal_pool_urbg" "random_internal_explicit_seed_seq" - "random_internal_sequence_urbg" - "random_internal_salted_seed_seq" - "random_internal_iostream_state_saver" - "random_internal_generate_real" - "random_internal_wide_multiply" "random_internal_fastmath" + "random_internal_fast_uniform_bits" + "random_internal_generate_real" + "random_internal_iostream_state_saver" "random_internal_nonsecure_base" "random_internal_pcg_engine" - "random_internal_randen_engine" "random_internal_platform" + "random_internal_pool_urbg" "random_internal_randen" - "random_internal_randen_slow" + "random_internal_randen_engine" "random_internal_randen_hwaes" "random_internal_randen_hwaes_impl" + "random_internal_randen_slow" + "random_internal_salted_seed_seq" + "random_internal_seed_material" + "random_internal_sequence_urbg" + "random_internal_traits" "random_internal_uniform_helper" + "random_internal_wide_multiply" + "random_random" + "random_seed_gen_exception" + "random_seed_sequences" + "raw_hash_map" + "raw_hash_set" + "raw_logging_internal" + "sample_recorder" + "scoped_set_env" + "span" + "spinlock_wait" + "spy_hash_state" + "stack_consumption" + "stacktrace" "status" + "str_format" + "str_format_internal" + "strings" + "strings_internal" + "symbolize" + "synchronization" + "thread_pool" + "throw_delegate" "time" - "civil_time" "time_zone" - "container" - "btree" - "compressed_tuple" - "fixed_array" - "inlined_vector_internal" - "inlined_vector" - "counting_allocator" - "flat_hash_map" - "flat_hash_set" - "node_hash_map" - "node_hash_set" - "container_memory" - "hash_function_defaults" - "hash_policy_traits" - "hashtablez_sampler" - "hashtable_debug" - "hashtable_debug_hooks" - "node_slot_policy" - "raw_hash_map" - "container_common" - "raw_hash_set" - "layout" "tracked" - "sample_recorder" + "type_traits" + "utility" + "variant" +) + +set(ABSL_INTERNAL_TEST_DLL_FILES + "hash/hash_testing.h" + "log/scoped_mock_log.cc" + "log/scoped_mock_log.h" + "random/internal/mock_helpers.h" + "random/internal/mock_overload_set.h" + "random/mocking_bit_gen.h" + "random/mock_distributions.h" + "strings/cordz_test_helpers.h" + "strings/cord_test_helpers.h" ) +set(ABSL_INTERNAL_TEST_DLL_TARGETS + "cord_test_helpers" + "cordz_test_helpers" + "hash_testing" + "random_mocking_bit_gen" + "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 +# error "The compiler defaults or is configured for C++ < 17" +# endif +#elif __cplusplus < 201700L +# error "The compiler defaults or is configured for C++ < 17" +#endif +int main() { return 0; } +]==] + ABSL_INTERNAL_AT_LEAST_CXX17) + +if(ABSL_INTERNAL_AT_LEAST_CXX17) + set(ABSL_INTERNAL_CXX_STD_FEATURE cxx_std_17) +else() + set(ABSL_INTERNAL_CXX_STD_FEATURE cxx_std_14) +endif() + function(absl_internal_dll_contains) cmake_parse_arguments(ABSL_INTERNAL_DLL "" @@ -491,6 +643,28 @@ function(absl_internal_dll_contains) endif() endfunction() +function(absl_internal_test_dll_contains) + cmake_parse_arguments(ABSL_INTERNAL_TEST_DLL + "" + "OUTPUT;TARGET" + "" + ${ARGN} + ) + + STRING(REGEX REPLACE "^absl::" "" _target ${ABSL_INTERNAL_TEST_DLL_TARGET}) + + list(FIND + ABSL_INTERNAL_TEST_DLL_TARGETS + "${_target}" + _index) + + if (${_index} GREATER -1) + set(${ABSL_INTERNAL_TEST_DLL_OUTPUT} 1 PARENT_SCOPE) + else() + set(${ABSL_INTERNAL_TEST_DLL_OUTPUT} 0 PARENT_SCOPE) + endif() +endfunction() + function(absl_internal_dll_targets) cmake_parse_arguments(ABSL_INTERNAL_DLL "" @@ -501,9 +675,12 @@ function(absl_internal_dll_targets) set(_deps "") foreach(dep IN LISTS ABSL_INTERNAL_DLL_DEPS) - absl_internal_dll_contains(TARGET ${dep} OUTPUT _contains) - if (_contains) + absl_internal_dll_contains(TARGET ${dep} OUTPUT _dll_contains) + absl_internal_test_dll_contains(TARGET ${dep} OUTPUT _test_dll_contains) + if (_dll_contains) list(APPEND _deps abseil_dll) + elseif (_test_dll_contains) + list(APPEND _deps abseil_test_dll) else() list(APPEND _deps ${dep}) endif() @@ -515,39 +692,116 @@ function(absl_internal_dll_targets) endfunction() function(absl_make_dll) + cmake_parse_arguments(ABSL_INTERNAL_MAKE_DLL + "" + "TEST" + "" + ${ARGN} + ) + + if (ABSL_INTERNAL_MAKE_DLL_TEST) + set(_dll "abseil_test_dll") + set(_dll_files ${ABSL_INTERNAL_TEST_DLL_FILES}) + set(_dll_libs "abseil_dll" "GTest::gtest" "GTest::gmock") + set(_dll_compile_definitions "GTEST_LINKED_AS_SHARED_LIBRARY=1") + set(_dll_includes ${absl_gtest_src_dir}/googletest/include ${absl_gtest_src_dir}/googlemock/include) + set(_dll_consume "ABSL_CONSUME_TEST_DLL") + set(_dll_build "ABSL_BUILD_TEST_DLL") + else() + set(_dll "abseil_dll") + set(_dll_files ${ABSL_INTERNAL_DLL_FILES}) + set(_dll_libs "") + set(_dll_compile_definitions "") + set(_dll_includes "") + set(_dll_consume "ABSL_CONSUME_DLL") + set(_dll_build "ABSL_BUILD_DLL") + endif() + add_library( - abseil_dll + ${_dll} SHARED - "${ABSL_INTERNAL_DLL_FILES}" + ${_dll_files} ) target_link_libraries( - abseil_dll + ${_dll} PRIVATE + ${_dll_libs} ${ABSL_DEFAULT_LINKOPTS} ) - set_property(TARGET abseil_dll PROPERTY LINKER_LANGUAGE "CXX") + set_property(TARGET ${_dll} PROPERTY LINKER_LANGUAGE "CXX") target_include_directories( - abseil_dll + ${_dll} PUBLIC "$<BUILD_INTERFACE:${ABSL_COMMON_INCLUDE_DIRS}>" $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> + PRIVATE + ${_dll_includes} ) target_compile_options( - abseil_dll + ${_dll} PRIVATE ${ABSL_DEFAULT_COPTS} ) + 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. + 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/${_dll}.pc" CONTENT "\ +prefix=${CMAKE_INSTALL_PREFIX}\n\ +exec_prefix=\${prefix}\n\ +libdir=${CMAKE_INSTALL_FULL_LIBDIR}\n\ +includedir=${CMAKE_INSTALL_FULL_INCLUDEDIR}\n\ +\n\ +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\ +Cflags: -I\${includedir}${PC_CFLAGS}\n") + INSTALL(FILES "${CMAKE_BINARY_DIR}/lib/pkgconfig/${_dll}.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") + target_compile_definitions( - abseil_dll + ${_dll} + PUBLIC + ${_dll_compile_definitions} PRIVATE - ABSL_BUILD_DLL + ${_dll_build} NOMINMAX INTERFACE ${ABSL_CC_LIB_DEFINES} + ${_dll_consume} ) - install(TARGETS abseil_dll EXPORT ${PROJECT_NAME}Targets + + if(ABSL_PROPAGATE_CXX_STD) + # Abseil libraries require C++14 as the current minimum standard. When + # compiled with C++17 (either because it is the compiler's default or + # explicitly requested), then Abseil requires C++17. + _absl_target_compile_features_if_available(${_NAME} PUBLIC ${ABSL_INTERNAL_CXX_STD_FEATURE}) + else() + # Note: This is legacy (before CMake 3.8) behavior. Setting the + # target-level CXX_STANDARD property to ABSL_CXX_STANDARD (which is + # initialized by CMAKE_CXX_STANDARD) should have no real effect, since + # that is the default value anyway. + # + # CXX_STANDARD_REQUIRED does guard against the top-level CMake project + # not having enabled CMAKE_CXX_STANDARD_REQUIRED (which prevents + # "decaying" to an older standard if the requested one isn't available). + set_property(TARGET ${_NAME} PROPERTY CXX_STANDARD ${ABSL_CXX_STANDARD}) + set_property(TARGET ${_NAME} PROPERTY CXX_STANDARD_REQUIRED ON) + endif() + + install(TARGETS ${_dll} EXPORT ${PROJECT_NAME}Targets RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} diff --git a/CMake/AbseilHelpers.cmake b/CMake/AbseilHelpers.cmake index dbb09fe2..f452a676 100644 --- a/CMake/AbseilHelpers.cmake +++ b/CMake/AbseilHelpers.cmake @@ -26,6 +26,12 @@ if(NOT DEFINED ABSL_IDE_FOLDER) set(ABSL_IDE_FOLDER Abseil) endif() +if(ABSL_USE_SYSTEM_INCLUDES) + set(ABSL_INTERNAL_INCLUDE_WARNING_GUARD SYSTEM) +else() + set(ABSL_INTERNAL_INCLUDE_WARNING_GUARD "") +endif() + # absl_cc_library() # # CMake function to imitate Bazel's cc_library rule. @@ -83,8 +89,9 @@ function(absl_cc_library) ${ARGN} ) - if(NOT ABSL_CC_LIB_PUBLIC AND ABSL_CC_LIB_TESTONLY AND - NOT (BUILD_TESTING AND ABSL_BUILD_TESTING)) + if(ABSL_CC_LIB_TESTONLY AND + NOT ((BUILD_TESTING AND ABSL_BUILD_TESTING) OR + (ABSL_BUILD_TEST_HELPERS AND ABSL_CC_LIB_PUBLIC))) return() endif() @@ -125,10 +132,12 @@ function(absl_cc_library) if (${ABSL_BUILD_DLL}) if(ABSL_ENABLE_INSTALL) absl_internal_dll_contains(TARGET ${_NAME} OUTPUT _in_dll) + absl_internal_test_dll_contains(TARGET ${_NAME} OUTPUT _in_test_dll) else() absl_internal_dll_contains(TARGET ${ABSL_CC_LIB_NAME} OUTPUT _in_dll) + absl_internal_test_dll_contains(TARGET ${ABSL_CC_LIB_NAME} OUTPUT _in_test_dll) endif() - if (${_in_dll}) + if (${_in_dll} OR ${_in_test_dll}) # This target should be replaced by the DLL set(_build_type "dll") set(ABSL_CC_LIB_IS_INTERFACE 1) @@ -143,35 +152,55 @@ function(absl_cc_library) endif() # Generate a pkg-config file for every library: - if((_build_type STREQUAL "static" OR _build_type STREQUAL "shared") - AND ABSL_ENABLE_INSTALL) - if(NOT ABSL_CC_LIB_TESTONLY) - if(absl_VERSION) - set(PC_VERSION "${absl_VERSION}") - else() - set(PC_VERSION "head") - endif() - foreach(dep ${ABSL_CC_LIB_DEPS}) - if(${dep} MATCHES "^absl::(.*)") - # Join deps with commas. + if(ABSL_ENABLE_INSTALL) + 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() + # 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() - 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. - 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 "\ + 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\ @@ -182,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}>>:-labsl_${_NAME}>\n\ +Libs: -L\${libdir} ${PC_LINKOPTS} $<$<NOT:$<BOOL:${ABSL_CC_LIB_IS_INTERFACE}>>:${LNK_LIB}>\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) @@ -239,7 +267,7 @@ Cflags: -I\${includedir}${PC_CFLAGS}\n") # unconditionally. set_property(TARGET ${_NAME} PROPERTY LINKER_LANGUAGE "CXX") - target_include_directories(${_NAME} + target_include_directories(${_NAME} ${ABSL_INTERNAL_INCLUDE_WARNING_GUARD} PUBLIC "$<BUILD_INTERFACE:${ABSL_COMMON_INCLUDE_DIRS}>" $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> @@ -258,10 +286,10 @@ Cflags: -I\${includedir}${PC_CFLAGS}\n") endif() if(ABSL_PROPAGATE_CXX_STD) - # Abseil libraries require C++11 as the current minimum standard. - # Top-level application CMake projects should ensure a consistent C++ - # standard for all compiled sources by setting CMAKE_CXX_STANDARD. - target_compile_features(${_NAME} PUBLIC cxx_std_11) + # 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 @@ -281,13 +309,13 @@ Cflags: -I\${includedir}${PC_CFLAGS}\n") if(ABSL_ENABLE_INSTALL) set_target_properties(${_NAME} PROPERTIES OUTPUT_NAME "absl_${_NAME}" - SOVERSION "2206.0.0" + SOVERSION "2301.0.0" ) endif() else() # Generating header-only library add_library(${_NAME} INTERFACE) - target_include_directories(${_NAME} + target_include_directories(${_NAME} ${ABSL_INTERNAL_INCLUDE_WARNING_GUARD} INTERFACE "$<BUILD_INTERFACE:${ABSL_COMMON_INCLUDE_DIRS}>" $<INSTALL_INTERFACE:${CMAKE_INSTALL_INCLUDEDIR}> @@ -306,19 +334,17 @@ Cflags: -I\${includedir}${PC_CFLAGS}\n") target_compile_definitions(${_NAME} INTERFACE ${ABSL_CC_LIB_DEFINES}) if(ABSL_PROPAGATE_CXX_STD) - # Abseil libraries require C++11 as the current minimum standard. + # Abseil libraries require C++14 as the current minimum standard. # Top-level application CMake projects should ensure a consistent C++ # standard for all compiled sources by setting CMAKE_CXX_STANDARD. - target_compile_features(${_NAME} INTERFACE cxx_std_11) + _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). 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} @@ -384,7 +410,7 @@ function(absl_cc_test) target_sources(${_NAME} PRIVATE ${ABSL_CC_TEST_SRCS}) target_include_directories(${_NAME} PUBLIC ${ABSL_COMMON_INCLUDE_DIRS} - PRIVATE ${GMOCK_INCLUDE_DIRS} ${GTEST_INCLUDE_DIRS} + PRIVATE ${absl_gtest_src_dir}/googletest/include ${absl_gtest_src_dir}/googlemock/include ) if (${ABSL_BUILD_DLL}) @@ -392,6 +418,7 @@ function(absl_cc_test) PUBLIC ${ABSL_CC_TEST_DEFINES} ABSL_CONSUME_DLL + ABSL_CONSUME_TEST_DLL GTEST_LINKED_AS_SHARED_LIBRARY=1 ) @@ -418,10 +445,10 @@ function(absl_cc_test) set_property(TARGET ${_NAME} PROPERTY FOLDER ${ABSL_IDE_FOLDER}/test) if(ABSL_PROPAGATE_CXX_STD) - # Abseil libraries require C++11 as the current minimum standard. + # Abseil libraries require C++14 as the current minimum standard. # Top-level application CMake projects should ensure a consistent C++ # standard for all compiled sources by setting CMAKE_CXX_STANDARD. - target_compile_features(${_NAME} PUBLIC cxx_std_11) + _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 @@ -437,11 +464,3 @@ function(absl_cc_test) add_test(NAME ${_NAME} COMMAND ${_NAME}) endfunction() - - -function(check_target my_target) - if(NOT TARGET ${my_target}) - message(FATAL_ERROR " ABSL: compiling absl requires a ${my_target} CMake target in your project, - see CMake/README.md for more details") - endif(NOT TARGET ${my_target}) -endfunction() diff --git a/CMake/Googletest/CMakeLists.txt.in b/CMake/Googletest/CMakeLists.txt.in index 5769e3a9..75691b11 100644 --- a/CMake/Googletest/CMakeLists.txt.in +++ b/CMake/Googletest/CMakeLists.txt.in @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 2.8.2) +cmake_minimum_required(VERSION 3.10) project(googletest-external NONE) diff --git a/CMake/README.md b/CMake/README.md index 8134615e..19fb327c 100644 --- a/CMake/README.md +++ b/CMake/README.md @@ -39,12 +39,12 @@ section of your executable or of your library.<br> Here is a short CMakeLists.txt example of an application project using Abseil. ```cmake -cmake_minimum_required(VERSION 3.8.2) +cmake_minimum_required(VERSION 3.10) project(my_app_project) # Pick the C++ standard to compile with. -# Abseil currently supports C++11, C++14, and C++17. -set(CMAKE_CXX_STANDARD 11) +# Abseil currently supports C++14, C++17, and C++20. +set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) add_subdirectory(abseil-cpp) @@ -62,7 +62,7 @@ will control Abseil library targets) is set to at least that minimum. For example: ```cmake -cmake_minimum_required(VERSION 3.8.2) +cmake_minimum_required(VERSION 3.10) project(my_lib_project) # Leave C++ standard up to the root application, so set it only if this is the diff --git a/CMake/install_test_project/CMakeLists.txt b/CMake/install_test_project/CMakeLists.txt index b865b2ec..30c23b2c 100644 --- a/CMake/install_test_project/CMakeLists.txt +++ b/CMake/install_test_project/CMakeLists.txt @@ -15,7 +15,7 @@ # A simple CMakeLists.txt for testing cmake installation -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.10) project(absl_cmake_testing CXX) add_executable(simple simple.cc) diff --git a/CMakeLists.txt b/CMakeLists.txt index 79869ff5..3a9e521f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,12 +14,9 @@ # limitations under the License. # -# Most widely used distributions have cmake 3.5 or greater available as of March -# 2019. A notable exception is RHEL-7 (CentOS7). You can install a current -# version of CMake by first installing Extra Packages for Enterprise Linux -# (https://fedoraproject.org/wiki/EPEL#Extra_Packages_for_Enterprise_Linux_.28EPEL.29) -# and then issuing `yum install cmake3` on the command line. -cmake_minimum_required(VERSION 3.5) +# https://github.com/google/oss-policies-info/blob/main/foundational-cxx-support-matrix.md +# As of 2022-09-06, CMake 3.10 is the minimum supported version. +cmake_minimum_required(VERSION 3.10) # Compiler id for Apple Clang is now AppleClang. if (POLICY CMP0025) @@ -46,7 +43,12 @@ if (POLICY CMP0091) cmake_policy(SET CMP0091 NEW) endif (POLICY CMP0091) -project(absl LANGUAGES CXX VERSION 20220623) +# try_compile() honors the CMAKE_CXX_STANDARD value +if (POLICY CMP0067) + cmake_policy(SET CMP0067 NEW) +endif (POLICY CMP0067) + +project(absl LANGUAGES CXX VERSION 20230125) include(CTest) # Output directory is correct by default for most build setups. However, when @@ -64,12 +66,16 @@ else() endif() option(ABSL_PROPAGATE_CXX_STD - "Use CMake C++ standard meta features (e.g. cxx_std_11) that propagate to targets that link to Abseil" + "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)) 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() +option(ABSL_USE_SYSTEM_INCLUDES + "Silence warnings in Abseil headers by marking them as SYSTEM includes" + OFF) + list(APPEND CMAKE_MODULE_PATH ${CMAKE_CURRENT_LIST_DIR}/CMake ${CMAKE_CURRENT_LIST_DIR}/absl/copts @@ -110,6 +116,10 @@ include(CMakeDependentOption) option(ABSL_BUILD_TESTING "If ON, Abseil will build all of Abseil's own tests." OFF) +option(ABSL_BUILD_TEST_HELPERS + "If ON, Abseil will build libraries that you can use to write tests against Abseil code. This option requires that Abseil is configured to use GoogleTest." + OFF) + option(ABSL_USE_EXTERNAL_GOOGLETEST "If ON, Abseil will assume that the targets for GoogleTest are already provided by the including project. This makes sense when Abseil is used with add_subdirectory." OFF) @@ -129,8 +139,7 @@ set(ABSL_LOCAL_GOOGLETEST_DIR "/usr/src/googletest" CACHE PATH "If ABSL_USE_GOOGLETEST_HEAD is OFF and ABSL_GOOGLETEST_URL is not set, specifies the directory of a local GoogleTest checkout." ) -if(BUILD_TESTING AND ABSL_BUILD_TESTING) - ## check targets +if((BUILD_TESTING AND ABSL_BUILD_TESTING) OR ABSL_BUILD_TEST_HELPERS) if (ABSL_USE_EXTERNAL_GOOGLETEST) if (ABSL_FIND_GOOGLETEST) find_package(GTest REQUIRED) @@ -162,11 +171,6 @@ if(BUILD_TESTING AND ABSL_BUILD_TESTING) endif() include(CMake/Googletest/DownloadGTest.cmake) endif() - - check_target(GTest::gtest) - check_target(GTest::gtest_main) - check_target(GTest::gmock) - check_target(GTest::gmock_main) endif() add_subdirectory(absl) @@ -211,4 +215,25 @@ if(ABSL_ENABLE_INSTALL) PATTERN "copts" EXCLUDE PATTERN "testdata" EXCLUDE ) + + 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" + 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" + ABSL_INTERNAL_OPTIONS_H_PINNED + "${ABSL_INTERNAL_OPTIONS_H_CONTENTS}") + endif() + file(WRITE "${CMAKE_BINARY_DIR}/options-pinned.h" "${ABSL_INTERNAL_OPTIONS_H_PINNED}") + + install(FILES "${CMAKE_BINARY_DIR}/options-pinned.h" + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/absl/base + RENAME "options.h") + endif() # ABSL_ENABLE_INSTALL @@ -1,7 +1,7 @@ # Abseil - C++ Common Libraries The repository contains the Abseil C++ library code. Abseil is an open-source -collection of C++ code (compliant to C++11) designed to augment the C++ +collection of C++ code (compliant to C++14) designed to augment the C++ standard library. ## Table of Contents @@ -46,26 +46,28 @@ the Abseil code, running tests, and getting a simple binary working. [Bazel](https://bazel.build) and [CMake](https://cmake.org/) are the official build systems for Abseil. - See the [quickstart](https://abseil.io/docs/cpp/quickstart) for more information on building Abseil using the Bazel build system. - If you require CMake support, please check the [CMake build instructions](CMake/README.md) and [CMake Quickstart](https://abseil.io/docs/cpp/quickstart-cmake). +<a name="support"></a> ## Support -Abseil is officially supported on many platforms. See the [Abseil -platform support -guide](https://abseil.io/docs/cpp/platforms/platforms) for details on -supported operating systems, compilers, CPUs, etc. +Abseil follows Google's [Foundational C++ Support +Policy](https://opensource.google/documentation/policies/cplusplus-support). See +[this +table](https://github.com/google/oss-policies-info/blob/main/foundational-cxx-support-matrix.md) +for a list of currently supported versions compilers, platforms, and build +tools. +<a name="codemap"></a> ## Codemap Abseil contains the following C++ library components: -* [`base`](absl/base/) Abseil Fundamentals +* [`base`](absl/base/) <br /> The `base` library contains initialization code and other code which all other Abseil code depends on. Code within `base` may not depend on any other code (other than the C++ standard library). @@ -78,29 +80,45 @@ Abseil contains the following C++ library components: * [`container`](absl/container/) <br /> The `container` library contains additional STL-style containers, including Abseil's unordered "Swiss table" containers. +* [`crc`](absl/crc/) The `crc` library contains code for + computing error-detecting cyclic redundancy checks on data. * [`debugging`](absl/debugging/) <br /> The `debugging` library contains code useful for enabling leak checks, and stacktrace and symbolization utilities. +* [`flags`](absl/flags/) + <br /> The `flags` library contains code for handling command line flags for + libraries and binaries built with Abseil. * [`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 + destinations. * [`memory`](absl/memory/) - <br /> The `memory` library contains C++11-compatible versions of - `std::make_unique()` and related memory management facilities. + <br /> The `memory` library contains memory management facilities that augment + C++'s `<memory>` library. * [`meta`](absl/meta/) - <br /> The `meta` library contains C++11-compatible versions of type checks + <br /> The `meta` library contains compatible versions of type checks available within C++14 and C++17 versions of the C++ `<type_traits>` library. * [`numeric`](absl/numeric/) - <br /> The `numeric` library contains C++11-compatible 128-bit integers. + <br /> The `numeric` library contains 128-bit integer types as well as + implementations of C++20's bitwise math functions. * [`profiling`](absl/profiling/) <br /> The `profiling` library contains utility code for profiling C++ entities. It is currently a private dependency of other Abseil libraries. +* [`random`](absl/random/) + <br /> The `random` library contains functions for generating psuedorandom + values. * [`status`](absl/status/) - <br /> The `status` contains abstractions for error handling, specifically - `absl::Status` and `absl::StatusOr<T>`. + <br /> The `status` library contains abstractions for error handling, + specifically `absl::Status` and `absl::StatusOr<T>`. * [`strings`](absl/strings/) <br /> The `strings` library contains a variety of strings routines and - utilities, including a C++11-compatible version of the C++17 + utilities, including a C++14-compatible version of the C++17 `std::string_view` type. * [`synchronization`](absl/synchronization/) <br /> The `synchronization` library contains concurrency primitives (Abseil's @@ -112,10 +130,11 @@ Abseil contains the following C++ library components: time zones. * [`types`](absl/types/) <br /> The `types` library contains non-container utility types, like a - C++11-compatible version of the C++17 `std::optional` type. + C++14-compatible version of the C++17 `std::optional` type. * [`utility`](absl/utility/) <br /> The `utility` library contains utility and helper code. +<a name="releases"></a> ## Releases Abseil recommends users "live-at-head" (update to the latest commit from the @@ -125,11 +144,13 @@ Releases](https://github.com/abseil/abseil-cpp/releases) to which we backport fixes for severe bugs. See our [release management](https://abseil.io/about/releases) document for more details. +<a name="license"></a> ## License The Abseil C++ library is licensed under the terms of the Apache license. See [LICENSE](LICENSE) for more information. +<a name="links"></a> ## Links For more information about Abseil: @@ -20,11 +20,11 @@ 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", - sha256 = "ce7366fe57eb49928311189cb0e40e0a8bf3d3682fca89af30d884c25e983786", - strip_prefix = "googletest-release-1.12.0", + 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/release-1.12.0.zip"], + urls = ["https://github.com/google/googletest/archive/refs/tags/v1.13.0.zip"], ) # RE2 (the regular expression library used by GoogleTest) @@ -39,23 +39,24 @@ http_archive( # Google benchmark. http_archive( - name = "com_github_google_benchmark", # 2021-09-20T09:19:51Z - sha256 = "62e2f2e6d8a744d67e4bbc212fcfd06647080de4253c97ad5c6749e09faf2cb0", - strip_prefix = "benchmark-0baacde3618ca617da95375e0af13ce1baadea47", - urls = ["https://github.com/google/benchmark/archive/0baacde3618ca617da95375e0af13ce1baadea47.zip"], + 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"], ) # Bazel Skylib. http_archive( - name = "bazel_skylib", - urls = ["https://github.com/bazelbuild/bazel-skylib/releases/download/1.2.1/bazel-skylib-1.2.1.tar.gz"], - sha256 = "f7be3474d42aae265405a592bb7da8e171919d74c16f082a5457840f06054728", + 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"], ) # Bazel platform rules. http_archive( - name = "platforms", - sha256 = "a879ea428c6d56ab0ec18224f976515948822451473a80d06c2e50af0bbe5121", - strip_prefix = "platforms-da5541f26b7de1dc8e04c075c99df5351742a4a2", - urls = ["https://github.com/bazelbuild/platforms/archive/da5541f26b7de1dc8e04c075c99df5351742a4a2.zip"], # 2022-05-27 + name = "platforms", # 2022-11-09T19:18:22Z + sha256 = "b4a3b45dc4202e2b3e34e3bc49d2b5b37295fc23ea58d88fb9e01f3642ad9b55", + strip_prefix = "platforms-3fbc687756043fb58a407c2ea8c944bc2fe1d922", + urls = ["https://github.com/bazelbuild/platforms/archive/3fbc687756043fb58a407c2ea8c944bc2fe1d922.zip"], ) diff --git a/absl/BUILD.bazel b/absl/BUILD.bazel index 7cccbbba..29963ccc 100644 --- a/absl/BUILD.bazel +++ b/absl/BUILD.bazel @@ -28,6 +28,14 @@ config_setting( ) config_setting( + name = "gcc_compiler", + flag_values = { + "@bazel_tools//tools/cpp:compiler": "gcc", + }, + visibility = [":__subpackages__"], +) + +config_setting( name = "msvc_compiler", flag_values = { "@bazel_tools//tools/cpp:compiler": "msvc-cl", diff --git a/absl/CMakeLists.txt b/absl/CMakeLists.txt index b1715846..3a7c12fe 100644 --- a/absl/CMakeLists.txt +++ b/absl/CMakeLists.txt @@ -18,10 +18,12 @@ add_subdirectory(base) add_subdirectory(algorithm) add_subdirectory(cleanup) add_subdirectory(container) +add_subdirectory(crc) add_subdirectory(debugging) add_subdirectory(flags) add_subdirectory(functional) add_subdirectory(hash) +add_subdirectory(log) add_subdirectory(memory) add_subdirectory(meta) add_subdirectory(numeric) @@ -36,4 +38,7 @@ add_subdirectory(utility) if (${ABSL_BUILD_DLL}) absl_make_dll() + if (${ABSL_BUILD_TEST_HELPERS}) + absl_make_dll(TEST ON) + endif() endif() diff --git a/absl/algorithm/BUILD.bazel b/absl/algorithm/BUILD.bazel index f6d74714..3a9ab013 100644 --- a/absl/algorithm/BUILD.bazel +++ b/absl/algorithm/BUILD.bazel @@ -48,8 +48,9 @@ cc_test( ], ) -cc_test( +cc_binary( name = "algorithm_benchmark", + testonly = 1, srcs = ["equal_benchmark.cc"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, diff --git a/absl/algorithm/container.h b/absl/algorithm/container.h index 26b19529..c7782d4f 100644 --- a/absl/algorithm/container.h +++ b/absl/algorithm/container.h @@ -77,9 +77,8 @@ using ContainerIterPairType = decltype(std::make_pair(ContainerIter<C1>(), ContainerIter<C2>())); template <typename C> -using ContainerDifferenceType = - decltype(std::distance(std::declval<ContainerIter<C>>(), - std::declval<ContainerIter<C>>())); +using ContainerDifferenceType = decltype(std::distance( + std::declval<ContainerIter<C>>(), std::declval<ContainerIter<C>>())); template <typename C> using ContainerPointerType = @@ -97,10 +96,14 @@ using ContainerPointerType = // These are meant for internal use only. template <typename C> -ContainerIter<C> c_begin(C& c) { return begin(c); } +ContainerIter<C> c_begin(C& c) { + return begin(c); +} template <typename C> -ContainerIter<C> c_end(C& c) { return end(c); } +ContainerIter<C> c_end(C& c) { + return end(c); +} template <typename T> struct IsUnorderedContainer : std::false_type {}; @@ -343,8 +346,8 @@ container_algorithm_internal::ContainerDifferenceType<const C> c_count_if( // return the first element where two ordered containers differ. Applies `==` to // the first N elements of `c1` and `c2`, where N = min(size(c1), size(c2)). template <typename C1, typename C2> -container_algorithm_internal::ContainerIterPairType<C1, C2> -c_mismatch(C1& c1, C2& 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); @@ -365,8 +368,8 @@ c_mismatch(C1& c1, C2& c2) { // the function's test condition. Applies `pred`to the first N elements of `c1` // and `c2`, where N = min(size(c1), size(c2)). template <typename C1, typename C2, typename BinaryPredicate> -container_algorithm_internal::ContainerIterPairType<C1, C2> -c_mismatch(C1& c1, C2& c2, BinaryPredicate pred) { +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); @@ -655,11 +658,10 @@ OutputIterator c_replace_copy(const C& c, OutputIterator result, T&& old_value, // some condition, and return the results within an iterator. template <typename C, typename OutputIterator, typename Pred, typename T> OutputIterator c_replace_copy_if(const C& c, OutputIterator result, Pred&& pred, - T&& new_value) { + const T& new_value) { return std::replace_copy_if(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), result, - std::forward<Pred>(pred), - std::forward<T>(new_value)); + std::forward<Pred>(pred), new_value); } // c_fill() @@ -667,9 +669,9 @@ OutputIterator c_replace_copy_if(const C& c, OutputIterator result, Pred&& pred, // Container-based version of the <algorithm> `std::fill()` function to fill a // container with some value. template <typename C, typename T> -void c_fill(C& c, T&& value) { +void c_fill(C& c, const T& value) { std::fill(container_algorithm_internal::c_begin(c), - container_algorithm_internal::c_end(c), std::forward<T>(value)); + container_algorithm_internal::c_end(c), value); } // c_fill_n() @@ -677,9 +679,8 @@ void c_fill(C& c, T&& value) { // Container-based version of the <algorithm> `std::fill_n()` function to fill // the first N elements in a container with some value. template <typename C, typename Size, typename T> -void c_fill_n(C& c, Size n, T&& value) { - std::fill_n(container_algorithm_internal::c_begin(c), n, - std::forward<T>(value)); +void c_fill_n(C& c, Size n, const T& value) { + std::fill_n(container_algorithm_internal::c_begin(c), n, value); } // c_generate() @@ -716,10 +717,11 @@ container_algorithm_internal::ContainerIter<C> c_generate_n(C& c, Size n, // copy a container's elements while removing any elements matching the given // `value`. template <typename C, typename OutputIterator, typename T> -OutputIterator c_remove_copy(const C& c, OutputIterator result, T&& value) { +OutputIterator c_remove_copy(const C& c, OutputIterator result, + const T& value) { return std::remove_copy(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), result, - std::forward<T>(value)); + value); } // c_remove_copy_if() @@ -1064,20 +1066,19 @@ void c_nth_element( // which does not compare less than `value`. template <typename Sequence, typename T> container_algorithm_internal::ContainerIter<Sequence> c_lower_bound( - Sequence& sequence, T&& value) { + Sequence& sequence, const T& value) { return std::lower_bound(container_algorithm_internal::c_begin(sequence), - container_algorithm_internal::c_end(sequence), - std::forward<T>(value)); + container_algorithm_internal::c_end(sequence), value); } // Overload of c_lower_bound() for performing a `comp` comparison other than // the default `operator<`. template <typename Sequence, typename T, typename LessThan> container_algorithm_internal::ContainerIter<Sequence> c_lower_bound( - Sequence& sequence, T&& value, LessThan&& comp) { + Sequence& sequence, const T& value, LessThan&& comp) { return std::lower_bound(container_algorithm_internal::c_begin(sequence), - container_algorithm_internal::c_end(sequence), - std::forward<T>(value), std::forward<LessThan>(comp)); + container_algorithm_internal::c_end(sequence), value, + std::forward<LessThan>(comp)); } // c_upper_bound() @@ -1087,20 +1088,19 @@ container_algorithm_internal::ContainerIter<Sequence> c_lower_bound( // which is greater than `value`. template <typename Sequence, typename T> container_algorithm_internal::ContainerIter<Sequence> c_upper_bound( - Sequence& sequence, T&& value) { + Sequence& sequence, const T& value) { return std::upper_bound(container_algorithm_internal::c_begin(sequence), - container_algorithm_internal::c_end(sequence), - std::forward<T>(value)); + container_algorithm_internal::c_end(sequence), value); } // Overload of c_upper_bound() for performing a `comp` comparison other than // the default `operator<`. template <typename Sequence, typename T, typename LessThan> container_algorithm_internal::ContainerIter<Sequence> c_upper_bound( - Sequence& sequence, T&& value, LessThan&& comp) { + Sequence& sequence, const T& value, LessThan&& comp) { return std::upper_bound(container_algorithm_internal::c_begin(sequence), - container_algorithm_internal::c_end(sequence), - std::forward<T>(value), std::forward<LessThan>(comp)); + container_algorithm_internal::c_end(sequence), value, + std::forward<LessThan>(comp)); } // c_equal_range() @@ -1110,20 +1110,19 @@ container_algorithm_internal::ContainerIter<Sequence> c_upper_bound( // sorted container which compare equal to `value`. template <typename Sequence, typename T> container_algorithm_internal::ContainerIterPairType<Sequence, Sequence> -c_equal_range(Sequence& sequence, T&& value) { +c_equal_range(Sequence& sequence, const T& value) { return std::equal_range(container_algorithm_internal::c_begin(sequence), - container_algorithm_internal::c_end(sequence), - std::forward<T>(value)); + container_algorithm_internal::c_end(sequence), value); } // Overload of c_equal_range() for performing a `comp` comparison other than // the default `operator<`. template <typename Sequence, typename T, typename LessThan> container_algorithm_internal::ContainerIterPairType<Sequence, Sequence> -c_equal_range(Sequence& sequence, T&& value, LessThan&& comp) { +c_equal_range(Sequence& sequence, const T& value, LessThan&& comp) { return std::equal_range(container_algorithm_internal::c_begin(sequence), - container_algorithm_internal::c_end(sequence), - std::forward<T>(value), std::forward<LessThan>(comp)); + container_algorithm_internal::c_end(sequence), value, + std::forward<LessThan>(comp)); } // c_binary_search() @@ -1132,20 +1131,19 @@ c_equal_range(Sequence& sequence, 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, T&& value) { +bool c_binary_search(Sequence&& sequence, const T& value) { return std::binary_search(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), - std::forward<T>(value)); + 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, T&& value, LessThan&& comp) { +bool c_binary_search(Sequence&& sequence, const T& value, LessThan&& comp) { return std::binary_search(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), - std::forward<T>(value), - std::forward<LessThan>(comp)); + value, std::forward<LessThan>(comp)); } //------------------------------------------------------------------------------ @@ -1560,8 +1558,8 @@ container_algorithm_internal::ContainerIter<Sequence> c_max_element( // smallest and largest values, respectively, using `operator<` to make the // comparisons. template <typename C> -container_algorithm_internal::ContainerIterPairType<C, C> -c_minmax_element(C& c) { +container_algorithm_internal::ContainerIterPairType<C, C> c_minmax_element( + C& c) { return std::minmax_element(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c)); } @@ -1569,8 +1567,8 @@ c_minmax_element(C& c) { // Overload of c_minmax_element() for performing `comp` comparisons other than // `operator<`. template <typename C, typename LessThan> -container_algorithm_internal::ContainerIterPairType<C, C> -c_minmax_element(C& c, LessThan&& comp) { +container_algorithm_internal::ContainerIterPairType<C, C> c_minmax_element( + C& c, LessThan&& comp) { return std::minmax_element(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), std::forward<LessThan>(comp)); @@ -1588,7 +1586,8 @@ c_minmax_element(C& c, LessThan&& comp) { // that capital letters ("A-Z") have ASCII values less than lowercase letters // ("a-z"). template <typename Sequence1, typename Sequence2> -bool c_lexicographical_compare(Sequence1&& sequence1, Sequence2&& sequence2) { +bool c_lexicographical_compare(const Sequence1& sequence1, + const Sequence2& sequence2) { return std::lexicographical_compare( container_algorithm_internal::c_begin(sequence1), container_algorithm_internal::c_end(sequence1), @@ -1599,8 +1598,8 @@ bool c_lexicographical_compare(Sequence1&& sequence1, Sequence2&& sequence2) { // Overload of c_lexicographical_compare() for performing a lexicographical // comparison using a `comp` operator instead of `operator<`. template <typename Sequence1, typename Sequence2, typename LessThan> -bool c_lexicographical_compare(Sequence1&& sequence1, Sequence2&& sequence2, - LessThan&& comp) { +bool c_lexicographical_compare(const Sequence1& sequence1, + const Sequence2& sequence2, LessThan&& comp) { return std::lexicographical_compare( container_algorithm_internal::c_begin(sequence1), container_algorithm_internal::c_end(sequence1), @@ -1655,18 +1654,18 @@ bool c_prev_permutation(C& c, LessThan&& comp) { // c_iota() // -// Container-based version of the <algorithm> `std::iota()` function +// 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. template <typename Sequence, typename T> -void c_iota(Sequence& sequence, T&& value) { +void c_iota(Sequence& sequence, const T& value) { std::iota(container_algorithm_internal::c_begin(sequence), - container_algorithm_internal::c_end(sequence), - std::forward<T>(value)); + container_algorithm_internal::c_end(sequence), value); } + // c_accumulate() // -// Container-based version of the <algorithm> `std::accumulate()` function +// Container-based version of the <numeric> `std::accumulate()` function // to accumulate the element values of a container to `init` and return that // accumulation by value. // @@ -1693,7 +1692,7 @@ decay_t<T> c_accumulate(const Sequence& sequence, T&& init, // c_inner_product() // -// Container-based version of the <algorithm> `std::inner_product()` function +// Container-based version of the <numeric> `std::inner_product()` function // to compute the cumulative inner product of container element pairs. // // Note: Due to a language technicality this function has return type @@ -1724,7 +1723,7 @@ decay_t<T> c_inner_product(const Sequence1& factors1, const Sequence2& factors2, // c_adjacent_difference() // -// Container-based version of the <algorithm> `std::adjacent_difference()` +// Container-based version of the <numeric> `std::adjacent_difference()` // function to compute the difference between each element and the one preceding // it and write it to an iterator. template <typename InputSequence, typename OutputIt> @@ -1747,7 +1746,7 @@ OutputIt c_adjacent_difference(const InputSequence& input, // c_partial_sum() // -// Container-based version of the <algorithm> `std::partial_sum()` function +// Container-based version of the <numeric> `std::partial_sum()` function // to compute the partial sum of the elements in a sequence and write them // to an iterator. The partial sum is the sum of all element values so far in // the sequence. diff --git a/absl/algorithm/container_test.cc b/absl/algorithm/container_test.cc index 605afc80..0fbc7773 100644 --- a/absl/algorithm/container_test.cc +++ b/absl/algorithm/container_test.cc @@ -67,13 +67,16 @@ bool Equals(int v1, int v2) { return v1 == v2; } bool IsOdd(int x) { return x % 2 != 0; } TEST_F(NonMutatingTest, Distance) { - EXPECT_EQ(container_.size(), absl::c_distance(container_)); - EXPECT_EQ(sequence_.size(), absl::c_distance(sequence_)); - EXPECT_EQ(vector_.size(), absl::c_distance(vector_)); - EXPECT_EQ(ABSL_ARRAYSIZE(array_), absl::c_distance(array_)); + EXPECT_EQ(container_.size(), + static_cast<size_t>(absl::c_distance(container_))); + EXPECT_EQ(sequence_.size(), static_cast<size_t>(absl::c_distance(sequence_))); + EXPECT_EQ(vector_.size(), static_cast<size_t>(absl::c_distance(vector_))); + EXPECT_EQ(ABSL_ARRAYSIZE(array_), + static_cast<size_t>(absl::c_distance(array_))); // Works with a temporary argument. - EXPECT_EQ(vector_.size(), absl::c_distance(std::vector<int>(vector_))); + EXPECT_EQ(vector_.size(), + static_cast<size_t>(absl::c_distance(std::vector<int>(vector_)))); } TEST_F(NonMutatingTest, Distance_OverloadedBeginEnd) { diff --git a/absl/algorithm/equal_benchmark.cc b/absl/algorithm/equal_benchmark.cc index 7bf62c9a..948cd65c 100644 --- a/absl/algorithm/equal_benchmark.cc +++ b/absl/algorithm/equal_benchmark.cc @@ -15,8 +15,8 @@ #include <cstdint> #include <cstring> -#include "benchmark/benchmark.h" #include "absl/algorithm/algorithm.h" +#include "benchmark/benchmark.h" namespace { diff --git a/absl/base/BUILD.bazel b/absl/base/BUILD.bazel index bd023ad8..ded26d6a 100644 --- a/absl/base/BUILD.bazel +++ b/absl/base/BUILD.bazel @@ -114,6 +114,23 @@ cc_library( ) cc_library( + name = "cycleclock_internal", + hdrs = [ + "internal/cycleclock_config.h", + "internal/unscaledcycleclock_config.h", + ], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl:__subpackages__", + ], + deps = [ + ":base_internal", + ":config", + ], +) + +cc_library( name = "dynamic_annotations", srcs = [ "internal/dynamic_annotations.h", @@ -237,6 +254,7 @@ cc_library( ":base_internal", ":config", ":core_headers", + ":cycleclock_internal", ":dynamic_annotations", ":log_severity", ":raw_logging_internal", diff --git a/absl/base/CMakeLists.txt b/absl/base/CMakeLists.txt index ed55093a..26e2b48a 100644 --- a/absl/base/CMakeLists.txt +++ b/absl/base/CMakeLists.txt @@ -181,6 +181,7 @@ absl_cc_library( "call_once.h" "casts.h" "internal/cycleclock.h" + "internal/cycleclock_config.h" "internal/low_level_scheduling.h" "internal/per_thread_tls.h" "internal/spinlock.h" @@ -188,6 +189,7 @@ absl_cc_library( "internal/thread_identity.h" "internal/tsan_mutex_interface.h" "internal/unscaledcycleclock.h" + "internal/unscaledcycleclock_config.h" SRCS "internal/cycleclock.cc" "internal/spinlock.cc" @@ -199,7 +201,7 @@ absl_cc_library( LINKOPTS ${ABSL_DEFAULT_LINKOPTS} $<$<BOOL:${LIBRT}>:-lrt> - $<$<BOOL:${MINGW}>:"advapi32"> + $<$<BOOL:${MINGW}>:-ladvapi32> DEPS absl::atomic_hook absl::base_internal diff --git a/absl/base/attributes.h b/absl/base/attributes.h index e4e7a3d8..b7826e77 100644 --- a/absl/base/attributes.h +++ b/absl/base/attributes.h @@ -716,26 +716,9 @@ #define ABSL_CONST_INIT #endif -// 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 int64_t ToInt64Milliseconds(Duration d); -#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 +// These annotations are not available yet due to fear of breaking code. #define ABSL_ATTRIBUTE_PURE_FUNCTION -#endif +#define ABSL_ATTRIBUTE_CONST_FUNCTION // 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 @@ -759,4 +742,41 @@ #define ABSL_ATTRIBUTE_LIFETIME_BOUND #endif +// ABSL_ATTRIBUTE_TRIVIAL_ABI +// Indicates that a type is "trivially relocatable" -- meaning it can be +// relocated without invoking the constructor/destructor, using a form of move +// elision. +// +// From a memory safety point of view, putting aside destructor ordering, it's +// safe to apply ABSL_ATTRIBUTE_TRIVIAL_ABI if an object's location +// can change over the course of its lifetime: if a constructor can be run one +// place, and then the object magically teleports to another place where some +// methods are run, and then the object teleports to yet another place where it +// is destroyed. This is notably not true for self-referential types, where the +// move-constructor must keep the self-reference up to date. If the type changed +// location without invoking the move constructor, it would have a dangling +// self-reference. +// +// The use of this teleporting machinery means that the number of paired +// move/destroy operations can change, and so it is a bad idea to apply this to +// a type meant to count the number of moves. +// +// Warning: applying this can, rarely, break callers. Objects passed by value +// will be destroyed at the end of the call, instead of the end of the +// full-expression containing the call. In addition, it changes the ABI +// of functions accepting this type by value (e.g. to pass in registers). +// +// 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 +#define ABSL_ATTRIBUTE_TRIVIAL_ABI +#endif + #endif // ABSL_BASE_ATTRIBUTES_H_ diff --git a/absl/base/config.h b/absl/base/config.h index 705ecea0..05d960b6 100644 --- a/absl/base/config.h +++ b/absl/base/config.h @@ -111,8 +111,8 @@ // // LTS releases can be obtained from // https://github.com/abseil/abseil-cpp/releases. -#define ABSL_LTS_RELEASE_VERSION 20220623 -#define ABSL_LTS_RELEASE_PATCH_LEVEL 1 +#define ABSL_LTS_RELEASE_VERSION 20230125 +#define ABSL_LTS_RELEASE_PATCH_LEVEL 3 // Helper macro to convert a CPP variable to a string literal. #define ABSL_INTERNAL_DO_TOKEN_STR(x) #x @@ -243,6 +243,7 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || #ifdef ABSL_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE #error ABSL_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE cannot be directly set #elif defined(_LIBCPP_VERSION) || defined(_MSC_VER) || \ + (defined(__clang__) && __clang_major__ >= 15) || \ (!defined(__clang__) && defined(__GLIBCXX__) && \ ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(4, 8)) #define ABSL_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE 1 @@ -264,15 +265,27 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || #elif defined(ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE) #error ABSL_HAVE_STD_IS_TRIVIALLY_ASSIGNABLE cannot directly set #elif (defined(__clang__) && defined(_LIBCPP_VERSION)) || \ + (defined(__clang__) && __clang_major__ >= 15) || \ (!defined(__clang__) && \ ((ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(7, 4) && defined(__GLIBCXX__)) || \ (ABSL_INTERNAL_HAVE_MIN_GNUC_VERSION(8, 2) && \ defined(_LIBCPP_VERSION)))) || \ - (defined(_MSC_VER) && !defined(__NVCC__)) + (defined(_MSC_VER) && !defined(__NVCC__) && !defined(__clang__)) #define ABSL_HAVE_STD_IS_TRIVIALLY_CONSTRUCTIBLE 1 #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) +#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 + // ABSL_HAVE_THREAD_LOCAL // // Checks whether C++11's `thread_local` storage duration specifier is @@ -746,6 +759,18 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || #define ABSL_DLL #endif // defined(_MSC_VER) +#if defined(_MSC_VER) +#if defined(ABSL_BUILD_TEST_DLL) +#define ABSL_TEST_DLL __declspec(dllexport) +#elif defined(ABSL_CONSUME_TEST_DLL) +#define ABSL_TEST_DLL __declspec(dllimport) +#else +#define ABSL_TEST_DLL +#endif +#else +#define ABSL_TEST_DLL +#endif // defined(_MSC_VER) + // ABSL_HAVE_MEMORY_SANITIZER // // MemorySanitizer (MSan) is a detector of uninitialized reads. It consists of @@ -904,10 +929,26 @@ static_assert(ABSL_INTERNAL_INLINE_NAMESPACE_STR[0] != 'h' || // ABSL_INTERNAL_HAVE_ARM_NEON is used for compile-time detection of NEON (ARM // SIMD). +// +// If __CUDA_ARCH__ is defined, then we are compiling CUDA code in device mode. +// In device mode, NEON intrinsics are not available, regardless of host +// platform. +// https://llvm.org/docs/CompileCudaWithLLVM.html#detecting-clang-vs-nvcc-from-code #ifdef ABSL_INTERNAL_HAVE_ARM_NEON #error ABSL_INTERNAL_HAVE_ARM_NEON cannot be directly set -#elif defined(__ARM_NEON) +#elif defined(__ARM_NEON) && !defined(__CUDA_ARCH__) #define ABSL_INTERNAL_HAVE_ARM_NEON 1 #endif +// ABSL_HAVE_CONSTANT_EVALUATED is used for compile-time detection of +// constant evaluation support through `absl::is_constant_evaluated`. +#ifdef ABSL_HAVE_CONSTANT_EVALUATED +#error ABSL_HAVE_CONSTANT_EVALUATED cannot be directly set +#endif +#ifdef __cpp_lib_is_constant_evaluated +#define ABSL_HAVE_CONSTANT_EVALUATED 1 +#elif ABSL_HAVE_BUILTIN(__builtin_is_constant_evaluated) +#define ABSL_HAVE_CONSTANT_EVALUATED 1 +#endif + #endif // ABSL_BASE_CONFIG_H_ diff --git a/absl/base/internal/atomic_hook_test_helper.h b/absl/base/internal/atomic_hook_test_helper.h index 3e72b497..c72015ef 100644 --- a/absl/base/internal/atomic_hook_test_helper.h +++ b/absl/base/internal/atomic_hook_test_helper.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef ABSL_BASE_ATOMIC_HOOK_TEST_HELPER_H_ -#define ABSL_BASE_ATOMIC_HOOK_TEST_HELPER_H_ +#ifndef ABSL_BASE_INTERNAL_ATOMIC_HOOK_TEST_HELPER_H_ +#define ABSL_BASE_INTERNAL_ATOMIC_HOOK_TEST_HELPER_H_ #include "absl/base/internal/atomic_hook.h" @@ -31,4 +31,4 @@ void RegisterFunc(VoidF func); ABSL_NAMESPACE_END } // namespace absl -#endif // ABSL_BASE_ATOMIC_HOOK_TEST_HELPER_H_ +#endif // ABSL_BASE_INTERNAL_ATOMIC_HOOK_TEST_HELPER_H_ diff --git a/absl/base/internal/cycleclock.h b/absl/base/internal/cycleclock.h index 9704e388..cbfdf579 100644 --- a/absl/base/internal/cycleclock.h +++ b/absl/base/internal/cycleclock.h @@ -47,6 +47,7 @@ #include "absl/base/attributes.h" #include "absl/base/config.h" +#include "absl/base/internal/cycleclock_config.h" #include "absl/base/internal/unscaledcycleclock.h" namespace absl { @@ -76,25 +77,9 @@ class CycleClock { #if ABSL_USE_UNSCALED_CYCLECLOCK static CycleClockSourceFunc LoadCycleClockSource(); -#ifdef NDEBUG -#ifdef ABSL_INTERNAL_UNSCALED_CYCLECLOCK_FREQUENCY_IS_CPU_FREQUENCY - // Not debug mode and the UnscaledCycleClock frequency is the CPU - // frequency. Scale the CycleClock to prevent overflow if someone - // tries to represent the time as cycles since the Unix epoch. - static constexpr int32_t kShift = 1; -#else - // Not debug mode and the UnscaledCycleClock isn't operating at the - // raw CPU frequency. There is no need to do any scaling, so don't - // needlessly sacrifice precision. - static constexpr int32_t kShift = 0; -#endif -#else // NDEBUG - // In debug mode use a different shift to discourage depending on a - // particular shift value. - static constexpr int32_t kShift = 2; -#endif // NDEBUG + static constexpr int32_t kShift = kCycleClockShift; + static constexpr double kFrequencyScale = kCycleClockFrequencyScale; - static constexpr double kFrequencyScale = 1.0 / (1 << kShift); ABSL_CONST_INIT static std::atomic<CycleClockSourceFunc> cycle_clock_source_; #endif // ABSL_USE_UNSCALED_CYCLECLOC diff --git a/absl/base/internal/cycleclock_config.h b/absl/base/internal/cycleclock_config.h new file mode 100644 index 00000000..191112b5 --- /dev/null +++ b/absl/base/internal/cycleclock_config.h @@ -0,0 +1,55 @@ +// Copyright 2022 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_BASE_INTERNAL_CYCLECLOCK_CONFIG_H_ +#define ABSL_BASE_INTERNAL_CYCLECLOCK_CONFIG_H_ + +#include <cstdint> + +#include "absl/base/config.h" +#include "absl/base/internal/inline_variable.h" +#include "absl/base/internal/unscaledcycleclock_config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace base_internal { + +#if ABSL_USE_UNSCALED_CYCLECLOCK +#ifdef NDEBUG +#ifdef ABSL_INTERNAL_UNSCALED_CYCLECLOCK_FREQUENCY_IS_CPU_FREQUENCY +// Not debug mode and the UnscaledCycleClock frequency is the CPU +// frequency. Scale the CycleClock to prevent overflow if someone +// tries to represent the time as cycles since the Unix epoch. +ABSL_INTERNAL_INLINE_CONSTEXPR(int32_t, kCycleClockShift, 1); +#else +// Not debug mode and the UnscaledCycleClock isn't operating at the +// raw CPU frequency. There is no need to do any scaling, so don't +// needlessly sacrifice precision. +ABSL_INTERNAL_INLINE_CONSTEXPR(int32_t, kCycleClockShift, 0); +#endif +#else // NDEBUG +// In debug mode use a different shift to discourage depending on a +// particular shift value. +ABSL_INTERNAL_INLINE_CONSTEXPR(int32_t, kCycleClockShift, 2); +#endif // NDEBUG + +ABSL_INTERNAL_INLINE_CONSTEXPR(double, kCycleClockFrequencyScale, + 1.0 / (1 << kCycleClockShift)); +#endif // ABSL_USE_UNSCALED_CYCLECLOC + +} // namespace base_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_BASE_INTERNAL_CYCLECLOCK_CONFIG_H_ diff --git a/absl/base/internal/direct_mmap.h b/absl/base/internal/direct_mmap.h index e492bb00..815b8d23 100644 --- a/absl/base/internal/direct_mmap.h +++ b/absl/base/internal/direct_mmap.h @@ -97,7 +97,8 @@ inline void* DirectMmap(void* start, size_t length, int prot, int flags, int fd, #ifdef __BIONIC__ // SYS_mmap2 has problems on Android API level <= 16. // Workaround by invoking __mmap2() instead. - return __mmap2(start, length, prot, flags, fd, offset / pagesize); + return __mmap2(start, length, prot, flags, fd, + static_cast<size_t>(offset / pagesize)); #else return reinterpret_cast<void*>( syscall(SYS_mmap2, start, length, prot, flags, fd, diff --git a/absl/base/internal/inline_variable.h b/absl/base/internal/inline_variable.h index 130d8c24..df933faf 100644 --- a/absl/base/internal/inline_variable.h +++ b/absl/base/internal/inline_variable.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef ABSL_BASE_INTERNAL_INLINE_VARIABLE_EMULATION_H_ -#define ABSL_BASE_INTERNAL_INLINE_VARIABLE_EMULATION_H_ +#ifndef ABSL_BASE_INTERNAL_INLINE_VARIABLE_H_ +#define ABSL_BASE_INTERNAL_INLINE_VARIABLE_H_ #include <type_traits> @@ -104,4 +104,4 @@ #endif // __cpp_inline_variables -#endif // ABSL_BASE_INTERNAL_INLINE_VARIABLE_EMULATION_H_ +#endif // ABSL_BASE_INTERNAL_INLINE_VARIABLE_H_ diff --git a/absl/base/internal/inline_variable_testing.h b/absl/base/internal/inline_variable_testing.h index 3856b9f8..f3c81459 100644 --- a/absl/base/internal/inline_variable_testing.h +++ b/absl/base/internal/inline_variable_testing.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef ABSL_BASE_INLINE_VARIABLE_TESTING_H_ -#define ABSL_BASE_INLINE_VARIABLE_TESTING_H_ +#ifndef ABSL_BASE_INTERNAL_INLINE_VARIABLE_TESTING_H_ +#define ABSL_BASE_INTERNAL_INLINE_VARIABLE_TESTING_H_ #include "absl/base/internal/inline_variable.h" @@ -43,4 +43,4 @@ const int& get_int_b(); ABSL_NAMESPACE_END } // namespace absl -#endif // ABSL_BASE_INLINE_VARIABLE_TESTING_H_ +#endif // ABSL_BASE_INTERNAL_INLINE_VARIABLE_TESTING_H_ diff --git a/absl/base/internal/low_level_alloc.cc b/absl/base/internal/low_level_alloc.cc index 229ab916..662167b0 100644 --- a/absl/base/internal/low_level_alloc.cc +++ b/absl/base/internal/low_level_alloc.cc @@ -332,7 +332,7 @@ size_t GetPageSize() { #elif defined(__wasm__) || defined(__asmjs__) return getpagesize(); #else - return sysconf(_SC_PAGESIZE); + return static_cast<size_t>(sysconf(_SC_PAGESIZE)); #endif } @@ -364,7 +364,7 @@ LowLevelAlloc::Arena::Arena(uint32_t flags_value) } // L < meta_data_arena->mu -LowLevelAlloc::Arena *LowLevelAlloc::NewArena(int32_t flags) { +LowLevelAlloc::Arena *LowLevelAlloc::NewArena(uint32_t flags) { Arena *meta_data_arena = DefaultArena(); #ifndef ABSL_LOW_LEVEL_ALLOC_ASYNC_SIGNAL_SAFE_MISSING if ((flags & LowLevelAlloc::kAsyncSignalSafe) != 0) { diff --git a/absl/base/internal/low_level_alloc.h b/absl/base/internal/low_level_alloc.h index db91951c..eabb14a9 100644 --- a/absl/base/internal/low_level_alloc.h +++ b/absl/base/internal/low_level_alloc.h @@ -103,7 +103,7 @@ class LowLevelAlloc { // the provided flags. For example, the call NewArena(kAsyncSignalSafe) // is itself async-signal-safe, as well as generatating an arena that provides // async-signal-safe Alloc/Free. - static Arena *NewArena(int32_t flags); + static Arena *NewArena(uint32_t flags); // Destroys an arena allocated by NewArena and returns true, // provided no allocated blocks remain in the arena. diff --git a/absl/base/internal/raw_logging.cc b/absl/base/internal/raw_logging.cc index 54e71a3f..6273e847 100644 --- a/absl/base/internal/raw_logging.cc +++ b/absl/base/internal/raw_logging.cc @@ -72,7 +72,7 @@ namespace absl { ABSL_NAMESPACE_BEGIN -namespace raw_logging_internal { +namespace raw_log_internal { namespace { // TODO(gfalcon): We want raw-logging to work on as many platforms as possible. @@ -89,12 +89,14 @@ 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) { - int n = vsnprintf(*buf, *size, format, ap); + if (*size < 0) + return false; + int n = vsnprintf(*buf, static_cast<size_t>(*size), format, ap); bool result = true; if (n < 0 || n > *size) { result = false; if (static_cast<size_t>(*size) > sizeof(kTruncated)) { - n = *size - sizeof(kTruncated); // room for truncation message + n = *size - static_cast<int>(sizeof(kTruncated)); } else { n = 0; // no room for truncation message } @@ -116,9 +118,11 @@ 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; va_list ap; va_start(ap, format); - int n = vsnprintf(*buf, *size, format, ap); + int n = vsnprintf(*buf, static_cast<size_t>(*size), format, ap); va_end(ap); if (n < 0 || n > *size) return false; *size -= n; @@ -206,7 +210,7 @@ void AsyncSignalSafeWriteToStderr(const char* s, size_t len) { #elif defined(ABSL_HAVE_POSIX_WRITE) write(STDERR_FILENO, s, len); #elif defined(ABSL_HAVE_RAW_IO) - _write(/* stderr */ 2, s, len); + _write(/* stderr */ 2, s, static_cast<unsigned>(len)); #else // stderr logging unsupported on this platform (void) s; @@ -244,6 +248,6 @@ void RegisterInternalLogFunction(InternalLogFunction func) { internal_log_function.Store(func); } -} // namespace raw_logging_internal +} // namespace raw_log_internal ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/base/internal/raw_logging.h b/absl/base/internal/raw_logging.h index 0747c9df..c7b889cd 100644 --- a/absl/base/internal/raw_logging.h +++ b/absl/base/internal/raw_logging.h @@ -43,12 +43,11 @@ #define ABSL_RAW_LOG(severity, ...) \ do { \ - constexpr const char* absl_raw_logging_internal_basename = \ - ::absl::raw_logging_internal::Basename(__FILE__, \ - sizeof(__FILE__) - 1); \ - ::absl::raw_logging_internal::RawLog(ABSL_RAW_LOGGING_INTERNAL_##severity, \ - absl_raw_logging_internal_basename, \ - __LINE__, __VA_ARGS__); \ + constexpr const char* absl_raw_log_internal_basename = \ + ::absl::raw_log_internal::Basename(__FILE__, sizeof(__FILE__) - 1); \ + ::absl::raw_log_internal::RawLog(ABSL_RAW_LOG_INTERNAL_##severity, \ + absl_raw_log_internal_basename, __LINE__, \ + __VA_ARGS__); \ } while (0) // Similar to CHECK(condition) << message, but for low-level modules: @@ -72,14 +71,14 @@ // // The API is a subset of the above: each macro only takes two arguments. Use // StrCat if you need to build a richer message. -#define ABSL_INTERNAL_LOG(severity, message) \ - do { \ - constexpr const char* absl_raw_logging_internal_filename = __FILE__; \ - ::absl::raw_logging_internal::internal_log_function( \ - ABSL_RAW_LOGGING_INTERNAL_##severity, \ - absl_raw_logging_internal_filename, __LINE__, message); \ - if (ABSL_RAW_LOGGING_INTERNAL_##severity == ::absl::LogSeverity::kFatal) \ - ABSL_INTERNAL_UNREACHABLE; \ +#define ABSL_INTERNAL_LOG(severity, message) \ + do { \ + constexpr const char* absl_raw_log_internal_filename = __FILE__; \ + ::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(); \ } while (0) #define ABSL_INTERNAL_CHECK(condition, message) \ @@ -91,16 +90,16 @@ } \ } while (0) -#define ABSL_RAW_LOGGING_INTERNAL_INFO ::absl::LogSeverity::kInfo -#define ABSL_RAW_LOGGING_INTERNAL_WARNING ::absl::LogSeverity::kWarning -#define ABSL_RAW_LOGGING_INTERNAL_ERROR ::absl::LogSeverity::kError -#define ABSL_RAW_LOGGING_INTERNAL_FATAL ::absl::LogSeverity::kFatal -#define ABSL_RAW_LOGGING_INTERNAL_LEVEL(severity) \ +#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_LEVEL(severity) \ ::absl::NormalizeLogSeverity(severity) namespace absl { ABSL_NAMESPACE_BEGIN -namespace raw_logging_internal { +namespace raw_log_internal { // Helper function to implement ABSL_RAW_LOG // Logs format... at "severity" level, reporting it @@ -130,7 +129,7 @@ constexpr const char* Basename(const char* fname, int offset) { // TODO(gfalcon): Come up with a better name for this method. bool RawLoggingFullySupported(); -// Function type for a raw_logging customization hook for suppressing messages +// Function type for a raw_log customization hook for suppressing messages // by severity, and for writing custom prefixes on non-suppressed messages. // // The installed hook is called for every raw log invocation. The message will @@ -139,7 +138,7 @@ bool RawLoggingFullySupported(); // also provided with an output buffer, where it can write a custom log message // prefix. // -// The raw_logging system does not allocate memory or grab locks. User-provided +// The raw_log system does not allocate memory or grab locks. User-provided // hooks must avoid these operations, and must not throw exceptions. // // 'severity' is the severity level of the message being written. @@ -152,7 +151,7 @@ using LogFilterAndPrefixHook = bool (*)(absl::LogSeverity severity, const char* file, int line, char** buf, int* buf_size); -// Function type for a raw_logging customization hook called to abort a process +// Function type for a raw_log customization hook called to abort a process // when a FATAL message is logged. If the provided AbortHook() returns, the // logging system will call abort(). // @@ -189,7 +188,7 @@ void RegisterLogFilterAndPrefixHook(LogFilterAndPrefixHook func); void RegisterAbortHook(AbortHook func); void RegisterInternalLogFunction(InternalLogFunction func); -} // namespace raw_logging_internal +} // namespace raw_log_internal ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/base/internal/spinlock.cc b/absl/base/internal/spinlock.cc index 9b5ed6e4..381b913b 100644 --- a/absl/base/internal/spinlock.cc +++ b/absl/base/internal/spinlock.cc @@ -178,7 +178,7 @@ void SpinLock::SlowUnlock(uint32_t lock_value) { // reserve a unitary wait time to represent that a waiter exists without our // own acquisition having been contended. if ((lock_value & kWaitTimeMask) != kSpinLockSleeper) { - const uint64_t wait_cycles = DecodeWaitCycles(lock_value); + const int64_t wait_cycles = DecodeWaitCycles(lock_value); ABSL_TSAN_MUTEX_PRE_DIVERT(this, 0); submit_profile_data(this, wait_cycles); ABSL_TSAN_MUTEX_POST_DIVERT(this, 0); @@ -220,9 +220,9 @@ uint32_t SpinLock::EncodeWaitCycles(int64_t wait_start_time, return clamped; } -uint64_t SpinLock::DecodeWaitCycles(uint32_t lock_value) { +int64_t SpinLock::DecodeWaitCycles(uint32_t lock_value) { // Cast to uint32_t first to ensure bits [63:32] are cleared. - const uint64_t scaled_wait_time = + const int64_t scaled_wait_time = static_cast<uint32_t>(lock_value & kWaitTimeMask); return scaled_wait_time << (kProfileTimestampShift - kLockwordReservedShift); } diff --git a/absl/base/internal/spinlock.h b/absl/base/internal/spinlock.h index 6d8d8ddd..09ba5824 100644 --- a/absl/base/internal/spinlock.h +++ b/absl/base/internal/spinlock.h @@ -29,10 +29,8 @@ #ifndef ABSL_BASE_INTERNAL_SPINLOCK_H_ #define ABSL_BASE_INTERNAL_SPINLOCK_H_ -#include <stdint.h> -#include <sys/types.h> - #include <atomic> +#include <cstdint> #include "absl/base/attributes.h" #include "absl/base/const_init.h" @@ -41,8 +39,6 @@ #include "absl/base/internal/raw_logging.h" #include "absl/base/internal/scheduling_mode.h" #include "absl/base/internal/tsan_mutex_interface.h" -#include "absl/base/macros.h" -#include "absl/base/port.h" #include "absl/base/thread_annotations.h" namespace absl { @@ -137,7 +133,7 @@ class ABSL_LOCKABLE SpinLock { int64_t wait_end_time); // Extract number of wait cycles in a lock value. - static uint64_t DecodeWaitCycles(uint32_t lock_value); + static int64_t DecodeWaitCycles(uint32_t lock_value); // Provide access to protected method above. Use for testing only. friend struct SpinLockTest; diff --git a/absl/base/internal/spinlock_win32.inc b/absl/base/internal/spinlock_win32.inc index 9d224813..934c2016 100644 --- a/absl/base/internal/spinlock_win32.inc +++ b/absl/base/internal/spinlock_win32.inc @@ -27,7 +27,10 @@ void ABSL_INTERNAL_C_SYMBOL(AbslInternalSpinLockDelay)( } else if (loop == 1) { Sleep(0); } else { - Sleep(absl::base_internal::SpinLockSuggestedDelayNS(loop) / 1000000); + // SpinLockSuggestedDelayNS() always returns a positive integer, so this + // static_cast is safe. + Sleep(static_cast<DWORD>( + absl::base_internal::SpinLockSuggestedDelayNS(loop) / 1000000)); } } diff --git a/absl/base/internal/strerror.cc b/absl/base/internal/strerror.cc index 0d6226fd..de91c05e 100644 --- a/absl/base/internal/strerror.cc +++ b/absl/base/internal/strerror.cc @@ -66,8 +66,8 @@ constexpr int kSysNerr = 135; std::array<std::string, kSysNerr>* NewStrErrorTable() { auto* table = new std::array<std::string, kSysNerr>; - for (int i = 0; i < static_cast<int>(table->size()); ++i) { - (*table)[i] = StrErrorInternal(i); + for (size_t i = 0; i < table->size(); ++i) { + (*table)[i] = StrErrorInternal(static_cast<int>(i)); } return table; } @@ -77,8 +77,8 @@ std::array<std::string, kSysNerr>* NewStrErrorTable() { std::string StrError(int errnum) { absl::base_internal::ErrnoSaver errno_saver; static const auto* table = NewStrErrorTable(); - if (errnum >= 0 && errnum < static_cast<int>(table->size())) { - return (*table)[errnum]; + if (errnum >= 0 && static_cast<size_t>(errnum) < table->size()) { + return (*table)[static_cast<size_t>(errnum)]; } return StrErrorInternal(errnum); } diff --git a/absl/base/internal/sysinfo.cc b/absl/base/internal/sysinfo.cc index c8366df1..da499d3a 100644 --- a/absl/base/internal/sysinfo.cc +++ b/absl/base/internal/sysinfo.cc @@ -117,7 +117,7 @@ int Win32NumCPUs() { } } free(info); - return logicalProcessorCount; + return static_cast<int>(logicalProcessorCount); } #endif @@ -128,7 +128,7 @@ static int GetNumCPUs() { #if defined(__myriad2__) return 1; #elif defined(_WIN32) - const unsigned hardware_concurrency = Win32NumCPUs(); + const int hardware_concurrency = Win32NumCPUs(); return hardware_concurrency ? hardware_concurrency : 1; #elif defined(_AIX) return sysconf(_SC_NPROCESSORS_ONLN); @@ -136,7 +136,7 @@ static int GetNumCPUs() { // Other possibilities: // - Read /sys/devices/system/cpu/online and use cpumask_parse() // - sysconf(_SC_NPROCESSORS_ONLN) - return std::thread::hardware_concurrency(); + return static_cast<int>(std::thread::hardware_concurrency()); #endif } @@ -189,12 +189,15 @@ 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); + int fd = open(file, O_RDONLY | O_CLOEXEC); if (fd != -1) { char line[1024]; char *err; memset(line, '\0', sizeof(line)); - int len = read(fd, line, sizeof(line) - 1); + ssize_t len; + do { + len = read(fd, line, sizeof(line) - 1); + } while (len < 0 && errno == EINTR); if (len <= 0) { ret = false; } else { @@ -376,7 +379,7 @@ pid_t GetTID() { #endif pid_t GetTID() { - return syscall(SYS_gettid); + return static_cast<pid_t>(syscall(SYS_gettid)); } #elif defined(__akaros__) @@ -429,11 +432,11 @@ 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); - int word = tid / kBitsPerWord; + 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)[word] &= mask; + (*tid_array)[static_cast<size_t>(word)] &= mask; } static void InitGetTID() { @@ -455,7 +458,7 @@ pid_t GetTID() { intptr_t tid = reinterpret_cast<intptr_t>(pthread_getspecific(tid_key)); if (tid != 0) { - return tid; + return static_cast<pid_t>(tid); } int bit; // tid_array[word] = 1u << bit; @@ -476,7 +479,8 @@ pid_t GetTID() { while (bit < kBitsPerWord && (((*tid_array)[word] >> bit) & 1) != 0) { ++bit; } - tid = (word * kBitsPerWord) + bit; + tid = + static_cast<intptr_t>((word * kBitsPerWord) + static_cast<size_t>(bit)); (*tid_array)[word] |= 1u << bit; // Mark the TID as allocated. } diff --git a/absl/base/internal/thread_annotations.h b/absl/base/internal/thread_annotations.h index 4dab6a9c..8c5c67e0 100644 --- a/absl/base/internal/thread_annotations.h +++ b/absl/base/internal/thread_annotations.h @@ -38,6 +38,13 @@ #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 @@ -268,4 +275,6 @@ inline T& ts_unchecked_read(T& v) NO_THREAD_SAFETY_ANALYSIS { } // 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.h b/absl/base/internal/thread_identity.h index 659694b3..463acbc7 100644 --- a/absl/base/internal/thread_identity.h +++ b/absl/base/internal/thread_identity.h @@ -134,6 +134,10 @@ struct PerThreadSynch { // The instances of this class are allocated in NewThreadIdentity() with an // alignment of PerThreadSynch::kAlignment. +// +// NOTE: The layout of fields in this structure is critical, please do not +// add, remove, or modify the field placements without fully auditing the +// layout. struct ThreadIdentity { // Must be the first member. The Mutex implementation requires that // the PerThreadSynch object associated with each thread is diff --git a/absl/base/internal/unscaledcycleclock.h b/absl/base/internal/unscaledcycleclock.h index 2cbeae31..cc1276ba 100644 --- a/absl/base/internal/unscaledcycleclock.h +++ b/absl/base/internal/unscaledcycleclock.h @@ -42,48 +42,11 @@ #include <TargetConditionals.h> #endif -#include "absl/base/port.h" - -// The following platforms have an implementation of a hardware counter. -#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || \ - defined(__powerpc__) || defined(__ppc__) || defined(__riscv) || \ - defined(_M_IX86) || (defined(_M_X64) && !defined(_M_ARM64EC)) -#define ABSL_HAVE_UNSCALED_CYCLECLOCK_IMPLEMENTATION 1 -#else -#define ABSL_HAVE_UNSCALED_CYCLECLOCK_IMPLEMENTATION 0 -#endif - -// The following platforms often disable access to the hardware -// counter (through a sandbox) even if the underlying hardware has a -// usable counter. The CycleTimer interface also requires a *scaled* -// CycleClock that runs at atleast 1 MHz. We've found some Android -// ARM64 devices where this is not the case, so we disable it by -// default on Android ARM64. -#if defined(__native_client__) || (defined(__APPLE__)) || \ - (defined(__ANDROID__) && defined(__aarch64__)) -#define ABSL_USE_UNSCALED_CYCLECLOCK_DEFAULT 0 -#else -#define ABSL_USE_UNSCALED_CYCLECLOCK_DEFAULT 1 -#endif - -// UnscaledCycleClock is an optional internal feature. -// Use "#if ABSL_USE_UNSCALED_CYCLECLOCK" to test for its presence. -// Can be overridden at compile-time via -DABSL_USE_UNSCALED_CYCLECLOCK=0|1 -#if !defined(ABSL_USE_UNSCALED_CYCLECLOCK) -#define ABSL_USE_UNSCALED_CYCLECLOCK \ - (ABSL_HAVE_UNSCALED_CYCLECLOCK_IMPLEMENTATION && \ - ABSL_USE_UNSCALED_CYCLECLOCK_DEFAULT) -#endif +#include "absl/base/config.h" +#include "absl/base/internal/unscaledcycleclock_config.h" #if ABSL_USE_UNSCALED_CYCLECLOCK -// This macro can be used to test if UnscaledCycleClock::Frequency() -// is NominalCPUFrequency() on a particular platform. -#if (defined(__i386__) || defined(__x86_64__) || defined(__riscv) || \ - defined(_M_IX86) || defined(_M_X64)) -#define ABSL_INTERNAL_UNSCALED_CYCLECLOCK_FREQUENCY_IS_CPU_FREQUENCY -#endif - namespace absl { ABSL_NAMESPACE_BEGIN namespace time_internal { @@ -119,7 +82,7 @@ class UnscaledCycleClock { inline int64_t UnscaledCycleClock::Now() { uint64_t low, high; __asm__ volatile("rdtsc" : "=a"(low), "=d"(high)); - return (high << 32) | low; + return static_cast<int64_t>((high << 32) | low); } #endif diff --git a/absl/base/internal/unscaledcycleclock_config.h b/absl/base/internal/unscaledcycleclock_config.h new file mode 100644 index 00000000..24b324ac --- /dev/null +++ b/absl/base/internal/unscaledcycleclock_config.h @@ -0,0 +1,62 @@ +// 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_UNSCALEDCYCLECLOCK_CONFIG_H_ +#define ABSL_BASE_INTERNAL_UNSCALEDCYCLECLOCK_CONFIG_H_ + +#if defined(__APPLE__) +#include <TargetConditionals.h> +#endif + +// The following platforms have an implementation of a hardware counter. +#if defined(__i386__) || defined(__x86_64__) || defined(__aarch64__) || \ + defined(__powerpc__) || defined(__ppc__) || defined(__riscv) || \ + defined(_M_IX86) || (defined(_M_X64) && !defined(_M_ARM64EC)) +#define ABSL_HAVE_UNSCALED_CYCLECLOCK_IMPLEMENTATION 1 +#else +#define ABSL_HAVE_UNSCALED_CYCLECLOCK_IMPLEMENTATION 0 +#endif + +// The following platforms often disable access to the hardware +// counter (through a sandbox) even if the underlying hardware has a +// usable counter. The CycleTimer interface also requires a *scaled* +// CycleClock that runs at atleast 1 MHz. We've found some Android +// ARM64 devices where this is not the case, so we disable it by +// default on Android ARM64. +#if defined(__native_client__) || (defined(__APPLE__)) || \ + (defined(__ANDROID__) && defined(__aarch64__)) +#define ABSL_USE_UNSCALED_CYCLECLOCK_DEFAULT 0 +#else +#define ABSL_USE_UNSCALED_CYCLECLOCK_DEFAULT 1 +#endif + +// UnscaledCycleClock is an optional internal feature. +// Use "#if ABSL_USE_UNSCALED_CYCLECLOCK" to test for its presence. +// Can be overridden at compile-time via -DABSL_USE_UNSCALED_CYCLECLOCK=0|1 +#if !defined(ABSL_USE_UNSCALED_CYCLECLOCK) +#define ABSL_USE_UNSCALED_CYCLECLOCK \ + (ABSL_HAVE_UNSCALED_CYCLECLOCK_IMPLEMENTATION && \ + ABSL_USE_UNSCALED_CYCLECLOCK_DEFAULT) +#endif + +#if ABSL_USE_UNSCALED_CYCLECLOCK +// This macro can be used to test if UnscaledCycleClock::Frequency() +// is NominalCPUFrequency() on a particular platform. +#if (defined(__i386__) || defined(__x86_64__) || defined(__riscv) || \ + defined(_M_IX86) || defined(_M_X64)) +#define ABSL_INTERNAL_UNSCALED_CYCLECLOCK_FREQUENCY_IS_CPU_FREQUENCY +#endif +#endif + +#endif // ABSL_BASE_INTERNAL_UNSCALEDCYCLECLOCK_CONFIG_H_ diff --git a/absl/base/macros.h b/absl/base/macros.h index 3e085a91..f33cd192 100644 --- a/absl/base/macros.h +++ b/absl/base/macros.h @@ -103,17 +103,11 @@ ABSL_NAMESPACE_END // aborts the program in release mode (when NDEBUG is defined). The // implementation should abort the program as quickly as possible and ideally it // should not be possible to ignore the abort request. -#if (ABSL_HAVE_BUILTIN(__builtin_trap) && \ - ABSL_HAVE_BUILTIN(__builtin_unreachable)) || \ - (defined(__GNUC__) && !defined(__clang__)) -#define ABSL_INTERNAL_HARDENING_ABORT() \ - do { \ - __builtin_trap(); \ - __builtin_unreachable(); \ +#define ABSL_INTERNAL_HARDENING_ABORT() \ + do { \ + ABSL_INTERNAL_IMMEDIATE_ABORT_IMPL(); \ + ABSL_INTERNAL_UNREACHABLE_IMPL(); \ } while (false) -#else -#define ABSL_INTERNAL_HARDENING_ABORT() abort() -#endif // ABSL_HARDENING_ASSERT() // @@ -144,15 +138,4 @@ ABSL_NAMESPACE_END #define ABSL_INTERNAL_RETHROW do {} while (false) #endif // ABSL_HAVE_EXCEPTIONS -// `ABSL_INTERNAL_UNREACHABLE` is an unreachable statement. A program which -// reaches one has undefined behavior, and the compiler may optimize -// accordingly. -#if defined(__GNUC__) || ABSL_HAVE_BUILTIN(__builtin_unreachable) -#define ABSL_INTERNAL_UNREACHABLE __builtin_unreachable() -#elif defined(_MSC_VER) -#define ABSL_INTERNAL_UNREACHABLE __assume(0) -#else -#define ABSL_INTERNAL_UNREACHABLE -#endif - #endif // ABSL_BASE_MACROS_H_ diff --git a/absl/base/optimization.h b/absl/base/optimization.h index db5cc097..ad0121ad 100644 --- a/absl/base/optimization.h +++ b/absl/base/optimization.h @@ -91,6 +91,7 @@ #define ABSL_CACHELINE_SIZE 64 #endif #endif +#endif #ifndef ABSL_CACHELINE_SIZE // A reasonable default guess. Note that overestimates tend to waste more @@ -141,12 +142,11 @@ // the generated machine code. // 3) Prefer applying this attribute to individual variables. Avoid // applying it to types. This tends to localize the effect. +#if defined(__clang__) || defined(__GNUC__) #define ABSL_CACHELINE_ALIGNED __attribute__((aligned(ABSL_CACHELINE_SIZE))) #elif defined(_MSC_VER) -#define ABSL_CACHELINE_SIZE 64 #define ABSL_CACHELINE_ALIGNED __declspec(align(ABSL_CACHELINE_SIZE)) #else -#define ABSL_CACHELINE_SIZE 64 #define ABSL_CACHELINE_ALIGNED #endif @@ -181,6 +181,53 @@ #define ABSL_PREDICT_TRUE(x) (x) #endif +// `ABSL_INTERNAL_IMMEDIATE_ABORT_IMPL()` aborts the program in the fastest +// possible way, with no attempt at logging. One use is to implement hardening +// aborts with ABSL_OPTION_HARDENED. Since this is an internal symbol, it +// should not be used directly outside of Abseil. +#if ABSL_HAVE_BUILTIN(__builtin_trap) || \ + (defined(__GNUC__) && !defined(__clang__)) +#define ABSL_INTERNAL_IMMEDIATE_ABORT_IMPL() __builtin_trap() +#else +#define ABSL_INTERNAL_IMMEDIATE_ABORT_IMPL() abort() +#endif + +// `ABSL_INTERNAL_UNREACHABLE_IMPL()` is the platform specific directive to +// indicate that a statement is unreachable, and to allow the compiler to +// optimize accordingly. Clients should use `ABSL_UNREACHABLE()`, which is +// defined below. +#if defined(__cpp_lib_unreachable) && __cpp_lib_unreachable >= 202202L +#define ABSL_INTERNAL_UNREACHABLE_IMPL() std::unreachable() +#elif defined(__GNUC__) || ABSL_HAVE_BUILTIN(__builtin_unreachable) +#define ABSL_INTERNAL_UNREACHABLE_IMPL() __builtin_unreachable() +#elif ABSL_HAVE_BUILTIN(__builtin_assume) +#define ABSL_INTERNAL_UNREACHABLE_IMPL() __builtin_assume(false) +#elif defined(_MSC_VER) +#define ABSL_INTERNAL_UNREACHABLE_IMPL() __assume(false) +#else +#define ABSL_INTERNAL_UNREACHABLE_IMPL() +#endif + +// `ABSL_UNREACHABLE()` is an unreachable statement. A program which reaches +// one has undefined behavior, and the compiler may optimize accordingly. +#if ABSL_OPTION_HARDENED == 1 && defined(NDEBUG) +// Abort in hardened mode to avoid dangerous undefined behavior. +#define ABSL_UNREACHABLE() \ + do { \ + ABSL_INTERNAL_IMMEDIATE_ABORT_IMPL(); \ + ABSL_INTERNAL_UNREACHABLE_IMPL(); \ + } while (false) +#else +// The assert only fires in debug mode to aid in debugging. +// When NDEBUG is defined, reaching ABSL_UNREACHABLE() is undefined behavior. +#define ABSL_UNREACHABLE() \ + do { \ + /* NOLINTNEXTLINE: misc-static-assert */ \ + assert(false && "ABSL_UNREACHABLE reached"); \ + ABSL_INTERNAL_UNREACHABLE_IMPL(); \ + } while (false) +#endif + // ABSL_ASSUME(cond) // // Informs the compiler that a condition is always true and that it can assume @@ -209,18 +256,23 @@ #define ABSL_ASSUME(cond) assert(cond) #elif ABSL_HAVE_BUILTIN(__builtin_assume) #define ABSL_ASSUME(cond) __builtin_assume(cond) +#elif defined(_MSC_VER) +#define ABSL_ASSUME(cond) __assume(cond) +#elif defined(__cpp_lib_unreachable) && __cpp_lib_unreachable >= 202202L +#define ABSL_ASSUME(cond) \ + do { \ + if (!(cond)) std::unreachable(); \ + } while (false) #elif defined(__GNUC__) || ABSL_HAVE_BUILTIN(__builtin_unreachable) #define ABSL_ASSUME(cond) \ do { \ if (!(cond)) __builtin_unreachable(); \ - } while (0) -#elif defined(_MSC_VER) -#define ABSL_ASSUME(cond) __assume(cond) + } while (false) #else #define ABSL_ASSUME(cond) \ do { \ static_cast<void>(false && (cond)); \ - } while (0) + } while (false) #endif // ABSL_INTERNAL_UNIQUE_SMALL_NAME(cond) diff --git a/absl/base/options.h b/absl/base/options.h index bc598470..b6de636d 100644 --- a/absl/base/options.h +++ b/absl/base/options.h @@ -67,12 +67,6 @@ #ifndef ABSL_BASE_OPTIONS_H_ #define ABSL_BASE_OPTIONS_H_ -// Include a standard library header to allow configuration based on the -// standard library in use. -#ifdef __cplusplus -#include <ciso646> -#endif - // ----------------------------------------------------------------------------- // Type Compatibility Options // ----------------------------------------------------------------------------- @@ -206,7 +200,7 @@ // allowed. #define ABSL_OPTION_USE_INLINE_NAMESPACE 1 -#define ABSL_OPTION_INLINE_NAMESPACE_NAME lts_20220623 +#define ABSL_OPTION_INLINE_NAMESPACE_NAME lts_20230125 // ABSL_OPTION_HARDENED // diff --git a/absl/base/policy_checks.h b/absl/base/policy_checks.h index 06b32439..b8cd4c94 100644 --- a/absl/base/policy_checks.h +++ b/absl/base/policy_checks.h @@ -44,17 +44,17 @@ // Toolchain Check // ----------------------------------------------------------------------------- -// We support MSVC++ 14.0 update 2 and later. +// We support Visual Studio 2017 (MSVC++ 15.0) and later. // This minimum will go up. -#if defined(_MSC_FULL_VER) && _MSC_FULL_VER < 190023918 && !defined(__clang__) -#error "This package requires Visual Studio 2015 Update 2 or higher." +#if defined(_MSC_VER) && _MSC_VER < 1910 && !defined(__clang__) +#error "This package requires Visual Studio 2017 (MSVC++ 15.0) or higher." #endif -// We support gcc 4.7 and later. +// We support GCC 7 and later. // This minimum will go up. #if defined(__GNUC__) && !defined(__clang__) -#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 7) -#error "This package requires gcc 4.7 or higher." +#if __GNUC__ < 7 +#error "This package requires GCC 7 or higher." #endif #endif @@ -69,13 +69,15 @@ // C++ Version Check // ----------------------------------------------------------------------------- -// Enforce C++11 as the minimum. Note that Visual Studio has not -// advanced __cplusplus despite being good enough for our purposes, so -// so we exempt it from the check. -#if defined(__cplusplus) && !defined(_MSC_VER) -#if __cplusplus < 201103L -#error "C++ versions less than C++11 are not supported." -#endif +// Enforce C++14 as the minimum. +#if defined(_MSVC_LANG) +#if _MSVC_LANG < 201402L +#error "C++ versions less than C++14 are not supported." +#endif // _MSVC_LANG < 201402L +#elif defined(__cplusplus) +#if __cplusplus < 201402L +#error "C++ versions less than C++14 are not supported." +#endif // __cplusplus < 201402L #endif // ----------------------------------------------------------------------------- diff --git a/absl/base/spinlock_test_common.cc b/absl/base/spinlock_test_common.cc index 2b572c5b..52ecf580 100644 --- a/absl/base/spinlock_test_common.cc +++ b/absl/base/spinlock_test_common.cc @@ -34,7 +34,7 @@ #include "absl/synchronization/blocking_counter.h" #include "absl/synchronization/notification.h" -constexpr int32_t kNumThreads = 10; +constexpr uint32_t kNumThreads = 10; constexpr int32_t kIters = 1000; namespace absl { @@ -48,14 +48,14 @@ struct SpinLockTest { int64_t wait_end_time) { return SpinLock::EncodeWaitCycles(wait_start_time, wait_end_time); } - static uint64_t DecodeWaitCycles(uint32_t lock_value) { + static int64_t DecodeWaitCycles(uint32_t lock_value) { return SpinLock::DecodeWaitCycles(lock_value); } }; namespace { -static constexpr int kArrayLength = 10; +static constexpr size_t kArrayLength = 10; static uint32_t values[kArrayLength]; ABSL_CONST_INIT static SpinLock static_cooperative_spinlock( @@ -79,11 +79,11 @@ static uint32_t Hash32(uint32_t a, uint32_t c) { return c; } -static void TestFunction(int thread_salt, SpinLock* spinlock) { +static void TestFunction(uint32_t thread_salt, SpinLock* spinlock) { for (int i = 0; i < kIters; i++) { SpinLockHolder h(spinlock); - for (int j = 0; j < kArrayLength; j++) { - const int index = (j + thread_salt) % kArrayLength; + for (size_t j = 0; j < kArrayLength; j++) { + const size_t index = (j + thread_salt) % kArrayLength; values[index] = Hash32(values[index], thread_salt); std::this_thread::yield(); } @@ -93,7 +93,7 @@ static void TestFunction(int thread_salt, SpinLock* spinlock) { static void ThreadedTest(SpinLock* spinlock) { std::vector<std::thread> threads; threads.reserve(kNumThreads); - for (int i = 0; i < kNumThreads; ++i) { + for (uint32_t i = 0; i < kNumThreads; ++i) { threads.push_back(std::thread(TestFunction, i, spinlock)); } for (auto& thread : threads) { @@ -101,7 +101,7 @@ static void ThreadedTest(SpinLock* spinlock) { } SpinLockHolder h(spinlock); - for (int i = 1; i < kArrayLength; i++) { + for (size_t i = 1; i < kArrayLength; i++) { EXPECT_EQ(values[0], values[i]); } } @@ -133,28 +133,28 @@ TEST(SpinLock, WaitCyclesEncoding) { // but the lower kProfileTimestampShift will be dropped. const int kMaxCyclesShift = 32 - kLockwordReservedShift + kProfileTimestampShift; - const uint64_t kMaxCycles = (int64_t{1} << kMaxCyclesShift) - 1; + const int64_t kMaxCycles = (int64_t{1} << kMaxCyclesShift) - 1; // These bits should be zero after encoding. const uint32_t kLockwordReservedMask = (1 << kLockwordReservedShift) - 1; // These bits are dropped when wait cycles are encoded. - const uint64_t kProfileTimestampMask = (1 << kProfileTimestampShift) - 1; + const int64_t kProfileTimestampMask = (1 << kProfileTimestampShift) - 1; // Test a bunch of random values std::default_random_engine generator; // Shift to avoid overflow below. - std::uniform_int_distribution<uint64_t> time_distribution( - 0, std::numeric_limits<uint64_t>::max() >> 4); - std::uniform_int_distribution<uint64_t> cycle_distribution(0, kMaxCycles); + std::uniform_int_distribution<int64_t> time_distribution( + 0, std::numeric_limits<int64_t>::max() >> 3); + std::uniform_int_distribution<int64_t> cycle_distribution(0, kMaxCycles); for (int i = 0; i < 100; i++) { int64_t start_time = time_distribution(generator); int64_t cycles = cycle_distribution(generator); int64_t end_time = start_time + cycles; uint32_t lock_value = SpinLockTest::EncodeWaitCycles(start_time, end_time); - EXPECT_EQ(0, lock_value & kLockwordReservedMask); - uint64_t decoded = SpinLockTest::DecodeWaitCycles(lock_value); + EXPECT_EQ(0u, lock_value & kLockwordReservedMask); + int64_t decoded = SpinLockTest::DecodeWaitCycles(lock_value); EXPECT_EQ(0, decoded & kProfileTimestampMask); EXPECT_EQ(cycles & ~kProfileTimestampMask, decoded); } @@ -178,21 +178,21 @@ TEST(SpinLock, WaitCyclesEncoding) { // Test clamping uint32_t max_value = SpinLockTest::EncodeWaitCycles(start_time, start_time + kMaxCycles); - uint64_t max_value_decoded = SpinLockTest::DecodeWaitCycles(max_value); - uint64_t expected_max_value_decoded = kMaxCycles & ~kProfileTimestampMask; + int64_t max_value_decoded = SpinLockTest::DecodeWaitCycles(max_value); + int64_t expected_max_value_decoded = kMaxCycles & ~kProfileTimestampMask; EXPECT_EQ(expected_max_value_decoded, max_value_decoded); const int64_t step = (1 << kProfileTimestampShift); uint32_t after_max_value = SpinLockTest::EncodeWaitCycles(start_time, start_time + kMaxCycles + step); - uint64_t after_max_value_decoded = + int64_t after_max_value_decoded = SpinLockTest::DecodeWaitCycles(after_max_value); EXPECT_EQ(expected_max_value_decoded, after_max_value_decoded); uint32_t before_max_value = SpinLockTest::EncodeWaitCycles( start_time, start_time + kMaxCycles - step); - uint64_t before_max_value_decoded = - SpinLockTest::DecodeWaitCycles(before_max_value); + int64_t before_max_value_decoded = + SpinLockTest::DecodeWaitCycles(before_max_value); EXPECT_GT(expected_max_value_decoded, before_max_value_decoded); } diff --git a/absl/base/throw_delegate_test.cc b/absl/base/throw_delegate_test.cc index 5ba4ce55..e74362b7 100644 --- a/absl/base/throw_delegate_test.cc +++ b/absl/base/throw_delegate_test.cc @@ -78,29 +78,97 @@ void ExpectThrowNoWhat(void (*f)()) { #endif } -TEST(ThrowHelper, Test) { - // Not using EXPECT_THROW because we want to check the .what() message too. +TEST(ThrowDelegate, ThrowStdLogicErrorChar) { ExpectThrowChar<std::logic_error>(ThrowStdLogicError); +} + +TEST(ThrowDelegate, ThrowStdInvalidArgumentChar) { ExpectThrowChar<std::invalid_argument>(ThrowStdInvalidArgument); +} + +TEST(ThrowDelegate, ThrowStdDomainErrorChar) { ExpectThrowChar<std::domain_error>(ThrowStdDomainError); +} + +TEST(ThrowDelegate, ThrowStdLengthErrorChar) { ExpectThrowChar<std::length_error>(ThrowStdLengthError); +} + +TEST(ThrowDelegate, ThrowStdOutOfRangeChar) { ExpectThrowChar<std::out_of_range>(ThrowStdOutOfRange); +} + +TEST(ThrowDelegate, ThrowStdRuntimeErrorChar) { ExpectThrowChar<std::runtime_error>(ThrowStdRuntimeError); +} + +TEST(ThrowDelegate, ThrowStdRangeErrorChar) { ExpectThrowChar<std::range_error>(ThrowStdRangeError); +} + +TEST(ThrowDelegate, ThrowStdOverflowErrorChar) { ExpectThrowChar<std::overflow_error>(ThrowStdOverflowError); +} + +TEST(ThrowDelegate, ThrowStdUnderflowErrorChar) { ExpectThrowChar<std::underflow_error>(ThrowStdUnderflowError); +} +TEST(ThrowDelegate, ThrowStdLogicErrorString) { ExpectThrowString<std::logic_error>(ThrowStdLogicError); +} + +TEST(ThrowDelegate, ThrowStdInvalidArgumentString) { ExpectThrowString<std::invalid_argument>(ThrowStdInvalidArgument); +} + +TEST(ThrowDelegate, ThrowStdDomainErrorString) { ExpectThrowString<std::domain_error>(ThrowStdDomainError); +} + +TEST(ThrowDelegate, ThrowStdLengthErrorString) { ExpectThrowString<std::length_error>(ThrowStdLengthError); +} + +TEST(ThrowDelegate, ThrowStdOutOfRangeString) { ExpectThrowString<std::out_of_range>(ThrowStdOutOfRange); +} + +TEST(ThrowDelegate, ThrowStdRuntimeErrorString) { ExpectThrowString<std::runtime_error>(ThrowStdRuntimeError); +} + +TEST(ThrowDelegate, ThrowStdRangeErrorString) { ExpectThrowString<std::range_error>(ThrowStdRangeError); +} + +TEST(ThrowDelegate, ThrowStdOverflowErrorString) { ExpectThrowString<std::overflow_error>(ThrowStdOverflowError); +} + +TEST(ThrowDelegate, ThrowStdUnderflowErrorString) { ExpectThrowString<std::underflow_error>(ThrowStdUnderflowError); +} + +TEST(ThrowDelegate, ThrowStdBadFunctionCallNoWhat) { +#ifdef ABSL_HAVE_EXCEPTIONS + try { + ThrowStdBadFunctionCall(); + FAIL() << "Didn't throw"; + } catch (const std::bad_function_call&) { + } +#ifdef _LIBCPP_VERSION + catch (const std::exception&) { + // https://reviews.llvm.org/D92397 causes issues with the vtable for + // std::bad_function_call when using libc++ as a shared library. + } +#endif +#else + EXPECT_DEATH_IF_SUPPORTED(ThrowStdBadFunctionCall(), ""); +#endif +} - ExpectThrowNoWhat<std::bad_function_call>(ThrowStdBadFunctionCall); +TEST(ThrowDelegate, ThrowStdBadAllocNoWhat) { ExpectThrowNoWhat<std::bad_alloc>(ThrowStdBadAlloc); } diff --git a/absl/container/BUILD.bazel b/absl/container/BUILD.bazel index d01d78e5..7a966d63 100644 --- a/absl/container/BUILD.bazel +++ b/absl/container/BUILD.bazel @@ -96,8 +96,9 @@ cc_test( ], ) -cc_test( +cc_binary( name = "fixed_array_benchmark", + testonly = 1, srcs = ["fixed_array_benchmark.cc"], copts = ABSL_TEST_COPTS + ["$(STACK_FRAME_UNLIMITED)"], linkopts = ABSL_DEFAULT_LINKOPTS, @@ -133,6 +134,7 @@ cc_library( "//absl/base:core_headers", "//absl/base:throw_delegate", "//absl/memory", + "//absl/meta:type_traits", ], ) @@ -166,8 +168,9 @@ cc_test( ], ) -cc_test( +cc_binary( name = "inlined_vector_benchmark", + testonly = 1, srcs = ["inlined_vector_benchmark.cc"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, @@ -390,6 +393,9 @@ cc_library( hdrs = ["internal/hash_function_defaults.h"], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//visibility:private", + ], deps = [ "//absl/base:config", "//absl/hash", @@ -458,7 +464,10 @@ cc_library( hdrs = ["internal/hash_policy_traits.h"], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, - deps = ["//absl/meta:type_traits"], + deps = [ + ":common_policy_traits", + "//absl/meta:type_traits", + ], ) cc_test( @@ -473,6 +482,26 @@ cc_test( ) cc_library( + name = "common_policy_traits", + hdrs = ["internal/common_policy_traits.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:private"], + deps = ["//absl/meta:type_traits"], +) + +cc_test( + name = "common_policy_traits_test", + srcs = ["internal/common_policy_traits_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":common_policy_traits", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( name = "hashtable_debug", hdrs = ["internal/hashtable_debug.h"], copts = ABSL_DEFAULT_COPTS, @@ -593,6 +622,7 @@ cc_library( "//absl/base:core_headers", "//absl/base:endian", "//absl/base:prefetch", + "//absl/base:raw_logging_internal", "//absl/memory", "//absl/meta:type_traits", "//absl/numeric:bits", @@ -605,9 +635,15 @@ cc_test( srcs = ["internal/raw_hash_set_test.cc"], copts = ABSL_TEST_COPTS, linkstatic = 1, - tags = NOTEST_TAGS_MOBILE + ["no_test_loonix"], + tags = NOTEST_TAGS_MOBILE + [ + "no_test_loonix", + # TODO(b/237097643): investigate race and remove + "noarm_gemu", + ], deps = [ ":container_memory", + ":flat_hash_map", + ":flat_hash_set", ":hash_function_defaults", ":hash_policy_testing", ":hashtable_debug", @@ -617,6 +653,7 @@ cc_test( "//absl/base:core_headers", "//absl/base:prefetch", "//absl/base:raw_logging_internal", + "//absl/log", "//absl/strings", "@com_google_googletest//:gtest_main", ], @@ -899,6 +936,7 @@ cc_library( visibility = ["//visibility:public"], deps = [ ":common", + ":common_policy_traits", ":compressed_tuple", ":container_memory", ":layout", @@ -940,6 +978,7 @@ cc_test( linkopts = ABSL_DEFAULT_LINKOPTS, shard_count = 10, tags = [ + "no_test:os:ios", "no_test_ios", "no_test_wasm", ], @@ -949,12 +988,13 @@ cc_test( ":btree_test_common", ":counting_allocator", ":test_instance_tracker", + "//absl/algorithm:container", "//absl/base:core_headers", "//absl/base:raw_logging_internal", "//absl/flags:flag", "//absl/hash:hash_testing", "//absl/memory", - "//absl/meta:type_traits", + "//absl/random", "//absl/strings", "//absl/types:compare", "@com_google_googletest//:gtest_main", @@ -977,10 +1017,12 @@ cc_binary( ":flat_hash_map", ":flat_hash_set", ":hashtable_debug", + "//absl/algorithm:container", "//absl/base:raw_logging_internal", - "//absl/flags:flag", "//absl/hash", + "//absl/log", "//absl/memory", + "//absl/random", "//absl/strings:cord", "//absl/strings:str_format", "//absl/time", diff --git a/absl/container/CMakeLists.txt b/absl/container/CMakeLists.txt index 9b5c59a4..416e3e38 100644 --- a/absl/container/CMakeLists.txt +++ b/absl/container/CMakeLists.txt @@ -28,6 +28,7 @@ absl_cc_library( ${ABSL_DEFAULT_LINKOPTS} DEPS absl::container_common + absl::common_policy_traits absl::compare absl::compressed_tuple absl::container_memory @@ -71,6 +72,7 @@ absl_cc_test( LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS + absl::algorithm_container absl::btree absl::btree_test_common absl::compare @@ -78,10 +80,10 @@ absl_cc_test( absl::counting_allocator absl::flags absl::hash_testing + absl::random_random absl::raw_logging_internal absl::strings absl::test_instance_tracker - absl::type_traits GTest::gmock_main ) @@ -194,6 +196,7 @@ absl_cc_library( absl::inlined_vector_internal absl::throw_delegate absl::memory + absl::type_traits PUBLIC ) @@ -532,6 +535,7 @@ absl_cc_library( COPTS ${ABSL_DEFAULT_COPTS} DEPS + absl::common_policy_traits absl::meta PUBLIC ) @@ -551,6 +555,31 @@ absl_cc_test( # Internal-only target, do not depend on directly. absl_cc_library( NAME + common_policy_traits + HDRS + "internal/common_policy_traits.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::meta + PUBLIC +) + +absl_cc_test( + NAME + common_policy_traits_test + SRCS + "internal/common_policy_traits_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::common_policy_traits + GTest::gmock_main +) + +# Internal-only target, do not depend on directly. +absl_cc_library( + NAME hashtablez_sampler HDRS "internal/hashtablez_sampler.h" @@ -678,12 +707,13 @@ absl_cc_library( absl::endian absl::hash_policy_traits absl::hashtable_debug_hooks + absl::hashtablez_sampler absl::memory absl::meta absl::optional absl::prefetch + absl::raw_logging_internal absl::utility - absl::hashtablez_sampler PUBLIC ) @@ -695,15 +725,18 @@ absl_cc_test( COPTS ${ABSL_TEST_COPTS} DEPS + absl::base + absl::config absl::container_memory + absl::core_headers + absl::flat_hash_map + absl::flat_hash_set absl::hash_function_defaults absl::hash_policy_testing absl::hashtable_debug - absl::raw_hash_set - absl::base - absl::config - absl::core_headers + absl::log absl::prefetch + absl::raw_hash_set absl::raw_logging_internal absl::strings GTest::gmock_main diff --git a/absl/container/btree_benchmark.cc b/absl/container/btree_benchmark.cc index 0ca497c8..0d26fd42 100644 --- a/absl/container/btree_benchmark.cc +++ b/absl/container/btree_benchmark.cc @@ -27,6 +27,7 @@ #include <vector> #include "benchmark/benchmark.h" +#include "absl/algorithm/container.h" #include "absl/base/internal/raw_logging.h" #include "absl/container/btree_map.h" #include "absl/container/btree_set.h" @@ -34,9 +35,10 @@ #include "absl/container/flat_hash_map.h" #include "absl/container/flat_hash_set.h" #include "absl/container/internal/hashtable_debug.h" -#include "absl/flags/flag.h" #include "absl/hash/hash.h" +#include "absl/log/log.h" #include "absl/memory/memory.h" +#include "absl/random/random.h" #include "absl/strings/cord.h" #include "absl/strings/str_format.h" #include "absl/time/time.h" @@ -733,6 +735,29 @@ double ContainerInfo(const btree_map<int, BigTypePtr<Size>>& b) { BIG_TYPE_PTR_BENCHMARKS(32); +void BM_BtreeSet_IteratorSubtraction(benchmark::State& state) { + absl::InsecureBitGen bitgen; + std::vector<int> vec; + // Randomize the set's insertion order so the nodes aren't all full. + vec.reserve(state.range(0)); + for (int i = 0; i < state.range(0); ++i) vec.push_back(i); + absl::c_shuffle(vec, bitgen); + + absl::btree_set<int> set; + for (int i : vec) set.insert(i); + + size_t distance = absl::Uniform(bitgen, 0u, set.size()); + while (state.KeepRunningBatch(distance)) { + size_t end = absl::Uniform(bitgen, distance, set.size()); + size_t begin = end - distance; + benchmark::DoNotOptimize(set.find(static_cast<int>(end)) - + set.find(static_cast<int>(begin))); + distance = absl::Uniform(bitgen, 0u, set.size()); + } +} + +BENCHMARK(BM_BtreeSet_IteratorSubtraction)->Range(1 << 10, 1 << 20); + } // namespace } // namespace container_internal ABSL_NAMESPACE_END diff --git a/absl/container/btree_map.h b/absl/container/btree_map.h index 286817f1..cd3ee2b4 100644 --- a/absl/container/btree_map.h +++ b/absl/container/btree_map.h @@ -42,10 +42,13 @@ // Importantly, insertions and deletions may invalidate outstanding iterators, // pointers, and references to elements. Such invalidations are typically only // an issue if insertion and deletion operations are interleaved with the use of -// more than one iterator, pointer, or reference simultaneously. For this -// reason, `insert()` and `erase()` return a valid iterator at the current -// position. Another important difference is that key-types must be -// copy-constructible. +// more than one iterator, pointer, or reference simultaneously. For this +// reason, `insert()`, `erase()`, and `extract_and_get_next()` return a valid +// iterator at the current position. Another important difference is that +// key-types must be copy-constructible. +// +// Another API difference is that btree iterators can be subtracted, and this +// is faster than using std::distance. #ifndef ABSL_CONTAINER_BTREE_MAP_H_ #define ABSL_CONTAINER_BTREE_MAP_H_ @@ -322,7 +325,8 @@ class btree_map // btree_map::extract() // // Extracts the indicated element, erasing it in the process, and returns it - // as a C++17-compatible node handle. Overloads are listed below. + // as a C++17-compatible node handle. Any references, pointers, or iterators + // are invalidated. Overloads are listed below. // // node_type extract(const_iterator position): // @@ -347,6 +351,21 @@ class btree_map // It does NOT refer to the data layout of the underlying btree. using Base::extract; + // btree_map::extract_and_get_next() + // + // Extracts the indicated element, erasing it in the process, and returns it + // as a C++17-compatible node handle along with an iterator to the next + // element. + // + // extract_and_get_next_return_type extract_and_get_next( + // const_iterator position): + // + // Extracts the element at the indicated position, returns a struct + // containing a member named `node`: a node handle owning that extracted + // data and a member named `next`: an iterator pointing to the next element + // in the btree. + using Base::extract_and_get_next; + // btree_map::merge() // // Extracts elements from a given `source` btree_map into this @@ -698,6 +717,21 @@ class btree_multimap // It does NOT refer to the data layout of the underlying btree. using Base::extract; + // btree_multimap::extract_and_get_next() + // + // Extracts the indicated element, erasing it in the process, and returns it + // as a C++17-compatible node handle along with an iterator to the next + // element. + // + // extract_and_get_next_return_type extract_and_get_next( + // const_iterator position): + // + // Extracts the element at the indicated position, returns a struct + // containing a member named `node`: a node handle owning that extracted + // data and a member named `next`: an iterator pointing to the next element + // in the btree. + using Base::extract_and_get_next; + // btree_multimap::merge() // // Extracts all elements from a given `source` btree_multimap into this diff --git a/absl/container/btree_set.h b/absl/container/btree_set.h index 695b09f5..51dc42b7 100644 --- a/absl/container/btree_set.h +++ b/absl/container/btree_set.h @@ -43,8 +43,11 @@ // pointers, and references to elements. Such invalidations are typically only // an issue if insertion and deletion operations are interleaved with the use of // more than one iterator, pointer, or reference simultaneously. For this -// reason, `insert()` and `erase()` return a valid iterator at the current -// position. +// reason, `insert()`, `erase()`, and `extract_and_get_next()` return a valid +// iterator at the current position. +// +// Another API difference is that btree iterators can be subtracted, and this +// is faster than using std::distance. #ifndef ABSL_CONTAINER_BTREE_SET_H_ #define ABSL_CONTAINER_BTREE_SET_H_ @@ -269,7 +272,8 @@ class btree_set // btree_set::extract() // // Extracts the indicated element, erasing it in the process, and returns it - // as a C++17-compatible node handle. Overloads are listed below. + // as a C++17-compatible node handle. Any references, pointers, or iterators + // are invalidated. Overloads are listed below. // // node_type extract(const_iterator position): // @@ -289,6 +293,21 @@ class btree_set // It does NOT refer to the data layout of the underlying btree. using Base::extract; + // btree_set::extract_and_get_next() + // + // Extracts the indicated element, erasing it in the process, and returns it + // as a C++17-compatible node handle along with an iterator to the next + // element. + // + // extract_and_get_next_return_type extract_and_get_next( + // const_iterator position): + // + // Extracts the element at the indicated position, returns a struct + // containing a member named `node`: a node handle owning that extracted + // data and a member named `next`: an iterator pointing to the next element + // in the btree. + using Base::extract_and_get_next; + // btree_set::merge() // // Extracts elements from a given `source` btree_set into this @@ -611,6 +630,21 @@ class btree_multiset // It does NOT refer to the data layout of the underlying btree. using Base::extract; + // btree_multiset::extract_and_get_next() + // + // Extracts the indicated element, erasing it in the process, and returns it + // as a C++17-compatible node handle along with an iterator to the next + // element. + // + // extract_and_get_next_return_type extract_and_get_next( + // const_iterator position): + // + // Extracts the element at the indicated position, returns a struct + // containing a member named `node`: a node handle owning that extracted + // data and a member named `next`: an iterator pointing to the next element + // in the btree. + using Base::extract_and_get_next; + // btree_multiset::merge() // // Extracts all elements from a given `source` btree_multiset into this @@ -760,12 +794,6 @@ struct set_slot_policy { static void destroy(Alloc *alloc, slot_type *slot) { absl::allocator_traits<Alloc>::destroy(*alloc, slot); } - - template <typename Alloc> - static void transfer(Alloc *alloc, slot_type *new_slot, slot_type *old_slot) { - construct(alloc, new_slot, old_slot); - destroy(alloc, old_slot); - } }; // A parameters structure for holding the type parameters for a btree_set. diff --git a/absl/container/btree_test.cc b/absl/container/btree_test.cc index f20f3430..cc763b29 100644 --- a/absl/container/btree_test.cc +++ b/absl/container/btree_test.cc @@ -18,6 +18,7 @@ #include <array> #include <cstdint> #include <functional> +#include <iterator> #include <limits> #include <map> #include <memory> @@ -26,9 +27,11 @@ #include <string> #include <type_traits> #include <utility> +#include <vector> #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/algorithm/container.h" #include "absl/base/internal/raw_logging.h" #include "absl/base/macros.h" #include "absl/container/btree_map.h" @@ -38,7 +41,7 @@ #include "absl/flags/flag.h" #include "absl/hash/hash_testing.h" #include "absl/memory/memory.h" -#include "absl/meta/type_traits.h" +#include "absl/random/random.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" @@ -71,6 +74,16 @@ 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 @@ -1648,10 +1661,9 @@ TEST(Btree, BtreeMultisetEmplace) { auto iter = s.emplace(value_to_insert); ASSERT_NE(iter, s.end()); EXPECT_EQ(*iter, value_to_insert); - auto iter2 = s.emplace(value_to_insert); - EXPECT_NE(iter2, iter); - ASSERT_NE(iter2, s.end()); - EXPECT_EQ(*iter2, value_to_insert); + iter = s.emplace(value_to_insert); + ASSERT_NE(iter, s.end()); + EXPECT_EQ(*iter, value_to_insert); auto result = s.equal_range(value_to_insert); EXPECT_EQ(std::distance(result.first, result.second), 2); } @@ -1662,44 +1674,45 @@ TEST(Btree, BtreeMultisetEmplaceHint) { auto iter = s.emplace(value_to_insert); ASSERT_NE(iter, s.end()); EXPECT_EQ(*iter, value_to_insert); - auto emplace_iter = s.emplace_hint(iter, value_to_insert); - EXPECT_NE(emplace_iter, iter); - ASSERT_NE(emplace_iter, s.end()); - EXPECT_EQ(*emplace_iter, value_to_insert); + iter = s.emplace_hint(iter, value_to_insert); + // The new element should be before the previously inserted one. + EXPECT_EQ(iter, s.lower_bound(value_to_insert)); + ASSERT_NE(iter, s.end()); + EXPECT_EQ(*iter, value_to_insert); } TEST(Btree, BtreeMultimapEmplace) { const int key_to_insert = 123456; const char value0[] = "a"; - absl::btree_multimap<int, std::string> s; - auto iter = s.emplace(key_to_insert, value0); - ASSERT_NE(iter, s.end()); + absl::btree_multimap<int, std::string> m; + auto iter = m.emplace(key_to_insert, value0); + ASSERT_NE(iter, m.end()); EXPECT_EQ(iter->first, key_to_insert); EXPECT_EQ(iter->second, value0); const char value1[] = "b"; - auto iter2 = s.emplace(key_to_insert, value1); - EXPECT_NE(iter2, iter); - ASSERT_NE(iter2, s.end()); - EXPECT_EQ(iter2->first, key_to_insert); - EXPECT_EQ(iter2->second, value1); - auto result = s.equal_range(key_to_insert); + iter = m.emplace(key_to_insert, value1); + ASSERT_NE(iter, m.end()); + EXPECT_EQ(iter->first, key_to_insert); + EXPECT_EQ(iter->second, value1); + auto result = m.equal_range(key_to_insert); EXPECT_EQ(std::distance(result.first, result.second), 2); } TEST(Btree, BtreeMultimapEmplaceHint) { const int key_to_insert = 123456; const char value0[] = "a"; - absl::btree_multimap<int, std::string> s; - auto iter = s.emplace(key_to_insert, value0); - ASSERT_NE(iter, s.end()); + absl::btree_multimap<int, std::string> m; + auto iter = m.emplace(key_to_insert, value0); + ASSERT_NE(iter, m.end()); EXPECT_EQ(iter->first, key_to_insert); EXPECT_EQ(iter->second, value0); const char value1[] = "b"; - auto emplace_iter = s.emplace_hint(iter, key_to_insert, value1); - EXPECT_NE(emplace_iter, iter); - ASSERT_NE(emplace_iter, s.end()); - EXPECT_EQ(emplace_iter->first, key_to_insert); - EXPECT_EQ(emplace_iter->second, value1); + iter = m.emplace_hint(iter, key_to_insert, value1); + // The new element should be before the previously inserted one. + EXPECT_EQ(iter, m.lower_bound(key_to_insert)); + ASSERT_NE(iter, m.end()); + EXPECT_EQ(iter->first, key_to_insert); + EXPECT_EQ(iter->second, value1); } TEST(Btree, ConstIteratorAccessors) { @@ -2110,6 +2123,79 @@ TEST(Btree, ExtractMultiMapEquivalentKeys) { } } +TEST(Btree, ExtractAndGetNextSet) { + absl::btree_set<int> src = {1, 2, 3, 4, 5}; + auto it = src.find(3); + auto extracted_and_next = src.extract_and_get_next(it); + EXPECT_THAT(src, ElementsAre(1, 2, 4, 5)); + EXPECT_EQ(extracted_and_next.node.value(), 3); + EXPECT_EQ(*extracted_and_next.next, 4); +} + +TEST(Btree, ExtractAndGetNextMultiSet) { + absl::btree_multiset<int> src = {1, 2, 3, 4, 5}; + auto it = src.find(3); + auto extracted_and_next = src.extract_and_get_next(it); + EXPECT_THAT(src, ElementsAre(1, 2, 4, 5)); + EXPECT_EQ(extracted_and_next.node.value(), 3); + EXPECT_EQ(*extracted_and_next.next, 4); +} + +TEST(Btree, ExtractAndGetNextMap) { + absl::btree_map<int, int> src = {{1, 2}, {3, 4}, {5, 6}}; + auto it = src.find(3); + auto extracted_and_next = src.extract_and_get_next(it); + EXPECT_THAT(src, ElementsAre(Pair(1, 2), Pair(5, 6))); + EXPECT_EQ(extracted_and_next.node.key(), 3); + EXPECT_EQ(extracted_and_next.node.mapped(), 4); + EXPECT_THAT(*extracted_and_next.next, Pair(5, 6)); +} + +TEST(Btree, ExtractAndGetNextMultiMap) { + absl::btree_multimap<int, int> src = {{1, 2}, {3, 4}, {5, 6}}; + auto it = src.find(3); + auto extracted_and_next = src.extract_and_get_next(it); + EXPECT_THAT(src, ElementsAre(Pair(1, 2), Pair(5, 6))); + EXPECT_EQ(extracted_and_next.node.key(), 3); + EXPECT_EQ(extracted_and_next.node.mapped(), 4); + EXPECT_THAT(*extracted_and_next.next, Pair(5, 6)); +} + +TEST(Btree, ExtractAndGetNextEndIter) { + absl::btree_set<int> src = {1, 2, 3, 4, 5}; + auto it = src.find(5); + auto extracted_and_next = src.extract_and_get_next(it); + EXPECT_THAT(src, ElementsAre(1, 2, 3, 4)); + EXPECT_EQ(extracted_and_next.node.value(), 5); + EXPECT_EQ(extracted_and_next.next, src.end()); +} + +TEST(Btree, ExtractDoesntCauseExtraMoves) { +#ifdef _MSC_VER + GTEST_SKIP() << "This test fails on MSVC."; +#endif + + using Set = absl::btree_set<MovableOnlyInstance>; + std::array<std::function<void(Set &)>, 3> extracters = { + [](Set &s) { auto node = s.extract(s.begin()); }, + [](Set &s) { auto ret = s.extract_and_get_next(s.begin()); }, + [](Set &s) { auto node = s.extract(MovableOnlyInstance(0)); }}; + + InstanceTracker tracker; + for (int i = 0; i < 3; ++i) { + Set s; + s.insert(MovableOnlyInstance(0)); + tracker.ResetCopiesMovesSwaps(); + + extracters[i](s); + // We expect to see exactly 1 move: from the original slot into the + // extracted node. + EXPECT_EQ(tracker.copies(), 0) << i; + EXPECT_EQ(tracker.moves(), 1) << i; + EXPECT_EQ(tracker.swaps(), 0) << i; + } +} + // For multisets, insert with hint also affects correctness because we need to // insert immediately before the hint if possible. struct InsertMultiHintData { @@ -3002,8 +3088,9 @@ TEST(Btree, ConstructImplicitlyWithUnadaptedComparator) { absl::btree_set<MultiKey, MultiKeyComp> set = {{}, MultiKeyComp{}}; } -#ifndef NDEBUG TEST(Btree, InvalidComparatorsCaught) { + if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled."; + { struct ZeroAlwaysLessCmp { bool operator()(int lhs, int rhs) const { @@ -3051,7 +3138,6 @@ TEST(Btree, InvalidComparatorsCaught) { EXPECT_DEATH(set.insert({0, 1, 2}), "lhs_comp_rhs < 0 -> rhs_comp_lhs > 0"); } } -#endif #ifndef _MSC_VER // This test crashes on MSVC. @@ -3081,6 +3167,14 @@ TEST(Btree, InvalidIteratorUse) { set.erase(1); EXPECT_DEATH(*it, "invalidated iterator"); } + { + 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"); + } } #endif @@ -3248,6 +3342,128 @@ TEST(Btree, NotAssignableType) { } } +struct ArenaLike { + void* recycled = nullptr; + size_t recycled_size = 0; +}; + +// A very simple implementation of arena allocation. +template <typename T> +class ArenaLikeAllocator : public std::allocator<T> { + public: + // Standard library containers require the ability to allocate objects of + // different types which they can do so via rebind.other. + template <typename U> + struct rebind { + using other = ArenaLikeAllocator<U>; + }; + + explicit ArenaLikeAllocator(ArenaLike* arena) noexcept : arena_(arena) {} + + ~ArenaLikeAllocator() { + if (arena_->recycled != nullptr) { + delete [] static_cast<T*>(arena_->recycled); + arena_->recycled = nullptr; + } + } + + template<typename U> + explicit ArenaLikeAllocator(const ArenaLikeAllocator<U>& other) noexcept + : arena_(other.arena_) {} + + T* allocate(size_t num_objects, const void* = nullptr) { + size_t size = num_objects * sizeof(T); + if (arena_->recycled != nullptr && arena_->recycled_size == size) { + T* result = static_cast<T*>(arena_->recycled); + arena_->recycled = nullptr; + return result; + } + return new T[num_objects]; + } + + void deallocate(T* p, size_t num_objects) { + size_t size = num_objects * sizeof(T); + + // Simulate writing to the freed memory as an actual arena allocator might + // do. This triggers an error report if the memory is poisoned. + memset(p, 0xde, size); + + if (arena_->recycled == nullptr) { + arena_->recycled = p; + arena_->recycled_size = size; + } else { + delete [] p; + } + } + + ArenaLike* arena_; +}; + +// This test verifies that an arena allocator that reuses memory will not be +// asked to free poisoned BTree memory. +TEST(Btree, ReusePoisonMemory) { + using Alloc = ArenaLikeAllocator<int64_t>; + using Set = absl::btree_set<int64_t, std::less<int64_t>, Alloc>; + ArenaLike arena; + Alloc alloc(&arena); + Set set(alloc); + + set.insert(0); + set.erase(0); + set.insert(0); +} + +TEST(Btree, IteratorSubtraction) { + absl::BitGen bitgen; + std::vector<int> vec; + // Randomize the set's insertion order so the nodes aren't all full. + for (int i = 0; i < 1000000; ++i) vec.push_back(i); + absl::c_shuffle(vec, bitgen); + + absl::btree_set<int> set; + for (int i : vec) set.insert(i); + + for (int i = 0; i < 1000; ++i) { + size_t begin = absl::Uniform(bitgen, 0u, set.size()); + size_t end = absl::Uniform(bitgen, begin, set.size()); + ASSERT_EQ(end - begin, set.find(end) - set.find(begin)) + << begin << " " << end; + } +} + +TEST(Btree, DereferencingEndIterator) { + if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled."; + + absl::btree_set<int> set; + for (int i = 0; i < 1000; ++i) set.insert(i); + EXPECT_DEATH(*set.end(), R"regex(Dereferencing end\(\) iterator)regex"); +} + +TEST(Btree, InvalidIteratorComparison) { + if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled."; + + absl::btree_set<int> set1, set2; + for (int i = 0; i < 1000; ++i) { + set1.insert(i); + set2.insert(i); + } + + constexpr const char *kValueInitDeathMessage = + "Comparing default-constructed iterator with .*non-default-constructed " + "iterator"; + typename absl::btree_set<int>::iterator iter1, iter2; + EXPECT_EQ(iter1, iter2); + EXPECT_DEATH(void(set1.begin() == iter1), kValueInitDeathMessage); + EXPECT_DEATH(void(iter1 == set1.begin()), kValueInitDeathMessage); + + constexpr const char *kDifferentContainerDeathMessage = + "Comparing iterators from different containers"; + iter1 = set1.begin(); + iter2 = set2.begin(); + EXPECT_DEATH(void(iter1 == iter2), kDifferentContainerDeathMessage); + EXPECT_DEATH(void(iter2 == iter1), kDifferentContainerDeathMessage); +} + } // namespace } // namespace container_internal ABSL_NAMESPACE_END diff --git a/absl/container/fixed_array.h b/absl/container/fixed_array.h index 2aefae3b..b67379cf 100644 --- a/absl/container/fixed_array.h +++ b/absl/container/fixed_array.h @@ -62,11 +62,10 @@ constexpr static auto kFixedArrayUseDefault = static_cast<size_t>(-1); // A `FixedArray` provides a run-time fixed-size array, allocating a small array // inline for efficiency. // -// Most users should not specify an `inline_elements` argument and let -// `FixedArray` automatically determine the number of elements -// to store inline based on `sizeof(T)`. If `inline_elements` is specified, the -// `FixedArray` implementation will use inline storage for arrays with a -// length <= `inline_elements`. +// Most users should not specify the `N` template parameter and let `FixedArray` +// automatically determine the number of elements to store inline based on +// `sizeof(T)`. If `N` is specified, the `FixedArray` implementation will use +// inline storage for arrays with a length <= `N`. // // Note that a `FixedArray` constructed with a `size_type` argument will // default-initialize its values by leaving trivially constructible types @@ -471,6 +470,9 @@ class FixedArray { return n <= inline_elements; } +#ifdef ABSL_HAVE_ADDRESS_SANITIZER + ABSL_ATTRIBUTE_NOINLINE +#endif // ABSL_HAVE_ADDRESS_SANITIZER StorageElement* InitializeData() { if (UsingInlinedStorage(size())) { InlinedStorage::AnnotateConstruct(size()); diff --git a/absl/container/fixed_array_benchmark.cc b/absl/container/fixed_array_benchmark.cc index 3c7a5a72..db6663e6 100644 --- a/absl/container/fixed_array_benchmark.cc +++ b/absl/container/fixed_array_benchmark.cc @@ -16,8 +16,8 @@ #include <string> -#include "benchmark/benchmark.h" #include "absl/container/fixed_array.h" +#include "benchmark/benchmark.h" namespace { diff --git a/absl/container/flat_hash_map_test.cc b/absl/container/flat_hash_map_test.cc index 263951f1..03171f6d 100644 --- a/absl/container/flat_hash_map_test.cc +++ b/absl/container/flat_hash_map_test.cc @@ -311,6 +311,14 @@ TEST(FlatHashMap, Reserve) { } } +TEST(FlatHashMap, RecursiveTypeCompiles) { + struct RecursiveType { + flat_hash_map<int, RecursiveType> m; + }; + RecursiveType t; + t.m[0] = RecursiveType{}; +} + } // namespace } // namespace container_internal ABSL_NAMESPACE_END diff --git a/absl/container/flat_hash_set.h b/absl/container/flat_hash_set.h index 4938c703..f5376f99 100644 --- a/absl/container/flat_hash_set.h +++ b/absl/container/flat_hash_set.h @@ -474,13 +474,6 @@ struct FlatHashSetPolicy { absl::allocator_traits<Allocator>::destroy(*alloc, slot); } - template <class Allocator> - static void transfer(Allocator* alloc, slot_type* new_slot, - slot_type* old_slot) { - construct(alloc, new_slot, std::move(*old_slot)); - destroy(alloc, old_slot); - } - static T& element(slot_type* slot) { return *slot; } template <class F, class... Args> diff --git a/absl/container/inlined_vector.h b/absl/container/inlined_vector.h index bc1c4a77..7058f375 100644 --- a/absl/container/inlined_vector.h +++ b/absl/container/inlined_vector.h @@ -52,6 +52,7 @@ #include "absl/base/port.h" #include "absl/container/internal/inlined_vector.h" #include "absl/memory/memory.h" +#include "absl/meta/type_traits.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -77,6 +78,8 @@ class InlinedVector { using MoveIterator = inlined_vector_internal::MoveIterator<TheA>; template <typename TheA> using IsMemcpyOk = inlined_vector_internal::IsMemcpyOk<TheA>; + template <typename TheA> + using IsMoveAssignOk = inlined_vector_internal::IsMoveAssignOk<TheA>; template <typename TheA, typename Iterator> using IteratorValueAdapter = @@ -94,6 +97,12 @@ class InlinedVector { using DisableIfAtLeastForwardIterator = absl::enable_if_t< !inlined_vector_internal::IsAtLeastForwardIterator<Iterator>::value, int>; + using MemcpyPolicy = typename Storage::MemcpyPolicy; + using ElementwiseAssignPolicy = typename Storage::ElementwiseAssignPolicy; + using ElementwiseConstructPolicy = + typename Storage::ElementwiseConstructPolicy; + using MoveAssignmentPolicy = typename Storage::MoveAssignmentPolicy; + public: using allocator_type = A; using value_type = inlined_vector_internal::ValueType<A>; @@ -275,8 +284,10 @@ class InlinedVector { size_type max_size() const noexcept { // One bit of the size storage is used to indicate whether the inlined // vector contains allocated memory. As a result, the maximum size that the - // inlined vector can express is half of the max for `size_type`. - return (std::numeric_limits<size_type>::max)() / 2; + // inlined vector can express is the minimum of the limit of how many + // objects we can allocate and std::numeric_limits<size_type>::max() / 2. + return (std::min)(AllocatorTraits<A>::max_size(storage_.GetAllocator()), + (std::numeric_limits<size_type>::max)() / 2); } // `InlinedVector::capacity()` @@ -484,18 +495,7 @@ class InlinedVector { // unspecified state. InlinedVector& operator=(InlinedVector&& other) { if (ABSL_PREDICT_TRUE(this != std::addressof(other))) { - if (IsMemcpyOk<A>::value || other.storage_.GetIsAllocated()) { - inlined_vector_internal::DestroyAdapter<A>::DestroyElements( - storage_.GetAllocator(), data(), size()); - storage_.DeallocateIfAllocated(); - storage_.MemcpyFrom(other.storage_); - - other.storage_.SetInlinedSize(0); - } else { - storage_.Assign(IteratorValueAdapter<A, MoveIterator<A>>( - MoveIterator<A>(other.storage_.GetInlinedData())), - other.size()); - } + MoveAssignment(MoveAssignmentPolicy{}, std::move(other)); } return *this; @@ -624,9 +624,9 @@ class InlinedVector { ABSL_HARDENING_ASSERT(pos <= end()); if (ABSL_PREDICT_TRUE(first != last)) { - return storage_.Insert(pos, - IteratorValueAdapter<A, ForwardIterator>(first), - std::distance(first, last)); + return storage_.Insert( + pos, IteratorValueAdapter<A, ForwardIterator>(first), + static_cast<size_type>(std::distance(first, last))); } else { return const_cast<iterator>(pos); } @@ -643,7 +643,7 @@ class InlinedVector { ABSL_HARDENING_ASSERT(pos >= begin()); ABSL_HARDENING_ASSERT(pos <= end()); - size_type index = std::distance(cbegin(), pos); + size_type index = static_cast<size_type>(std::distance(cbegin(), pos)); for (size_type i = index; first != last; ++i, static_cast<void>(++first)) { insert(data() + i, *first); } @@ -661,10 +661,22 @@ class InlinedVector { ABSL_HARDENING_ASSERT(pos <= end()); value_type dealias(std::forward<Args>(args)...); + // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=102329#c2 + // It appears that GCC thinks that since `pos` is a const pointer and may + // point to uninitialized memory at this point, a warning should be + // issued. But `pos` is actually only used to compute an array index to + // write to. +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif return storage_.Insert(pos, IteratorValueAdapter<A, MoveIterator<A>>( MoveIterator<A>(std::addressof(dealias))), 1); +#if !defined(__clang__) && defined(__GNUC__) +#pragma GCC diagnostic pop +#endif } // `InlinedVector::emplace_back(...)` @@ -771,6 +783,42 @@ class InlinedVector { template <typename H, typename TheT, size_t TheN, typename TheA> friend H AbslHashValue(H h, const absl::InlinedVector<TheT, TheN, TheA>& a); + void MoveAssignment(MemcpyPolicy, InlinedVector&& other) { + inlined_vector_internal::DestroyAdapter<A>::DestroyElements( + storage_.GetAllocator(), data(), size()); + storage_.DeallocateIfAllocated(); + storage_.MemcpyFrom(other.storage_); + + other.storage_.SetInlinedSize(0); + } + + void MoveAssignment(ElementwiseAssignPolicy, InlinedVector&& other) { + if (other.storage_.GetIsAllocated()) { + MoveAssignment(MemcpyPolicy{}, std::move(other)); + } else { + storage_.Assign(IteratorValueAdapter<A, MoveIterator<A>>( + MoveIterator<A>(other.storage_.GetInlinedData())), + other.size()); + } + } + + void MoveAssignment(ElementwiseConstructPolicy, InlinedVector&& other) { + if (other.storage_.GetIsAllocated()) { + MoveAssignment(MemcpyPolicy{}, std::move(other)); + } else { + inlined_vector_internal::DestroyAdapter<A>::DestroyElements( + storage_.GetAllocator(), data(), size()); + storage_.DeallocateIfAllocated(); + + IteratorValueAdapter<A, MoveIterator<A>> other_values( + MoveIterator<A>(other.storage_.GetInlinedData())); + inlined_vector_internal::ConstructElements<A>( + storage_.GetAllocator(), storage_.GetInlinedData(), other_values, + other.storage_.GetSize()); + storage_.SetInlinedSize(other.storage_.GetSize()); + } + } + Storage storage_; }; diff --git a/absl/container/inlined_vector_benchmark.cc b/absl/container/inlined_vector_benchmark.cc index e256fad6..56a6bfd2 100644 --- a/absl/container/inlined_vector_benchmark.cc +++ b/absl/container/inlined_vector_benchmark.cc @@ -16,11 +16,11 @@ #include <string> #include <vector> -#include "benchmark/benchmark.h" #include "absl/base/internal/raw_logging.h" #include "absl/base/macros.h" #include "absl/container/inlined_vector.h" #include "absl/strings/str_cat.h" +#include "benchmark/benchmark.h" namespace { diff --git a/absl/container/inlined_vector_test.cc b/absl/container/inlined_vector_test.cc index 4c1ba04a..898b40db 100644 --- a/absl/container/inlined_vector_test.cc +++ b/absl/container/inlined_vector_test.cc @@ -16,12 +16,14 @@ #include <algorithm> #include <forward_list> +#include <iterator> #include <list> #include <memory> #include <scoped_allocator> #include <sstream> #include <stdexcept> #include <string> +#include <utility> #include <vector> #include "gmock/gmock.h" @@ -49,6 +51,7 @@ using testing::ElementsAre; using testing::ElementsAreArray; using testing::Eq; using testing::Gt; +using testing::Pointwise; using testing::PrintToString; using IntVec = absl::InlinedVector<int, 8>; @@ -126,20 +129,20 @@ using DynamicVec = absl::InlinedVector<Dynamic, 8>; // Append 0..len-1 to *v template <typename Container> -static void Fill(Container* v, int len, int offset = 0) { - for (int i = 0; i < len; i++) { - v->push_back(i + offset); +static void Fill(Container* v, size_t len, int offset = 0) { + for (size_t i = 0; i < len; i++) { + v->push_back(static_cast<int>(i) + offset); } } -static IntVec Fill(int len, int offset = 0) { +static IntVec Fill(size_t len, int offset = 0) { IntVec v; Fill(&v, len, offset); return v; } TEST(IntVec, SimpleOps) { - for (int len = 0; len < 20; len++) { + for (size_t len = 0; len < 20; len++) { IntVec v; const IntVec& cv = v; // const alias @@ -147,42 +150,42 @@ TEST(IntVec, SimpleOps) { EXPECT_EQ(len, v.size()); EXPECT_LE(len, v.capacity()); - for (int i = 0; i < len; i++) { - EXPECT_EQ(i, v[i]); - EXPECT_EQ(i, v.at(i)); + for (size_t i = 0; i < len; i++) { + EXPECT_EQ(static_cast<int>(i), v[i]); + EXPECT_EQ(static_cast<int>(i), v.at(i)); } EXPECT_EQ(v.begin(), v.data()); EXPECT_EQ(cv.begin(), cv.data()); - int counter = 0; + size_t counter = 0; for (IntVec::iterator iter = v.begin(); iter != v.end(); ++iter) { - EXPECT_EQ(counter, *iter); + EXPECT_EQ(static_cast<int>(counter), *iter); counter++; } EXPECT_EQ(counter, len); counter = 0; for (IntVec::const_iterator iter = v.begin(); iter != v.end(); ++iter) { - EXPECT_EQ(counter, *iter); + EXPECT_EQ(static_cast<int>(counter), *iter); counter++; } EXPECT_EQ(counter, len); counter = 0; for (IntVec::const_iterator iter = v.cbegin(); iter != v.cend(); ++iter) { - EXPECT_EQ(counter, *iter); + EXPECT_EQ(static_cast<int>(counter), *iter); counter++; } EXPECT_EQ(counter, len); if (len > 0) { EXPECT_EQ(0, v.front()); - EXPECT_EQ(len - 1, v.back()); + EXPECT_EQ(static_cast<int>(len - 1), v.back()); v.pop_back(); EXPECT_EQ(len - 1, v.size()); - for (int i = 0; i < v.size(); ++i) { - EXPECT_EQ(i, v[i]); - EXPECT_EQ(i, v.at(i)); + for (size_t i = 0; i < v.size(); ++i) { + EXPECT_EQ(static_cast<int>(i), v[i]); + EXPECT_EQ(static_cast<int>(i), v.at(i)); } } } @@ -191,7 +194,7 @@ TEST(IntVec, SimpleOps) { TEST(IntVec, PopBackNoOverflow) { IntVec v = {1}; v.pop_back(); - EXPECT_EQ(v.size(), 0); + EXPECT_EQ(v.size(), 0u); } TEST(IntVec, AtThrows) { @@ -202,47 +205,47 @@ TEST(IntVec, AtThrows) { } TEST(IntVec, ReverseIterator) { - for (int len = 0; len < 20; len++) { + for (size_t len = 0; len < 20; len++) { IntVec v; Fill(&v, len); - int counter = len; + size_t counter = len; for (IntVec::reverse_iterator iter = v.rbegin(); iter != v.rend(); ++iter) { counter--; - EXPECT_EQ(counter, *iter); + EXPECT_EQ(static_cast<int>(counter), *iter); } - EXPECT_EQ(counter, 0); + EXPECT_EQ(counter, 0u); counter = len; for (IntVec::const_reverse_iterator iter = v.rbegin(); iter != v.rend(); ++iter) { counter--; - EXPECT_EQ(counter, *iter); + EXPECT_EQ(static_cast<int>(counter), *iter); } - EXPECT_EQ(counter, 0); + EXPECT_EQ(counter, 0u); counter = len; for (IntVec::const_reverse_iterator iter = v.crbegin(); iter != v.crend(); ++iter) { counter--; - EXPECT_EQ(counter, *iter); + EXPECT_EQ(static_cast<int>(counter), *iter); } - EXPECT_EQ(counter, 0); + EXPECT_EQ(counter, 0u); } } TEST(IntVec, Erase) { - for (int len = 1; len < 20; len++) { - for (int i = 0; i < len; ++i) { + for (size_t len = 1; len < 20; len++) { + for (size_t i = 0; i < len; ++i) { IntVec v; Fill(&v, len); v.erase(v.begin() + i); EXPECT_EQ(len - 1, v.size()); - for (int j = 0; j < i; ++j) { - EXPECT_EQ(j, v[j]); + for (size_t j = 0; j < i; ++j) { + EXPECT_EQ(static_cast<int>(j), v[j]); } - for (int j = i; j < len - 1; ++j) { - EXPECT_EQ(j + 1, v[j]); + for (size_t j = i; j < len - 1; ++j) { + EXPECT_EQ(static_cast<int>(j + 1), v[j]); } } } @@ -254,7 +257,8 @@ TEST(IntVec, Hardened) { EXPECT_EQ(v[9], 9); #if !defined(NDEBUG) || ABSL_OPTION_HARDENED EXPECT_DEATH_IF_SUPPORTED(v[10], ""); - EXPECT_DEATH_IF_SUPPORTED(v[-1], ""); + EXPECT_DEATH_IF_SUPPORTED(v[static_cast<size_t>(-1)], ""); + EXPECT_DEATH_IF_SUPPORTED(v.resize(v.max_size() + 1), ""); #endif } @@ -262,43 +266,43 @@ TEST(IntVec, Hardened) { // should have reference counts == 0, and all others elements should have // reference counts == 1. TEST(RefCountedVec, EraseBeginEnd) { - for (int len = 1; len < 20; ++len) { - for (int erase_begin = 0; erase_begin < len; ++erase_begin) { - for (int erase_end = erase_begin; erase_end <= len; ++erase_end) { + for (size_t len = 1; len < 20; ++len) { + for (size_t erase_begin = 0; erase_begin < len; ++erase_begin) { + for (size_t erase_end = erase_begin; erase_end <= len; ++erase_end) { std::vector<int> counts(len, 0); RefCountedVec v; - for (int i = 0; i < len; ++i) { - v.push_back(RefCounted(i, &counts[i])); + for (size_t i = 0; i < len; ++i) { + v.push_back(RefCounted(static_cast<int>(i), &counts[i])); } - int erase_len = erase_end - erase_begin; + size_t erase_len = erase_end - erase_begin; v.erase(v.begin() + erase_begin, v.begin() + erase_end); EXPECT_EQ(len - erase_len, v.size()); // Check the elements before the first element erased. - for (int i = 0; i < erase_begin; ++i) { - EXPECT_EQ(i, v[i].value_); + for (size_t i = 0; i < erase_begin; ++i) { + EXPECT_EQ(static_cast<int>(i), v[i].value_); } // Check the elements after the first element erased. - for (int i = erase_begin; i < v.size(); ++i) { - EXPECT_EQ(i + erase_len, v[i].value_); + for (size_t i = erase_begin; i < v.size(); ++i) { + EXPECT_EQ(static_cast<int>(i + erase_len), v[i].value_); } // Check that the elements at the beginning are preserved. - for (int i = 0; i < erase_begin; ++i) { + for (size_t i = 0; i < erase_begin; ++i) { EXPECT_EQ(1, counts[i]); } // Check that the erased elements are destroyed - for (int i = erase_begin; i < erase_end; ++i) { + for (size_t i = erase_begin; i < erase_end; ++i) { EXPECT_EQ(0, counts[i]); } // Check that the elements at the end are preserved. - for (int i = erase_end; i < len; ++i) { + for (size_t i = erase_end; i < len; ++i) { EXPECT_EQ(1, counts[i]); } } @@ -377,21 +381,21 @@ TEST(InlinedVectorTest, ShrinkToFitGrowingVector) { absl::InlinedVector<std::pair<std::string, int>, 1> v; v.shrink_to_fit(); - EXPECT_EQ(v.capacity(), 1); + EXPECT_EQ(v.capacity(), 1u); v.emplace_back("answer", 42); v.shrink_to_fit(); - EXPECT_EQ(v.capacity(), 1); + EXPECT_EQ(v.capacity(), 1u); v.emplace_back("taxicab", 1729); - EXPECT_GE(v.capacity(), 2); + EXPECT_GE(v.capacity(), 2u); v.shrink_to_fit(); - EXPECT_EQ(v.capacity(), 2); + EXPECT_EQ(v.capacity(), 2u); v.reserve(100); - EXPECT_GE(v.capacity(), 100); + EXPECT_GE(v.capacity(), 100u); v.shrink_to_fit(); - EXPECT_EQ(v.capacity(), 2); + EXPECT_EQ(v.capacity(), 2u); } TEST(InlinedVectorTest, ShrinkToFitEdgeCases) { @@ -399,10 +403,10 @@ TEST(InlinedVectorTest, ShrinkToFitEdgeCases) { absl::InlinedVector<std::pair<std::string, int>, 1> v; v.emplace_back("answer", 42); v.emplace_back("taxicab", 1729); - EXPECT_GE(v.capacity(), 2); + EXPECT_GE(v.capacity(), 2u); v.pop_back(); v.shrink_to_fit(); - EXPECT_EQ(v.capacity(), 1); + EXPECT_EQ(v.capacity(), 1u); EXPECT_EQ(v[0].first, "answer"); EXPECT_EQ(v[0].second, 42); } @@ -411,34 +415,34 @@ TEST(InlinedVectorTest, ShrinkToFitEdgeCases) { absl::InlinedVector<std::string, 2> v(100); v.resize(0); v.shrink_to_fit(); - EXPECT_EQ(v.capacity(), 2); // inlined capacity + EXPECT_EQ(v.capacity(), 2u); // inlined capacity } { absl::InlinedVector<std::string, 2> v(100); v.resize(1); v.shrink_to_fit(); - EXPECT_EQ(v.capacity(), 2); // inlined capacity + EXPECT_EQ(v.capacity(), 2u); // inlined capacity } { absl::InlinedVector<std::string, 2> v(100); v.resize(2); v.shrink_to_fit(); - EXPECT_EQ(v.capacity(), 2); + EXPECT_EQ(v.capacity(), 2u); } { absl::InlinedVector<std::string, 2> v(100); v.resize(3); v.shrink_to_fit(); - EXPECT_EQ(v.capacity(), 3); + EXPECT_EQ(v.capacity(), 3u); } } TEST(IntVec, Insert) { - for (int len = 0; len < 20; len++) { - for (int pos = 0; pos <= len; pos++) { + for (size_t len = 0; len < 20; len++) { + for (ptrdiff_t pos = 0; pos <= static_cast<ptrdiff_t>(len); pos++) { { // Single element std::vector<int> std_v; @@ -526,16 +530,16 @@ TEST(IntVec, Insert) { TEST(RefCountedVec, InsertConstructorDestructor) { // Make sure the proper construction/destruction happen during insert // operations. - for (int len = 0; len < 20; len++) { + for (size_t len = 0; len < 20; len++) { SCOPED_TRACE(len); - for (int pos = 0; pos <= len; pos++) { + for (size_t pos = 0; pos <= len; pos++) { SCOPED_TRACE(pos); std::vector<int> counts(len, 0); int inserted_count = 0; RefCountedVec v; - for (int i = 0; i < len; ++i) { + for (size_t i = 0; i < len; ++i) { SCOPED_TRACE(i); - v.push_back(RefCounted(i, &counts[i])); + v.push_back(RefCounted(static_cast<int>(i), &counts[i])); } EXPECT_THAT(counts, Each(Eq(1))); @@ -552,20 +556,20 @@ TEST(RefCountedVec, InsertConstructorDestructor) { } TEST(IntVec, Resize) { - for (int len = 0; len < 20; len++) { + for (size_t len = 0; len < 20; len++) { IntVec v; Fill(&v, len); // Try resizing up and down by k elements static const int kResizeElem = 1000000; - for (int k = 0; k < 10; k++) { + for (size_t k = 0; k < 10; k++) { // Enlarging resize v.resize(len + k, kResizeElem); EXPECT_EQ(len + k, v.size()); EXPECT_LE(len + k, v.capacity()); - for (int i = 0; i < len + k; i++) { + for (size_t i = 0; i < len + k; i++) { if (i < len) { - EXPECT_EQ(i, v[i]); + EXPECT_EQ(static_cast<int>(i), v[i]); } else { EXPECT_EQ(kResizeElem, v[i]); } @@ -575,26 +579,26 @@ TEST(IntVec, Resize) { v.resize(len, kResizeElem); EXPECT_EQ(len, v.size()); EXPECT_LE(len, v.capacity()); - for (int i = 0; i < len; i++) { - EXPECT_EQ(i, v[i]); + for (size_t i = 0; i < len; i++) { + EXPECT_EQ(static_cast<int>(i), v[i]); } } } } TEST(IntVec, InitWithLength) { - for (int len = 0; len < 20; len++) { + for (size_t len = 0; len < 20; len++) { IntVec v(len, 7); EXPECT_EQ(len, v.size()); EXPECT_LE(len, v.capacity()); - for (int i = 0; i < len; i++) { + for (size_t i = 0; i < len; i++) { EXPECT_EQ(7, v[i]); } } } TEST(IntVec, CopyConstructorAndAssignment) { - for (int len = 0; len < 20; len++) { + for (size_t len = 0; len < 20; len++) { IntVec v; Fill(&v, len); EXPECT_EQ(len, v.size()); @@ -603,7 +607,7 @@ TEST(IntVec, CopyConstructorAndAssignment) { IntVec v2(v); EXPECT_TRUE(v == v2) << PrintToString(v) << PrintToString(v2); - for (int start_len = 0; start_len < 20; start_len++) { + for (size_t start_len = 0; start_len < 20; start_len++) { IntVec v3; Fill(&v3, start_len, 99); // Add dummy elements that should go away v3 = v; @@ -613,7 +617,7 @@ TEST(IntVec, CopyConstructorAndAssignment) { } TEST(IntVec, AliasingCopyAssignment) { - for (int len = 0; len < 20; ++len) { + for (size_t len = 0; len < 20; ++len) { IntVec original; Fill(&original, len); IntVec dup = original; @@ -623,9 +627,9 @@ TEST(IntVec, AliasingCopyAssignment) { } TEST(IntVec, MoveConstructorAndAssignment) { - for (int len = 0; len < 20; len++) { + for (size_t len = 0; len < 20; len++) { IntVec v_in; - const int inlined_capacity = v_in.capacity(); + const size_t inlined_capacity = v_in.capacity(); Fill(&v_in, len); EXPECT_EQ(len, v_in.size()); EXPECT_LE(len, v_in.capacity()); @@ -642,7 +646,7 @@ TEST(IntVec, MoveConstructorAndAssignment) { EXPECT_FALSE(v_out.data() == old_data); } } - for (int start_len = 0; start_len < 20; start_len++) { + for (size_t start_len = 0; start_len < 20; start_len++) { IntVec v_out; Fill(&v_out, start_len, 99); // Add dummy elements that should go away IntVec v_temp(v_in); @@ -681,10 +685,10 @@ class NotTriviallyDestructible { }; TEST(AliasingTest, Emplace) { - for (int i = 2; i < 20; ++i) { + for (size_t i = 2; i < 20; ++i) { absl::InlinedVector<NotTriviallyDestructible, 10> vec; - for (int j = 0; j < i; ++j) { - vec.push_back(NotTriviallyDestructible(j)); + for (size_t j = 0; j < i; ++j) { + vec.push_back(NotTriviallyDestructible(static_cast<int>(j))); } vec.emplace(vec.begin(), vec[0]); EXPECT_EQ(vec[0], vec[1]); @@ -696,12 +700,12 @@ TEST(AliasingTest, Emplace) { } TEST(AliasingTest, InsertWithCount) { - for (int i = 1; i < 20; ++i) { + for (size_t i = 1; i < 20; ++i) { absl::InlinedVector<NotTriviallyDestructible, 10> vec; - for (int j = 0; j < i; ++j) { - vec.push_back(NotTriviallyDestructible(j)); + for (size_t j = 0; j < i; ++j) { + vec.push_back(NotTriviallyDestructible(static_cast<int>(j))); } - for (int n = 0; n < 5; ++n) { + for (size_t n = 0; n < 5; ++n) { // We use back where we can because it's guaranteed to become invalidated vec.insert(vec.begin(), n, vec.back()); auto b = vec.begin(); @@ -759,22 +763,22 @@ TEST(OverheadTest, Storage) { } TEST(IntVec, Clear) { - for (int len = 0; len < 20; len++) { + for (size_t len = 0; len < 20; len++) { SCOPED_TRACE(len); IntVec v; Fill(&v, len); v.clear(); - EXPECT_EQ(0, v.size()); + EXPECT_EQ(0u, v.size()); EXPECT_EQ(v.begin(), v.end()); } } TEST(IntVec, Reserve) { - for (int len = 0; len < 20; len++) { + for (size_t len = 0; len < 20; len++) { IntVec v; Fill(&v, len); - for (int newlen = 0; newlen < 100; newlen++) { + for (size_t newlen = 0; newlen < 100; newlen++) { const int* start_rep = v.data(); v.reserve(newlen); const int* final_rep = v.data(); @@ -841,9 +845,9 @@ TEST(StringVec, SelfMove) { } TEST(IntVec, Swap) { - for (int l1 = 0; l1 < 20; l1++) { + for (size_t l1 = 0; l1 < 20; l1++) { SCOPED_TRACE(l1); - for (int l2 = 0; l2 < 20; l2++) { + for (size_t l2 = 0; l2 < 20; l2++) { SCOPED_TRACE(l2); IntVec a = Fill(l1, 0); IntVec b = Fill(l2, 100); @@ -853,13 +857,13 @@ TEST(IntVec, Swap) { } EXPECT_EQ(l1, b.size()); EXPECT_EQ(l2, a.size()); - for (int i = 0; i < l1; i++) { + for (size_t i = 0; i < l1; i++) { SCOPED_TRACE(i); - EXPECT_EQ(i, b[i]); + EXPECT_EQ(static_cast<int>(i), b[i]); } - for (int i = 0; i < l2; i++) { + for (size_t i = 0; i < l2; i++) { SCOPED_TRACE(i); - EXPECT_EQ(100 + i, a[i]); + EXPECT_EQ(100 + static_cast<int>(i), a[i]); } } } @@ -868,46 +872,48 @@ TEST(IntVec, Swap) { TYPED_TEST_P(InstanceTest, Swap) { using Instance = TypeParam; using InstanceVec = absl::InlinedVector<Instance, 8>; - for (int l1 = 0; l1 < 20; l1++) { + for (size_t l1 = 0; l1 < 20; l1++) { SCOPED_TRACE(l1); - for (int l2 = 0; l2 < 20; l2++) { + for (size_t l2 = 0; l2 < 20; l2++) { SCOPED_TRACE(l2); InstanceTracker tracker; InstanceVec a, b; const size_t inlined_capacity = a.capacity(); auto min_len = std::min(l1, l2); auto max_len = std::max(l1, l2); - for (int i = 0; i < l1; i++) a.push_back(Instance(i)); - for (int i = 0; i < l2; i++) b.push_back(Instance(100 + i)); - EXPECT_EQ(tracker.instances(), l1 + l2); + for (size_t i = 0; i < l1; i++) + a.push_back(Instance(static_cast<int>(i))); + for (size_t i = 0; i < l2; i++) + b.push_back(Instance(100 + static_cast<int>(i))); + EXPECT_EQ(tracker.instances(), static_cast<int>(l1 + l2)); tracker.ResetCopiesMovesSwaps(); { using std::swap; swap(a, b); } - EXPECT_EQ(tracker.instances(), l1 + l2); + EXPECT_EQ(tracker.instances(), static_cast<int>(l1 + l2)); if (a.size() > inlined_capacity && b.size() > inlined_capacity) { EXPECT_EQ(tracker.swaps(), 0); // Allocations are swapped. EXPECT_EQ(tracker.moves(), 0); } else if (a.size() <= inlined_capacity && b.size() <= inlined_capacity) { - EXPECT_EQ(tracker.swaps(), min_len); + EXPECT_EQ(tracker.swaps(), static_cast<int>(min_len)); EXPECT_EQ((tracker.moves() ? tracker.moves() : tracker.copies()), - max_len - min_len); + static_cast<int>(max_len - min_len)); } else { // One is allocated and the other isn't. The allocation is transferred // without copying elements, and the inlined instances are copied/moved. EXPECT_EQ(tracker.swaps(), 0); EXPECT_EQ((tracker.moves() ? tracker.moves() : tracker.copies()), - min_len); + static_cast<int>(min_len)); } EXPECT_EQ(l1, b.size()); EXPECT_EQ(l2, a.size()); - for (int i = 0; i < l1; i++) { - EXPECT_EQ(i, b[i].value()); + for (size_t i = 0; i < l1; i++) { + EXPECT_EQ(static_cast<int>(i), b[i].value()); } - for (int i = 0; i < l2; i++) { - EXPECT_EQ(100 + i, a[i].value()); + for (size_t i = 0; i < l2; i++) { + EXPECT_EQ(100 + static_cast<int>(i), a[i].value()); } } } @@ -936,9 +942,9 @@ TEST(IntVec, EqualAndNotEqual) { a.clear(); b.clear(); - for (int i = 0; i < 100; i++) { - a.push_back(i); - b.push_back(i); + for (size_t i = 0; i < 100; i++) { + a.push_back(static_cast<int>(i)); + b.push_back(static_cast<int>(i)); EXPECT_TRUE(a == b); EXPECT_FALSE(a != b); @@ -977,26 +983,26 @@ TYPED_TEST_P(InstanceTest, CountConstructorsDestructors) { using Instance = TypeParam; using InstanceVec = absl::InlinedVector<Instance, 8>; InstanceTracker tracker; - for (int len = 0; len < 20; len++) { + for (size_t len = 0; len < 20; len++) { SCOPED_TRACE(len); tracker.ResetCopiesMovesSwaps(); InstanceVec v; const size_t inlined_capacity = v.capacity(); - for (int i = 0; i < len; i++) { - v.push_back(Instance(i)); + for (size_t i = 0; i < len; i++) { + v.push_back(Instance(static_cast<int>(i))); } - EXPECT_EQ(tracker.instances(), len); + EXPECT_EQ(tracker.instances(), static_cast<int>(len)); EXPECT_GE(tracker.copies() + tracker.moves(), - len); // More due to reallocation. + static_cast<int>(len)); // More due to reallocation. tracker.ResetCopiesMovesSwaps(); // Enlarging resize() must construct some objects tracker.ResetCopiesMovesSwaps(); v.resize(len + 10, Instance(100)); - EXPECT_EQ(tracker.instances(), len + 10); + EXPECT_EQ(tracker.instances(), static_cast<int>(len) + 10); if (len <= inlined_capacity && len + 10 > inlined_capacity) { - EXPECT_EQ(tracker.copies() + tracker.moves(), 10 + len); + EXPECT_EQ(tracker.copies() + tracker.moves(), 10 + static_cast<int>(len)); } else { // Only specify a minimum number of copies + moves. We don't want to // depend on the reallocation policy here. @@ -1007,29 +1013,30 @@ TYPED_TEST_P(InstanceTest, CountConstructorsDestructors) { // Shrinking resize() must destroy some objects tracker.ResetCopiesMovesSwaps(); v.resize(len, Instance(100)); - EXPECT_EQ(tracker.instances(), len); + EXPECT_EQ(tracker.instances(), static_cast<int>(len)); EXPECT_EQ(tracker.copies(), 0); EXPECT_EQ(tracker.moves(), 0); // reserve() must not increase the number of initialized objects SCOPED_TRACE("reserve"); v.reserve(len + 1000); - EXPECT_EQ(tracker.instances(), len); - EXPECT_EQ(tracker.copies() + tracker.moves(), len); + EXPECT_EQ(tracker.instances(), static_cast<int>(len)); + EXPECT_EQ(tracker.copies() + tracker.moves(), static_cast<int>(len)); // pop_back() and erase() must destroy one object if (len > 0) { tracker.ResetCopiesMovesSwaps(); v.pop_back(); - EXPECT_EQ(tracker.instances(), len - 1); + EXPECT_EQ(tracker.instances(), static_cast<int>(len) - 1); EXPECT_EQ(tracker.copies(), 0); EXPECT_EQ(tracker.moves(), 0); if (!v.empty()) { tracker.ResetCopiesMovesSwaps(); v.erase(v.begin()); - EXPECT_EQ(tracker.instances(), len - 2); - EXPECT_EQ(tracker.copies() + tracker.moves(), len - 2); + EXPECT_EQ(tracker.instances(), static_cast<int>(len) - 2); + EXPECT_EQ(tracker.copies() + tracker.moves(), + static_cast<int>(len) - 2); } } @@ -1086,12 +1093,12 @@ TYPED_TEST_P(InstanceTest, CountConstructorsDestructorsOnMoveConstruction) { tracker.ResetCopiesMovesSwaps(); { InstanceVec v_copy(std::move(v)); - if (len > inlined_capacity) { + if (static_cast<size_t>(len) > inlined_capacity) { // Allocation is moved as a whole. EXPECT_EQ(tracker.instances(), len); EXPECT_EQ(tracker.live_instances(), len); // Tests an implementation detail, don't rely on this in your code. - EXPECT_EQ(v.size(), 0); // NOLINT misc-use-after-move + EXPECT_EQ(v.size(), 0u); // NOLINT misc-use-after-move EXPECT_EQ(tracker.copies(), 0); EXPECT_EQ(tracker.moves(), 0); } else { @@ -1157,7 +1164,7 @@ TYPED_TEST_P(InstanceTest, CountConstructorsDestructorsOnMoveAssignment) { tracker.ResetCopiesMovesSwaps(); InstanceVec longer, shorter; - const int inlined_capacity = longer.capacity(); + const size_t inlined_capacity = longer.capacity(); for (int i = 0; i < len; i++) { longer.push_back(Instance(i)); shorter.push_back(Instance(i)); @@ -1176,7 +1183,7 @@ TYPED_TEST_P(InstanceTest, CountConstructorsDestructorsOnMoveAssignment) { src_len = len; longer = std::move(shorter); } - if (src_len > inlined_capacity) { + if (static_cast<size_t>(src_len) > inlined_capacity) { // Allocation moved as a whole. EXPECT_EQ(tracker.instances(), src_len); EXPECT_EQ(tracker.live_instances(), src_len); @@ -1201,6 +1208,8 @@ TYPED_TEST_P(InstanceTest, CountConstructorsDestructorsOnMoveAssignment) { } TEST(CountElemAssign, SimpleTypeWithInlineBacking) { + const size_t inlined_capacity = absl::InlinedVector<int, 2>().capacity(); + for (size_t original_size = 0; original_size <= 5; ++original_size) { SCOPED_TRACE(original_size); // Original contents are [12345, 12345, ...] @@ -1209,10 +1218,10 @@ TEST(CountElemAssign, SimpleTypeWithInlineBacking) { absl::InlinedVector<int, 2> v(original_contents.begin(), original_contents.end()); v.assign(2, 123); - EXPECT_THAT(v, AllOf(SizeIs(2), ElementsAre(123, 123))); - if (original_size <= 2) { + EXPECT_THAT(v, AllOf(SizeIs(2u), ElementsAre(123, 123))); + if (original_size <= inlined_capacity) { // If the original had inline backing, it should stay inline. - EXPECT_EQ(2, v.capacity()); + EXPECT_EQ(v.capacity(), inlined_capacity); } } } @@ -1226,7 +1235,7 @@ TEST(CountElemAssign, SimpleTypeWithAllocation) { absl::InlinedVector<int, 2> v(original_contents.begin(), original_contents.end()); v.assign(3, 123); - EXPECT_THAT(v, AllOf(SizeIs(3), ElementsAre(123, 123, 123))); + EXPECT_THAT(v, AllOf(SizeIs(3u), ElementsAre(123, 123, 123))); EXPECT_LE(v.size(), v.capacity()); } } @@ -1241,10 +1250,10 @@ TYPED_TEST_P(InstanceTest, CountElemAssignInlineBacking) { absl::InlinedVector<Instance, 2> v(original_contents.begin(), original_contents.end()); v.assign(2, Instance(123)); - EXPECT_THAT(v, AllOf(SizeIs(2), ElementsAre(ValueIs(123), ValueIs(123)))); + EXPECT_THAT(v, AllOf(SizeIs(2u), ElementsAre(ValueIs(123), ValueIs(123)))); if (original_size <= 2) { // If the original had inline backing, it should stay inline. - EXPECT_EQ(2, v.capacity()); + EXPECT_EQ(2u, v.capacity()); } } } @@ -1259,8 +1268,8 @@ void InstanceCountElemAssignWithAllocationTest() { absl::InlinedVector<Instance, 2> v(original_contents.begin(), original_contents.end()); v.assign(3, Instance(123)); - EXPECT_THAT(v, AllOf(SizeIs(3), ElementsAre(ValueIs(123), ValueIs(123), - ValueIs(123)))); + EXPECT_THAT(v, AllOf(SizeIs(3u), ElementsAre(ValueIs(123), ValueIs(123), + ValueIs(123)))); EXPECT_LE(v.size(), v.capacity()); } } @@ -1275,16 +1284,17 @@ TEST(RangedConstructor, SimpleType) { std::vector<int> source_v = {4, 5, 6}; // First try to fit in inline backing absl::InlinedVector<int, 4> v(source_v.begin(), source_v.end()); - EXPECT_EQ(3, v.size()); - EXPECT_EQ(4, v.capacity()); // Indication that we're still on inlined storage + EXPECT_EQ(3u, v.size()); + EXPECT_EQ(4u, + v.capacity()); // Indication that we're still on inlined storage EXPECT_EQ(4, v[0]); EXPECT_EQ(5, v[1]); EXPECT_EQ(6, v[2]); // Now, force a re-allocate absl::InlinedVector<int, 2> realloc_v(source_v.begin(), source_v.end()); - EXPECT_EQ(3, realloc_v.size()); - EXPECT_LT(2, realloc_v.capacity()); + EXPECT_EQ(3u, realloc_v.size()); + EXPECT_LT(2u, realloc_v.capacity()); EXPECT_EQ(4, realloc_v[0]); EXPECT_EQ(5, realloc_v[1]); EXPECT_EQ(6, realloc_v[2]); @@ -1299,8 +1309,8 @@ void InstanceRangedConstructorTestForContainer() { tracker.ResetCopiesMovesSwaps(); absl::InlinedVector<Instance, inlined_capacity> v(source_v.begin(), source_v.end()); - EXPECT_EQ(2, v.size()); - EXPECT_LT(1, v.capacity()); + EXPECT_EQ(2u, v.size()); + EXPECT_LT(1u, v.capacity()); EXPECT_EQ(0, v[0].value()); EXPECT_EQ(1, v[1].value()); EXPECT_EQ(tracker.copies(), 2); @@ -1352,6 +1362,8 @@ TEST(RangedConstructor, ElementsAreConstructed) { } TEST(RangedAssign, SimpleType) { + const size_t inlined_capacity = absl::InlinedVector<int, 3>().capacity(); + // Test for all combinations of original sizes (empty and non-empty inline, // and out of line) and target sizes. for (size_t original_size = 0; original_size <= 5; ++original_size) { @@ -1365,7 +1377,7 @@ TEST(RangedAssign, SimpleType) { // New contents are [3, 4, ...] std::vector<int> new_contents; for (size_t i = 0; i < target_size; ++i) { - new_contents.push_back(i + 3); + new_contents.push_back(static_cast<int>(i + 3)); } absl::InlinedVector<int, 3> v(original_contents.begin(), @@ -1374,9 +1386,10 @@ TEST(RangedAssign, SimpleType) { EXPECT_EQ(new_contents.size(), v.size()); EXPECT_LE(new_contents.size(), v.capacity()); - if (target_size <= 3 && original_size <= 3) { + if (target_size <= inlined_capacity && + original_size <= inlined_capacity) { // Storage should stay inline when target size is small. - EXPECT_EQ(3, v.capacity()); + EXPECT_EQ(v.capacity(), inlined_capacity); } EXPECT_THAT(v, ElementsAreArray(new_contents)); } @@ -1409,7 +1422,7 @@ void InstanceRangedAssignTestForContainer() { // TODO(bsamwel): Test with an input iterator. std::vector<Instance> new_contents_in; for (size_t i = 0; i < target_size; ++i) { - new_contents_in.push_back(Instance(i + 3)); + new_contents_in.push_back(Instance(static_cast<int>(i) + 3)); } SourceContainer new_contents(new_contents_in.begin(), new_contents_in.end()); @@ -1422,7 +1435,7 @@ void InstanceRangedAssignTestForContainer() { EXPECT_LE(new_contents.size(), v.capacity()); if (target_size <= 3 && original_size <= 3) { // Storage should stay inline when target size is small. - EXPECT_EQ(3, v.capacity()); + EXPECT_EQ(3u, v.capacity()); } EXPECT_TRUE(std::equal(v.begin(), v.end(), new_contents.begin(), InstanceValuesEqual<Instance>)); @@ -1446,12 +1459,12 @@ TYPED_TEST_P(InstanceTest, RangedAssign) { TEST(InitializerListConstructor, SimpleTypeWithInlineBacking) { EXPECT_THAT((absl::InlinedVector<int, 4>{4, 5, 6}), - AllOf(SizeIs(3), CapacityIs(4), ElementsAre(4, 5, 6))); + AllOf(SizeIs(3u), CapacityIs(4u), ElementsAre(4, 5, 6))); } TEST(InitializerListConstructor, SimpleTypeWithReallocationRequired) { EXPECT_THAT((absl::InlinedVector<int, 2>{4, 5, 6}), - AllOf(SizeIs(3), CapacityIs(Gt(2)), ElementsAre(4, 5, 6))); + AllOf(SizeIs(3u), CapacityIs(Gt(2u)), ElementsAre(4, 5, 6))); } TEST(InitializerListConstructor, DisparateTypesInList) { @@ -1462,16 +1475,19 @@ TEST(InitializerListConstructor, DisparateTypesInList) { } TEST(InitializerListConstructor, ComplexTypeWithInlineBacking) { - EXPECT_THAT((absl::InlinedVector<CopyableMovableInstance, 1>{ - CopyableMovableInstance(0)}), - AllOf(SizeIs(1), CapacityIs(1), ElementsAre(ValueIs(0)))); + const size_t inlined_capacity = + absl::InlinedVector<CopyableMovableInstance, 1>().capacity(); + EXPECT_THAT( + (absl::InlinedVector<CopyableMovableInstance, 1>{ + CopyableMovableInstance(0)}), + AllOf(SizeIs(1u), CapacityIs(inlined_capacity), ElementsAre(ValueIs(0)))); } TEST(InitializerListConstructor, ComplexTypeWithReallocationRequired) { - EXPECT_THAT( - (absl::InlinedVector<CopyableMovableInstance, 1>{ - CopyableMovableInstance(0), CopyableMovableInstance(1)}), - AllOf(SizeIs(2), CapacityIs(Gt(1)), ElementsAre(ValueIs(0), ValueIs(1)))); + EXPECT_THAT((absl::InlinedVector<CopyableMovableInstance, 1>{ + CopyableMovableInstance(0), CopyableMovableInstance(1)}), + AllOf(SizeIs(2u), CapacityIs(Gt(1u)), + ElementsAre(ValueIs(0), ValueIs(1)))); } TEST(InitializerListAssign, SimpleTypeFitsInlineBacking) { @@ -1481,14 +1497,14 @@ TEST(InitializerListAssign, SimpleTypeFitsInlineBacking) { absl::InlinedVector<int, 2> v1(original_size, 12345); const size_t original_capacity_v1 = v1.capacity(); v1.assign({3}); - EXPECT_THAT( - v1, AllOf(SizeIs(1), CapacityIs(original_capacity_v1), ElementsAre(3))); + EXPECT_THAT(v1, AllOf(SizeIs(1u), CapacityIs(original_capacity_v1), + ElementsAre(3))); absl::InlinedVector<int, 2> v2(original_size, 12345); const size_t original_capacity_v2 = v2.capacity(); v2 = {3}; - EXPECT_THAT( - v2, AllOf(SizeIs(1), CapacityIs(original_capacity_v2), ElementsAre(3))); + EXPECT_THAT(v2, AllOf(SizeIs(1u), CapacityIs(original_capacity_v2), + ElementsAre(3))); } } @@ -1497,13 +1513,13 @@ TEST(InitializerListAssign, SimpleTypeDoesNotFitInlineBacking) { SCOPED_TRACE(original_size); absl::InlinedVector<int, 2> v1(original_size, 12345); v1.assign({3, 4, 5}); - EXPECT_THAT(v1, AllOf(SizeIs(3), ElementsAre(3, 4, 5))); - EXPECT_LE(3, v1.capacity()); + EXPECT_THAT(v1, AllOf(SizeIs(3u), ElementsAre(3, 4, 5))); + EXPECT_LE(3u, v1.capacity()); absl::InlinedVector<int, 2> v2(original_size, 12345); v2 = {3, 4, 5}; - EXPECT_THAT(v2, AllOf(SizeIs(3), ElementsAre(3, 4, 5))); - EXPECT_LE(3, v2.capacity()); + EXPECT_THAT(v2, AllOf(SizeIs(3u), ElementsAre(3, 4, 5))); + EXPECT_LE(3u, v2.capacity()); } } @@ -1532,7 +1548,7 @@ TYPED_TEST_P(InstanceTest, InitializerListAssign) { absl::InlinedVector<Instance, 2> v(original_size, Instance(12345)); const size_t original_capacity = v.capacity(); v.assign({Instance(3)}); - EXPECT_THAT(v, AllOf(SizeIs(1), CapacityIs(original_capacity), + EXPECT_THAT(v, AllOf(SizeIs(1u), CapacityIs(original_capacity), ElementsAre(ValueIs(3)))); } for (size_t original_size = 0; original_size <= 4; ++original_size) { @@ -1540,8 +1556,8 @@ TYPED_TEST_P(InstanceTest, InitializerListAssign) { absl::InlinedVector<Instance, 2> v(original_size, Instance(12345)); v.assign({Instance(3), Instance(4), Instance(5)}); EXPECT_THAT( - v, AllOf(SizeIs(3), ElementsAre(ValueIs(3), ValueIs(4), ValueIs(5)))); - EXPECT_LE(3, v.capacity()); + v, AllOf(SizeIs(3u), ElementsAre(ValueIs(3), ValueIs(4), ValueIs(5)))); + EXPECT_LE(3u, v.capacity()); } } @@ -1587,54 +1603,54 @@ TEST(AllocatorSupportTest, CountAllocations) { MyAlloc alloc(&allocated); { AllocVec ABSL_ATTRIBUTE_UNUSED v(ia, ia + 4, alloc); - EXPECT_THAT(allocated, 0); + EXPECT_THAT(allocated, Eq(0)); } - EXPECT_THAT(allocated, 0); + EXPECT_THAT(allocated, Eq(0)); { AllocVec ABSL_ATTRIBUTE_UNUSED v(ia, ia + ABSL_ARRAYSIZE(ia), alloc); - EXPECT_THAT(allocated, v.size() * sizeof(int)); + EXPECT_THAT(allocated, Eq(static_cast<int64_t>(v.size() * sizeof(int)))); } - EXPECT_THAT(allocated, 0); + EXPECT_THAT(allocated, Eq(0)); { AllocVec v(4, 1, alloc); - EXPECT_THAT(allocated, 0); + EXPECT_THAT(allocated, Eq(0)); int64_t allocated2 = 0; MyAlloc alloc2(&allocated2); AllocVec v2(v, alloc2); - EXPECT_THAT(allocated2, 0); + EXPECT_THAT(allocated2, Eq(0)); int64_t allocated3 = 0; MyAlloc alloc3(&allocated3); AllocVec v3(std::move(v), alloc3); - EXPECT_THAT(allocated3, 0); + EXPECT_THAT(allocated3, Eq(0)); } EXPECT_THAT(allocated, 0); { AllocVec v(8, 2, alloc); - EXPECT_THAT(allocated, v.size() * sizeof(int)); + EXPECT_THAT(allocated, Eq(static_cast<int64_t>(v.size() * sizeof(int)))); int64_t allocated2 = 0; MyAlloc alloc2(&allocated2); AllocVec v2(v, alloc2); - EXPECT_THAT(allocated2, v2.size() * sizeof(int)); + EXPECT_THAT(allocated2, Eq(static_cast<int64_t>(v2.size() * sizeof(int)))); int64_t allocated3 = 0; MyAlloc alloc3(&allocated3); AllocVec v3(std::move(v), alloc3); - EXPECT_THAT(allocated3, v3.size() * sizeof(int)); + EXPECT_THAT(allocated3, Eq(static_cast<int64_t>(v3.size() * sizeof(int)))); } EXPECT_EQ(allocated, 0); { // Test shrink_to_fit deallocations. AllocVec v(8, 2, alloc); - EXPECT_EQ(allocated, 8 * sizeof(int)); + EXPECT_EQ(allocated, static_cast<int64_t>(8 * sizeof(int))); v.resize(5); - EXPECT_EQ(allocated, 8 * sizeof(int)); + EXPECT_EQ(allocated, static_cast<int64_t>(8 * sizeof(int))); v.shrink_to_fit(); - EXPECT_EQ(allocated, 5 * sizeof(int)); + EXPECT_EQ(allocated, static_cast<int64_t>(5 * sizeof(int))); v.resize(4); - EXPECT_EQ(allocated, 5 * sizeof(int)); + EXPECT_EQ(allocated, static_cast<int64_t>(5 * sizeof(int))); v.shrink_to_fit(); EXPECT_EQ(allocated, 0); } @@ -1653,13 +1669,17 @@ TEST(AllocatorSupportTest, SwapBothAllocated) { AllocVec v1(ia1, ia1 + ABSL_ARRAYSIZE(ia1), a1); AllocVec v2(ia2, ia2 + ABSL_ARRAYSIZE(ia2), a2); EXPECT_LT(v1.capacity(), v2.capacity()); - EXPECT_THAT(allocated1, v1.capacity() * sizeof(int)); - EXPECT_THAT(allocated2, v2.capacity() * sizeof(int)); + EXPECT_THAT(allocated1, + Eq(static_cast<int64_t>(v1.capacity() * sizeof(int)))); + EXPECT_THAT(allocated2, + Eq(static_cast<int64_t>(v2.capacity() * sizeof(int)))); v1.swap(v2); EXPECT_THAT(v1, ElementsAreArray(ia2)); EXPECT_THAT(v2, ElementsAreArray(ia1)); - EXPECT_THAT(allocated1, v2.capacity() * sizeof(int)); - EXPECT_THAT(allocated2, v1.capacity() * sizeof(int)); + EXPECT_THAT(allocated1, + Eq(static_cast<int64_t>(v2.capacity() * sizeof(int)))); + EXPECT_THAT(allocated2, + Eq(static_cast<int64_t>(v1.capacity() * sizeof(int)))); } EXPECT_THAT(allocated1, 0); EXPECT_THAT(allocated2, 0); @@ -1677,13 +1697,15 @@ TEST(AllocatorSupportTest, SwapOneAllocated) { MyAlloc a2(&allocated2); AllocVec v1(ia1, ia1 + ABSL_ARRAYSIZE(ia1), a1); AllocVec v2(ia2, ia2 + ABSL_ARRAYSIZE(ia2), a2); - EXPECT_THAT(allocated1, v1.capacity() * sizeof(int)); - EXPECT_THAT(allocated2, 0); + EXPECT_THAT(allocated1, + Eq(static_cast<int64_t>(v1.capacity() * sizeof(int)))); + EXPECT_THAT(allocated2, Eq(0)); v1.swap(v2); EXPECT_THAT(v1, ElementsAreArray(ia2)); EXPECT_THAT(v2, ElementsAreArray(ia1)); - EXPECT_THAT(allocated1, v2.capacity() * sizeof(int)); - EXPECT_THAT(allocated2, 0); + EXPECT_THAT(allocated1, + Eq(static_cast<int64_t>(v2.capacity() * sizeof(int)))); + EXPECT_THAT(allocated2, Eq(0)); EXPECT_TRUE(v2.get_allocator() == a1); EXPECT_TRUE(v1.get_allocator() == a2); } @@ -1745,7 +1767,7 @@ TEST(AllocatorSupportTest, ScopedAllocatorWorksAllocated) { } TEST(AllocatorSupportTest, SizeAllocConstructor) { - constexpr int inlined_size = 4; + constexpr size_t inlined_size = 4; using Alloc = CountingAllocator<int>; using AllocVec = absl::InlinedVector<int, inlined_size, Alloc>; @@ -1755,7 +1777,7 @@ TEST(AllocatorSupportTest, SizeAllocConstructor) { auto v = AllocVec(len, Alloc(&allocated)); // Inline storage used; allocator should not be invoked - EXPECT_THAT(allocated, 0); + EXPECT_THAT(allocated, Eq(0)); EXPECT_THAT(v, AllOf(SizeIs(len), Each(0))); } @@ -1765,7 +1787,7 @@ TEST(AllocatorSupportTest, SizeAllocConstructor) { auto v = AllocVec(len, Alloc(&allocated)); // Out of line storage used; allocation of 8 elements expected - EXPECT_THAT(allocated, len * sizeof(int)); + EXPECT_THAT(allocated, Eq(static_cast<int64_t>(len * sizeof(int)))); EXPECT_THAT(v, AllOf(SizeIs(len), Each(0))); } } @@ -1800,9 +1822,9 @@ TEST(InlinedVectorTest, AbslHashValueWorks) { // Generate a variety of vectors some of these are small enough for the inline // space but are stored out of line. - for (int i = 0; i < 10; ++i) { + for (size_t i = 0; i < 10; ++i) { V v; - for (int j = 0; j < i; ++j) { + for (int j = 0; j < static_cast<int>(i); ++j) { v.push_back(j); } cases.push_back(v); @@ -1813,4 +1835,226 @@ TEST(InlinedVectorTest, AbslHashValueWorks) { EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(cases)); } +class MoveConstructibleOnlyInstance + : public absl::test_internal::BaseCountedInstance { + public: + explicit MoveConstructibleOnlyInstance(int x) : BaseCountedInstance(x) {} + MoveConstructibleOnlyInstance(MoveConstructibleOnlyInstance&& other) = + default; + MoveConstructibleOnlyInstance& operator=( + MoveConstructibleOnlyInstance&& other) = delete; +}; + +MATCHER(HasValue, "") { + return ::testing::get<0>(arg).value() == ::testing::get<1>(arg); +} + +TEST(NonAssignableMoveAssignmentTest, AllocatedToInline) { + using X = MoveConstructibleOnlyInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> inlined; + inlined.emplace_back(1); + absl::InlinedVector<X, 2> allocated; + allocated.emplace_back(1); + allocated.emplace_back(2); + allocated.emplace_back(3); + tracker.ResetCopiesMovesSwaps(); + + inlined = std::move(allocated); + // passed ownership of the allocated storage + EXPECT_EQ(tracker.moves(), 0); + EXPECT_EQ(tracker.live_instances(), 3); + + EXPECT_THAT(inlined, Pointwise(HasValue(), {1, 2, 3})); +} + +TEST(NonAssignableMoveAssignmentTest, InlineToAllocated) { + using X = MoveConstructibleOnlyInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> inlined; + inlined.emplace_back(1); + absl::InlinedVector<X, 2> allocated; + allocated.emplace_back(1); + allocated.emplace_back(2); + allocated.emplace_back(3); + tracker.ResetCopiesMovesSwaps(); + + allocated = std::move(inlined); + // Moved elements + EXPECT_EQ(tracker.moves(), 1); + EXPECT_EQ(tracker.live_instances(), 1); + + EXPECT_THAT(allocated, Pointwise(HasValue(), {1})); +} + +TEST(NonAssignableMoveAssignmentTest, InlineToInline) { + using X = MoveConstructibleOnlyInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> inlined_a; + inlined_a.emplace_back(1); + absl::InlinedVector<X, 2> inlined_b; + inlined_b.emplace_back(1); + tracker.ResetCopiesMovesSwaps(); + + inlined_a = std::move(inlined_b); + // Moved elements + EXPECT_EQ(tracker.moves(), 1); + EXPECT_EQ(tracker.live_instances(), 1); + + EXPECT_THAT(inlined_a, Pointwise(HasValue(), {1})); +} + +TEST(NonAssignableMoveAssignmentTest, AllocatedToAllocated) { + using X = MoveConstructibleOnlyInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> allocated_a; + allocated_a.emplace_back(1); + allocated_a.emplace_back(2); + allocated_a.emplace_back(3); + absl::InlinedVector<X, 2> allocated_b; + allocated_b.emplace_back(4); + allocated_b.emplace_back(5); + allocated_b.emplace_back(6); + allocated_b.emplace_back(7); + tracker.ResetCopiesMovesSwaps(); + + allocated_a = std::move(allocated_b); + // passed ownership of the allocated storage + EXPECT_EQ(tracker.moves(), 0); + EXPECT_EQ(tracker.live_instances(), 4); + + EXPECT_THAT(allocated_a, Pointwise(HasValue(), {4, 5, 6, 7})); +} + +TEST(NonAssignableMoveAssignmentTest, AssignThis) { + using X = MoveConstructibleOnlyInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> v; + v.emplace_back(1); + v.emplace_back(2); + v.emplace_back(3); + + tracker.ResetCopiesMovesSwaps(); + + // Obfuscated in order to pass -Wself-move. + v = std::move(*std::addressof(v)); + // nothing happens + EXPECT_EQ(tracker.moves(), 0); + EXPECT_EQ(tracker.live_instances(), 3); + + EXPECT_THAT(v, Pointwise(HasValue(), {1, 2, 3})); +} + +class NonSwappableInstance : public absl::test_internal::BaseCountedInstance { + public: + explicit NonSwappableInstance(int x) : BaseCountedInstance(x) {} + NonSwappableInstance(const NonSwappableInstance& other) = default; + NonSwappableInstance& operator=(const NonSwappableInstance& other) = default; + NonSwappableInstance(NonSwappableInstance&& other) = default; + NonSwappableInstance& operator=(NonSwappableInstance&& other) = default; +}; + +void swap(NonSwappableInstance&, NonSwappableInstance&) = delete; + +TEST(NonSwappableSwapTest, InlineAndAllocatedTransferStorageAndMove) { + using X = NonSwappableInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> inlined; + inlined.emplace_back(1); + absl::InlinedVector<X, 2> allocated; + allocated.emplace_back(1); + allocated.emplace_back(2); + allocated.emplace_back(3); + tracker.ResetCopiesMovesSwaps(); + + inlined.swap(allocated); + EXPECT_EQ(tracker.moves(), 1); + EXPECT_EQ(tracker.live_instances(), 4); + + EXPECT_THAT(inlined, Pointwise(HasValue(), {1, 2, 3})); +} + +TEST(NonSwappableSwapTest, InlineAndInlineMoveIndividualElements) { + using X = NonSwappableInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> inlined_a; + inlined_a.emplace_back(1); + absl::InlinedVector<X, 2> inlined_b; + inlined_b.emplace_back(2); + tracker.ResetCopiesMovesSwaps(); + + inlined_a.swap(inlined_b); + EXPECT_EQ(tracker.moves(), 3); + EXPECT_EQ(tracker.live_instances(), 2); + + EXPECT_THAT(inlined_a, Pointwise(HasValue(), {2})); + EXPECT_THAT(inlined_b, Pointwise(HasValue(), {1})); +} + +TEST(NonSwappableSwapTest, AllocatedAndAllocatedOnlyTransferStorage) { + using X = NonSwappableInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> allocated_a; + allocated_a.emplace_back(1); + allocated_a.emplace_back(2); + allocated_a.emplace_back(3); + absl::InlinedVector<X, 2> allocated_b; + allocated_b.emplace_back(4); + allocated_b.emplace_back(5); + allocated_b.emplace_back(6); + allocated_b.emplace_back(7); + tracker.ResetCopiesMovesSwaps(); + + allocated_a.swap(allocated_b); + EXPECT_EQ(tracker.moves(), 0); + EXPECT_EQ(tracker.live_instances(), 7); + + EXPECT_THAT(allocated_a, Pointwise(HasValue(), {4, 5, 6, 7})); + EXPECT_THAT(allocated_b, Pointwise(HasValue(), {1, 2, 3})); +} + +TEST(NonSwappableSwapTest, SwapThis) { + using X = NonSwappableInstance; + InstanceTracker tracker; + absl::InlinedVector<X, 2> v; + v.emplace_back(1); + v.emplace_back(2); + v.emplace_back(3); + + tracker.ResetCopiesMovesSwaps(); + + v.swap(v); + EXPECT_EQ(tracker.moves(), 0); + EXPECT_EQ(tracker.live_instances(), 3); + + EXPECT_THAT(v, Pointwise(HasValue(), {1, 2, 3})); +} + +template <size_t N> +using CharVec = absl::InlinedVector<char, N>; + +// Warning: This struct "simulates" the type `InlinedVector::Storage::Allocated` +// to make reasonable expectations for inlined storage capacity optimization. If +// implementation changes `Allocated`, then `MySpan` and tests that use it need +// to be updated accordingly. +template <typename T> +struct MySpan { + T* data; + size_t size; +}; + +TEST(StorageTest, InlinedCapacityAutoIncrease) { + // The requested capacity is auto increased to `sizeof(MySpan<char>)`. + EXPECT_GT(CharVec<1>().capacity(), 1); + EXPECT_EQ(CharVec<1>().capacity(), sizeof(MySpan<char>)); + EXPECT_EQ(CharVec<1>().capacity(), CharVec<2>().capacity()); + EXPECT_EQ(sizeof(CharVec<1>), sizeof(CharVec<2>)); + + // The requested capacity is auto increased to + // `sizeof(MySpan<int>) / sizeof(int)`. + EXPECT_GT((absl::InlinedVector<int, 1>().capacity()), 1); + EXPECT_EQ((absl::InlinedVector<int, 1>().capacity()), + sizeof(MySpan<int>) / sizeof(int)); +} + } // anonymous namespace diff --git a/absl/container/internal/btree.h b/absl/container/internal/btree.h index 01f4e749..d734676a 100644 --- a/absl/container/internal/btree.h +++ b/absl/container/internal/btree.h @@ -61,6 +61,7 @@ #include "absl/base/internal/raw_logging.h" #include "absl/base/macros.h" #include "absl/container/internal/common.h" +#include "absl/container/internal/common_policy_traits.h" #include "absl/container/internal/compressed_tuple.h" #include "absl/container/internal/container_memory.h" #include "absl/container/internal/layout.h" @@ -100,7 +101,7 @@ struct StringBtreeDefaultLess { StringBtreeDefaultLess() = default; // Compatibility constructor. - StringBtreeDefaultLess(std::less<std::string>) {} // NOLINT + StringBtreeDefaultLess(std::less<std::string>) {} // NOLINT StringBtreeDefaultLess(std::less<absl::string_view>) {} // NOLINT // Allow converting to std::less for use in key_comp()/value_comp(). @@ -132,7 +133,7 @@ struct StringBtreeDefaultGreater { StringBtreeDefaultGreater() = default; - StringBtreeDefaultGreater(std::greater<std::string>) {} // NOLINT + StringBtreeDefaultGreater(std::greater<std::string>) {} // NOLINT StringBtreeDefaultGreater(std::greater<absl::string_view>) {} // NOLINT // Allow converting to std::greater for use in key_comp()/value_comp(). @@ -356,7 +357,7 @@ class map_value_compare { template <typename Key, typename Compare, typename Alloc, int TargetNodeSize, bool IsMulti, bool IsMap, typename SlotPolicy> -struct common_params { +struct common_params : common_policy_traits<SlotPolicy> { using original_key_compare = Compare; // If Compare is a common comparator for a string-like type, then we adapt it @@ -376,8 +377,7 @@ struct common_params { std::is_same<key_compare, StringBtreeDefaultLess>::value || std::is_same<key_compare, StringBtreeDefaultGreater>::value; static constexpr bool kIsKeyCompareTransparent = - IsTransparent<original_key_compare>::value || - kIsKeyCompareStringAdapted; + IsTransparent<original_key_compare>::value || kIsKeyCompareStringAdapted; static constexpr bool kEnableGenerations = #ifdef ABSL_BTREE_ENABLE_GENERATIONS true; @@ -430,8 +430,7 @@ struct common_params { // Upper bound for the available space for slots. This is largest for leaf // nodes, which have overhead of at least a pointer + 4 bytes (for storing // 3 field_types and an enum). - kNodeSlotSpace = - TargetNodeSize - /*minimum overhead=*/(sizeof(void *) + 4), + kNodeSlotSpace = TargetNodeSize - /*minimum overhead=*/(sizeof(void *) + 4), }; // This is an integral type large enough to hold as many slots as will fit a @@ -440,28 +439,6 @@ struct common_params { absl::conditional_t<(kNodeSlotSpace / sizeof(slot_type) > (std::numeric_limits<uint8_t>::max)()), uint16_t, uint8_t>; // NOLINT - - // The following methods are necessary for passing this struct as PolicyTraits - // for node_handle and/or are used within btree. - static value_type &element(slot_type *slot) { - return slot_policy::element(slot); - } - static const value_type &element(const slot_type *slot) { - return slot_policy::element(slot); - } - template <class... Args> - static void construct(Alloc *alloc, slot_type *slot, Args &&... args) { - slot_policy::construct(alloc, slot, std::forward<Args>(args)...); - } - static void construct(Alloc *alloc, slot_type *slot, slot_type *other) { - slot_policy::construct(alloc, slot, other); - } - static void destroy(Alloc *alloc, slot_type *slot) { - slot_policy::destroy(alloc, slot); - } - static void transfer(Alloc *alloc, slot_type *new_slot, slot_type *old_slot) { - slot_policy::transfer(alloc, new_slot, old_slot); - } }; // An adapter class that converts a lower-bound compare into an upper-bound @@ -628,33 +605,33 @@ class btree_node { constexpr static size_type NodeTargetSlots(const size_type begin, const size_type end) { return begin == end ? begin - : SizeWithNSlots((begin + end) / 2 + 1) > - params_type::kTargetNodeSize - ? NodeTargetSlots(begin, (begin + end) / 2) - : NodeTargetSlots((begin + end) / 2 + 1, end); + : SizeWithNSlots((begin + end) / 2 + 1) > + params_type::kTargetNodeSize + ? NodeTargetSlots(begin, (begin + end) / 2) + : NodeTargetSlots((begin + end) / 2 + 1, end); } - enum { - kTargetNodeSize = params_type::kTargetNodeSize, - kNodeTargetSlots = NodeTargetSlots(0, params_type::kTargetNodeSize), - - // We need a minimum of 3 slots per internal node in order to perform - // splitting (1 value for the two nodes involved in the split and 1 value - // propagated to the parent as the delimiter for the split). For performance - // reasons, we don't allow 3 slots-per-node due to bad worst case occupancy - // of 1/3 (for a node, not a b-tree). - kMinNodeSlots = 4, - - kNodeSlots = - kNodeTargetSlots >= kMinNodeSlots ? kNodeTargetSlots : kMinNodeSlots, - - // The node is internal (i.e. is not a leaf node) if and only if `max_count` - // has this value. - kInternalNodeMaxCount = 0, - }; + constexpr static size_type kTargetNodeSize = params_type::kTargetNodeSize; + constexpr static size_type kNodeTargetSlots = + NodeTargetSlots(0, kTargetNodeSize); + + // We need a minimum of 3 slots per internal node in order to perform + // splitting (1 value for the two nodes involved in the split and 1 value + // propagated to the parent as the delimiter for the split). For performance + // reasons, we don't allow 3 slots-per-node due to bad worst case occupancy of + // 1/3 (for a node, not a b-tree). + constexpr static size_type kMinNodeSlots = 4; + + constexpr static size_type kNodeSlots = + kNodeTargetSlots >= kMinNodeSlots ? kNodeTargetSlots : kMinNodeSlots; + + // The node is internal (i.e. is not a leaf node) if and only if `max_count` + // has this value. + constexpr static field_type kInternalNodeMaxCount = 0; // Leaves can have less than kNodeSlots values. - constexpr static layout_type LeafLayout(const int slot_count = kNodeSlots) { + constexpr static layout_type LeafLayout( + const size_type slot_count = kNodeSlots) { return layout_type( /*parent*/ 1, /*generation*/ params_type::kEnableGenerations ? 1 : 0, @@ -670,7 +647,7 @@ class btree_node { /*slots*/ kNodeSlots, /*children*/ kNodeSlots + 1); } - constexpr static size_type LeafSize(const int slot_count = kNodeSlots) { + constexpr static size_type LeafSize(const size_type slot_count = kNodeSlots) { return LeafLayout(slot_count).AllocSize(); } constexpr static size_type InternalSize() { @@ -693,10 +670,10 @@ class btree_node { } void set_parent(btree_node *p) { *GetField<0>() = p; } field_type &mutable_finish() { return GetField<2>()[2]; } - slot_type *slot(int i) { return &GetField<3>()[i]; } + slot_type *slot(size_type i) { return &GetField<3>()[i]; } slot_type *start_slot() { return slot(start()); } slot_type *finish_slot() { return slot(finish()); } - const slot_type *slot(int i) const { return &GetField<3>()[i]; } + const slot_type *slot(size_type i) const { return &GetField<3>()[i]; } void set_position(field_type v) { GetField<2>()[0] = v; } void set_start(field_type v) { GetField<2>()[1] = v; } void set_finish(field_type v) { GetField<2>()[2] = v; } @@ -773,51 +750,53 @@ class btree_node { } // Getters for the key/value at position i in the node. - const key_type &key(int i) const { return params_type::key(slot(i)); } - reference value(int i) { return params_type::element(slot(i)); } - const_reference value(int i) const { return params_type::element(slot(i)); } + const key_type &key(size_type i) const { return params_type::key(slot(i)); } + reference value(size_type i) { return params_type::element(slot(i)); } + const_reference value(size_type i) const { + return params_type::element(slot(i)); + } // Getters/setter for the child at position i in the node. - btree_node *child(int i) const { return GetField<4>()[i]; } + btree_node *child(field_type i) const { return GetField<4>()[i]; } btree_node *start_child() const { return child(start()); } - btree_node *&mutable_child(int i) { return GetField<4>()[i]; } - void clear_child(int i) { + btree_node *&mutable_child(field_type i) { return GetField<4>()[i]; } + void clear_child(field_type i) { absl::container_internal::SanitizerPoisonObject(&mutable_child(i)); } - void set_child(int i, btree_node *c) { + void set_child(field_type i, btree_node *c) { absl::container_internal::SanitizerUnpoisonObject(&mutable_child(i)); mutable_child(i) = c; c->set_position(i); } - void init_child(int i, btree_node *c) { + void init_child(field_type i, btree_node *c) { set_child(i, c); c->set_parent(this); } // Returns the position of the first value whose key is not less than k. template <typename K> - SearchResult<int, is_key_compare_to::value> lower_bound( + SearchResult<size_type, is_key_compare_to::value> lower_bound( const K &k, const key_compare &comp) const { return use_linear_search::value ? linear_search(k, comp) : binary_search(k, comp); } // Returns the position of the first value whose key is greater than k. template <typename K> - int upper_bound(const K &k, const key_compare &comp) const { + size_type upper_bound(const K &k, const key_compare &comp) const { auto upper_compare = upper_bound_adapter<key_compare>(comp); return use_linear_search::value ? linear_search(k, upper_compare).value : binary_search(k, upper_compare).value; } template <typename K, typename Compare> - SearchResult<int, btree_is_key_compare_to<Compare, key_type>::value> + SearchResult<size_type, btree_is_key_compare_to<Compare, key_type>::value> linear_search(const K &k, const Compare &comp) const { return linear_search_impl(k, start(), finish(), comp, btree_is_key_compare_to<Compare, key_type>()); } template <typename K, typename Compare> - SearchResult<int, btree_is_key_compare_to<Compare, key_type>::value> + SearchResult<size_type, btree_is_key_compare_to<Compare, key_type>::value> binary_search(const K &k, const Compare &comp) const { return binary_search_impl(k, start(), finish(), comp, btree_is_key_compare_to<Compare, key_type>()); @@ -826,8 +805,8 @@ class btree_node { // Returns the position of the first value whose key is not less than k using // linear search performed using plain compare. template <typename K, typename Compare> - SearchResult<int, false> linear_search_impl( - const K &k, int s, const int e, const Compare &comp, + SearchResult<size_type, false> linear_search_impl( + const K &k, size_type s, const size_type e, const Compare &comp, std::false_type /* IsCompareTo */) const { while (s < e) { if (!comp(key(s), k)) { @@ -835,14 +814,14 @@ class btree_node { } ++s; } - return SearchResult<int, false>{s}; + return SearchResult<size_type, false>{s}; } // Returns the position of the first value whose key is not less than k using // linear search performed using compare-to. template <typename K, typename Compare> - SearchResult<int, true> linear_search_impl( - const K &k, int s, const int e, const Compare &comp, + SearchResult<size_type, true> linear_search_impl( + const K &k, size_type s, const size_type e, const Compare &comp, std::true_type /* IsCompareTo */) const { while (s < e) { const absl::weak_ordering c = comp(key(s), k); @@ -859,30 +838,30 @@ class btree_node { // Returns the position of the first value whose key is not less than k using // binary search performed using plain compare. template <typename K, typename Compare> - SearchResult<int, false> binary_search_impl( - const K &k, int s, int e, const Compare &comp, + SearchResult<size_type, false> binary_search_impl( + const K &k, size_type s, size_type e, const Compare &comp, std::false_type /* IsCompareTo */) const { while (s != e) { - const int mid = (s + e) >> 1; + const size_type mid = (s + e) >> 1; if (comp(key(mid), k)) { s = mid + 1; } else { e = mid; } } - return SearchResult<int, false>{s}; + return SearchResult<size_type, false>{s}; } // Returns the position of the first value whose key is not less than k using // binary search performed using compare-to. template <typename K, typename CompareTo> - SearchResult<int, true> binary_search_impl( - const K &k, int s, int e, const CompareTo &comp, + SearchResult<size_type, true> binary_search_impl( + const K &k, size_type s, size_type e, const CompareTo &comp, std::true_type /* IsCompareTo */) const { if (params_type::template can_have_multiple_equivalent_keys<K>()) { MatchKind exact_match = MatchKind::kNe; while (s != e) { - const int mid = (s + e) >> 1; + const size_type mid = (s + e) >> 1; const absl::weak_ordering c = comp(key(mid), k); if (c < 0) { s = mid + 1; @@ -899,7 +878,7 @@ class btree_node { return {s, exact_match}; } else { // Can't have multiple equivalent keys. while (s != e) { - const int mid = (s + e) >> 1; + const size_type mid = (s + e) >> 1; const absl::weak_ordering c = comp(key(mid), k); if (c < 0) { s = mid + 1; @@ -916,7 +895,7 @@ class btree_node { // Emplaces a value at position i, shifting all existing values and // children at positions >= i to the right by 1. template <typename... Args> - void emplace_value(size_type i, allocator_type *alloc, Args &&... args); + void emplace_value(field_type i, allocator_type *alloc, Args &&...args); // Removes the values at positions [i, i + to_erase), shifting all existing // values and children after that range to the left by to_erase. Clears all @@ -924,9 +903,9 @@ class btree_node { void remove_values(field_type i, field_type to_erase, allocator_type *alloc); // Rebalances a node with its right sibling. - void rebalance_right_to_left(int to_move, btree_node *right, + void rebalance_right_to_left(field_type to_move, btree_node *right, allocator_type *alloc); - void rebalance_left_to_right(int to_move, btree_node *right, + void rebalance_left_to_right(field_type to_move, btree_node *right, allocator_type *alloc); // Splits a node, moving a portion of the node's values to its right sibling. @@ -937,7 +916,7 @@ class btree_node { void merge(btree_node *src, allocator_type *alloc); // Node allocation/deletion routines. - void init_leaf(int max_count, btree_node *parent) { + void init_leaf(field_type max_count, btree_node *parent) { set_generation(0); set_parent(parent); set_position(0); @@ -958,6 +937,7 @@ class btree_node { static void deallocate(const size_type size, btree_node *node, allocator_type *alloc) { + absl::container_internal::SanitizerUnpoisonMemoryRegion(node, size); absl::container_internal::Deallocate<Alignment()>(alloc, node, size); } @@ -966,7 +946,7 @@ class btree_node { private: template <typename... Args> - void value_init(const field_type i, allocator_type *alloc, Args &&... args) { + void value_init(const field_type i, allocator_type *alloc, Args &&...args) { next_generation(); absl::container_internal::SanitizerUnpoisonObject(slot(i)); params_type::construct(alloc, slot(i), std::forward<Args>(args)...); @@ -1017,10 +997,15 @@ class btree_node { const size_type src_i, btree_node *src_node, allocator_type *alloc) { next_generation(); - for (slot_type *src = src_node->slot(src_i + n - 1), *end = src - n, - *dest = slot(dest_i + n - 1); + for (slot_type *src = src_node->slot(src_i + n), *end = src - n, + *dest = slot(dest_i + n); src != end; --src, --dest) { - transfer(dest, src, alloc); + // If we modified the loop index calculations above to avoid the -1s here, + // it would result in UB in the computation of `end` (and possibly `src` + // as well, if n == 0), since slot() is effectively an array index and it + // is UB to compute the address of any out-of-bounds array element except + // for one-past-the-end. + transfer(dest - 1, src - 1, alloc); } } @@ -1032,8 +1017,62 @@ class btree_node { friend struct btree_access; }; +template <typename Node> +bool AreNodesFromSameContainer(const Node *node_a, const Node *node_b) { + // If either node is null, then give up on checking whether they're from the + // same container. (If exactly one is null, then we'll trigger the + // default-constructed assert in Equals.) + if (node_a == nullptr || node_b == nullptr) return true; + while (!node_a->is_root()) node_a = node_a->parent(); + while (!node_b->is_root()) node_b = node_b->parent(); + return node_a == node_b; +} + +class btree_iterator_generation_info_enabled { + public: + explicit btree_iterator_generation_info_enabled(uint32_t g) + : generation_(g) {} + + // Updates the generation. For use internally right before we return an + // iterator to the user. + template <typename Node> + void update_generation(const Node *node) { + if (node != nullptr) generation_ = node->generation(); + } + uint32_t generation() const { return generation_; } + + template <typename Node> + void assert_valid_generation(const Node *node) const { + if (node != nullptr && node->generation() != generation_) { + ABSL_INTERNAL_LOG( + FATAL, + "Attempting to use an invalidated iterator. The corresponding b-tree " + "container has been mutated since this iterator was constructed."); + } + } + + private: + // Used to check that the iterator hasn't been invalidated. + uint32_t generation_; +}; + +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 {} +}; + +#ifdef ABSL_BTREE_ENABLE_GENERATIONS +using btree_iterator_generation_info = btree_iterator_generation_info_enabled; +#else +using btree_iterator_generation_info = btree_iterator_generation_info_disabled; +#endif + template <typename Node, typename Reference, typename Pointer> -class btree_iterator { +class btree_iterator : private btree_iterator_generation_info { + using field_type = typename Node::field_type; using key_type = typename Node::key_type; using size_type = typename Node::size_type; using params_type = typename Node::params_type; @@ -1063,13 +1102,11 @@ class btree_iterator { btree_iterator() : btree_iterator(nullptr, -1) {} explicit btree_iterator(Node *n) : btree_iterator(n, n->start()) {} - btree_iterator(Node *n, int p) : node_(n), position_(p) { -#ifdef ABSL_BTREE_ENABLE_GENERATIONS - // Use `~uint32_t{}` as a sentinel value for iterator generations so it - // doesn't match the initial value for the actual generation. - generation_ = n != nullptr ? n->generation() : ~uint32_t{}; -#endif - } + btree_iterator(Node *n, int p) + : btree_iterator_generation_info(n != nullptr ? n->generation() + : ~uint32_t{}), + node_(n), + position_(p) {} // NOTE: this SFINAE allows for implicit conversions from iterator to // const_iterator, but it specifically avoids hiding the copy constructor so @@ -1080,32 +1117,43 @@ class btree_iterator { std::is_same<btree_iterator, const_iterator>::value, int> = 0> btree_iterator(const btree_iterator<N, R, P> other) // NOLINT - : node_(other.node_), position_(other.position_) { -#ifdef ABSL_BTREE_ENABLE_GENERATIONS - generation_ = other.generation_; -#endif - } + : btree_iterator_generation_info(other), + node_(other.node_), + position_(other.position_) {} bool operator==(const iterator &other) const { - return node_ == other.node_ && position_ == other.position_; + return Equals(other); } bool operator==(const const_iterator &other) const { - return node_ == other.node_ && position_ == other.position_; + return Equals(other); } bool operator!=(const iterator &other) const { - return node_ != other.node_ || position_ != other.position_; + return !Equals(other); } bool operator!=(const const_iterator &other) const { - return node_ != other.node_ || position_ != other.position_; + return !Equals(other); + } + + // Returns n such that n calls to ++other yields *this. + // Precondition: n exists. + difference_type operator-(const_iterator other) const { + if (node_ == other.node_) { + if (node_->is_leaf()) return position_ - other.position_; + if (position_ == other.position_) return 0; + } + return distance_slow(other); } // Accessors for the key/value the iterator is pointing at. reference operator*() const { ABSL_HARDENING_ASSERT(node_ != nullptr); - ABSL_HARDENING_ASSERT(node_->start() <= position_); - ABSL_HARDENING_ASSERT(node_->finish() > position_); - assert_valid_generation(); - return node_->value(position_); + assert_valid_generation(node_); + ABSL_HARDENING_ASSERT(position_ >= node_->start()); + if (position_ >= node_->finish()) { + ABSL_HARDENING_ASSERT(!IsEndIterator() && "Dereferencing end() iterator"); + ABSL_HARDENING_ASSERT(position_ < node_->finish()); + } + return node_->value(static_cast<field_type>(position_)); } pointer operator->() const { return &operator*(); } @@ -1155,16 +1203,43 @@ class btree_iterator { std::is_same<btree_iterator, iterator>::value, int> = 0> explicit btree_iterator(const btree_iterator<N, R, P> other) - : node_(const_cast<node_type *>(other.node_)), - position_(other.position_) { -#ifdef ABSL_BTREE_ENABLE_GENERATIONS - generation_ = other.generation_; -#endif + : btree_iterator_generation_info(other.generation()), + node_(const_cast<node_type *>(other.node_)), + position_(other.position_) {} + + bool Equals(const const_iterator other) const { + ABSL_HARDENING_ASSERT(((node_ == nullptr && other.node_ == nullptr) || + (node_ != nullptr && other.node_ != nullptr)) && + "Comparing default-constructed iterator with " + "non-default-constructed iterator."); + // Note: we use assert instead of ABSL_HARDENING_ASSERT here because this + // changes the complexity of Equals from O(1) to O(log(N) + log(M)) where + // N/M are sizes of the containers containing node_/other.node_. + assert(AreNodesFromSameContainer(node_, other.node_) && + "Comparing iterators from different containers."); + assert_valid_generation(node_); + other.assert_valid_generation(other.node_); + return node_ == other.node_ && position_ == other.position_; + } + + bool IsEndIterator() const { + if (position_ != node_->finish()) return false; + node_type *node = node_; + while (!node->is_root()) { + if (node->position() != node->parent()->finish()) return false; + node = node->parent(); + } + return true; } + // Returns n such that n calls to ++other yields *this. + // Precondition: n exists && (this->node_ != other.node_ || + // !this->node_->is_leaf() || this->position_ != other.position_). + difference_type distance_slow(const_iterator other) const; + // Increment/decrement the iterator. void increment() { - assert_valid_generation(); + assert_valid_generation(node_); if (node_->is_leaf() && ++position_ < node_->finish()) { return; } @@ -1173,7 +1248,7 @@ class btree_iterator { void increment_slow(); void decrement() { - assert_valid_generation(); + assert_valid_generation(node_); if (node_->is_leaf() && --position_ >= node_->start()) { return; } @@ -1181,28 +1256,15 @@ class btree_iterator { } void decrement_slow(); - // Updates the generation. For use internally right before we return an - // iterator to the user. - void update_generation() { -#ifdef ABSL_BTREE_ENABLE_GENERATIONS - if (node_ != nullptr) generation_ = node_->generation(); -#endif + const key_type &key() const { + return node_->key(static_cast<size_type>(position_)); } - - const key_type &key() const { return node_->key(position_); } decltype(std::declval<Node *>()->slot(0)) slot() { - return node_->slot(position_); + return node_->slot(static_cast<size_type>(position_)); } - void assert_valid_generation() const { -#ifdef ABSL_BTREE_ENABLE_GENERATIONS - if (node_ != nullptr && node_->generation() != generation_) { - ABSL_INTERNAL_LOG( - FATAL, - "Attempting to use an invalidated iterator. The corresponding b-tree " - "container has been mutated since this iterator was constructed."); - } -#endif + void update_generation() { + btree_iterator_generation_info::update_generation(node_); } // The node in the tree the iterator is pointing at. @@ -1211,10 +1273,6 @@ class btree_iterator { // NOTE: this is an int rather than a field_type because iterators can point // to invalid positions (such as -1) in certain circumstances. int position_; -#ifdef ABSL_BTREE_ENABLE_GENERATIONS - // Used to check that the iterator hasn't been invalidated. - uint32_t generation_; -#endif }; template <typename Params> @@ -1242,7 +1300,7 @@ class btree { // MSVC has constexpr code generations bugs here. EmptyNodeType() : parent(this) {} #else - constexpr EmptyNodeType(node_type *p) : parent(p) {} + explicit constexpr EmptyNodeType(node_type *p) : parent(p) {} #endif }; @@ -1326,7 +1384,7 @@ class btree { btree(btree &&other) noexcept : root_(absl::exchange(other.root_, EmptyNode())), rightmost_(std::move(other.rightmost_)), - size_(absl::exchange(other.size_, 0)) { + size_(absl::exchange(other.size_, 0u)) { other.mutable_rightmost() = EmptyNode(); } btree(btree &&other, const allocator_type &alloc) @@ -1405,7 +1463,7 @@ class btree { // Requirement: if `key` already exists in the btree, does not consume `args`. // Requirement: `key` is never referenced after consuming `args`. template <typename K, typename... Args> - std::pair<iterator, bool> insert_unique(const K &key, Args &&... args); + std::pair<iterator, bool> insert_unique(const K &key, Args &&...args); // Inserts with hint. Checks to see if the value should be placed immediately // before `position` in the tree. If so, then the insertion will take @@ -1414,9 +1472,8 @@ class btree { // Requirement: if `key` already exists in the btree, does not consume `args`. // Requirement: `key` is never referenced after consuming `args`. template <typename K, typename... Args> - std::pair<iterator, bool> insert_hint_unique(iterator position, - const K &key, - Args &&... args); + std::pair<iterator, bool> insert_hint_unique(iterator position, const K &key, + Args &&...args); // Insert a range of values into the btree. // Note: the first overload avoids constructing a value_type if the key @@ -1543,8 +1600,7 @@ class btree { static double average_bytes_per_value() { // The expected number of values per node with random insertion order is the // average of the maximum and minimum numbers of values per node. - const double expected_values_per_node = - (kNodeSlots + kMinNodeValues) / 2.0; + const double expected_values_per_node = (kNodeSlots + kMinNodeValues) / 2.0; return node_type::LeafSize() / expected_values_per_node; } @@ -1600,7 +1656,7 @@ class btree { // Allocates a correctly aligned node of at least size bytes using the // allocator. - node_type *allocate(const size_type size) { + node_type *allocate(size_type size) { return reinterpret_cast<node_type *>( absl::container_internal::Allocate<node_type::Alignment()>( mutable_allocator(), size)); @@ -1617,7 +1673,7 @@ class btree { n->init_leaf(kNodeSlots, parent); return n; } - node_type *new_leaf_root_node(const int max_count) { + 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); return n; @@ -1652,7 +1708,7 @@ class btree { // Emplaces a value into the btree immediately before iter. Requires that // key(v) <= iter.key() and (--iter).key() <= key(v). template <typename... Args> - iterator internal_emplace(iterator iter, Args &&... args); + iterator internal_emplace(iterator iter, Args &&...args); // Returns an iterator pointing to the first value >= the value "iter" is // pointing at. Note that "iter" might be pointing to an invalid location such @@ -1685,8 +1741,8 @@ class btree { iterator internal_find(const K &key) const; // Verifies the tree structure of node. - int internal_verify(const node_type *node, const key_type *lo, - const key_type *hi) const; + size_type internal_verify(const node_type *node, const key_type *lo, + const key_type *hi) const; node_stats internal_stats(const node_type *node) const { // The root can be a static empty node. @@ -1720,9 +1776,9 @@ class btree { // btree_node methods template <typename P> template <typename... Args> -inline void btree_node<P>::emplace_value(const size_type i, +inline void btree_node<P>::emplace_value(const field_type i, allocator_type *alloc, - Args &&... args) { + Args &&...args) { assert(i >= start()); assert(i <= finish()); // Shift old values to create space for new value and then construct it in @@ -1731,7 +1787,7 @@ inline void btree_node<P>::emplace_value(const size_type i, transfer_n_backward(finish() - i, /*dest_i=*/i + 1, /*src_i=*/i, this, alloc); } - value_init(i, alloc, std::forward<Args>(args)...); + value_init(static_cast<field_type>(i), alloc, std::forward<Args>(args)...); set_finish(finish() + 1); if (is_internal() && finish() > i + 1) { @@ -1754,11 +1810,11 @@ inline void btree_node<P>::remove_values(const field_type i, if (is_internal()) { // Delete all children between begin and end. - for (int j = 0; j < to_erase; ++j) { + for (field_type j = 0; j < to_erase; ++j) { clear_and_delete(child(i + j + 1), alloc); } // Rotate children after end into new positions. - for (int j = i + to_erase + 1; j <= orig_finish; ++j) { + for (field_type j = i + to_erase + 1; j <= orig_finish; ++j) { set_child(j - to_erase, child(j)); clear_child(j); } @@ -1767,7 +1823,7 @@ inline void btree_node<P>::remove_values(const field_type i, } template <typename P> -void btree_node<P>::rebalance_right_to_left(const int to_move, +void btree_node<P>::rebalance_right_to_left(field_type to_move, btree_node *right, allocator_type *alloc) { assert(parent() == right->parent()); @@ -1791,10 +1847,10 @@ void btree_node<P>::rebalance_right_to_left(const int to_move, if (is_internal()) { // Move the child pointers from the right to the left node. - for (int i = 0; i < to_move; ++i) { + for (field_type i = 0; i < to_move; ++i) { init_child(finish() + i + 1, right->child(i)); } - for (int i = right->start(); i <= right->finish() - to_move; ++i) { + for (field_type i = right->start(); i <= right->finish() - to_move; ++i) { assert(i + to_move <= right->max_count()); right->init_child(i, right->child(i + to_move)); right->clear_child(i + to_move); @@ -1807,7 +1863,7 @@ void btree_node<P>::rebalance_right_to_left(const int to_move, } template <typename P> -void btree_node<P>::rebalance_left_to_right(const int to_move, +void btree_node<P>::rebalance_left_to_right(field_type to_move, btree_node *right, allocator_type *alloc) { assert(parent() == right->parent()); @@ -1838,11 +1894,11 @@ void btree_node<P>::rebalance_left_to_right(const int to_move, if (is_internal()) { // Move the child pointers from the left to the right node. - for (int i = right->finish(); i >= right->start(); --i) { - right->init_child(i + to_move, right->child(i)); - right->clear_child(i); + for (field_type i = right->finish() + 1; i > right->start(); --i) { + right->init_child(i - 1 + to_move, right->child(i - 1)); + right->clear_child(i - 1); } - for (int i = 1; i <= to_move; ++i) { + for (field_type i = 1; i <= to_move; ++i) { right->init_child(i - 1, child(finish() - to_move + i)); clear_child(finish() - to_move + i); } @@ -1883,7 +1939,7 @@ void btree_node<P>::split(const int insert_position, btree_node *dest, parent()->init_child(position() + 1, dest); if (is_internal()) { - for (int i = dest->start(), j = finish() + 1; i <= dest->finish(); + for (field_type i = dest->start(), j = finish() + 1; i <= dest->finish(); ++i, ++j) { assert(child(j) != nullptr); dest->init_child(i, child(j)); @@ -1905,7 +1961,8 @@ void btree_node<P>::merge(btree_node *src, allocator_type *alloc) { if (is_internal()) { // Move the child pointers from the right to the left node. - for (int i = src->start(), j = finish() + 1; i <= src->finish(); ++i, ++j) { + for (field_type i = src->start(), j = finish() + 1; i <= src->finish(); + ++i, ++j) { init_child(j, src->child(i)); src->clear_child(i); } @@ -1944,15 +2001,15 @@ void btree_node<P>::clear_and_delete(btree_node *node, allocator_type *alloc) { // instead of checking whether the parent is a leaf, we can remove this logic. btree_node *leftmost_leaf = node; #endif - // Use `int` because `pos` needs to be able to hold `kNodeSlots+1`, which - // isn't guaranteed to be a valid `field_type`. - int pos = node->position(); + // Use `size_type` because `pos` needs to be able to hold `kNodeSlots+1`, + // which isn't guaranteed to be a valid `field_type`. + size_type pos = node->position(); btree_node *parent = node->parent(); for (;;) { // In each iteration of the next loop, we delete one leaf node and go right. assert(pos <= parent->finish()); do { - node = parent->child(pos); + node = parent->child(static_cast<field_type>(pos)); if (node->is_internal()) { // Navigate to the leftmost leaf under node. while (node->is_internal()) node = node->start_child(); @@ -1988,6 +2045,64 @@ void btree_node<P>::clear_and_delete(btree_node *node, allocator_type *alloc) { //// // btree_iterator methods + +// Note: the implementation here is based on btree_node::clear_and_delete. +template <typename N, typename R, typename P> +auto btree_iterator<N, R, P>::distance_slow(const_iterator other) const + -> difference_type { + const_iterator begin = other; + const_iterator end = *this; + assert(begin.node_ != end.node_ || !begin.node_->is_leaf() || + begin.position_ != end.position_); + + const node_type *node = begin.node_; + // We need to compensate for double counting if begin.node_ is a leaf node. + difference_type count = node->is_leaf() ? -begin.position_ : 0; + + // First navigate to the leftmost leaf node past begin. + if (node->is_internal()) { + ++count; + node = node->child(begin.position_ + 1); + } + while (node->is_internal()) node = node->start_child(); + + // Use `size_type` because `pos` needs to be able to hold `kNodeSlots+1`, + // which isn't guaranteed to be a valid `field_type`. + size_type pos = node->position(); + const node_type *parent = node->parent(); + for (;;) { + // In each iteration of the next loop, we count one leaf node and go right. + assert(pos <= parent->finish()); + do { + node = parent->child(static_cast<field_type>(pos)); + if (node->is_internal()) { + // Navigate to the leftmost leaf under node. + while (node->is_internal()) node = node->start_child(); + pos = node->position(); + parent = node->parent(); + } + if (node == end.node_) return count + end.position_; + if (parent == end.node_ && pos == static_cast<size_type>(end.position_)) + return count + node->count(); + // +1 is for the next internal node value. + count += node->count() + 1; + ++pos; + } while (pos <= parent->finish()); + + // Once we've counted all children of parent, go up/right. + assert(pos > parent->finish()); + do { + node = parent; + pos = node->position(); + parent = node->parent(); + // -1 because we counted the value at end and shouldn't. + if (parent == end.node_ && pos == static_cast<size_type>(end.position_)) + return count - 1; + ++pos; + } while (pos > parent->finish()); + } +} + template <typename N, typename R, typename P> void btree_iterator<N, R, P>::increment_slow() { if (node_->is_leaf()) { @@ -2004,7 +2119,7 @@ void btree_iterator<N, R, P>::increment_slow() { } } else { assert(position_ < node_->finish()); - node_ = node_->child(position_ + 1); + node_ = node_->child(static_cast<field_type>(position_ + 1)); while (node_->is_internal()) { node_ = node_->start_child(); } @@ -2028,7 +2143,7 @@ void btree_iterator<N, R, P>::decrement_slow() { } } else { assert(position_ >= node_->start()); - node_ = node_->child(position_); + node_ = node_->child(static_cast<field_type>(position_)); while (node_->is_internal()) { node_ = node_->child(node_->finish()); } @@ -2131,7 +2246,7 @@ auto btree<P>::equal_range(const K &key) -> std::pair<iterator, iterator> { template <typename P> template <typename K, typename... Args> -auto btree<P>::insert_unique(const K &key, Args &&... args) +auto btree<P>::insert_unique(const K &key, Args &&...args) -> std::pair<iterator, bool> { if (empty()) { mutable_root() = mutable_rightmost() = new_leaf_root_node(1); @@ -2158,7 +2273,7 @@ auto btree<P>::insert_unique(const K &key, Args &&... args) template <typename P> template <typename K, typename... Args> inline auto btree<P>::insert_hint_unique(iterator position, const K &key, - Args &&... args) + Args &&...args) -> std::pair<iterator, bool> { if (!empty()) { if (position == end() || compare_keys(key, position.key())) { @@ -2294,7 +2409,8 @@ auto btree<P>::operator=(btree &&other) noexcept -> btree & { template <typename P> auto btree<P>::erase(iterator iter) -> iterator { - iter.node_->value_destroy(iter.position_, mutable_allocator()); + iter.node_->value_destroy(static_cast<field_type>(iter.position_), + mutable_allocator()); iter.update_generation(); const bool internal_delete = iter.node_->is_internal(); @@ -2305,15 +2421,19 @@ auto btree<P>::erase(iterator iter) -> iterator { iterator internal_iter(iter); --iter; assert(iter.node_->is_leaf()); - internal_iter.node_->transfer(internal_iter.position_, iter.position_, - iter.node_, mutable_allocator()); + internal_iter.node_->transfer( + static_cast<size_type>(internal_iter.position_), + static_cast<size_type>(iter.position_), iter.node_, + mutable_allocator()); } else { // Shift values after erased position in leaf. In the internal case, we // don't need to do this because the leaf position is the end of the node. - const field_type transfer_from = iter.position_ + 1; + const field_type transfer_from = + static_cast<field_type>(iter.position_ + 1); const field_type num_to_transfer = iter.node_->finish() - transfer_from; - iter.node_->transfer_n(num_to_transfer, iter.position_, transfer_from, - iter.node_, mutable_allocator()); + iter.node_->transfer_n(num_to_transfer, + static_cast<size_type>(iter.position_), + transfer_from, iter.node_, mutable_allocator()); } // Update node finish and container size. iter.node_->set_finish(iter.node_->finish() - 1); @@ -2379,7 +2499,7 @@ auto btree<P>::rebalance_after_delete(iterator iter) -> iterator { template <typename P> auto btree<P>::erase_range(iterator begin, iterator end) -> std::pair<size_type, iterator> { - difference_type count = std::distance(begin, end); + size_type count = static_cast<size_type>(end - begin); assert(count >= 0); if (count == 0) { @@ -2393,8 +2513,10 @@ auto btree<P>::erase_range(iterator begin, iterator end) if (begin.node_ == end.node_) { assert(end.position_ > begin.position_); - begin.node_->remove_values(begin.position_, end.position_ - begin.position_, - mutable_allocator()); + begin.node_->remove_values( + static_cast<field_type>(begin.position_), + static_cast<field_type>(end.position_ - begin.position_), + mutable_allocator()); size_ -= count; return {count, rebalance_after_delete(begin)}; } @@ -2404,11 +2526,11 @@ auto btree<P>::erase_range(iterator begin, iterator end) if (begin.node_->is_leaf()) { const size_type remaining_to_erase = size_ - target_size; const size_type remaining_in_node = - begin.node_->finish() - begin.position_; - const size_type to_erase = - (std::min)(remaining_to_erase, remaining_in_node); - begin.node_->remove_values(begin.position_, to_erase, - mutable_allocator()); + static_cast<size_type>(begin.node_->finish() - begin.position_); + const field_type to_erase = static_cast<field_type>( + (std::min)(remaining_to_erase, remaining_in_node)); + begin.node_->remove_values(static_cast<field_type>(begin.position_), + to_erase, mutable_allocator()); size_ -= to_erase; begin = rebalance_after_delete(begin); } else { @@ -2475,16 +2597,19 @@ void btree<P>::rebalance_or_split(iterator *iter) { // We bias rebalancing based on the position being inserted. If we're // inserting at the end of the right node then we bias rebalancing to // fill up the left node. - int to_move = (kNodeSlots - left->count()) / - (1 + (insert_position < static_cast<int>(kNodeSlots))); - to_move = (std::max)(1, to_move); - - if (insert_position - to_move >= node->start() || - left->count() + to_move < static_cast<int>(kNodeSlots)) { + field_type to_move = + (kNodeSlots - left->count()) / + (1 + (static_cast<field_type>(insert_position) < kNodeSlots)); + to_move = (std::max)(field_type{1}, to_move); + + if (static_cast<field_type>(insert_position) - to_move >= + node->start() || + left->count() + to_move < kNodeSlots) { left->rebalance_right_to_left(to_move, node, mutable_allocator()); assert(node->max_count() - node->count() == to_move); - insert_position = insert_position - to_move; + insert_position = static_cast<int>( + static_cast<field_type>(insert_position) - to_move); if (insert_position < node->start()) { insert_position = insert_position + left->count() + 1; node = left; @@ -2504,12 +2629,13 @@ void btree<P>::rebalance_or_split(iterator *iter) { // We bias rebalancing based on the position being inserted. If we're // inserting at the beginning of the left node then we bias rebalancing // to fill up the right node. - int to_move = (static_cast<int>(kNodeSlots) - right->count()) / - (1 + (insert_position > node->start())); - to_move = (std::max)(1, to_move); + field_type to_move = (kNodeSlots - right->count()) / + (1 + (insert_position > node->start())); + to_move = (std::max)(field_type{1}, to_move); - if (insert_position <= node->finish() - to_move || - right->count() + to_move < static_cast<int>(kNodeSlots)) { + if (static_cast<field_type>(insert_position) <= + node->finish() - to_move || + right->count() + to_move < kNodeSlots) { node->rebalance_left_to_right(to_move, right, mutable_allocator()); if (insert_position > node->finish()) { @@ -2594,8 +2720,9 @@ bool btree<P>::try_merge_or_rebalance(iterator *iter) { // from the front of the tree. if (right->count() > kMinNodeValues && (iter->node_->count() == 0 || iter->position_ > iter->node_->start())) { - int to_move = (right->count() - iter->node_->count()) / 2; - to_move = (std::min)(to_move, right->count() - 1); + field_type to_move = (right->count() - iter->node_->count()) / 2; + to_move = + (std::min)(to_move, static_cast<field_type>(right->count() - 1)); iter->node_->rebalance_right_to_left(to_move, right, mutable_allocator()); return false; } @@ -2609,8 +2736,8 @@ bool btree<P>::try_merge_or_rebalance(iterator *iter) { if (left->count() > kMinNodeValues && (iter->node_->count() == 0 || iter->position_ < iter->node_->finish())) { - int to_move = (left->count() - iter->node_->count()) / 2; - to_move = (std::min)(to_move, left->count() - 1); + field_type to_move = (left->count() - iter->node_->count()) / 2; + to_move = (std::min)(to_move, static_cast<field_type>(left->count() - 1)); left->rebalance_left_to_right(to_move, iter->node_, mutable_allocator()); iter->position_ += to_move; return false; @@ -2655,7 +2782,7 @@ inline IterType btree<P>::internal_last(IterType iter) { template <typename P> template <typename... Args> -inline auto btree<P>::internal_emplace(iterator iter, Args &&... args) +inline auto btree<P>::internal_emplace(iterator iter, Args &&...args) -> iterator { if (iter.node_->is_internal()) { // We can't insert on an internal node. Instead, we'll insert after the @@ -2671,8 +2798,8 @@ inline auto btree<P>::internal_emplace(iterator iter, Args &&... args) // 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((std::min<int>)(kNodeSlots, 2 * max_count)); + iter.node_ = new_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_; @@ -2687,7 +2814,8 @@ inline auto btree<P>::internal_emplace(iterator iter, Args &&... args) rebalance_or_split(&iter); } } - iter.node_->emplace_value(iter.position_, alloc, std::forward<Args>(args)...); + iter.node_->emplace_value(static_cast<field_type>(iter.position_), alloc, + std::forward<Args>(args)...); ++size_; iter.update_generation(); return iter; @@ -2699,9 +2827,9 @@ inline auto btree<P>::internal_locate(const K &key) const -> SearchResult<iterator, is_key_compare_to::value> { iterator iter(const_cast<node_type *>(root())); for (;;) { - SearchResult<int, is_key_compare_to::value> res = + SearchResult<size_type, is_key_compare_to::value> res = iter.node_->lower_bound(key, key_comp()); - iter.position_ = res.value; + iter.position_ = static_cast<int>(res.value); if (res.IsEq()) { return {iter, MatchKind::kEq}; } @@ -2712,7 +2840,7 @@ inline auto btree<P>::internal_locate(const K &key) const if (iter.node_->is_leaf()) { break; } - iter.node_ = iter.node_->child(iter.position_); + iter.node_ = iter.node_->child(static_cast<field_type>(iter.position_)); } // Note: in the non-key-compare-to case, the key may actually be equivalent // here (and the MatchKind::kNe is ignored). @@ -2729,16 +2857,16 @@ auto btree<P>::internal_lower_bound(const K &key) const return ret; } iterator iter(const_cast<node_type *>(root())); - SearchResult<int, is_key_compare_to::value> res; + SearchResult<size_type, is_key_compare_to::value> res; bool seen_eq = false; for (;;) { res = iter.node_->lower_bound(key, key_comp()); - iter.position_ = res.value; + iter.position_ = static_cast<int>(res.value); if (iter.node_->is_leaf()) { break; } seen_eq = seen_eq || res.IsEq(); - iter.node_ = iter.node_->child(iter.position_); + iter.node_ = iter.node_->child(static_cast<field_type>(iter.position_)); } if (res.IsEq()) return {iter, MatchKind::kEq}; return {internal_last(iter), seen_eq ? MatchKind::kEq : MatchKind::kNe}; @@ -2749,11 +2877,11 @@ template <typename K> auto btree<P>::internal_upper_bound(const K &key) const -> iterator { iterator iter(const_cast<node_type *>(root())); for (;;) { - iter.position_ = iter.node_->upper_bound(key, key_comp()); + iter.position_ = static_cast<int>(iter.node_->upper_bound(key, key_comp())); if (iter.node_->is_leaf()) { break; } - iter.node_ = iter.node_->child(iter.position_); + iter.node_ = iter.node_->child(static_cast<field_type>(iter.position_)); } return internal_last(iter); } @@ -2776,8 +2904,8 @@ auto btree<P>::internal_find(const K &key) const -> iterator { } template <typename P> -int btree<P>::internal_verify(const node_type *node, const key_type *lo, - const key_type *hi) const { +typename btree<P>::size_type btree<P>::internal_verify( + const node_type *node, const key_type *lo, const key_type *hi) const { assert(node->count() > 0); assert(node->count() <= node->max_count()); if (lo) { @@ -2789,9 +2917,9 @@ int btree<P>::internal_verify(const node_type *node, const key_type *lo, for (int i = node->start() + 1; i < node->finish(); ++i) { assert(!compare_keys(node->key(i), node->key(i - 1))); } - int count = node->count(); + size_type count = node->count(); if (node->is_internal()) { - for (int i = node->start(); i <= node->finish(); ++i) { + for (field_type i = node->start(); i <= node->finish(); ++i) { assert(node->child(i) != nullptr); assert(node->child(i)->parent() == node); assert(node->child(i)->position() == i); @@ -2805,8 +2933,8 @@ int btree<P>::internal_verify(const node_type *node, const key_type *lo, struct btree_access { template <typename BtreeContainer, typename Pred> - static auto erase_if(BtreeContainer &container, Pred pred) - -> typename BtreeContainer::size_type { + static auto erase_if(BtreeContainer &container, Pred pred) -> + typename BtreeContainer::size_type { const auto initial_size = container.size(); auto &tree = container.tree_; auto *alloc = tree.mutable_allocator(); diff --git a/absl/container/internal/btree_container.h b/absl/container/internal/btree_container.h index fc2f740a..2bff11db 100644 --- a/absl/container/internal/btree_container.h +++ b/absl/container/internal/btree_container.h @@ -65,6 +65,11 @@ class btree_container { using const_reverse_iterator = typename Tree::const_reverse_iterator; using node_type = typename Tree::node_handle_type; + struct extract_and_get_next_return_type { + node_type node; + iterator next; + }; + // Constructors/assignments. btree_container() : tree_(key_compare(), allocator_type()) {} explicit btree_container(const key_compare &comp, @@ -107,7 +112,7 @@ class btree_container { template <typename K = key_type> size_type count(const key_arg<K> &key) const { auto equal_range = this->equal_range(key); - return std::distance(equal_range.first, equal_range.second); + return equal_range.second - equal_range.first; } template <typename K = key_type> iterator find(const key_arg<K> &key) { @@ -165,6 +170,15 @@ class btree_container { } // Extract routines. + extract_and_get_next_return_type extract_and_get_next( + const_iterator position) { + // Use Construct instead of Transfer because the rebalancing code will + // destroy the slot later. + // Note: we rely on erase() taking place after Construct(). + return {CommonAccess::Construct<node_type>(get_allocator(), + iterator(position).slot()), + erase(position)}; + } node_type extract(iterator position) { // Use Construct instead of Transfer because the rebalancing code will // destroy the slot later. diff --git a/absl/container/internal/common.h b/absl/container/internal/common.h index 416d9aa3..9239bb4d 100644 --- a/absl/container/internal/common.h +++ b/absl/container/internal/common.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef ABSL_CONTAINER_INTERNAL_CONTAINER_H_ -#define ABSL_CONTAINER_INTERNAL_CONTAINER_H_ +#ifndef ABSL_CONTAINER_INTERNAL_COMMON_H_ +#define ABSL_CONTAINER_INTERNAL_COMMON_H_ #include <cassert> #include <type_traits> @@ -204,4 +204,4 @@ struct InsertReturnType { ABSL_NAMESPACE_END } // namespace absl -#endif // ABSL_CONTAINER_INTERNAL_CONTAINER_H_ +#endif // ABSL_CONTAINER_INTERNAL_COMMON_H_ diff --git a/absl/container/internal/common_policy_traits.h b/absl/container/internal/common_policy_traits.h new file mode 100644 index 00000000..b42c9a48 --- /dev/null +++ b/absl/container/internal/common_policy_traits.h @@ -0,0 +1,132 @@ +// 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_CONTAINER_INTERNAL_COMMON_POLICY_TRAITS_H_ +#define ABSL_CONTAINER_INTERNAL_COMMON_POLICY_TRAITS_H_ + +#include <cstddef> +#include <cstring> +#include <memory> +#include <new> +#include <type_traits> +#include <utility> + +#include "absl/meta/type_traits.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace container_internal { + +// Defines how slots are initialized/destroyed/moved. +template <class Policy, class = void> +struct common_policy_traits { + // The actual object stored in the container. + using slot_type = typename Policy::slot_type; + using reference = decltype(Policy::element(std::declval<slot_type*>())); + using value_type = typename std::remove_reference<reference>::type; + + // PRECONDITION: `slot` is UNINITIALIZED + // POSTCONDITION: `slot` is INITIALIZED + template <class Alloc, class... Args> + static void construct(Alloc* alloc, slot_type* slot, Args&&... args) { + Policy::construct(alloc, slot, std::forward<Args>(args)...); + } + + // PRECONDITION: `slot` is INITIALIZED + // POSTCONDITION: `slot` is UNINITIALIZED + template <class Alloc> + static void destroy(Alloc* alloc, slot_type* slot) { + Policy::destroy(alloc, slot); + } + + // Transfers the `old_slot` to `new_slot`. Any memory allocated by the + // allocator inside `old_slot` to `new_slot` can be transferred. + // + // OPTIONAL: defaults to: + // + // clone(new_slot, std::move(*old_slot)); + // destroy(old_slot); + // + // PRECONDITION: `new_slot` is UNINITIALIZED and `old_slot` is INITIALIZED + // POSTCONDITION: `new_slot` is INITIALIZED and `old_slot` is + // UNINITIALIZED + template <class Alloc> + static void transfer(Alloc* alloc, slot_type* new_slot, slot_type* old_slot) { + transfer_impl(alloc, new_slot, old_slot, Rank0{}); + } + + // PRECONDITION: `slot` is INITIALIZED + // POSTCONDITION: `slot` is INITIALIZED + // Note: we use remove_const_t so that the two overloads have different args + // in the case of sets with explicitly const value_types. + template <class P = Policy> + static auto element(absl::remove_const_t<slot_type>* slot) + -> decltype(P::element(slot)) { + return P::element(slot); + } + template <class P = Policy> + static auto element(const slot_type* slot) -> decltype(P::element(slot)) { + return P::element(slot); + } + + static constexpr bool transfer_uses_memcpy() { + return std::is_same<decltype(transfer_impl<std::allocator<char>>( + nullptr, nullptr, nullptr, Rank0{})), + std::true_type>::value; + } + + private: + // To rank the overloads below for overload resoltion. Rank0 is preferred. + struct Rank2 {}; + struct Rank1 : Rank2 {}; + struct Rank0 : Rank1 {}; + + // Use auto -> decltype as an enabler. + 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); + } +#if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606 + // This overload returns true_type for the trait below. + // The conditional_t is to make the enabler type dependent. + template <class Alloc, + typename = std::enable_if_t<absl::is_trivially_relocatable< + std::conditional_t<false, Alloc, value_type>>::value>> + static std::true_type transfer_impl(Alloc*, slot_type* new_slot, + slot_type* old_slot, Rank1) { + // TODO(b/247130232): remove casts after fixing warnings. + // TODO(b/251814870): remove casts after fixing warnings. + std::memcpy( + static_cast<void*>(std::launder( + const_cast<std::remove_const_t<value_type>*>(&element(new_slot)))), + static_cast<const void*>(&element(old_slot)), sizeof(value_type)); + return {}; + } +#endif + + template <class Alloc> + static void transfer_impl(Alloc* alloc, slot_type* new_slot, + slot_type* old_slot, Rank2) { + construct(alloc, new_slot, std::move(element(old_slot))); + destroy(alloc, old_slot); + } +}; + +} // namespace container_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CONTAINER_INTERNAL_COMMON_POLICY_TRAITS_H_ diff --git a/absl/container/internal/common_policy_traits_test.cc b/absl/container/internal/common_policy_traits_test.cc new file mode 100644 index 00000000..5eaa4aae --- /dev/null +++ b/absl/container/internal/common_policy_traits_test.cc @@ -0,0 +1,120 @@ +// 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/container/internal/common_policy_traits.h" + +#include <functional> +#include <memory> +#include <new> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace container_internal { +namespace { + +using ::testing::MockFunction; +using ::testing::AnyNumber; +using ::testing::ReturnRef; + +using Slot = int; + +struct PolicyWithoutOptionalOps { + using slot_type = Slot; + using key_type = Slot; + using init_type = Slot; + + static std::function<void(void*, Slot*, Slot)> construct; + static std::function<void(void*, Slot*)> destroy; + + static std::function<Slot&(Slot*)> element; +}; + +std::function<void(void*, Slot*, Slot)> PolicyWithoutOptionalOps::construct; +std::function<void(void*, Slot*)> PolicyWithoutOptionalOps::destroy; + +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 Test : ::testing::Test { + Test() { + PolicyWithoutOptionalOps::construct = [&](void* a1, Slot* a2, Slot a3) { + construct.Call(a1, a2, std::move(a3)); + }; + PolicyWithoutOptionalOps::destroy = [&](void* a1, Slot* a2) { + destroy.Call(a1, a2); + }; + + PolicyWithoutOptionalOps::element = [&](Slot* a1) -> Slot& { + return element.Call(a1); + }; + + PolicyWithOptionalOps::transfer = [&](void* a1, Slot* a2, Slot* a3) { + return transfer.Call(a1, a2, a3); + }; + } + + std::allocator<Slot> alloc; + int a = 53; + + MockFunction<void(void*, Slot*, Slot)> construct; + MockFunction<void(void*, Slot*)> destroy; + + MockFunction<Slot&(Slot*)> element; + + MockFunction<void(void*, Slot*, Slot*)> transfer; +}; + +TEST_F(Test, construct) { + EXPECT_CALL(construct, Call(&alloc, &a, 53)); + common_policy_traits<PolicyWithoutOptionalOps>::construct(&alloc, &a, 53); +} + +TEST_F(Test, destroy) { + EXPECT_CALL(destroy, Call(&alloc, &a)); + common_policy_traits<PolicyWithoutOptionalOps>::destroy(&alloc, &a); +} + +TEST_F(Test, element) { + int b = 0; + EXPECT_CALL(element, Call(&a)).WillOnce(ReturnRef(b)); + EXPECT_EQ(&b, &common_policy_traits<PolicyWithoutOptionalOps>::element(&a)); +} + +TEST_F(Test, without_transfer) { + int b = 42; + EXPECT_CALL(element, Call(&a)).Times(AnyNumber()).WillOnce(ReturnRef(a)); + EXPECT_CALL(element, Call(&b)).WillOnce(ReturnRef(b)); + EXPECT_CALL(construct, Call(&alloc, &a, b)).Times(AnyNumber()); + EXPECT_CALL(destroy, Call(&alloc, &b)).Times(AnyNumber()); + common_policy_traits<PolicyWithoutOptionalOps>::transfer(&alloc, &a, &b); +} + +TEST_F(Test, with_transfer) { + int b = 42; + EXPECT_CALL(transfer, Call(&alloc, &a, &b)); + common_policy_traits<PolicyWithOptionalOps>::transfer(&alloc, &a, &b); +} + +} // namespace +} // namespace container_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/container/internal/container_memory.h b/absl/container/internal/container_memory.h index 00e9f6d7..bfa4ff93 100644 --- a/absl/container/internal/container_memory.h +++ b/absl/container/internal/container_memory.h @@ -17,6 +17,7 @@ #include <cassert> #include <cstddef> +#include <cstring> #include <memory> #include <new> #include <tuple> @@ -340,7 +341,8 @@ template <class K, class V> struct map_slot_policy { using slot_type = map_slot_type<K, V>; using value_type = std::pair<const K, V>; - using mutable_value_type = std::pair<K, V>; + using mutable_value_type = + std::pair<absl::remove_const_t<K>, absl::remove_const_t<V>>; private: static void emplace(slot_type* slot) { @@ -424,6 +426,16 @@ struct map_slot_policy { static void transfer(Allocator* alloc, slot_type* new_slot, slot_type* old_slot) { emplace(new_slot); +#if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606 + if (absl::is_trivially_relocatable<value_type>()) { + // 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; + } +#endif + if (kMutableKeys::value) { absl::allocator_traits<Allocator>::construct( *alloc, &new_slot->mutable_value, std::move(old_slot->mutable_value)); diff --git a/absl/container/internal/hash_policy_traits.h b/absl/container/internal/hash_policy_traits.h index 46c97b18..164ec123 100644 --- a/absl/container/internal/hash_policy_traits.h +++ b/absl/container/internal/hash_policy_traits.h @@ -21,6 +21,7 @@ #include <type_traits> #include <utility> +#include "absl/container/internal/common_policy_traits.h" #include "absl/meta/type_traits.h" namespace absl { @@ -29,7 +30,7 @@ namespace container_internal { // Defines how slots are initialized/destroyed/moved. template <class Policy, class = void> -struct hash_policy_traits { +struct hash_policy_traits : common_policy_traits<Policy> { // The type of the keys stored in the hashtable. using key_type = typename Policy::key_type; @@ -87,43 +88,6 @@ struct hash_policy_traits { // Defaults to false if not provided by the policy. using constant_iterators = ConstantIteratorsImpl<>; - // PRECONDITION: `slot` is UNINITIALIZED - // POSTCONDITION: `slot` is INITIALIZED - template <class Alloc, class... Args> - static void construct(Alloc* alloc, slot_type* slot, Args&&... args) { - Policy::construct(alloc, slot, std::forward<Args>(args)...); - } - - // PRECONDITION: `slot` is INITIALIZED - // POSTCONDITION: `slot` is UNINITIALIZED - template <class Alloc> - static void destroy(Alloc* alloc, slot_type* slot) { - Policy::destroy(alloc, slot); - } - - // Transfers the `old_slot` to `new_slot`. Any memory allocated by the - // allocator inside `old_slot` to `new_slot` can be transferred. - // - // OPTIONAL: defaults to: - // - // clone(new_slot, std::move(*old_slot)); - // destroy(old_slot); - // - // PRECONDITION: `new_slot` is UNINITIALIZED and `old_slot` is INITIALIZED - // POSTCONDITION: `new_slot` is INITIALIZED and `old_slot` is - // UNINITIALIZED - template <class Alloc> - static void transfer(Alloc* alloc, slot_type* new_slot, slot_type* old_slot) { - transfer_impl(alloc, new_slot, old_slot, 0); - } - - // PRECONDITION: `slot` is INITIALIZED - // POSTCONDITION: `slot` is INITIALIZED - template <class P = Policy> - static auto element(slot_type* slot) -> decltype(P::element(slot)) { - return P::element(slot); - } - // Returns the amount of memory owned by `slot`, exclusive of `sizeof(*slot)`. // // If `slot` is nullptr, returns the constant amount of memory owned by any @@ -174,8 +138,8 @@ struct hash_policy_traits { // Used for node handle manipulation. template <class P = Policy> static auto mutable_key(slot_type* slot) - -> decltype(P::apply(ReturnKey(), element(slot))) { - return P::apply(ReturnKey(), element(slot)); + -> decltype(P::apply(ReturnKey(), hash_policy_traits::element(slot))) { + return P::apply(ReturnKey(), hash_policy_traits::element(slot)); } // Returns the "value" (as opposed to the "key") portion of the element. Used @@ -184,21 +148,6 @@ struct hash_policy_traits { static auto value(T* elem) -> decltype(P::value(elem)) { return P::value(elem); } - - private: - // Use auto -> decltype as an enabler. - template <class Alloc, class P = Policy> - static auto transfer_impl(Alloc* alloc, slot_type* new_slot, - slot_type* old_slot, int) - -> decltype((void)P::transfer(alloc, new_slot, old_slot)) { - P::transfer(alloc, new_slot, old_slot); - } - template <class Alloc> - static void transfer_impl(Alloc* alloc, slot_type* new_slot, - slot_type* old_slot, char) { - construct(alloc, new_slot, std::move(element(old_slot))); - destroy(alloc, old_slot); - } }; } // namespace container_internal diff --git a/absl/container/internal/hash_policy_traits_test.cc b/absl/container/internal/hash_policy_traits_test.cc index 6ef8b9e0..82d7cc3a 100644 --- a/absl/container/internal/hash_policy_traits_test.cc +++ b/absl/container/internal/hash_policy_traits_test.cc @@ -38,81 +38,31 @@ struct PolicyWithoutOptionalOps { using key_type = Slot; using init_type = Slot; - static std::function<void(void*, Slot*, Slot)> construct; - static std::function<void(void*, Slot*)> destroy; - static std::function<Slot&(Slot*)> element; static int apply(int v) { return apply_impl(v); } static std::function<int(int)> apply_impl; static std::function<Slot&(Slot*)> value; }; -std::function<void(void*, Slot*, Slot)> PolicyWithoutOptionalOps::construct; -std::function<void(void*, Slot*)> PolicyWithoutOptionalOps::destroy; - -std::function<Slot&(Slot*)> PolicyWithoutOptionalOps::element; std::function<int(int)> PolicyWithoutOptionalOps::apply_impl; std::function<Slot&(Slot*)> PolicyWithoutOptionalOps::value; -struct PolicyWithOptionalOps : PolicyWithoutOptionalOps { - static std::function<void(void*, Slot*, Slot*)> transfer; -}; - -std::function<void(void*, Slot*, Slot*)> PolicyWithOptionalOps::transfer; - struct Test : ::testing::Test { Test() { - PolicyWithoutOptionalOps::construct = [&](void* a1, Slot* a2, Slot a3) { - construct.Call(a1, a2, std::move(a3)); - }; - PolicyWithoutOptionalOps::destroy = [&](void* a1, Slot* a2) { - destroy.Call(a1, a2); - }; - - PolicyWithoutOptionalOps::element = [&](Slot* a1) -> Slot& { - return element.Call(a1); - }; PolicyWithoutOptionalOps::apply_impl = [&](int a1) -> int { return apply.Call(a1); }; PolicyWithoutOptionalOps::value = [&](Slot* a1) -> Slot& { return value.Call(a1); }; - - PolicyWithOptionalOps::transfer = [&](void* a1, Slot* a2, Slot* a3) { - return transfer.Call(a1, a2, a3); - }; } std::allocator<int> alloc; int a = 53; - - MockFunction<void(void*, Slot*, Slot)> construct; - MockFunction<void(void*, Slot*)> destroy; - - MockFunction<Slot&(Slot*)> element; MockFunction<int(int)> apply; MockFunction<Slot&(Slot*)> value; - - MockFunction<void(void*, Slot*, Slot*)> transfer; }; -TEST_F(Test, construct) { - EXPECT_CALL(construct, Call(&alloc, &a, 53)); - hash_policy_traits<PolicyWithoutOptionalOps>::construct(&alloc, &a, 53); -} - -TEST_F(Test, destroy) { - EXPECT_CALL(destroy, Call(&alloc, &a)); - hash_policy_traits<PolicyWithoutOptionalOps>::destroy(&alloc, &a); -} - -TEST_F(Test, element) { - int b = 0; - EXPECT_CALL(element, Call(&a)).WillOnce(ReturnRef(b)); - EXPECT_EQ(&b, &hash_policy_traits<PolicyWithoutOptionalOps>::element(&a)); -} - TEST_F(Test, apply) { EXPECT_CALL(apply, Call(42)).WillOnce(Return(1337)); EXPECT_EQ(1337, (hash_policy_traits<PolicyWithoutOptionalOps>::apply(42))); @@ -124,20 +74,6 @@ TEST_F(Test, value) { EXPECT_EQ(&b, &hash_policy_traits<PolicyWithoutOptionalOps>::value(&a)); } -TEST_F(Test, without_transfer) { - int b = 42; - EXPECT_CALL(element, Call(&b)).WillOnce(::testing::ReturnRef(b)); - EXPECT_CALL(construct, Call(&alloc, &a, b)); - EXPECT_CALL(destroy, Call(&alloc, &b)); - hash_policy_traits<PolicyWithoutOptionalOps>::transfer(&alloc, &a, &b); -} - -TEST_F(Test, with_transfer) { - int b = 42; - EXPECT_CALL(transfer, Call(&alloc, &a, &b)); - hash_policy_traits<PolicyWithOptionalOps>::transfer(&alloc, &a, &b); -} - } // namespace } // namespace container_internal ABSL_NAMESPACE_END diff --git a/absl/container/internal/hashtablez_sampler.cc b/absl/container/internal/hashtablez_sampler.cc index efc1be58..6b6d3491 100644 --- a/absl/container/internal/hashtablez_sampler.cc +++ b/absl/container/internal/hashtablez_sampler.cc @@ -14,6 +14,7 @@ #include "absl/container/internal/hashtablez_sampler.h" +#include <algorithm> #include <atomic> #include <cassert> #include <cmath> @@ -158,6 +159,43 @@ void UnsampleSlow(HashtablezInfo* info) { GlobalHashtablezSampler().Unregister(info); } +void RecordRehashSlow(HashtablezInfo* info, size_t total_probe_length) { +#ifdef ABSL_INTERNAL_HAVE_SSE2 + total_probe_length /= 16; +#else + total_probe_length /= 8; +#endif + info->total_probe_length.store(total_probe_length, std::memory_order_relaxed); + info->num_erases.store(0, std::memory_order_relaxed); + // There is only one concurrent writer, so `load` then `store` is sufficient + // instead of using `fetch_add`. + info->num_rehashes.store( + 1 + info->num_rehashes.load(std::memory_order_relaxed), + std::memory_order_relaxed); +} + +void RecordReservationSlow(HashtablezInfo* info, size_t target_capacity) { + info->max_reserve.store( + (std::max)(info->max_reserve.load(std::memory_order_relaxed), + target_capacity), + std::memory_order_relaxed); +} + +void RecordClearedReservationSlow(HashtablezInfo* info) { + info->max_reserve.store(0, std::memory_order_relaxed); +} + +void RecordStorageChangedSlow(HashtablezInfo* info, size_t size, + size_t capacity) { + info->size.store(size, std::memory_order_relaxed); + info->capacity.store(capacity, std::memory_order_relaxed); + if (size == 0) { + // This is a clear, reset the total/num_erases too. + info->total_probe_length.store(0, std::memory_order_relaxed); + info->num_erases.store(0, std::memory_order_relaxed); + } +} + void RecordInsertSlow(HashtablezInfo* info, size_t hash, size_t distance_from_desired) { // SwissTables probe in groups of 16, so scale this to count items probes and @@ -180,6 +218,14 @@ void RecordInsertSlow(HashtablezInfo* info, size_t hash, info->size.fetch_add(1, std::memory_order_relaxed); } +void RecordEraseSlow(HashtablezInfo* info) { + info->size.fetch_sub(1, std::memory_order_relaxed); + // There is only one concurrent writer, so `load` then `store` is sufficient + // instead of using `fetch_add`. + info->num_erases.store(1 + info->num_erases.load(std::memory_order_relaxed), + std::memory_order_relaxed); +} + void SetHashtablezConfigListener(HashtablezConfigListener l) { g_hashtablez_config_listener.store(l, std::memory_order_release); } @@ -215,21 +261,20 @@ void SetHashtablezSampleParameterInternal(int32_t rate) { } } -int32_t GetHashtablezMaxSamples() { +size_t GetHashtablezMaxSamples() { return GlobalHashtablezSampler().GetMaxSamples(); } -void SetHashtablezMaxSamples(int32_t max) { +void SetHashtablezMaxSamples(size_t max) { SetHashtablezMaxSamplesInternal(max); TriggerHashtablezConfigListener(); } -void SetHashtablezMaxSamplesInternal(int32_t max) { +void SetHashtablezMaxSamplesInternal(size_t max) { if (max > 0) { GlobalHashtablezSampler().SetMaxSamples(max); } else { - ABSL_RAW_LOG(ERROR, "Invalid hashtablez max samples: %lld", - static_cast<long long>(max)); // NOLINT(runtime/int) + ABSL_RAW_LOG(ERROR, "Invalid hashtablez max samples: 0"); } } diff --git a/absl/container/internal/hashtablez_sampler.h b/absl/container/internal/hashtablez_sampler.h index d4016d8a..d8fd8f34 100644 --- a/absl/container/internal/hashtablez_sampler.h +++ b/absl/container/internal/hashtablez_sampler.h @@ -95,55 +95,19 @@ struct HashtablezInfo : public profiling_internal::Sample<HashtablezInfo> { size_t inline_element_size; // How big is the slot? }; -inline void RecordRehashSlow(HashtablezInfo* info, size_t total_probe_length) { -#ifdef ABSL_INTERNAL_HAVE_SSE2 - total_probe_length /= 16; -#else - total_probe_length /= 8; -#endif - info->total_probe_length.store(total_probe_length, std::memory_order_relaxed); - info->num_erases.store(0, std::memory_order_relaxed); - // There is only one concurrent writer, so `load` then `store` is sufficient - // instead of using `fetch_add`. - info->num_rehashes.store( - 1 + info->num_rehashes.load(std::memory_order_relaxed), - std::memory_order_relaxed); -} +void RecordRehashSlow(HashtablezInfo* info, size_t total_probe_length); -inline void RecordReservationSlow(HashtablezInfo* info, - size_t target_capacity) { - info->max_reserve.store( - (std::max)(info->max_reserve.load(std::memory_order_relaxed), - target_capacity), - std::memory_order_relaxed); -} +void RecordReservationSlow(HashtablezInfo* info, size_t target_capacity); -inline void RecordClearedReservationSlow(HashtablezInfo* info) { - info->max_reserve.store(0, std::memory_order_relaxed); -} +void RecordClearedReservationSlow(HashtablezInfo* info); -inline void RecordStorageChangedSlow(HashtablezInfo* info, size_t size, - size_t capacity) { - info->size.store(size, std::memory_order_relaxed); - info->capacity.store(capacity, std::memory_order_relaxed); - if (size == 0) { - // This is a clear, reset the total/num_erases too. - info->total_probe_length.store(0, std::memory_order_relaxed); - info->num_erases.store(0, std::memory_order_relaxed); - } -} +void RecordStorageChangedSlow(HashtablezInfo* info, size_t size, + size_t capacity); void RecordInsertSlow(HashtablezInfo* info, size_t hash, size_t distance_from_desired); -inline void RecordEraseSlow(HashtablezInfo* info) { - info->size.fetch_sub(1, std::memory_order_relaxed); - // There is only one concurrent writer, so `load` then `store` is sufficient - // instead of using `fetch_add`. - info->num_erases.store( - 1 + info->num_erases.load(std::memory_order_relaxed), - std::memory_order_relaxed); -} +void RecordEraseSlow(HashtablezInfo* info); struct SamplingState { int64_t next_sample; @@ -165,7 +129,10 @@ class HashtablezInfoHandle { public: explicit HashtablezInfoHandle() : info_(nullptr) {} explicit HashtablezInfoHandle(HashtablezInfo* info) : info_(info) {} - ~HashtablezInfoHandle() { + + // We do not have a destructor. Caller is responsible for calling Unregister + // before destroying the handle. + void Unregister() { if (ABSL_PREDICT_TRUE(info_ == nullptr)) return; UnsampleSlow(info_); } @@ -230,6 +197,7 @@ class HashtablezInfoHandle { explicit HashtablezInfoHandle() = default; explicit HashtablezInfoHandle(std::nullptr_t) {} + inline void Unregister() {} inline void RecordStorageChanged(size_t /*size*/, size_t /*capacity*/) {} inline void RecordRehash(size_t /*total_probe_length*/) {} inline void RecordReservation(size_t /*target_capacity*/) {} @@ -281,9 +249,9 @@ void SetHashtablezSampleParameter(int32_t rate); void SetHashtablezSampleParameterInternal(int32_t rate); // Sets a soft max for the number of samples that will be kept. -int32_t GetHashtablezMaxSamples(); -void SetHashtablezMaxSamples(int32_t max); -void SetHashtablezMaxSamplesInternal(int32_t max); +size_t GetHashtablezMaxSamples(); +void SetHashtablezMaxSamples(size_t max); +void SetHashtablezMaxSamplesInternal(size_t max); // Configuration override. // This allows process-wide sampling without depending on order of diff --git a/absl/container/internal/inlined_vector.h b/absl/container/internal/inlined_vector.h index 54c92a01..0398f530 100644 --- a/absl/container/internal/inlined_vector.h +++ b/absl/container/internal/inlined_vector.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef ABSL_CONTAINER_INTERNAL_INLINED_VECTOR_INTERNAL_H_ -#define ABSL_CONTAINER_INTERNAL_INLINED_VECTOR_INTERNAL_H_ +#ifndef ABSL_CONTAINER_INTERNAL_INLINED_VECTOR_H_ +#define ABSL_CONTAINER_INTERNAL_INLINED_VECTOR_H_ #include <algorithm> #include <cstddef> @@ -83,6 +83,11 @@ using IsMemcpyOk = absl::is_trivially_copy_assignable<ValueType<A>>, absl::is_trivially_destructible<ValueType<A>>>; +template <typename A> +using IsMoveAssignOk = std::is_move_assignable<ValueType<A>>; +template <typename A> +using IsSwapOk = absl::type_traits_internal::IsSwappable<ValueType<A>>; + template <typename T> struct TypeIdentity { using type = T; @@ -120,8 +125,8 @@ struct DestroyAdapter<A, /* IsTriviallyDestructible */ true> { template <typename A> struct Allocation { - Pointer<A> data; - SizeType<A> capacity; + Pointer<A> data = nullptr; + SizeType<A> capacity = 0; }; template <typename A, @@ -297,6 +302,20 @@ class ConstructionTransaction { template <typename T, size_t N, typename A> class Storage { public: + struct MemcpyPolicy {}; + struct ElementwiseAssignPolicy {}; + struct ElementwiseSwapPolicy {}; + struct ElementwiseConstructPolicy {}; + + using MoveAssignmentPolicy = absl::conditional_t< + IsMemcpyOk<A>::value, MemcpyPolicy, + absl::conditional_t<IsMoveAssignOk<A>::value, ElementwiseAssignPolicy, + ElementwiseConstructPolicy>>; + using SwapPolicy = absl::conditional_t< + IsMemcpyOk<A>::value, MemcpyPolicy, + absl::conditional_t<IsSwapOk<A>::value, ElementwiseSwapPolicy, + ElementwiseConstructPolicy>>; + static SizeType<A> NextCapacity(SizeType<A> current_capacity) { return current_capacity * 2; } @@ -360,7 +379,9 @@ class Storage { return data_.allocated.allocated_capacity; } - SizeType<A> GetInlinedCapacity() const { return static_cast<SizeType<A>>(N); } + SizeType<A> GetInlinedCapacity() const { + return static_cast<SizeType<A>>(kOptimalInlinedSize); + } StorageView<A> MakeStorageView() { return GetIsAllocated() ? StorageView<A>{GetAllocatedData(), GetSize(), @@ -464,8 +485,15 @@ class Storage { SizeType<A> allocated_capacity; }; + // `kOptimalInlinedSize` is an automatically adjusted inlined capacity of the + // `InlinedVector`. Sometimes, it is possible to increase the capacity (from + // the user requested `N`) without increasing the size of the `InlinedVector`. + static constexpr size_t kOptimalInlinedSize = + (std::max)(N, sizeof(Allocated) / sizeof(ValueType<A>)); + struct Inlined { - alignas(ValueType<A>) char inlined_data[sizeof(ValueType<A>[N])]; + alignas(ValueType<A>) char inlined_data[sizeof( + ValueType<A>[kOptimalInlinedSize])]; }; union Data { @@ -473,6 +501,13 @@ class Storage { Inlined inlined; }; + void SwapN(ElementwiseSwapPolicy, Storage* other, SizeType<A> n); + void SwapN(ElementwiseConstructPolicy, Storage* other, SizeType<A> n); + + void SwapInlinedElements(MemcpyPolicy, Storage* other); + template <typename NotMemcpyPolicy> + void SwapInlinedElements(NotMemcpyPolicy, Storage* other); + template <typename... Args> ABSL_ATTRIBUTE_NOINLINE Reference<A> EmplaceBackSlow(Args&&... args); @@ -641,8 +676,8 @@ auto Storage<T, N, A>::Insert(ConstIterator<A> pos, ValueAdapter values, SizeType<A> insert_count) -> Iterator<A> { StorageView<A> storage_view = MakeStorageView(); - SizeType<A> insert_index = - std::distance(ConstIterator<A>(storage_view.data), pos); + auto insert_index = static_cast<SizeType<A>>( + std::distance(ConstIterator<A>(storage_view.data), pos)); SizeType<A> insert_end_index = insert_index + insert_count; SizeType<A> new_size = storage_view.size + insert_count; @@ -784,9 +819,9 @@ auto Storage<T, N, A>::Erase(ConstIterator<A> from, ConstIterator<A> to) -> Iterator<A> { StorageView<A> storage_view = MakeStorageView(); - SizeType<A> erase_size = std::distance(from, to); - SizeType<A> erase_index = - std::distance(ConstIterator<A>(storage_view.data), from); + auto erase_size = static_cast<SizeType<A>>(std::distance(from, to)); + auto erase_index = static_cast<SizeType<A>>( + std::distance(ConstIterator<A>(storage_view.data), from)); SizeType<A> erase_end_index = erase_index + erase_size; IteratorValueAdapter<A, MoveIterator<A>> move_values( @@ -886,26 +921,7 @@ auto Storage<T, N, A>::Swap(Storage* other_storage_ptr) -> void { if (GetIsAllocated() && other_storage_ptr->GetIsAllocated()) { swap(data_.allocated, other_storage_ptr->data_.allocated); } else if (!GetIsAllocated() && !other_storage_ptr->GetIsAllocated()) { - Storage* small_ptr = this; - Storage* large_ptr = other_storage_ptr; - if (small_ptr->GetSize() > large_ptr->GetSize()) swap(small_ptr, large_ptr); - - for (SizeType<A> i = 0; i < small_ptr->GetSize(); ++i) { - swap(small_ptr->GetInlinedData()[i], large_ptr->GetInlinedData()[i]); - } - - IteratorValueAdapter<A, MoveIterator<A>> move_values( - MoveIterator<A>(large_ptr->GetInlinedData() + small_ptr->GetSize())); - - ConstructElements<A>(large_ptr->GetAllocator(), - small_ptr->GetInlinedData() + small_ptr->GetSize(), - move_values, - large_ptr->GetSize() - small_ptr->GetSize()); - - DestroyAdapter<A>::DestroyElements( - large_ptr->GetAllocator(), - large_ptr->GetInlinedData() + small_ptr->GetSize(), - large_ptr->GetSize() - small_ptr->GetSize()); + SwapInlinedElements(SwapPolicy{}, other_storage_ptr); } else { Storage* allocated_ptr = this; Storage* inlined_ptr = other_storage_ptr; @@ -941,6 +957,68 @@ auto Storage<T, N, A>::Swap(Storage* other_storage_ptr) -> void { swap(GetAllocator(), other_storage_ptr->GetAllocator()); } +template <typename T, size_t N, typename A> +void Storage<T, N, A>::SwapN(ElementwiseSwapPolicy, Storage* other, + SizeType<A> n) { + std::swap_ranges(GetInlinedData(), GetInlinedData() + n, + other->GetInlinedData()); +} + +template <typename T, size_t N, typename A> +void Storage<T, N, A>::SwapN(ElementwiseConstructPolicy, Storage* other, + SizeType<A> n) { + Pointer<A> a = GetInlinedData(); + Pointer<A> b = other->GetInlinedData(); + // see note on allocators in `SwapInlinedElements`. + A& allocator_a = GetAllocator(); + A& allocator_b = other->GetAllocator(); + for (SizeType<A> i = 0; i < n; ++i, ++a, ++b) { + ValueType<A> tmp(std::move(*a)); + + AllocatorTraits<A>::destroy(allocator_a, a); + AllocatorTraits<A>::construct(allocator_b, a, std::move(*b)); + + AllocatorTraits<A>::destroy(allocator_b, b); + AllocatorTraits<A>::construct(allocator_a, b, std::move(tmp)); + } +} + +template <typename T, size_t N, typename A> +void Storage<T, N, A>::SwapInlinedElements(MemcpyPolicy, Storage* other) { + Data tmp = data_; + data_ = other->data_; + other->data_ = tmp; +} + +template <typename T, size_t N, typename A> +template <typename NotMemcpyPolicy> +void Storage<T, N, A>::SwapInlinedElements(NotMemcpyPolicy policy, + Storage* other) { + // Note: `destroy` needs to use pre-swap allocator while `construct` - + // post-swap allocator. Allocators will be swaped later on outside of + // `SwapInlinedElements`. + Storage* small_ptr = this; + Storage* large_ptr = other; + if (small_ptr->GetSize() > large_ptr->GetSize()) { + std::swap(small_ptr, large_ptr); + } + + auto small_size = small_ptr->GetSize(); + auto diff = large_ptr->GetSize() - small_size; + SwapN(policy, other, small_size); + + IteratorValueAdapter<A, MoveIterator<A>> move_values( + MoveIterator<A>(large_ptr->GetInlinedData() + small_size)); + + ConstructElements<A>(large_ptr->GetAllocator(), + small_ptr->GetInlinedData() + small_size, move_values, + diff); + + DestroyAdapter<A>::DestroyElements(large_ptr->GetAllocator(), + large_ptr->GetInlinedData() + small_size, + diff); +} + // End ignore "array-bounds" #if !defined(__clang__) && defined(__GNUC__) #pragma GCC diagnostic pop @@ -950,4 +1028,4 @@ auto Storage<T, N, A>::Swap(Storage* other_storage_ptr) -> void { ABSL_NAMESPACE_END } // namespace absl -#endif // ABSL_CONTAINER_INTERNAL_INLINED_VECTOR_INTERNAL_H_ +#endif // ABSL_CONTAINER_INTERNAL_INLINED_VECTOR_H_ diff --git a/absl/container/internal/raw_hash_set.cc b/absl/container/internal/raw_hash_set.cc index c63a2e02..3677ac59 100644 --- a/absl/container/internal/raw_hash_set.cc +++ b/absl/container/internal/raw_hash_set.cc @@ -16,6 +16,7 @@ #include <atomic> #include <cstddef> +#include <cstring> #include "absl/base/config.h" @@ -25,11 +26,14 @@ 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(). -alignas(16) ABSL_CONST_INIT ABSL_DLL const ctrl_t kEmptyGroup[16] = { +// 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] = { 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}; + ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, + static_cast<ctrl_t>(0)}; #ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL constexpr size_t Group::kWidth; @@ -63,8 +67,155 @@ void ConvertDeletedToEmptyAndFullToDeleted(ctrl_t* ctrl, size_t capacity) { std::memcpy(ctrl + capacity + 1, ctrl, NumClonedBytes()); ctrl[capacity] = ctrl_t::kSentinel; } -// Extern template instantiotion for inline function. -template FindInfo find_first_non_full(const ctrl_t*, size_t, size_t); +// Extern template instantiation for inline function. +template FindInfo find_first_non_full(const CommonFields&, size_t); + +FindInfo find_first_non_full_outofline(const CommonFields& common, + size_t hash) { + 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. +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. +static inline void* PrevSlot(void* slot, size_t slot_size) { + return reinterpret_cast<void*>(reinterpret_cast<uintptr_t>(slot) - 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_; + assert(IsValidCapacity(capacity)); + assert(!is_small(capacity)); + // Algorithm: + // - mark all DELETED slots as EMPTY + // - mark all FULL slots as DELETED + // - for each slot marked as DELETED + // hash = Hash(element) + // target = find_first_non_full(hash) + // if target is in the same group + // mark slot as FULL + // else if target is EMPTY + // transfer element to target + // mark slot as EMPTY + // mark target as FULL + // else if target is DELETED + // 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_; + ConvertDeletedToEmptyAndFullToDeleted(ctrl, capacity); + auto hasher = policy.hash_slot; + auto transfer = policy.transfer; + const size_t slot_size = policy.slot_size; + + size_t total_probe_length = 0; + void* slot_ptr = SlotAddress(slot_array, 0, slot_size); + for (size_t i = 0; i != capacity; + ++i, slot_ptr = NextSlot(slot_ptr, slot_size)) { + assert(slot_ptr == SlotAddress(slot_array, i, slot_size)); + if (!IsDeleted(ctrl[i])) continue; + const size_t hash = (*hasher)(set, slot_ptr); + const FindInfo target = find_first_non_full(common, hash); + const size_t new_i = target.offset; + total_probe_length += target.probe_length; + + // Verify if the old and new i fall within the same group wrt the hash. + // If they do, we don't need to move the object as it falls already in the + // best probe we can. + const size_t probe_offset = probe(common, hash).offset(); + const auto probe_index = [probe_offset, capacity](size_t pos) { + return ((pos - probe_offset) & capacity) / Group::kWidth; + }; + + // Element doesn't move. + if (ABSL_PREDICT_TRUE(probe_index(new_i) == probe_index(i))) { + SetCtrl(common, i, H2(hash), slot_size); + continue; + } + + void* new_slot_ptr = SlotAddress(slot_array, new_i, slot_size); + if (IsEmpty(ctrl[new_i])) { + // Transfer element to the empty spot. + // SetCtrl poisons/unpoisons the slots so we have to call it at the + // right time. + SetCtrl(common, new_i, H2(hash), slot_size); + (*transfer)(set, new_slot_ptr, slot_ptr); + SetCtrl(common, i, ctrl_t::kEmpty, slot_size); + } else { + assert(IsDeleted(ctrl[new_i])); + SetCtrl(common, new_i, H2(hash), slot_size); + // Until we are done rehashing, DELETED marks previously FULL slots. + + // Swap i and new_i elements. + (*transfer)(set, tmp_space, new_slot_ptr); + (*transfer)(set, new_slot_ptr, slot_ptr); + (*transfer)(set, slot_ptr, tmp_space); + + // repeat the processing of the ith slot + --i; + slot_ptr = PrevSlot(slot_ptr, slot_size); + } + } + ResetGrowthLeft(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(); + + // 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); + c.infoz().RecordErase(); +} + +void ClearBackingArray(CommonFields& c, const PolicyFunctions& policy, + bool reuse) { + c.size_ = 0; + if (reuse) { + ResetCtrl(c, policy.slot_size); + 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; + c.infoz().RecordClearedReservation(); + assert(c.size_ == 0); + c.infoz().RecordStorageChanged(0, 0); + } +} } // namespace container_internal ABSL_NAMESPACE_END diff --git a/absl/container/internal/raw_hash_set.h b/absl/container/internal/raw_hash_set.h index ea912f83..61ef196d 100644 --- a/absl/container/internal/raw_hash_set.h +++ b/absl/container/internal/raw_hash_set.h @@ -186,6 +186,7 @@ #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/optimization.h" #include "absl/base/port.h" #include "absl/container/internal/common.h" @@ -219,6 +220,29 @@ namespace absl { ABSL_NAMESPACE_BEGIN 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_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 +// the control bytes and the slots. When iterators are dereferenced, we assert +// that the container has not been mutated in a way that could cause iterator +// invalidation since the iterator was initialized. +#define ABSL_SWISSTABLE_ENABLE_GENERATIONS +#endif + +// We use uint8_t so we don't need to worry about padding. +using GenerationType = uint8_t; + +#ifdef ABSL_SWISSTABLE_ENABLE_GENERATIONS +constexpr bool SwisstableGenerationsEnabled() { return true; } +constexpr size_t NumGenerationBytes() { return sizeof(GenerationType); } +#else +constexpr bool SwisstableGenerationsEnabled() { return false; } +constexpr size_t NumGenerationBytes() { return 0; } +#endif + template <typename AllocType> void SwapAlloc(AllocType& lhs, AllocType& rhs, std::true_type /* propagate_on_container_swap */) { @@ -451,7 +475,7 @@ 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[16]; +ABSL_DLL extern const ctrl_t kEmptyGroup[17]; // Returns a pointer to a control byte group that can be used by empty tables. inline ctrl_t* EmptyGroup() { @@ -460,6 +484,12 @@ inline ctrl_t* EmptyGroup() { return const_cast<ctrl_t*>(kEmptyGroup); } +// 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); +} + // Mixes a randomly generated per-process seed with `hash` and `ctrl` to // randomize insertion order within groups. bool ShouldInsertBackwards(size_t hash, const ctrl_t* ctrl); @@ -545,7 +575,7 @@ struct GroupSse2Impl { // Returns a bitmask representing the positions of slots that match hash. BitMask<uint32_t, kWidth> Match(h2_t hash) const { - auto match = _mm_set1_epi8(hash); + 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)))); } @@ -557,7 +587,7 @@ struct GroupSse2Impl { return NonIterableBitMask<uint32_t, kWidth>( static_cast<uint32_t>(_mm_movemask_epi8(_mm_sign_epi8(ctrl, ctrl)))); #else - auto match = _mm_set1_epi8(static_cast<h2_t>(ctrl_t::kEmpty)); + 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)))); #endif @@ -565,14 +595,14 @@ struct GroupSse2Impl { // Returns a bitmask representing the positions of empty or deleted slots. NonIterableBitMask<uint32_t, kWidth> MaskEmptyOrDeleted() const { - auto special = _mm_set1_epi8(static_cast<uint8_t>(ctrl_t::kSentinel)); + auto special = _mm_set1_epi8(static_cast<char>(ctrl_t::kSentinel)); return NonIterableBitMask<uint32_t, kWidth>(static_cast<uint32_t>( _mm_movemask_epi8(_mm_cmpgt_epi8_fixed(special, ctrl)))); } // Returns the number of trailing empty or deleted elements in the group. uint32_t CountLeadingEmptyOrDeleted() const { - auto special = _mm_set1_epi8(static_cast<uint8_t>(ctrl_t::kSentinel)); + auto special = _mm_set1_epi8(static_cast<char>(ctrl_t::kSentinel)); return TrailingZeros(static_cast<uint32_t>( _mm_movemask_epi8(_mm_cmpgt_epi8_fixed(special, ctrl)) + 1)); } @@ -612,9 +642,9 @@ struct GroupAArch64Impl { NonIterableBitMask<uint64_t, kWidth, 3> MaskEmpty() const { uint64_t mask = - vget_lane_u64(vreinterpret_u64_u8( - vceq_s8(vdup_n_s8(static_cast<h2_t>(ctrl_t::kEmpty)), - vreinterpret_s8_u8(ctrl))), + vget_lane_u64(vreinterpret_u64_u8(vceq_s8( + vdup_n_s8(static_cast<int8_t>(ctrl_t::kEmpty)), + vreinterpret_s8_u8(ctrl))), 0); return NonIterableBitMask<uint64_t, kWidth, 3>(mask); } @@ -629,13 +659,16 @@ struct GroupAArch64Impl { } uint32_t CountLeadingEmptyOrDeleted() const { - uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(ctrl), 0); - // ctrl | ~(ctrl >> 7) will have the lowest bit set to zero for kEmpty and - // kDeleted. We lower all other bits and count number of trailing zeros. + uint64_t mask = + vget_lane_u64(vreinterpret_u64_u8(vcle_s8( + vdup_n_s8(static_cast<int8_t>(ctrl_t::kSentinel)), + vreinterpret_s8_u8(ctrl))), + 0); + // Similar to MaskEmptyorDeleted() but we invert the logic to invert the + // produced bitfield. We then count number of trailing zeros. // Clang and GCC optimize countr_zero to rbit+clz without any check for 0, // so we should be fine. - constexpr uint64_t bits = 0x0101010101010101ULL; - return countr_zero((mask | ~(mask >> 7)) & bits) >> 3; + return static_cast<uint32_t>(countr_zero(mask)) >> 3; } void ConvertSpecialToEmptyAndFullToDeleted(ctrl_t* dst) const { @@ -693,7 +726,8 @@ struct GroupPortableImpl { // ctrl | ~(ctrl >> 7) will have the lowest bit set to zero for kEmpty and // kDeleted. We lower all other bits and count number of trailing zeros. constexpr uint64_t bits = 0x0101010101010101ULL; - return countr_zero((ctrl | ~(ctrl >> 7)) & bits) >> 3; + return static_cast<uint32_t>(countr_zero((ctrl | ~(ctrl >> 7)) & bits) >> + 3); } void ConvertSpecialToEmptyAndFullToDeleted(ctrl_t* dst) const { @@ -715,6 +749,192 @@ using Group = GroupAArch64Impl; using Group = GroupPortableImpl; #endif +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 + // insertions take place, reserved_growth_'s state machine is N, ..., 1, + // kReservedGrowthJustRanOut, 0. + static constexpr size_t kReservedGrowthJustRanOut = + (std::numeric_limits<size_t>::max)(); + + public: + CommonFieldsGenerationInfoEnabled() = default; + CommonFieldsGenerationInfoEnabled(CommonFieldsGenerationInfoEnabled&& that) + : reserved_growth_(that.reserved_growth_), generation_(that.generation_) { + that.reserved_growth_ = 0; + that.generation_ = EmptyGeneration(); + } + CommonFieldsGenerationInfoEnabled& operator=( + CommonFieldsGenerationInfoEnabled&&) = default; + + // 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 + // whenever reserved_growth_ is zero. + bool should_rehash_for_bug_detection_on_insert() const { + return reserved_growth_ == kReservedGrowthJustRanOut; + } + 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_; + } + } + 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; } + GenerationType generation() const { return *generation_; } + void set_generation(GenerationType g) { *generation_ = g; } + GenerationType* generation_ptr() const { return generation_; } + void set_generation_ptr(GenerationType* g) { generation_ = g; } + + 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 + // reservation size because calls to erase() decrease size_ but don't decrease + // reserved growth. + size_t reserved_growth_ = 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 + // keep a pointer to the container in the iterators because iterators must + // remain valid when the container is moved. + // Note: we could derive this pointer from the control pointer, but it makes + // the code more complicated, and there's a benefit in having the sizes of + // raw_hash_set in sanitizer mode and non-sanitizer mode a bit more different, + // which is that tests are less likely to rely on the size remaining the same. + GenerationType* generation_ = EmptyGeneration(); +}; + +class CommonFieldsGenerationInfoDisabled { + public: + CommonFieldsGenerationInfoDisabled() = default; + CommonFieldsGenerationInfoDisabled(CommonFieldsGenerationInfoDisabled&&) = + default; + CommonFieldsGenerationInfoDisabled& operator=( + CommonFieldsGenerationInfoDisabled&&) = default; + + bool should_rehash_for_bug_detection_on_insert() const { return false; } + void maybe_increment_generation_on_insert() {} + void reset_reserved_growth(size_t, size_t) {} + size_t reserved_growth() const { return 0; } + void set_reserved_growth(size_t) {} + GenerationType generation() const { return 0; } + void set_generation(GenerationType) {} + GenerationType* generation_ptr() const { return nullptr; } + void set_generation_ptr(GenerationType*) {} +}; + +class HashSetIteratorGenerationInfoEnabled { + public: + HashSetIteratorGenerationInfoEnabled() = default; + explicit HashSetIteratorGenerationInfoEnabled( + const GenerationType* generation_ptr) + : generation_ptr_(generation_ptr), generation_(*generation_ptr) {} + + GenerationType generation() const { return generation_; } + void reset_generation() { generation_ = *generation_ptr_; } + const GenerationType* generation_ptr() const { return generation_ptr_; } + void set_generation_ptr(const GenerationType* ptr) { generation_ptr_ = ptr; } + + private: + const GenerationType* generation_ptr_ = EmptyGeneration(); + GenerationType generation_ = *generation_ptr_; +}; + +class HashSetIteratorGenerationInfoDisabled { + public: + HashSetIteratorGenerationInfoDisabled() = default; + explicit HashSetIteratorGenerationInfoDisabled(const GenerationType*) {} + + GenerationType generation() const { return 0; } + void reset_generation() {} + const GenerationType* generation_ptr() const { return nullptr; } + void set_generation_ptr(const GenerationType*) {} +}; + +#ifdef ABSL_SWISSTABLE_ENABLE_GENERATIONS +using CommonFieldsGenerationInfo = CommonFieldsGenerationInfoEnabled; +using HashSetIteratorGenerationInfo = HashSetIteratorGenerationInfoEnabled; +#else +using CommonFieldsGenerationInfo = CommonFieldsGenerationInfoDisabled; +using HashSetIteratorGenerationInfo = HashSetIteratorGenerationInfoDisabled; +#endif + +// 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. +class CommonFields : public CommonFieldsGenerationInfo { + public: + CommonFields() = default; + + // Not copyable + CommonFields(const CommonFields&) = delete; + 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& operator=(CommonFields&&) = default; + + // The number of slots we can still fill without needing to rehash. + size_t& growth_left() { return compressed_tuple_.template get<0>(); } + + HashtablezInfoHandle& infoz() { return compressed_tuple_.template get<1>(); } + const HashtablezInfoHandle& infoz() const { + return compressed_tuple_.template get<1>(); + } + + void reset_reserved_growth(size_t reservation) { + CommonFieldsGenerationInfo::reset_reserved_growth(reservation, size_); + } + + // TODO(b/259599413): 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). + // + // This contains `capacity + 1 + NumClonedBytes()` entries, even + // when the table is empty (hence EmptyGroup). + 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. + 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{}}; +}; + // Returns he number of "cloned control bytes". // // This is the number of control bytes that are present both at the beginning @@ -730,6 +950,12 @@ class raw_hash_set; // 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); + return n * 2 + 1; +} + // Applies the following mapping to every byte in the control array: // * kDeleted -> kEmpty // * kEmpty -> kEmpty @@ -795,15 +1021,69 @@ size_t SelectBucketCountForIterRange(InputIter first, InputIter last, return 0; } -#define ABSL_INTERNAL_ASSERT_IS_FULL(ctrl, msg) \ - ABSL_HARDENING_ASSERT((ctrl != nullptr && IsFull(*ctrl)) && msg) +#define ABSL_INTERNAL_ASSERT_IS_FULL(ctrl, 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) + +// 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 the two iterators come from the same container, then their pointers will +// interleave such that ctrl_a <= ctrl_b < slot_a <= slot_b or vice/versa. +// 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). +inline bool AreItersFromSameContainer(const ctrl_t* ctrl_a, + const ctrl_t* ctrl_b, + const void* const& slot_a, + const void* const& slot_b) { + // If either control byte is null, then we can't tell. + if (ctrl_a == nullptr || ctrl_b == nullptr) return true; + const void* low_slot = slot_a; + const void* hi_slot = slot_b; + if (ctrl_a > ctrl_b) { + std::swap(ctrl_a, ctrl_b); + std::swap(low_slot, hi_slot); + } + return ctrl_b < low_slot && low_slot <= hi_slot; +} -inline void AssertIsValid(ctrl_t* ctrl) { +// 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( - (ctrl == nullptr || IsFull(*ctrl)) && - "Invalid operation on iterator. The element might have " - "been erased, the table might have rehashed, or this may " - "be an end() iterator."); + 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."); } struct FindInfo { @@ -825,9 +1105,10 @@ struct FindInfo { // `ShouldInsertBackwards()` for small tables. inline bool is_small(size_t capacity) { return capacity < Group::kWidth - 1; } -// Begins a probing operation on `ctrl`, using `hash`. -inline probe_seq<Group::kWidth> probe(const ctrl_t* ctrl, size_t hash, - size_t capacity) { +// 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_; return probe_seq<Group::kWidth>(H1(hash, ctrl), capacity); } @@ -839,9 +1120,9 @@ inline probe_seq<Group::kWidth> probe(const ctrl_t* ctrl, size_t hash, // NOTE: this function must work with tables having both empty and deleted // slots in the same group. Such tables appear during `erase()`. template <typename = void> -inline FindInfo find_first_non_full(const ctrl_t* ctrl, size_t hash, - size_t capacity) { - auto seq = probe(ctrl, hash, capacity); +inline FindInfo find_first_non_full(const CommonFields& common, size_t hash) { + auto seq = probe(common, hash); + const ctrl_t* ctrl = common.control_; while (true) { Group g{ctrl + seq.offset()}; auto mask = g.MaskEmptyOrDeleted(); @@ -851,55 +1132,75 @@ inline FindInfo find_first_non_full(const ctrl_t* ctrl, 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(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() <= capacity && "full table!"); + assert(seq.index() <= common.capacity_ && "full table!"); } } // Extern template for inline function keep possibility of inlining. // When compiler decided to not inline, no symbols will be added to the // corresponding translation unit. -extern template FindInfo find_first_non_full(const ctrl_t*, size_t, size_t); +extern template FindInfo find_first_non_full(const CommonFields&, size_t); + +// Non-inlined version of find_first_non_full for use in less +// performance critical routines. +FindInfo find_first_non_full_outofline(const CommonFields&, size_t); + +inline void ResetGrowthLeft(CommonFields& common) { + common.growth_left() = CapacityToGrowth(common.capacity_) - common.size_; +} // Sets `ctrl` to `{kEmpty, kSentinel, ..., kEmpty}`, marking the entire // array as marked as empty. -inline void ResetCtrl(size_t capacity, ctrl_t* ctrl, const void* slot, - size_t slot_size) { +inline void ResetCtrl(CommonFields& common, size_t slot_size) { + 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(slot, slot_size * capacity); + SanitizerPoisonMemoryRegion(common.slots_, slot_size * capacity); + ResetGrowthLeft(common); } // Sets `ctrl[i]` to `h`. // // Unlike setting it directly, this function will perform bounds checks and // mirror the value to the cloned tail if necessary. -inline void SetCtrl(size_t i, ctrl_t h, size_t capacity, ctrl_t* ctrl, - const void* slot, size_t slot_size) { +inline void SetCtrl(const CommonFields& common, size_t i, ctrl_t h, + size_t slot_size) { + const size_t capacity = common.capacity_; assert(i < capacity); - auto* slot_i = static_cast<const char*>(slot) + i * slot_size; + auto* slot_i = static_cast<const char*>(common.slots_) + i * slot_size; if (IsFull(h)) { SanitizerUnpoisonMemoryRegion(slot_i, slot_size); } else { SanitizerPoisonMemoryRegion(slot_i, slot_size); } + ctrl_t* ctrl = common.control_; ctrl[i] = h; ctrl[((i - NumClonedBytes()) & capacity) + (NumClonedBytes() & capacity)] = h; } // Overload for setting to an occupied `h2_t` rather than a special `ctrl_t`. -inline void SetCtrl(size_t i, h2_t h, size_t capacity, ctrl_t* ctrl, - const void* slot, size_t slot_size) { - SetCtrl(i, static_cast<ctrl_t>(h), capacity, ctrl, slot, slot_size); +inline void SetCtrl(const CommonFields& common, size_t i, h2_t h, + size_t slot_size) { + 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; } // Given the capacity of a table, computes the offset (from the start of the @@ -907,7 +1208,8 @@ inline void SetCtrl(size_t i, h2_t h, size_t capacity, ctrl_t* ctrl, 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 + slot_align - 1) & (~slot_align + 1); + return (num_control_bytes + NumGenerationBytes() + slot_align - 1) & + (~slot_align + 1); } // Given the capacity of a table, computes the total size of the backing @@ -916,6 +1218,91 @@ inline size_t AllocSize(size_t capacity, size_t slot_size, size_t slot_align) { return SlotOffset(capacity, slot_align) + capacity * slot_size; } +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); +} + +// PolicyFunctions bundles together some information for a particular +// raw_hash_set<T, ...> instantiation. This information is passed to +// type-erased functions that want to do small amounts of type-specific +// work. +struct PolicyFunctions { + size_t slot_size; + + // Return 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); +}; + +// ClearBackingArray clears the backing array, either modifying it in place, +// or creating a new one based on the value of "reuse". +// REQUIRES: c.capacity > 0 +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); + +// 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) { + // Unpoison before returning the memory to the allocator. + SanitizerUnpoisonMemoryRegion(slot_array, policy.slot_size * n); + + std::allocator<char> alloc; + Deallocate<AlignOfSlot>(&alloc, ctrl, + AllocSize(n, policy.slot_size, AlignOfSlot)); +} + +// For trivially relocatable types we use memcpy directly. This allows us to +// share the same function body for raw_hash_set instantiations that have the +// same slot size as long as they are relocatable. +template <size_t SizeOfSlot> +ABSL_ATTRIBUTE_NOINLINE void TransferRelocatable(void*, void* dst, void* src) { + memcpy(dst, src, SizeOfSlot); +} + +// Type-erased version of raw_hash_set::drop_deletes_without_resize. +void DropDeletesWithoutResize(CommonFields& common, + const PolicyFunctions& policy, void* tmp_space); + // A SwissTable. // // Policy: a policy defines how to perform different operations on @@ -1016,7 +1403,7 @@ class raw_hash_set { static_assert(std::is_same<const_pointer, const value_type*>::value, "Allocators with custom pointer types are not supported"); - class iterator { + class iterator : private HashSetIteratorGenerationInfo { friend class raw_hash_set; public: @@ -1032,22 +1419,22 @@ class raw_hash_set { // PRECONDITION: not an end() iterator. reference operator*() const { - ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, - "operator*() called on invalid iterator."); + ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, generation(), generation_ptr(), + "operator*()"); return PolicyTraits::element(slot_); } // PRECONDITION: not an end() iterator. pointer operator->() const { - ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, - "operator-> called on invalid iterator."); + ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, generation(), generation_ptr(), + "operator->"); return &operator*(); } // PRECONDITION: not an end() iterator. iterator& operator++() { - ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, - "operator++ called on invalid iterator."); + ABSL_INTERNAL_ASSERT_IS_FULL(ctrl_, generation(), generation_ptr(), + "operator++"); ++ctrl_; ++slot_; skip_empty_or_deleted(); @@ -1061,8 +1448,9 @@ class raw_hash_set { } friend bool operator==(const iterator& a, const iterator& b) { - AssertIsValid(a.ctrl_); - AssertIsValid(b.ctrl_); + 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()); return a.ctrl_ == b.ctrl_; } friend bool operator!=(const iterator& a, const iterator& b) { @@ -1070,16 +1458,23 @@ class raw_hash_set { } private: - iterator(ctrl_t* ctrl, slot_type* slot) : ctrl_(ctrl), slot_(slot) { + iterator(ctrl_t* ctrl, slot_type* slot, + const GenerationType* generation_ptr) + : HashSetIteratorGenerationInfo(generation_ptr), + ctrl_(ctrl), + slot_(slot) { // This assumption helps the compiler know that any non-end iterator is // not equal to any end iterator. ABSL_ASSUME(ctrl != nullptr); } + // For end() iterators. + explicit iterator(const GenerationType* generation_ptr) + : HashSetIteratorGenerationInfo(generation_ptr) {} // Fixes up `ctrl_` to point to a full by advancing it and `slot_` until // they reach one. // - // If a sentinel is reached, we null both of them out instead. + // If a sentinel is reached, we null `ctrl_` out instead. void skip_empty_or_deleted() { while (IsEmptyOrDeleted(*ctrl_)) { uint32_t shift = Group{ctrl_}.CountLeadingEmptyOrDeleted(); @@ -1107,9 +1502,9 @@ class raw_hash_set { using pointer = typename raw_hash_set::const_pointer; using difference_type = typename raw_hash_set::difference_type; - const_iterator() {} + const_iterator() = default; // Implicit construction from iterator. - const_iterator(iterator i) : inner_(std::move(i)) {} + const_iterator(iterator i) : inner_(std::move(i)) {} // NOLINT reference operator*() const { return *inner_; } pointer operator->() const { return inner_.operator->(); } @@ -1128,8 +1523,10 @@ class raw_hash_set { } private: - const_iterator(const ctrl_t* ctrl, const slot_type* slot) - : inner_(const_cast<ctrl_t*>(ctrl), const_cast<slot_type*>(slot)) {} + const_iterator(const ctrl_t* ctrl, const slot_type* slot, + const GenerationType* gen) + : inner_(const_cast<ctrl_t*>(ctrl), const_cast<slot_type*>(slot), gen) { + } iterator inner_; }; @@ -1137,18 +1534,20 @@ class raw_hash_set { using node_type = node_handle<Policy, hash_policy_traits<Policy>, Alloc>; using insert_return_type = InsertReturnType<iterator, node_type>; + // 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) {} - explicit raw_hash_set(size_t bucket_count, const hasher& hash = hasher(), - const key_equal& eq = key_equal(), - const allocator_type& alloc = allocator_type()) - : ctrl_(EmptyGroup()), - settings_(0, HashtablezInfoHandle(), hash, eq, alloc) { + ABSL_ATTRIBUTE_NOINLINE explicit raw_hash_set( + size_t bucket_count, const hasher& hash = hasher(), + const key_equal& eq = key_equal(), + const allocator_type& alloc = allocator_type()) + : settings_(CommonFields{}, hash, eq, alloc) { if (bucket_count) { - capacity_ = NormalizeCapacity(bucket_count); + common().capacity_ = NormalizeCapacity(bucket_count); initialize_slots(); } } @@ -1255,45 +1654,30 @@ class raw_hash_set { // than a full `insert`. for (const auto& v : that) { const size_t hash = PolicyTraits::apply(HashElement{hash_ref()}, v); - auto target = find_first_non_full(ctrl_, hash, capacity_); - SetCtrl(target.offset, H2(hash), capacity_, ctrl_, slots_, - sizeof(slot_type)); + auto target = find_first_non_full_outofline(common(), hash); + SetCtrl(common(), target.offset, H2(hash), sizeof(slot_type)); emplace_at(target.offset, v); + common().maybe_increment_generation_on_insert(); infoz().RecordInsert(hash, target.probe_length); } - size_ = that.size(); + common().size_ = that.size(); growth_left() -= that.size(); } - raw_hash_set(raw_hash_set&& that) noexcept( + 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) - : ctrl_(absl::exchange(that.ctrl_, EmptyGroup())), - slots_(absl::exchange(that.slots_, nullptr)), - size_(absl::exchange(that.size_, 0)), - capacity_(absl::exchange(that.capacity_, 0)), - // 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.growth_left(), 0), - absl::exchange(that.infoz(), HashtablezInfoHandle()), + : // 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()) {} raw_hash_set(raw_hash_set&& that, const allocator_type& a) - : ctrl_(EmptyGroup()), - slots_(nullptr), - size_(0), - capacity_(0), - settings_(0, HashtablezInfoHandle(), that.hash_ref(), that.eq_ref(), - a) { + : settings_(CommonFields{}, that.hash_ref(), that.eq_ref(), a) { if (a == that.alloc_ref()) { - std::swap(ctrl_, that.ctrl_); - std::swap(slots_, that.slots_); - std::swap(size_, that.size_); - std::swap(capacity_, that.capacity_); - std::swap(growth_left(), that.growth_left()); - std::swap(infoz(), that.infoz()); + std::swap(common(), that.common()); } else { reserve(that.size()); // Note: this will copy elements of dense_set and unordered_set instead of @@ -1317,30 +1701,43 @@ class raw_hash_set { 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. return move_assign( std::move(that), typename AllocTraits::propagate_on_container_move_assignment()); } - ~raw_hash_set() { destroy_slots(); } + ~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))); + + infoz().Unregister(); + } iterator begin() { auto it = iterator_at(0); it.skip_empty_or_deleted(); return it; } - iterator end() { return {}; } + iterator end() { return iterator(common().generation_ptr()); } const_iterator begin() const { return const_cast<raw_hash_set*>(this)->begin(); } - const_iterator end() const { return {}; } + const_iterator end() const { return iterator(common().generation_ptr()); } const_iterator cbegin() const { return begin(); } const_iterator cend() const { return end(); } bool empty() const { return !size(); } - size_t size() const { return size_; } - size_t capacity() const { return 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() { @@ -1351,22 +1748,26 @@ class raw_hash_set { // compared to destruction of the elements of the container. So we pick the // largest bucket_count() threshold for which iteration is still fast and // past that we simply deallocate the array. - if (capacity_ > 127) { + const size_t cap = capacity(); + if (cap == 0) { + // Already guaranteed to be empty; so nothing to do. + } else { destroy_slots(); + ClearBackingArray(common(), GetPolicyFunctions(), + /*reuse=*/cap < 128); + } + common().set_reserved_growth(0); + } - infoz().RecordClearedReservation(); - } else if (capacity_) { - for (size_t i = 0; i != capacity_; ++i) { - if (IsFull(ctrl_[i])) { - PolicyTraits::destroy(&alloc_ref(), slots_ + i); - } + 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); } - size_ = 0; - ResetCtrl(capacity_, ctrl_, slots_, sizeof(slot_type)); - reset_growth_left(); } - assert(empty()); - infoz().RecordStorageChanged(0, capacity_); } // This overload kicks in when the argument is an rvalue of insertable and @@ -1554,7 +1955,7 @@ class raw_hash_set { iterator lazy_emplace(const key_arg<K>& key, F&& f) { auto res = find_or_prepare_insert(key); if (res.second) { - slot_type* slot = slots_ + res.first; + slot_type* slot = slot_array() + res.first; std::forward<F>(f)(constructor(&alloc_ref(), &slot)); assert(!slot); } @@ -1596,8 +1997,8 @@ class raw_hash_set { // This overload is necessary because otherwise erase<K>(const K&) would be // a better match if non-const iterator is passed as an argument. void erase(iterator it) { - ABSL_INTERNAL_ASSERT_IS_FULL(it.ctrl_, - "erase() called on invalid iterator."); + ABSL_INTERNAL_ASSERT_IS_FULL(it.ctrl_, it.generation(), it.generation_ptr(), + "erase()"); PolicyTraits::destroy(&alloc_ref(), it.slot_); erase_meta_only(it); } @@ -1632,7 +2033,8 @@ class raw_hash_set { node_type extract(const_iterator position) { ABSL_INTERNAL_ASSERT_IS_FULL(position.inner_.ctrl_, - "extract() called on invalid iterator."); + position.inner_.generation(), + position.inner_.generation_ptr(), "extract()"); auto node = CommonAccess::Transfer<node_type>(alloc_ref(), position.inner_.slot_); erase_meta_only(position); @@ -1652,24 +2054,18 @@ class raw_hash_set { IsNoThrowSwappable<allocator_type>( typename AllocTraits::propagate_on_container_swap{})) { using std::swap; - swap(ctrl_, that.ctrl_); - swap(slots_, that.slots_); - swap(size_, that.size_); - swap(capacity_, that.capacity_); - swap(growth_left(), that.growth_left()); + swap(common(), that.common()); swap(hash_ref(), that.hash_ref()); swap(eq_ref(), that.eq_ref()); - swap(infoz(), that.infoz()); SwapAlloc(alloc_ref(), that.alloc_ref(), typename AllocTraits::propagate_on_container_swap{}); } void rehash(size_t n) { - if (n == 0 && capacity_ == 0) return; - if (n == 0 && size_ == 0) { - destroy_slots(); - infoz().RecordStorageChanged(0, 0); - infoz().RecordClearedReservation(); + if (n == 0 && capacity() == 0) return; + if (n == 0 && size() == 0) { + ClearBackingArray(common(), GetPolicyFunctions(), + /*reuse=*/false); return; } @@ -1677,7 +2073,7 @@ class raw_hash_set { // power-of-2-minus-1, so bitor is good enough. auto m = NormalizeCapacity(n | GrowthToLowerboundCapacity(size())); // n == 0 unconditionally rehashes as per the standard. - if (n == 0 || m > capacity_) { + if (n == 0 || m > capacity()) { resize(m); // This is after resize, to ensure that we have completed the allocation @@ -1695,6 +2091,7 @@ class raw_hash_set { // and have potentially sampled the hashtable. infoz().RecordReservation(n); } + common().reset_reserved_growth(n); } // Extension API: support for heterogeneous keys. @@ -1722,9 +2119,9 @@ class raw_hash_set { // Avoid probing if we won't be able to prefetch the addresses received. #ifdef ABSL_INTERNAL_HAVE_PREFETCH prefetch_heap_block(); - auto seq = probe(ctrl_, hash_ref()(key), capacity_); - base_internal::PrefetchT0(ctrl_ + seq.offset()); - base_internal::PrefetchT0(slots_ + seq.offset()); + 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 } @@ -1737,18 +2134,20 @@ class raw_hash_set { // called heterogeneous key support. template <class K = key_type> iterator find(const key_arg<K>& key, size_t hash) { - auto seq = probe(ctrl_, hash, capacity_); + auto seq = probe(common(), hash); + slot_type* slot_ptr = slot_array(); + const ctrl_t* ctrl = control(); while (true) { - Group g{ctrl_ + seq.offset()}; + Group g{ctrl + seq.offset()}; for (uint32_t i : g.Match(H2(hash))) { if (ABSL_PREDICT_TRUE(PolicyTraits::apply( EqualElement<K>{key, eq_ref()}, - PolicyTraits::element(slots_ + seq.offset(i))))) + PolicyTraits::element(slot_ptr + seq.offset(i))))) return iterator_at(seq.offset(i)); } if (ABSL_PREDICT_TRUE(g.MaskEmpty())) return end(); seq.next(); - assert(seq.index() <= capacity_ && "full table!"); + assert(seq.index() <= capacity() && "full table!"); } } template <class K = key_type> @@ -1786,9 +2185,9 @@ class raw_hash_set { return {it, it}; } - size_t bucket_count() const { return capacity_; } + size_t bucket_count() const { return capacity(); } float load_factor() const { - return capacity_ ? static_cast<double>(size()) / capacity_ : 0.0; + return capacity() ? static_cast<double>(size()) / capacity() : 0.0; } float max_load_factor() const { return 1.0f; } void max_load_factor(float) { @@ -1875,7 +2274,8 @@ 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.slots_ + res.first, &slot); + PolicyTraits::transfer(&s.alloc_ref(), s.slot_array() + res.first, + &slot); } else if (do_destroy) { PolicyTraits::destroy(&s.alloc_ref(), &slot); } @@ -1891,102 +2291,43 @@ class raw_hash_set { // 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) { - assert(IsFull(*it.inner_.ctrl_) && "erasing a dangling iterator"); - --size_; - const size_t index = static_cast<size_t>(it.inner_.ctrl_ - ctrl_); - const size_t index_before = (index - Group::kWidth) & capacity_; - const auto empty_after = Group(it.inner_.ctrl_).MaskEmpty(); - const auto empty_before = Group(ctrl_ + 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(index, was_never_full ? ctrl_t::kEmpty : ctrl_t::kDeleted, - capacity_, ctrl_, slots_, sizeof(slot_type)); - growth_left() += was_never_full; - infoz().RecordErase(); + EraseMetaOnly(common(), it.inner_.ctrl_, 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 + // This reads `capacity` and updates all other fields based on the result of // the allocation. // - // This does not free the currently held array; `capacity_` must be nonzero. - void initialize_slots() { - assert(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 wont 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. - // + // 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). To avoid the ambiguity, we work off SlotAlloc which we have - // bound more carefully. - if (std::is_same<SlotAlloc, std::allocator<slot_type>>::value && - slots_ == nullptr) { - infoz() = Sample(sizeof(slot_type)); - } - - char* mem = static_cast<char*>(Allocate<alignof(slot_type)>( - &alloc_ref(), - AllocSize(capacity_, sizeof(slot_type), alignof(slot_type)))); - ctrl_ = reinterpret_cast<ctrl_t*>(mem); - slots_ = reinterpret_cast<slot_type*>( - mem + SlotOffset(capacity_, alignof(slot_type))); - ResetCtrl(capacity_, ctrl_, slots_, sizeof(slot_type)); - reset_growth_left(); - infoz().RecordStorageChanged(size_, capacity_); - } - - // Destroys all slots in the backing array, frees the backing array, and - // clears all top-level book-keeping data. - // - // This essentially implements `map = raw_hash_set();`. - void destroy_slots() { - if (!capacity_) return; - for (size_t i = 0; i != capacity_; ++i) { - if (IsFull(ctrl_[i])) { - PolicyTraits::destroy(&alloc_ref(), slots_ + i); - } - } - - // Unpoison before returning the memory to the allocator. - SanitizerUnpoisonMemoryRegion(slots_, sizeof(slot_type) * capacity_); - Deallocate<alignof(slot_type)>( - &alloc_ref(), ctrl_, - AllocSize(capacity_, sizeof(slot_type), alignof(slot_type))); - ctrl_ = EmptyGroup(); - slots_ = nullptr; - size_ = 0; - capacity_ = 0; - growth_left() = 0; + // anyway). + using CharAlloc = + typename absl::allocator_traits<Alloc>::template rebind_alloc<char>; + InitializeSlots<CharAlloc, sizeof(slot_type), alignof(slot_type)>( + common(), CharAlloc(alloc_ref())); } - void resize(size_t new_capacity) { + ABSL_ATTRIBUTE_NOINLINE void resize(size_t new_capacity) { assert(IsValidCapacity(new_capacity)); - auto* old_ctrl = ctrl_; - auto* old_slots = slots_; - const size_t old_capacity = capacity_; - capacity_ = new_capacity; + auto* old_ctrl = control(); + 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(ctrl_, hash, capacity_); + auto target = find_first_non_full(common(), hash); size_t new_i = target.offset; total_probe_length += target.probe_length; - SetCtrl(new_i, H2(hash), capacity_, ctrl_, slots_, sizeof(slot_type)); - PolicyTraits::transfer(&alloc_ref(), slots_ + new_i, old_slots + i); + SetCtrl(common(), new_i, H2(hash), sizeof(slot_type)); + PolicyTraits::transfer(&alloc_ref(), new_slots + new_i, old_slots + i); } } if (old_capacity) { @@ -2002,70 +2343,10 @@ class raw_hash_set { // Prunes control bytes to remove as many tombstones as possible. // // See the comment on `rehash_and_grow_if_necessary()`. - void drop_deletes_without_resize() ABSL_ATTRIBUTE_NOINLINE { - assert(IsValidCapacity(capacity_)); - assert(!is_small(capacity_)); - // Algorithm: - // - mark all DELETED slots as EMPTY - // - mark all FULL slots as DELETED - // - for each slot marked as DELETED - // hash = Hash(element) - // target = find_first_non_full(hash) - // if target is in the same group - // mark slot as FULL - // else if target is EMPTY - // transfer element to target - // mark slot as EMPTY - // mark target as FULL - // else if target is DELETED - // swap current element with target element - // mark target as FULL - // repeat procedure for current slot with moved from element (target) - ConvertDeletedToEmptyAndFullToDeleted(ctrl_, capacity_); - alignas(slot_type) unsigned char raw[sizeof(slot_type)]; - size_t total_probe_length = 0; - slot_type* slot = reinterpret_cast<slot_type*>(&raw); - for (size_t i = 0; i != capacity_; ++i) { - if (!IsDeleted(ctrl_[i])) continue; - const size_t hash = PolicyTraits::apply( - HashElement{hash_ref()}, PolicyTraits::element(slots_ + i)); - const FindInfo target = find_first_non_full(ctrl_, hash, capacity_); - const size_t new_i = target.offset; - total_probe_length += target.probe_length; - - // Verify if the old and new i fall within the same group wrt the hash. - // If they do, we don't need to move the object as it falls already in the - // best probe we can. - const size_t probe_offset = probe(ctrl_, hash, capacity_).offset(); - const auto probe_index = [probe_offset, this](size_t pos) { - return ((pos - probe_offset) & capacity_) / Group::kWidth; - }; - - // Element doesn't move. - if (ABSL_PREDICT_TRUE(probe_index(new_i) == probe_index(i))) { - SetCtrl(i, H2(hash), capacity_, ctrl_, slots_, sizeof(slot_type)); - continue; - } - if (IsEmpty(ctrl_[new_i])) { - // Transfer element to the empty spot. - // SetCtrl poisons/unpoisons the slots so we have to call it at the - // right time. - SetCtrl(new_i, H2(hash), capacity_, ctrl_, slots_, sizeof(slot_type)); - PolicyTraits::transfer(&alloc_ref(), slots_ + new_i, slots_ + i); - SetCtrl(i, ctrl_t::kEmpty, capacity_, ctrl_, slots_, sizeof(slot_type)); - } else { - assert(IsDeleted(ctrl_[new_i])); - SetCtrl(new_i, H2(hash), capacity_, ctrl_, slots_, sizeof(slot_type)); - // Until we are done rehashing, DELETED marks previously FULL slots. - // Swap i and new_i elements. - PolicyTraits::transfer(&alloc_ref(), slot, slots_ + i); - PolicyTraits::transfer(&alloc_ref(), slots_ + i, slots_ + new_i); - PolicyTraits::transfer(&alloc_ref(), slots_ + new_i, slot); - --i; // repeat - } - } - reset_growth_left(); - infoz().RecordRehash(total_probe_length); + inline void drop_deletes_without_resize() { + // Stack-allocate space for swapping elements. + alignas(slot_type) unsigned char tmp[sizeof(slot_type)]; + DropDeletesWithoutResize(common(), GetPolicyFunctions(), tmp); } // Called whenever the table *might* need to conditionally grow. @@ -2074,14 +2355,13 @@ class raw_hash_set { // growth is unnecessary, because vacating tombstones is beneficial for // performance in the long-run. void rehash_and_grow_if_necessary() { - if (capacity_ == 0) { - resize(1); - } else if (capacity_ > Group::kWidth && - // Do these calcuations in 64-bit to avoid overflow. - size() * uint64_t{32} <= capacity_ * uint64_t{25}) { + 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}) { // Squash DELETED without growing if there is enough capacity. // - // Rehash in place if the current size is <= 25/32 of capacity_. + // Rehash in place if the current size is <= 25/32 of capacity. // Rationale for such a high factor: 1) drop_deletes_without_resize() is // faster than resize, and 2) it takes quite a bit of work to add // tombstones. In the worst case, seems to take approximately 4 @@ -2099,8 +2379,8 @@ class raw_hash_set { // // Here is output of an experiment using the BM_CacheInSteadyState // benchmark running the old case (where we rehash-in-place only if we can - // reclaim at least 7/16*capacity_) vs. this code (which rehashes in place - // if we can recover 3/32*capacity_). + // reclaim at least 7/16*capacity) vs. this code (which rehashes in place + // if we can recover 3/32*capacity). // // Note that although in the worst-case number of rehashes jumped up from // 15 to 190, but the number of operations per second is almost the same. @@ -2123,23 +2403,24 @@ class raw_hash_set { drop_deletes_without_resize(); } else { // Otherwise grow the container. - resize(capacity_ * 2 + 1); + resize(NextCapacity(cap)); } } bool has_element(const value_type& elem) const { size_t hash = PolicyTraits::apply(HashElement{hash_ref()}, elem); - auto seq = probe(ctrl_, hash, capacity_); + auto seq = probe(common(), hash); + const ctrl_t* ctrl = control(); while (true) { - Group g{ctrl_ + seq.offset()}; + Group g{ctrl + seq.offset()}; for (uint32_t i : g.Match(H2(hash))) { - if (ABSL_PREDICT_TRUE(PolicyTraits::element(slots_ + seq.offset(i)) == - elem)) + 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!"); + assert(seq.index() <= capacity() && "full table!"); } return false; } @@ -2164,18 +2445,19 @@ class raw_hash_set { std::pair<size_t, bool> find_or_prepare_insert(const K& key) { prefetch_heap_block(); auto hash = hash_ref()(key); - auto seq = probe(ctrl_, hash, capacity_); + auto seq = probe(common(), hash); + const ctrl_t* ctrl = control(); while (true) { - Group g{ctrl_ + seq.offset()}; + Group g{ctrl + seq.offset()}; for (uint32_t i : g.Match(H2(hash))) { if (ABSL_PREDICT_TRUE(PolicyTraits::apply( EqualElement<K>{key, eq_ref()}, - PolicyTraits::element(slots_ + seq.offset(i))))) + PolicyTraits::element(slot_array() + seq.offset(i))))) return {seq.offset(i), false}; } if (ABSL_PREDICT_TRUE(g.MaskEmpty())) break; seq.next(); - assert(seq.index() <= capacity_ && "full table!"); + assert(seq.index() <= capacity() && "full table!"); } return {prepare_insert(hash), true}; } @@ -2185,16 +2467,24 @@ class raw_hash_set { // // REQUIRES: At least one non-full slot available. size_t prepare_insert(size_t hash) ABSL_ATTRIBUTE_NOINLINE { - auto target = find_first_non_full(ctrl_, hash, capacity_); - if (ABSL_PREDICT_FALSE(growth_left() == 0 && - !IsDeleted(ctrl_[target.offset]))) { + const bool rehash_for_bug_detection = + common().should_rehash_for_bug_detection_on_insert(); + if (rehash_for_bug_detection) { + // Move to a different heap allocation in order to detect bugs. + const size_t cap = capacity(); + resize(growth_left() > 0 ? cap : NextCapacity(cap)); + } + auto target = find_first_non_full(common(), hash); + if (!rehash_for_bug_detection && + ABSL_PREDICT_FALSE(growth_left() == 0 && + !IsDeleted(control()[target.offset]))) { rehash_and_grow_if_necessary(); - target = find_first_non_full(ctrl_, hash, capacity_); + target = find_first_non_full(common(), hash); } - ++size_; - growth_left() -= IsEmpty(ctrl_[target.offset]); - SetCtrl(target.offset, H2(hash), capacity_, ctrl_, slots_, - sizeof(slot_type)); + ++common().size_; + 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); return target.offset; } @@ -2209,7 +2499,7 @@ 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(), slots_ + i, + PolicyTraits::construct(&alloc_ref(), slot_array() + i, std::forward<Args>(args)...); assert(PolicyTraits::apply(FindElement{*this}, *iterator_at(i)) == @@ -2217,16 +2507,16 @@ class raw_hash_set { "constructed value does not match the lookup key"); } - iterator iterator_at(size_t i) { return {ctrl_ + i, slots_ + i}; } - const_iterator iterator_at(size_t i) const { return {ctrl_ + i, slots_ + i}; } + iterator iterator_at(size_t i) { + return {control() + i, slot_array() + i, common().generation_ptr()}; + } + const_iterator iterator_at(size_t i) const { + return {control() + i, slot_array() + i, common().generation_ptr()}; + } private: friend struct RawHashSetTestOnlyAccess; - void reset_growth_left() { - growth_left() = CapacityToGrowth(capacity()) - size_; - } - // The number of slots we can still fill without needing to rehash. // // This is stored separately due to tombstones: we do not include tombstones @@ -2237,49 +2527,76 @@ class raw_hash_set { // side-effect. // // See `CapacityToGrowth()`. - size_t& growth_left() { return settings_.template get<0>(); } + 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(ctrl_); - } + void prefetch_heap_block() const { base_internal::PrefetchT2(control()); } + + CommonFields& common() { return settings_.template get<0>(); } + const CommonFields& common() const { return settings_.template get<0>(); } - HashtablezInfoHandle& infoz() { return settings_.template get<1>(); } + ctrl_t* control() const { return common().control_; } + slot_type* slot_array() const { + return static_cast<slot_type*>(common().slots_); + } + HashtablezInfoHandle& infoz() { return common().infoz(); } - hasher& hash_ref() { return settings_.template get<2>(); } - const hasher& hash_ref() const { return settings_.template get<2>(); } - key_equal& eq_ref() { return settings_.template get<3>(); } - const key_equal& eq_ref() const { return settings_.template get<3>(); } - allocator_type& alloc_ref() { return settings_.template get<4>(); } + hasher& hash_ref() { return settings_.template get<1>(); } + const hasher& hash_ref() const { return settings_.template get<1>(); } + key_equal& eq_ref() { return settings_.template get<2>(); } + const key_equal& eq_ref() const { return settings_.template get<2>(); } + allocator_type& alloc_ref() { return settings_.template get<3>(); } const allocator_type& alloc_ref() const { - return settings_.template get<4>(); + return settings_.template get<3>(); } - // TODO(alkis): Investigate removing some of these fields: - // - ctrl/slots can be derived from each other - // - size can be moved into the slot array + // Make type-specific functions for this type's PolicyFunctions struct. + static size_t hash_slot_fn(void* set, void* slot) { + auto* h = static_cast<raw_hash_set*>(set); + return PolicyTraits::apply( + HashElement{h->hash_ref()}, + PolicyTraits::element(static_cast<slot_type*>(slot))); + } + 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)); + } + // 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); - // The control bytes (and, also, a pointer to the base of the backing array). - // - // This contains `capacity_ + 1 + NumClonedBytes()` entries, even - // when the table is empty (hence EmptyGroup). - ctrl_t* ctrl_ = EmptyGroup(); - // The beginning of the slots, located at `SlotOffset()` bytes after - // `ctrl_`. May be null for empty tables. - slot_type* slots_ = nullptr; + // Unpoison before returning the memory to the allocator. + SanitizerUnpoisonMemoryRegion(slot_mem, sizeof(slot_type) * n); - // The number of filled slots. - size_t size_ = 0; + Deallocate<alignof(slot_type)>( + &h->alloc_ref(), ctrl, + AllocSize(n, sizeof(slot_type), alignof(slot_type))); + } + + static const PolicyFunctions& GetPolicyFunctions() { + static constexpr PolicyFunctions value = { + sizeof(slot_type), + &raw_hash_set::hash_slot_fn, + PolicyTraits::transfer_uses_memcpy() + ? TransferRelocatable<sizeof(slot_type)> + : &raw_hash_set::transfer_slot_fn, + (std::is_same<SlotAlloc, std::allocator<slot_type>>::value + ? &DeallocateStandard<alignof(slot_type)> + : &raw_hash_set::dealloc_fn), + }; + return value; + } - // The total number of available slots. - size_t capacity_ = 0; - absl::container_internal::CompressedTuple<size_t /* growth_left */, - HashtablezInfoHandle, hasher, - key_equal, allocator_type> - settings_{0u, HashtablezInfoHandle{}, hasher{}, key_equal{}, - allocator_type{}}; + // Bundle together CommonFields plus other objects which might be empty. + // CompressedTuple will ensure that sizeof is not affected by any of the empty + // fields that occur after CommonFields. + absl::container_internal::CompressedTuple<CommonFields, hasher, key_equal, + allocator_type> + settings_{CommonFields{}, hasher{}, key_equal{}, allocator_type{}}; }; // Erases all elements that satisfy the predicate `pred` from the container `c`. @@ -2307,14 +2624,15 @@ struct HashtableDebugAccess<Set, absl::void_t<typename Set::raw_hash_set>> { const typename Set::key_type& key) { size_t num_probes = 0; size_t hash = set.hash_ref()(key); - auto seq = probe(set.ctrl_, hash, set.capacity_); + auto seq = probe(set.common(), hash); + const ctrl_t* ctrl = set.control(); while (true) { - container_internal::Group g{set.ctrl_ + seq.offset()}; + container_internal::Group g{ctrl + seq.offset()}; for (uint32_t i : g.Match(container_internal::H2(hash))) { if (Traits::apply( typename Set::template EqualElement<typename Set::key_type>{ key, set.eq_ref()}, - Traits::element(set.slots_ + seq.offset(i)))) + Traits::element(set.slot_array() + seq.offset(i)))) return num_probes; ++num_probes; } @@ -2325,7 +2643,7 @@ struct HashtableDebugAccess<Set, absl::void_t<typename Set::raw_hash_set>> { } static size_t AllocatedByteSize(const Set& c) { - size_t capacity = c.capacity_; + size_t capacity = c.capacity(); if (capacity == 0) return 0; size_t m = AllocSize(capacity, sizeof(Slot), alignof(Slot)); @@ -2333,9 +2651,10 @@ struct HashtableDebugAccess<Set, absl::void_t<typename Set::raw_hash_set>> { 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(c.ctrl_[i])) { - m += Traits::space_used(c.slots_ + i); + if (container_internal::IsFull(ctrl[i])) { + m += Traits::space_used(c.slot_array() + i); } } } @@ -2360,6 +2679,7 @@ struct HashtableDebugAccess<Set, absl::void_t<typename Set::raw_hash_set>> { 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_benchmark.cc b/absl/container/internal/raw_hash_set_benchmark.cc index 47dc9048..f77f2a7b 100644 --- a/absl/container/internal/raw_hash_set_benchmark.cc +++ b/absl/container/internal/raw_hash_set_benchmark.cc @@ -12,13 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "absl/container/internal/raw_hash_set.h" - +#include <array> +#include <cmath> #include <numeric> #include <random> +#include <tuple> +#include <utility> +#include <vector> #include "absl/base/internal/raw_logging.h" #include "absl/container/internal/hash_function_defaults.h" +#include "absl/container/internal/raw_hash_set.h" #include "absl/strings/str_format.h" #include "benchmark/benchmark.h" @@ -202,40 +206,113 @@ void CacheInSteadyStateArgs(Benchmark* bm) { BENCHMARK(BM_CacheInSteadyState)->Apply(CacheInSteadyStateArgs); void BM_EndComparison(benchmark::State& state) { + StringTable t = {{"a", "a"}, {"b", "b"}}; + auto it = t.begin(); + for (auto i : state) { + benchmark::DoNotOptimize(t); + benchmark::DoNotOptimize(it); + benchmark::DoNotOptimize(it != t.end()); + } +} +BENCHMARK(BM_EndComparison); + +void BM_Iteration(benchmark::State& state) { std::random_device rd; std::mt19937 rng(rd()); string_generator gen{12}; StringTable t; - while (t.size() < state.range(0)) { + + size_t capacity = state.range(0); + size_t size = state.range(1); + t.reserve(capacity); + + while (t.size() < size) { t.emplace(gen(rng), gen(rng)); } - for (auto _ : state) { + for (auto i : state) { + benchmark::DoNotOptimize(t); for (auto it = t.begin(); it != t.end(); ++it) { - benchmark::DoNotOptimize(it); - benchmark::DoNotOptimize(t); - benchmark::DoNotOptimize(it != t.end()); + benchmark::DoNotOptimize(*it); } } } -BENCHMARK(BM_EndComparison)->Arg(400); -void BM_CopyCtor(benchmark::State& state) { +BENCHMARK(BM_Iteration) + ->ArgPair(1, 1) + ->ArgPair(2, 2) + ->ArgPair(4, 4) + ->ArgPair(7, 7) + ->ArgPair(10, 10) + ->ArgPair(15, 15) + ->ArgPair(16, 16) + ->ArgPair(54, 54) + ->ArgPair(100, 100) + ->ArgPair(400, 400) + // empty + ->ArgPair(0, 0) + ->ArgPair(10, 0) + ->ArgPair(100, 0) + ->ArgPair(1000, 0) + ->ArgPair(10000, 0) + // sparse + ->ArgPair(100, 1) + ->ArgPair(1000, 10); + +void BM_CopyCtorSparseInt(benchmark::State& state) { std::random_device rd; std::mt19937 rng(rd()); IntTable t; std::uniform_int_distribution<uint64_t> dist(0, ~uint64_t{}); - while (t.size() < state.range(0)) { + size_t size = state.range(0); + t.reserve(size * 10); + while (t.size() < size) { t.emplace(dist(rng)); } - for (auto _ : state) { + for (auto i : state) { IntTable t2 = t; benchmark::DoNotOptimize(t2); } } -BENCHMARK(BM_CopyCtor)->Range(128, 4096); +BENCHMARK(BM_CopyCtorSparseInt)->Range(128, 4096); + +void BM_CopyCtorInt(benchmark::State& state) { + std::random_device rd; + std::mt19937 rng(rd()); + IntTable t; + std::uniform_int_distribution<uint64_t> dist(0, ~uint64_t{}); + + size_t size = state.range(0); + while (t.size() < size) { + t.emplace(dist(rng)); + } + + for (auto i : state) { + IntTable t2 = t; + benchmark::DoNotOptimize(t2); + } +} +BENCHMARK(BM_CopyCtorInt)->Range(128, 4096); + +void BM_CopyCtorString(benchmark::State& state) { + std::random_device rd; + std::mt19937 rng(rd()); + StringTable t; + std::uniform_int_distribution<uint64_t> dist(0, ~uint64_t{}); + + size_t size = state.range(0); + while (t.size() < size) { + t.emplace(std::to_string(dist(rng)), std::to_string(dist(rng))); + } + + for (auto i : state) { + StringTable t2 = t; + benchmark::DoNotOptimize(t2); + } +} +BENCHMARK(BM_CopyCtorString)->Range(128, 4096); void BM_CopyAssign(benchmark::State& state) { std::random_device rd; @@ -401,6 +478,24 @@ void BM_DropDeletes(benchmark::State& state) { } BENCHMARK(BM_DropDeletes); +void BM_Resize(benchmark::State& state) { + // For now just measure a small cheap hash table since we + // are mostly interested in the overhead of type-erasure + // in resize(). + constexpr int kElements = 64; + const int kCapacity = kElements * 2; + + IntTable table; + for (int i = 0; i < kElements; i++) { + table.insert(i); + } + for (auto unused : state) { + table.rehash(0); + table.rehash(kCapacity); + } +} +BENCHMARK(BM_Resize); + } // namespace } // namespace container_internal ABSL_NAMESPACE_END @@ -418,6 +513,12 @@ bool CodegenAbslRawHashSetInt64FindNeEnd( return table->find(key) != table->end(); } +// This is useful because the find isn't inlined but the iterator comparison is. +bool CodegenAbslRawHashSetStringFindNeEnd( + absl::container_internal::StringTable* table, const std::string& key) { + return table->find(key) != table->end(); +} + auto CodegenAbslRawHashSetInt64Insert(absl::container_internal::IntTable* table, int64_t key) -> decltype(table->insert(key)) { @@ -437,7 +538,7 @@ void CodegenAbslRawHashSetInt64Iterate( int odr = (::benchmark::DoNotOptimize(std::make_tuple( &CodegenAbslRawHashSetInt64Find, &CodegenAbslRawHashSetInt64FindNeEnd, - &CodegenAbslRawHashSetInt64Insert, - &CodegenAbslRawHashSetInt64Contains, + &CodegenAbslRawHashSetStringFindNeEnd, + &CodegenAbslRawHashSetInt64Insert, &CodegenAbslRawHashSetInt64Contains, &CodegenAbslRawHashSetInt64Iterate)), 1); diff --git a/absl/container/internal/raw_hash_set_test.cc b/absl/container/internal/raw_hash_set_test.cc index f77ffbc1..3d3b089c 100644 --- a/absl/container/internal/raw_hash_set_test.cc +++ b/absl/container/internal/raw_hash_set_test.cc @@ -14,17 +14,26 @@ #include "absl/container/internal/raw_hash_set.h" +#include <algorithm> #include <atomic> #include <cmath> #include <cstdint> #include <deque> #include <functional> +#include <iostream> +#include <iterator> +#include <list> +#include <map> #include <memory> #include <numeric> +#include <ostream> #include <random> #include <string> +#include <type_traits> #include <unordered_map> #include <unordered_set> +#include <utility> +#include <vector> #include "gmock/gmock.h" #include "gtest/gtest.h" @@ -33,10 +42,13 @@ #include "absl/base/internal/cycleclock.h" #include "absl/base/internal/prefetch.h" #include "absl/base/internal/raw_logging.h" +#include "absl/container/flat_hash_map.h" +#include "absl/container/flat_hash_set.h" #include "absl/container/internal/container_memory.h" #include "absl/container/internal/hash_function_defaults.h" #include "absl/container/internal/hash_policy_testing.h" #include "absl/container/internal/hashtable_debug.h" +#include "absl/log/log.h" #include "absl/strings/string_view.h" namespace absl { @@ -45,8 +57,8 @@ namespace container_internal { struct RawHashSetTestOnlyAccess { template <typename C> - static auto GetSlots(const C& c) -> decltype(c.slots_) { - return c.slots_; + static auto GetSlots(const C& c) -> decltype(c.slot_array()) { + return c.slot_array(); } }; @@ -339,7 +351,7 @@ class StringPolicy { struct ctor {}; template <class... Ts> - slot_type(ctor, Ts&&... ts) : pair(std::forward<Ts>(ts)...) {} + explicit slot_type(ctor, Ts&&... ts) : pair(std::forward<Ts>(ts)...) {} std::pair<std::string, std::string> pair; }; @@ -388,7 +400,7 @@ struct StringEq : std::equal_to<absl::string_view> { struct StringTable : raw_hash_set<StringPolicy, StringHash, StringEq, std::allocator<int>> { using Base = typename StringTable::raw_hash_set; - StringTable() {} + StringTable() = default; using Base::Base; }; @@ -408,10 +420,10 @@ struct Uint8Table template <typename T> struct CustomAlloc : std::allocator<T> { - CustomAlloc() {} + CustomAlloc() = default; template <typename U> - CustomAlloc(const CustomAlloc<U>& other) {} + explicit CustomAlloc(const CustomAlloc<U>& /*other*/) {} template<class U> struct rebind { using other = CustomAlloc<U>; @@ -435,7 +447,7 @@ struct BadFastHash { struct BadTable : raw_hash_set<IntPolicy, BadFastHash, std::equal_to<int>, std::allocator<int>> { using Base = typename BadTable::raw_hash_set; - BadTable() {} + BadTable() = default; using Base::Base; }; @@ -444,12 +456,12 @@ 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; - void* infoz; }; struct MockTableInfozDisabled { void* ctrl; @@ -465,27 +477,37 @@ TEST(Table, EmptyFunctorOptimization) { size_t dummy; }; - if (std::is_empty<HashtablezInfoHandle>::value) { - EXPECT_EQ(sizeof(MockTableInfozDisabled), - sizeof(raw_hash_set<StringPolicy, StatelessHash, - std::equal_to<absl::string_view>, - std::allocator<int>>)); - - EXPECT_EQ(sizeof(MockTableInfozDisabled) + sizeof(StatefulHash), - sizeof(raw_hash_set<StringPolicy, StatefulHash, - std::equal_to<absl::string_view>, - std::allocator<int>>)); - } else { - EXPECT_EQ(sizeof(MockTable), - sizeof(raw_hash_set<StringPolicy, StatelessHash, - std::equal_to<absl::string_view>, - std::allocator<int>>)); + struct GenerationData { + size_t reserved_growth; + GenerationType* generation; + }; - EXPECT_EQ(sizeof(MockTable) + sizeof(StatefulHash), - sizeof(raw_hash_set<StringPolicy, StatefulHash, - std::equal_to<absl::string_view>, - std::allocator<int>>)); - } +// Ignore unreachable-code warning. Compiler thinks one branch of each ternary +// conditional is unreachable. +#if defined(__clang__) +#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 generation_size = + SwisstableGenerationsEnabled() ? sizeof(GenerationData) : 0; +#if defined(__clang__) +#pragma clang diagnostic pop +#endif + + EXPECT_EQ( + mock_size + generation_size, + sizeof( + raw_hash_set<StringPolicy, StatelessHash, + std::equal_to<absl::string_view>, std::allocator<int>>)); + + EXPECT_EQ( + mock_size + sizeof(StatefulHash) + generation_size, + sizeof( + raw_hash_set<StringPolicy, StatefulHash, + std::equal_to<absl::string_view>, std::allocator<int>>)); } TEST(Table, Empty) { @@ -992,7 +1014,7 @@ TEST(Table, ClearBug) { // We are checking that original and second are close enough to each other // that they are probably still in the same group. This is not strictly // guaranteed. - EXPECT_LT(std::abs(original - second), + EXPECT_LT(static_cast<size_t>(std::abs(original - second)), capacity * sizeof(IntTable::value_type)); } @@ -1069,19 +1091,6 @@ struct ProbeStats { // Ratios total_probe_length/size for every tested table. std::vector<double> single_table_ratios; - friend ProbeStats operator+(const ProbeStats& a, const ProbeStats& b) { - ProbeStats res = a; - res.all_probes_histogram.resize(std::max(res.all_probes_histogram.size(), - b.all_probes_histogram.size())); - std::transform(b.all_probes_histogram.begin(), b.all_probes_histogram.end(), - res.all_probes_histogram.begin(), - res.all_probes_histogram.begin(), std::plus<size_t>()); - res.single_table_ratios.insert(res.single_table_ratios.end(), - b.single_table_ratios.begin(), - b.single_table_ratios.end()); - return res; - } - // Average ratio total_probe_length/size over tables. double AvgRatio() const { return std::accumulate(single_table_ratios.begin(), @@ -1275,6 +1284,7 @@ TEST(Table, DISABLED_EnsureNonQuadraticTopNXorSeedByProbeSeqLength) { for (size_t size : sizes) { auto& stat = stats[size]; VerifyStats(size, expected, stat); + LOG(INFO) << size << " " << stat; } } @@ -1370,6 +1380,7 @@ TEST(Table, DISABLED_EnsureNonQuadraticTopNLinearTransformByProbeSeqLength) { for (size_t size : sizes) { auto& stat = stats[size]; VerifyStats(size, expected, stat); + LOG(INFO) << size << " " << stat; } } @@ -1504,7 +1515,7 @@ TEST(Table, RehashZeroForcesRehash) { TEST(Table, ConstructFromInitList) { using P = std::pair<std::string, std::string>; struct Q { - operator P() const { return {}; } + operator P() const { return {}; } // NOLINT }; StringTable t = {P(), Q(), {}, {{}, {}}}; } @@ -1542,7 +1553,7 @@ TEST(Table, CopyConstructWithAlloc) { struct ExplicitAllocIntTable : raw_hash_set<IntPolicy, container_internal::hash_default_hash<int64_t>, std::equal_to<int64_t>, Alloc<int64_t>> { - ExplicitAllocIntTable() {} + ExplicitAllocIntTable() = default; }; TEST(Table, AllocWithExplicitCtor) { @@ -1809,7 +1820,6 @@ TEST(Table, HeterogeneousLookupOverloads) { EXPECT_TRUE((VerifyResultOf<CallCount, TransparentTable>())); } -// TODO(alkis): Expand iterator tests. TEST(Iterator, IsDefaultConstructible) { StringTable::iterator i; EXPECT_TRUE(i == StringTable::iterator()); @@ -1930,7 +1940,7 @@ TEST(Nodes, ExtractInsert) { EXPECT_FALSE(res.inserted); EXPECT_THAT(*res.position, Pair(k0, "")); EXPECT_TRUE(res.node); - EXPECT_FALSE(node); + EXPECT_FALSE(node); // NOLINT(bugprone-use-after-move) } TEST(Nodes, HintInsert) { @@ -1940,7 +1950,7 @@ TEST(Nodes, HintInsert) { auto it = t.insert(t.begin(), std::move(node)); EXPECT_THAT(t, UnorderedElementsAre(1, 2, 3)); EXPECT_EQ(*it, 1); - EXPECT_FALSE(node); + EXPECT_FALSE(node); // NOLINT(bugprone-use-after-move) node = t.extract(2); EXPECT_THAT(t, UnorderedElementsAre(1, 3)); @@ -1950,7 +1960,7 @@ TEST(Nodes, HintInsert) { it = t.insert(t.begin(), std::move(node)); EXPECT_EQ(*it, 2); // The node was not emptied by the insert call. - EXPECT_TRUE(node); + EXPECT_TRUE(node); // NOLINT(bugprone-use-after-move) } IntTable MakeSimpleTable(size_t size) { @@ -2023,20 +2033,75 @@ TEST(Table, UnstablePointers) { EXPECT_NE(old_ptr, addr(0)); } -// Confirm that we assert if we try to erase() end(). -TEST(TableDeathTest, EraseOfEndAsserts) { +bool IsAssertEnabled() { // Use an assert with side-effects to figure out if they are actually enabled. bool assert_enabled = false; - assert([&]() { + assert([&]() { // NOLINT assert_enabled = true; return true; }()); - if (!assert_enabled) return; + return assert_enabled; +} + +TEST(TableDeathTest, InvalidIteratorAsserts) { + if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled."; IntTable t; // Extra simple "regexp" as regexp support is highly varied across platforms. - constexpr char kDeathMsg[] = "erase.. called on invalid iterator"; - EXPECT_DEATH_IF_SUPPORTED(t.erase(t.end()), kDeathMsg); + EXPECT_DEATH_IF_SUPPORTED( + t.erase(t.end()), + "erase.* called on invalid iterator. The iterator might be an " + "end.*iterator or may have been default constructed."); + typename IntTable::iterator iter; + EXPECT_DEATH_IF_SUPPORTED( + ++iter, + "operator.* called on invalid iterator. The iterator might be an " + "end.*iterator or may have been default constructed."); + t.insert(0); + iter = t.begin(); + t.erase(iter); + EXPECT_DEATH_IF_SUPPORTED( + ++iter, + "operator.* called on invalid iterator. The element might have been " + "erased or .*the table might have rehashed."); +} + +TEST(TableDeathTest, IteratorInvalidAssertsEqualityOperator) { + if (!IsAssertEnabled()) GTEST_SKIP() << "Assertions not enabled."; + + IntTable t; + t.insert(1); + t.insert(2); + t.insert(3); + auto iter1 = t.begin(); + auto iter2 = std::next(iter1); + ASSERT_NE(iter1, t.end()); + ASSERT_NE(iter2, t.end()); + t.erase(iter1); + // Extra simple "regexp" as regexp support is highly varied across platforms. + const char* const kErasedDeathMessage = + "Invalid iterator comparison. The element might have .*been erased or " + "the table might have rehashed."; + EXPECT_DEATH_IF_SUPPORTED(void(iter1 == iter2), kErasedDeathMessage); + EXPECT_DEATH_IF_SUPPORTED(void(iter2 != iter1), kErasedDeathMessage); + t.erase(iter2); + EXPECT_DEATH_IF_SUPPORTED(void(iter1 == iter2), kErasedDeathMessage); + + IntTable t1, t2; + t1.insert(0); + t2.insert(0); + 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."; + 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 defined(ABSL_INTERNAL_HASHTABLEZ_SAMPLE) @@ -2047,7 +2112,7 @@ TEST(RawHashSamplerTest, Sample) { auto& sampler = GlobalHashtablezSampler(); size_t start_size = 0; - std::unordered_set<const HashtablezInfo*> preexisting_info; + absl::flat_hash_set<const HashtablezInfo*> preexisting_info; start_size += sampler.Iterate([&](const HashtablezInfo& info) { preexisting_info.insert(&info); ++start_size; @@ -2074,8 +2139,8 @@ TEST(RawHashSamplerTest, Sample) { } } size_t end_size = 0; - std::unordered_map<size_t, int> observed_checksums; - std::unordered_map<ssize_t, int> reservations; + absl::flat_hash_map<size_t, int> observed_checksums; + absl::flat_hash_map<ssize_t, int> reservations; end_size += sampler.Iterate([&](const HashtablezInfo& info) { if (preexisting_info.count(&info) == 0) { observed_checksums[info.hashes_bitwise_xor.load( @@ -2181,6 +2246,78 @@ 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."; + + IntTable t; + // Start with 1 element so that `it` is never an end iterator. + t.insert(-1); + for (int i = 0; i < 10; ++i) { + auto it = t.begin(); + t.insert(i); + EXPECT_DEATH_IF_SUPPORTED(*it, kInvalidIteratorDeathMessage); + EXPECT_DEATH_IF_SUPPORTED(void(it == t.begin()), + kInvalidIteratorDeathMessage); + } +} + +TEST(Iterator, InvalidUseWithReserveCrashesWithSanitizers) { + if (!SwisstableGenerationsEnabled()) GTEST_SKIP() << "Generations disabled."; + if (kLexan) GTEST_SKIP() << "Lexan doesn't support | in regexp."; + + IntTable t; + t.reserve(10); + t.insert(0); + auto it = t.begin(); + // Reserved growth can't rehash. + for (int i = 1; i < 10; ++i) { + t.insert(i); + EXPECT_EQ(*it, 0); + } + // ptr will become invalidated on rehash. + const int64_t* ptr = &*it; + + // erase decreases size but does not decrease reserved growth so the next + // insertion still invalidates iterators. + t.erase(0); + // The first insert after reserved growth is 0 is guaranteed to rehash when + // generations are enabled. + t.insert(10); + 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"); +} + +TEST(Table, ReservedGrowthUpdatesWhenTableDoesntGrow) { + IntTable t; + for (int i = 0; i < 8; ++i) t.insert(i); + // Want to insert twice without invalidating iterators so reserve. + const size_t cap = t.capacity(); + t.reserve(t.size() + 2); + // We want to be testing the case in which the reserve doesn't grow the table. + ASSERT_EQ(cap, t.capacity()); + auto it = t.find(0); + t.insert(100); + t.insert(200); + // `it` shouldn't have been invalidated. + EXPECT_EQ(*it, 0); +} + } // namespace } // namespace container_internal ABSL_NAMESPACE_END diff --git a/absl/container/node_hash_map_test.cc b/absl/container/node_hash_map_test.cc index e941a836..9bcf470c 100644 --- a/absl/container/node_hash_map_test.cc +++ b/absl/container/node_hash_map_test.cc @@ -272,6 +272,14 @@ TEST(NodeHashMap, NodeHandleMutableKeyAccess) { } #endif +TEST(NodeHashMap, RecursiveTypeCompiles) { + struct RecursiveType { + node_hash_map<int, RecursiveType> m; + }; + RecursiveType t; + t.m[0] = RecursiveType{}; +} + } // namespace } // namespace container_internal ABSL_NAMESPACE_END diff --git a/absl/copts/AbseilConfigureCopts.cmake b/absl/copts/AbseilConfigureCopts.cmake index 73435e99..8209b262 100644 --- a/absl/copts/AbseilConfigureCopts.cmake +++ b/absl/copts/AbseilConfigureCopts.cmake @@ -67,26 +67,25 @@ elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "arm.*|aarch64") message(WARNING "Value of CMAKE_SIZEOF_VOID_P (${CMAKE_SIZEOF_VOID_P}) is not supported.") endif() else() - message(WARNING "Value of CMAKE_SYSTEM_PROCESSOR (${CMAKE_SYSTEM_PROCESSOR}) is unknown and cannot be used to set ABSL_RANDOM_RANDEN_COPTS") set(ABSL_RANDOM_RANDEN_COPTS "") endif() if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") set(ABSL_DEFAULT_COPTS "${ABSL_GCC_FLAGS}") - set(ABSL_TEST_COPTS "${ABSL_GCC_FLAGS};${ABSL_GCC_TEST_FLAGS}") + set(ABSL_TEST_COPTS "${ABSL_GCC_TEST_FLAGS}") elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") # MATCHES so we get both Clang and AppleClang if(MSVC) # clang-cl is half MSVC, half LLVM set(ABSL_DEFAULT_COPTS "${ABSL_CLANG_CL_FLAGS}") - set(ABSL_TEST_COPTS "${ABSL_CLANG_CL_FLAGS};${ABSL_CLANG_CL_TEST_FLAGS}") + set(ABSL_TEST_COPTS "${ABSL_CLANG_CL_TEST_FLAGS}") else() set(ABSL_DEFAULT_COPTS "${ABSL_LLVM_FLAGS}") - set(ABSL_TEST_COPTS "${ABSL_LLVM_FLAGS};${ABSL_LLVM_TEST_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_FLAGS};${ABSL_MSVC_TEST_FLAGS}") + set(ABSL_TEST_COPTS "${ABSL_MSVC_TEST_FLAGS}") set(ABSL_DEFAULT_LINKOPTS "${ABSL_MSVC_LINKOPTS}") else() message(WARNING "Unknown compiler: ${CMAKE_CXX_COMPILER}. Building with no default flags") diff --git a/absl/copts/GENERATED_AbseilCopts.cmake b/absl/copts/GENERATED_AbseilCopts.cmake index a4ab1aa2..430916f7 100644 --- a/absl/copts/GENERATED_AbseilCopts.cmake +++ b/absl/copts/GENERATED_AbseilCopts.cmake @@ -13,22 +13,27 @@ list(APPEND ABSL_CLANG_CL_FLAGS ) list(APPEND ABSL_CLANG_CL_TEST_FLAGS - "-Wno-c99-extensions" + "/W3" + "/DNOMINMAX" + "/DWIN32_LEAN_AND_MEAN" + "/D_CRT_SECURE_NO_WARNINGS" + "/D_SCL_SECURE_NO_WARNINGS" + "/D_ENABLE_EXTENDED_ALIGNED_STORAGE" "-Wno-deprecated-declarations" - "-Wno-missing-noreturn" + "-Wno-implicit-int-conversion" "-Wno-missing-prototypes" "-Wno-missing-variable-declarations" - "-Wno-null-conversion" "-Wno-shadow" - "-Wno-shift-sign-overflow" + "-Wno-shorten-64-to-32" "-Wno-sign-compare" + "-Wno-sign-conversion" + "-Wno-unreachable-code-loop-increment" "-Wno-unused-function" "-Wno-unused-member-function" "-Wno-unused-parameter" "-Wno-unused-private-field" "-Wno-unused-template" "-Wno-used-but-marked-unused" - "-Wno-zero-as-null-pointer-constant" "-Wno-gnu-zero-variadic-macro-arguments" ) @@ -51,9 +56,23 @@ list(APPEND ABSL_GCC_FLAGS ) list(APPEND ABSL_GCC_TEST_FLAGS - "-Wno-conversion-null" + "-Wall" + "-Wextra" + "-Wcast-qual" + "-Wconversion-null" + "-Wformat-security" + "-Woverlength-strings" + "-Wpointer-arith" + "-Wundef" + "-Wunused-local-typedefs" + "-Wunused-result" + "-Wvarargs" + "-Wvla" + "-Wwrite-strings" + "-DNOMINMAX" "-Wno-deprecated-declarations" "-Wno-missing-declarations" + "-Wno-self-move" "-Wno-sign-compare" "-Wno-unused-function" "-Wno-unused-parameter" @@ -78,8 +97,11 @@ list(APPEND ABSL_LLVM_FLAGS "-Wpointer-arith" "-Wself-assign" "-Wshadow-all" + "-Wshorten-64-to-32" + "-Wsign-conversion" "-Wstring-conversion" "-Wtautological-overlap-compare" + "-Wtautological-unsigned-zero-compare" "-Wundef" "-Wuninitialized" "-Wunreachable-code" @@ -91,40 +113,64 @@ list(APPEND ABSL_LLVM_FLAGS "-Wno-float-conversion" "-Wno-implicit-float-conversion" "-Wno-implicit-int-float-conversion" - "-Wno-implicit-int-conversion" - "-Wno-shorten-64-to-32" - "-Wno-sign-conversion" "-Wno-unknown-warning-option" "-DNOMINMAX" ) list(APPEND ABSL_LLVM_TEST_FLAGS - "-Wno-c99-extensions" + "-Wall" + "-Wextra" + "-Wcast-qual" + "-Wconversion" + "-Wfloat-overflow-conversion" + "-Wfloat-zero-conversion" + "-Wfor-loop-analysis" + "-Wformat-security" + "-Wgnu-redeclared-enum" + "-Winfinite-recursion" + "-Winvalid-constexpr" + "-Wliteral-conversion" + "-Wmissing-declarations" + "-Woverlength-strings" + "-Wpointer-arith" + "-Wself-assign" + "-Wshadow-all" + "-Wstring-conversion" + "-Wtautological-overlap-compare" + "-Wtautological-unsigned-zero-compare" + "-Wundef" + "-Wuninitialized" + "-Wunreachable-code" + "-Wunused-comparison" + "-Wunused-local-typedefs" + "-Wunused-result" + "-Wvla" + "-Wwrite-strings" + "-Wno-float-conversion" + "-Wno-implicit-float-conversion" + "-Wno-implicit-int-float-conversion" + "-Wno-unknown-warning-option" + "-DNOMINMAX" "-Wno-deprecated-declarations" - "-Wno-missing-noreturn" + "-Wno-implicit-int-conversion" "-Wno-missing-prototypes" "-Wno-missing-variable-declarations" - "-Wno-null-conversion" "-Wno-shadow" - "-Wno-shift-sign-overflow" + "-Wno-shorten-64-to-32" "-Wno-sign-compare" + "-Wno-sign-conversion" + "-Wno-unreachable-code-loop-increment" "-Wno-unused-function" "-Wno-unused-member-function" "-Wno-unused-parameter" "-Wno-unused-private-field" "-Wno-unused-template" "-Wno-used-but-marked-unused" - "-Wno-zero-as-null-pointer-constant" "-Wno-gnu-zero-variadic-macro-arguments" ) list(APPEND ABSL_MSVC_FLAGS "/W3" - "/DNOMINMAX" - "/DWIN32_LEAN_AND_MEAN" - "/D_CRT_SECURE_NO_WARNINGS" - "/D_SCL_SECURE_NO_WARNINGS" - "/D_ENABLE_EXTENDED_ALIGNED_STORAGE" "/bigobj" "/wd4005" "/wd4068" @@ -133,6 +179,11 @@ list(APPEND ABSL_MSVC_FLAGS "/wd4267" "/wd4503" "/wd4800" + "/DNOMINMAX" + "/DWIN32_LEAN_AND_MEAN" + "/D_CRT_SECURE_NO_WARNINGS" + "/D_SCL_SECURE_NO_WARNINGS" + "/D_ENABLE_EXTENDED_ALIGNED_STORAGE" ) list(APPEND ABSL_MSVC_LINKOPTS @@ -140,6 +191,20 @@ list(APPEND ABSL_MSVC_LINKOPTS ) list(APPEND ABSL_MSVC_TEST_FLAGS + "/W3" + "/bigobj" + "/wd4005" + "/wd4068" + "/wd4180" + "/wd4244" + "/wd4267" + "/wd4503" + "/wd4800" + "/DNOMINMAX" + "/DWIN32_LEAN_AND_MEAN" + "/D_CRT_SECURE_NO_WARNINGS" + "/D_SCL_SECURE_NO_WARNINGS" + "/D_ENABLE_EXTENDED_ALIGNED_STORAGE" "/wd4018" "/wd4101" "/wd4503" diff --git a/absl/copts/GENERATED_copts.bzl b/absl/copts/GENERATED_copts.bzl index a6efc98e..011d8a98 100644 --- a/absl/copts/GENERATED_copts.bzl +++ b/absl/copts/GENERATED_copts.bzl @@ -14,22 +14,27 @@ ABSL_CLANG_CL_FLAGS = [ ] ABSL_CLANG_CL_TEST_FLAGS = [ - "-Wno-c99-extensions", + "/W3", + "/DNOMINMAX", + "/DWIN32_LEAN_AND_MEAN", + "/D_CRT_SECURE_NO_WARNINGS", + "/D_SCL_SECURE_NO_WARNINGS", + "/D_ENABLE_EXTENDED_ALIGNED_STORAGE", "-Wno-deprecated-declarations", - "-Wno-missing-noreturn", + "-Wno-implicit-int-conversion", "-Wno-missing-prototypes", "-Wno-missing-variable-declarations", - "-Wno-null-conversion", "-Wno-shadow", - "-Wno-shift-sign-overflow", + "-Wno-shorten-64-to-32", "-Wno-sign-compare", + "-Wno-sign-conversion", + "-Wno-unreachable-code-loop-increment", "-Wno-unused-function", "-Wno-unused-member-function", "-Wno-unused-parameter", "-Wno-unused-private-field", "-Wno-unused-template", "-Wno-used-but-marked-unused", - "-Wno-zero-as-null-pointer-constant", "-Wno-gnu-zero-variadic-macro-arguments", ] @@ -52,9 +57,23 @@ ABSL_GCC_FLAGS = [ ] ABSL_GCC_TEST_FLAGS = [ - "-Wno-conversion-null", + "-Wall", + "-Wextra", + "-Wcast-qual", + "-Wconversion-null", + "-Wformat-security", + "-Woverlength-strings", + "-Wpointer-arith", + "-Wundef", + "-Wunused-local-typedefs", + "-Wunused-result", + "-Wvarargs", + "-Wvla", + "-Wwrite-strings", + "-DNOMINMAX", "-Wno-deprecated-declarations", "-Wno-missing-declarations", + "-Wno-self-move", "-Wno-sign-compare", "-Wno-unused-function", "-Wno-unused-parameter", @@ -79,8 +98,11 @@ ABSL_LLVM_FLAGS = [ "-Wpointer-arith", "-Wself-assign", "-Wshadow-all", + "-Wshorten-64-to-32", + "-Wsign-conversion", "-Wstring-conversion", "-Wtautological-overlap-compare", + "-Wtautological-unsigned-zero-compare", "-Wundef", "-Wuninitialized", "-Wunreachable-code", @@ -92,40 +114,64 @@ ABSL_LLVM_FLAGS = [ "-Wno-float-conversion", "-Wno-implicit-float-conversion", "-Wno-implicit-int-float-conversion", - "-Wno-implicit-int-conversion", - "-Wno-shorten-64-to-32", - "-Wno-sign-conversion", "-Wno-unknown-warning-option", "-DNOMINMAX", ] ABSL_LLVM_TEST_FLAGS = [ - "-Wno-c99-extensions", + "-Wall", + "-Wextra", + "-Wcast-qual", + "-Wconversion", + "-Wfloat-overflow-conversion", + "-Wfloat-zero-conversion", + "-Wfor-loop-analysis", + "-Wformat-security", + "-Wgnu-redeclared-enum", + "-Winfinite-recursion", + "-Winvalid-constexpr", + "-Wliteral-conversion", + "-Wmissing-declarations", + "-Woverlength-strings", + "-Wpointer-arith", + "-Wself-assign", + "-Wshadow-all", + "-Wstring-conversion", + "-Wtautological-overlap-compare", + "-Wtautological-unsigned-zero-compare", + "-Wundef", + "-Wuninitialized", + "-Wunreachable-code", + "-Wunused-comparison", + "-Wunused-local-typedefs", + "-Wunused-result", + "-Wvla", + "-Wwrite-strings", + "-Wno-float-conversion", + "-Wno-implicit-float-conversion", + "-Wno-implicit-int-float-conversion", + "-Wno-unknown-warning-option", + "-DNOMINMAX", "-Wno-deprecated-declarations", - "-Wno-missing-noreturn", + "-Wno-implicit-int-conversion", "-Wno-missing-prototypes", "-Wno-missing-variable-declarations", - "-Wno-null-conversion", "-Wno-shadow", - "-Wno-shift-sign-overflow", + "-Wno-shorten-64-to-32", "-Wno-sign-compare", + "-Wno-sign-conversion", + "-Wno-unreachable-code-loop-increment", "-Wno-unused-function", "-Wno-unused-member-function", "-Wno-unused-parameter", "-Wno-unused-private-field", "-Wno-unused-template", "-Wno-used-but-marked-unused", - "-Wno-zero-as-null-pointer-constant", "-Wno-gnu-zero-variadic-macro-arguments", ] ABSL_MSVC_FLAGS = [ "/W3", - "/DNOMINMAX", - "/DWIN32_LEAN_AND_MEAN", - "/D_CRT_SECURE_NO_WARNINGS", - "/D_SCL_SECURE_NO_WARNINGS", - "/D_ENABLE_EXTENDED_ALIGNED_STORAGE", "/bigobj", "/wd4005", "/wd4068", @@ -134,6 +180,11 @@ ABSL_MSVC_FLAGS = [ "/wd4267", "/wd4503", "/wd4800", + "/DNOMINMAX", + "/DWIN32_LEAN_AND_MEAN", + "/D_CRT_SECURE_NO_WARNINGS", + "/D_SCL_SECURE_NO_WARNINGS", + "/D_ENABLE_EXTENDED_ALIGNED_STORAGE", ] ABSL_MSVC_LINKOPTS = [ @@ -141,6 +192,20 @@ ABSL_MSVC_LINKOPTS = [ ] ABSL_MSVC_TEST_FLAGS = [ + "/W3", + "/bigobj", + "/wd4005", + "/wd4068", + "/wd4180", + "/wd4244", + "/wd4267", + "/wd4503", + "/wd4800", + "/DNOMINMAX", + "/DWIN32_LEAN_AND_MEAN", + "/D_CRT_SECURE_NO_WARNINGS", + "/D_SCL_SECURE_NO_WARNINGS", + "/D_ENABLE_EXTENDED_ALIGNED_STORAGE", "/wd4018", "/wd4101", "/wd4503", diff --git a/absl/copts/configure_copts.bzl b/absl/copts/configure_copts.bzl index 40d5849a..ca5f26da 100644 --- a/absl/copts/configure_copts.bzl +++ b/absl/copts/configure_copts.bzl @@ -25,13 +25,15 @@ ABSL_DEFAULT_COPTS = select({ "//absl:msvc_compiler": ABSL_MSVC_FLAGS, "//absl:clang-cl_compiler": ABSL_CLANG_CL_FLAGS, "//absl:clang_compiler": ABSL_LLVM_FLAGS, + "//absl:gcc_compiler": ABSL_GCC_FLAGS, "//conditions:default": ABSL_GCC_FLAGS, }) -ABSL_TEST_COPTS = ABSL_DEFAULT_COPTS + select({ +ABSL_TEST_COPTS = select({ "//absl:msvc_compiler": ABSL_MSVC_TEST_FLAGS, "//absl:clang-cl_compiler": ABSL_CLANG_CL_TEST_FLAGS, "//absl:clang_compiler": ABSL_LLVM_TEST_FLAGS, + "//absl:gcc_compiler": ABSL_GCC_TEST_FLAGS, "//conditions:default": ABSL_GCC_TEST_FLAGS, }) diff --git a/absl/copts/copts.py b/absl/copts/copts.py index 0d6c1ec3..e6e11949 100644 --- a/absl/copts/copts.py +++ b/absl/copts/copts.py @@ -11,32 +11,120 @@ The generated copts are consumed by configure_copts.bzl and AbseilConfigureCopts.cmake. """ -# /Wall with msvc includes unhelpful warnings such as C4711, C4710, ... -MSVC_BIG_WARNING_FLAGS = [ - "/W3", +ABSL_GCC_FLAGS = [ + "-Wall", + "-Wextra", + "-Wcast-qual", + "-Wconversion-null", + "-Wformat-security", + "-Wmissing-declarations", + "-Woverlength-strings", + "-Wpointer-arith", + "-Wundef", + "-Wunused-local-typedefs", + "-Wunused-result", + "-Wvarargs", + "-Wvla", # variable-length array + "-Wwrite-strings", + # Don't define min and max macros (Build on Windows using gcc) + "-DNOMINMAX", +] + +ABSL_GCC_TEST_ADDITIONAL_FLAGS = [ + "-Wno-deprecated-declarations", + "-Wno-missing-declarations", + "-Wno-self-move", + "-Wno-sign-compare", + "-Wno-unused-function", + "-Wno-unused-parameter", + "-Wno-unused-private-field", ] -LLVM_TEST_DISABLE_WARNINGS_FLAGS = [ - "-Wno-c99-extensions", +ABSL_LLVM_FLAGS = [ + "-Wall", + "-Wextra", + "-Wcast-qual", + "-Wconversion", + "-Wfloat-overflow-conversion", + "-Wfloat-zero-conversion", + "-Wfor-loop-analysis", + "-Wformat-security", + "-Wgnu-redeclared-enum", + "-Winfinite-recursion", + "-Winvalid-constexpr", + "-Wliteral-conversion", + "-Wmissing-declarations", + "-Woverlength-strings", + "-Wpointer-arith", + "-Wself-assign", + "-Wshadow-all", + "-Wshorten-64-to-32", + "-Wsign-conversion", + "-Wstring-conversion", + "-Wtautological-overlap-compare", + "-Wtautological-unsigned-zero-compare", + "-Wundef", + "-Wuninitialized", + "-Wunreachable-code", + "-Wunused-comparison", + "-Wunused-local-typedefs", + "-Wunused-result", + "-Wvla", + "-Wwrite-strings", + # Warnings that are enabled by group warning flags like -Wall that we + # explicitly disable. + "-Wno-float-conversion", + "-Wno-implicit-float-conversion", + "-Wno-implicit-int-float-conversion", + # Disable warnings on unknown warning flags (when warning flags are + # unknown on older compiler versions) + "-Wno-unknown-warning-option", + # Don't define min and max macros (Build on Windows using clang) + "-DNOMINMAX", +] + +ABSL_LLVM_TEST_ADDITIONAL_FLAGS = [ "-Wno-deprecated-declarations", - "-Wno-missing-noreturn", + "-Wno-implicit-int-conversion", "-Wno-missing-prototypes", "-Wno-missing-variable-declarations", - "-Wno-null-conversion", "-Wno-shadow", - "-Wno-shift-sign-overflow", + "-Wno-shorten-64-to-32", "-Wno-sign-compare", + "-Wno-sign-conversion", + "-Wno-unreachable-code-loop-increment", "-Wno-unused-function", "-Wno-unused-member-function", "-Wno-unused-parameter", "-Wno-unused-private-field", "-Wno-unused-template", "-Wno-used-but-marked-unused", - "-Wno-zero-as-null-pointer-constant", # gtest depends on this GNU extension being offered. "-Wno-gnu-zero-variadic-macro-arguments", ] +# /Wall with msvc includes unhelpful warnings such as C4711, C4710, ... +MSVC_BIG_WARNING_FLAGS = [ + "/W3", +] + +MSVC_WARNING_FLAGS = [ + # Increase the number of sections available in object files + "/bigobj", + "/wd4005", # macro-redefinition + "/wd4068", # unknown pragma + # qualifier applied to function type has no meaning; ignored + "/wd4180", + # conversion from 'type1' to 'type2', possible loss of data + "/wd4244", + # conversion from 'size_t' to 'type', possible loss of data + "/wd4267", + # The decorated name was longer than the compiler limit + "/wd4503", + # forcing value to bool 'true' or 'false' (performance warning) + "/wd4800", +] + MSVC_DEFINES = [ "/DNOMINMAX", # Don't define min and max macros (windows.h) # Don't bloat namespace with incompatible winsock versions. @@ -48,106 +136,43 @@ MSVC_DEFINES = [ "/D_ENABLE_EXTENDED_ALIGNED_STORAGE", ] + +def GccStyleFilterAndCombine(default_flags, test_flags): + """Merges default_flags and test_flags for GCC and LLVM. + + Args: + default_flags: A list of default compiler flags + test_flags: A list of flags that are only used in tests + + Returns: + A combined list of default_flags and test_flags, but with all flags of the + form '-Wwarning' removed if test_flags contains a flag of the form + '-Wno-warning' + """ + remove = set(["-W" + f[5:] for f in test_flags if f[:5] == "-Wno-"]) + return [f for f in default_flags if f not in remove] + test_flags + COPT_VARS = { - "ABSL_GCC_FLAGS": [ - "-Wall", - "-Wextra", - "-Wcast-qual", - "-Wconversion-null", - "-Wformat-security", - "-Wmissing-declarations", - "-Woverlength-strings", - "-Wpointer-arith", - "-Wundef", - "-Wunused-local-typedefs", - "-Wunused-result", - "-Wvarargs", - "-Wvla", # variable-length array - "-Wwrite-strings", - # Don't define min and max macros (Build on Windows using gcc) - "-DNOMINMAX", - ], - "ABSL_GCC_TEST_FLAGS": [ - "-Wno-conversion-null", - "-Wno-deprecated-declarations", - "-Wno-missing-declarations", - "-Wno-sign-compare", - "-Wno-unused-function", - "-Wno-unused-parameter", - "-Wno-unused-private-field", - ], - "ABSL_LLVM_FLAGS": [ - "-Wall", - "-Wextra", - "-Wcast-qual", - "-Wconversion", - "-Wfloat-overflow-conversion", - "-Wfloat-zero-conversion", - "-Wfor-loop-analysis", - "-Wformat-security", - "-Wgnu-redeclared-enum", - "-Winfinite-recursion", - "-Winvalid-constexpr", - "-Wliteral-conversion", - "-Wmissing-declarations", - "-Woverlength-strings", - "-Wpointer-arith", - "-Wself-assign", - "-Wshadow-all", - "-Wstring-conversion", - "-Wtautological-overlap-compare", - "-Wundef", - "-Wuninitialized", - "-Wunreachable-code", - "-Wunused-comparison", - "-Wunused-local-typedefs", - "-Wunused-result", - "-Wvla", - "-Wwrite-strings", - # Warnings that are enabled by group warning flags like -Wall that we - # explicitly disable. - "-Wno-float-conversion", - "-Wno-implicit-float-conversion", - "-Wno-implicit-int-float-conversion", - "-Wno-implicit-int-conversion", - "-Wno-shorten-64-to-32", - "-Wno-sign-conversion", - # Disable warnings on unknown warning flags (when warning flags are - # unknown on older compiler versions) - "-Wno-unknown-warning-option", - # Don't define min and max macros (Build on Windows using clang) - "-DNOMINMAX", - ], - "ABSL_LLVM_TEST_FLAGS": - LLVM_TEST_DISABLE_WARNINGS_FLAGS, + "ABSL_GCC_FLAGS": ABSL_GCC_FLAGS, + "ABSL_GCC_TEST_FLAGS": GccStyleFilterAndCombine( + ABSL_GCC_FLAGS, ABSL_GCC_TEST_ADDITIONAL_FLAGS), + "ABSL_LLVM_FLAGS": ABSL_LLVM_FLAGS, + "ABSL_LLVM_TEST_FLAGS": GccStyleFilterAndCombine( + ABSL_LLVM_FLAGS, ABSL_LLVM_TEST_ADDITIONAL_FLAGS), "ABSL_CLANG_CL_FLAGS": - (MSVC_BIG_WARNING_FLAGS + MSVC_DEFINES), + MSVC_BIG_WARNING_FLAGS + MSVC_DEFINES, "ABSL_CLANG_CL_TEST_FLAGS": - LLVM_TEST_DISABLE_WARNINGS_FLAGS, + MSVC_BIG_WARNING_FLAGS + MSVC_DEFINES + ABSL_LLVM_TEST_ADDITIONAL_FLAGS, "ABSL_MSVC_FLAGS": - MSVC_BIG_WARNING_FLAGS + MSVC_DEFINES + [ - # Increase the number of sections available in object files - "/bigobj", - "/wd4005", # macro-redefinition - "/wd4068", # unknown pragma - # qualifier applied to function type has no meaning; ignored - "/wd4180", - # conversion from 'type1' to 'type2', possible loss of data - "/wd4244", - # conversion from 'size_t' to 'type', possible loss of data - "/wd4267", - # The decorated name was longer than the compiler limit - "/wd4503", - # forcing value to bool 'true' or 'false' (performance warning) - "/wd4800", + MSVC_BIG_WARNING_FLAGS + MSVC_WARNING_FLAGS + MSVC_DEFINES, + "ABSL_MSVC_TEST_FLAGS": + MSVC_BIG_WARNING_FLAGS + MSVC_WARNING_FLAGS + MSVC_DEFINES + [ + "/wd4018", # signed/unsigned mismatch + "/wd4101", # unreferenced local variable + "/wd4503", # decorated name length exceeded, name was truncated + "/wd4996", # use of deprecated symbol + "/DNOMINMAX", # disable the min() and max() macros from <windows.h> ], - "ABSL_MSVC_TEST_FLAGS": [ - "/wd4018", # signed/unsigned mismatch - "/wd4101", # unreferenced local variable - "/wd4503", # decorated name length exceeded, name was truncated - "/wd4996", # use of deprecated symbol - "/DNOMINMAX", # disable the min() and max() macros from <windows.h> - ], "ABSL_MSVC_LINKOPTS": [ # Object file doesn't export any previously undefined symbols "-ignore:4221", diff --git a/absl/crc/BUILD.bazel b/absl/crc/BUILD.bazel new file mode 100644 index 00000000..1c58f46c --- /dev/null +++ b/absl/crc/BUILD.bazel @@ -0,0 +1,210 @@ +# 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. + +load( + "//absl:copts/configure_copts.bzl", + "ABSL_DEFAULT_COPTS", + "ABSL_DEFAULT_LINKOPTS", + "ABSL_TEST_COPTS", +) + +package(default_visibility = ["//visibility:private"]) + +licenses(["notice"]) + +cc_library( + name = "cpu_detect", + srcs = [ + "internal/cpu_detect.cc", + ], + hdrs = ["internal/cpu_detect.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:private"], + deps = [ + "//absl/base", + "//absl/base:config", + ], +) + +cc_library( + name = "crc_internal", + srcs = [ + "internal/crc.cc", + "internal/crc_internal.h", + "internal/crc_x86_arm_combined.cc", + ], + hdrs = [ + "internal/crc.h", + "internal/crc32_x86_arm_combined_simd.h", + ], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + 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", + "//absl/memory", + "//absl/numeric:bits", + ], +) + +cc_library( + name = "crc32c", + srcs = [ + "crc32c.cc", + "internal/crc32c_inline.h", + "internal/crc_memcpy_fallback.cc", + "internal/crc_memcpy_x86_64.cc", + "internal/crc_non_temporal_memcpy.cc", + ], + hdrs = [ + "crc32c.h", + "internal/crc32c.h", + "internal/crc_memcpy.h", + ], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:public"], + deps = [ + ":cpu_detect", + ":crc_internal", + ":non_temporal_memcpy", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:dynamic_annotations", + "//absl/base:endian", + "//absl/base:prefetch", + "//absl/strings", + ], +) + +cc_test( + name = "crc32c_test", + srcs = ["crc32c_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:private"], + deps = [ + ":crc32c", + "//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "non_temporal_arm_intrinsics", + hdrs = ["internal/non_temporal_arm_intrinsics.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:private"], + deps = [ + "//absl/base:config", + ], +) + +cc_library( + name = "non_temporal_memcpy", + hdrs = ["internal/non_temporal_memcpy.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:private"], + deps = [ + ":non_temporal_arm_intrinsics", + "//absl/base:config", + "//absl/base:core_headers", + ], +) + +cc_test( + name = "crc_memcpy_test", + size = "large", + srcs = ["internal/crc_memcpy_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + shard_count = 3, + visibility = ["//visibility:private"], + deps = [ + ":crc32c", + "//absl/memory", + "//absl/random", + "//absl/random:distributions", + "//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "non_temporal_memcpy_test", + srcs = ["internal/non_temporal_memcpy_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:private"], + deps = [ + ":non_temporal_memcpy", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "crc_cord_state", + srcs = ["internal/crc_cord_state.cc"], + hdrs = ["internal/crc_cord_state.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//absl/strings:__pkg__"], + deps = [ + ":crc32c", + "//absl/base:config", + "//absl/numeric:bits", + "//absl/strings", + ], +) + +cc_test( + name = "crc_cord_state_test", + srcs = ["internal/crc_cord_state_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:private"], + deps = [ + ":crc32c", + ":crc_cord_state", + "@com_google_googletest//:gtest_main", + ], +) + +cc_binary( + name = "crc32c_benchmark", + testonly = 1, + srcs = ["crc32c_benchmark.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = [ + "benchmark", + ], + visibility = ["//visibility:private"], + deps = [ + ":crc32c", + "//absl/memory", + "//absl/strings", + "@com_github_google_benchmark//:benchmark_main", + ], +) diff --git a/absl/crc/CMakeLists.txt b/absl/crc/CMakeLists.txt new file mode 100644 index 00000000..72ea2094 --- /dev/null +++ b/absl/crc/CMakeLists.txt @@ -0,0 +1,176 @@ +# 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. + +# Internal-only target, do not depend on directly. +absl_cc_library( + NAME + crc_cpu_detect + HDRS + "internal/cpu_detect.h" + SRCS + "internal/cpu_detect.cc" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::base + absl::config +) + +# Internal-only target, do not depend on directly. +absl_cc_library( + NAME + crc_internal + HDRS + "internal/crc.h" + "internal/crc32_x86_arm_combined_simd.h" + SRCS + "internal/crc.cc" + "internal/crc_internal.h" + "internal/crc_x86_arm_combined.cc" + COPTS + ${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 + absl::memory + absl::bits +) + +absl_cc_library( + NAME + crc32c + HDRS + "crc32c.h" + "internal/crc32c.h" + "internal/crc_memcpy.h" + SRCS + "crc32c.cc" + "internal/crc32c_inline.h" + "internal/crc_memcpy_fallback.cc" + "internal/crc_memcpy_x86_64.cc" + "internal/crc_non_temporal_memcpy.cc" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::crc_cpu_detect + absl::crc_internal + absl::non_temporal_memcpy + absl::config + absl::core_headers + absl::dynamic_annotations + absl::endian + absl::prefetch + absl::strings +) + +absl_cc_test( + NAME + crc32c_test + SRCS + "crc32c_test.cc" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::crc32c + absl::strings + GTest::gtest_main +) + +# Internal-only target, do not depend on directly. +absl_cc_library( + NAME + non_temporal_arm_intrinsics + HDRS + "internal/non_temporal_arm_intrinsics.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::config +) + +# Internal-only target, do not depend on directly. +absl_cc_library( + NAME + non_temporal_memcpy + HDRS + "internal/non_temporal_memcpy.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::non_temporal_arm_intrinsics + absl::config + absl::core_headers +) + +absl_cc_test( + NAME + crc_memcpy_test + SRCS + "internal/crc_memcpy_test.cc" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::crc32c + absl::memory + absl::random_random + absl::random_distributions + absl::strings + GTest::gtest_main +) + +absl_cc_test( + NAME + non_temporal_memcpy_test + SRCS + "internal/non_temporal_memcpy_test.cc" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::non_temporal_memcpy + GTest::gtest_main +) + +absl_cc_library( + NAME + crc_cord_state + HDRS + "internal/crc_cord_state.h" + SRCS + "internal/crc_cord_state.cc" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::crc32c + absl::config + absl::strings +) + +absl_cc_test( + NAME + crc_cord_state_test + SRCS + "internal/crc_cord_state_test.cc" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::crc_cord_state + absl::crc32c + GTest::gtest_main +) diff --git a/absl/crc/crc32c.cc b/absl/crc/crc32c.cc new file mode 100644 index 00000000..468c1b3b --- /dev/null +++ b/absl/crc/crc32c.cc @@ -0,0 +1,99 @@ +// Copyright 2022 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/crc/crc32c.h" + +#include <cstdint> + +#include "absl/crc/internal/crc.h" +#include "absl/crc/internal/crc32c.h" +#include "absl/crc/internal/crc_memcpy.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +namespace { + +const crc_internal::CRC* CrcEngine() { + static const crc_internal::CRC* engine = crc_internal::CRC::Crc32c(); + return engine; +} + +constexpr uint32_t kCRC32Xor = 0xffffffffU; + +} // namespace + +namespace crc_internal { + +crc32c_t UnextendCrc32cByZeroes(crc32c_t initial_crc, size_t length) { + uint32_t crc = static_cast<uint32_t>(initial_crc) ^ kCRC32Xor; + CrcEngine()->UnextendByZeroes(&crc, length); + return static_cast<crc32c_t>(crc ^ kCRC32Xor); +} + +// Called by `absl::ExtendCrc32c()` on strings with size > 64 or when hardware +// CRC32C support is missing. +crc32c_t ExtendCrc32cInternal(crc32c_t initial_crc, + absl::string_view buf_to_add) { + uint32_t crc = static_cast<uint32_t>(initial_crc) ^ kCRC32Xor; + CrcEngine()->Extend(&crc, buf_to_add.data(), buf_to_add.size()); + return static_cast<crc32c_t>(crc ^ kCRC32Xor); +} + +} // namespace crc_internal + +crc32c_t ComputeCrc32c(absl::string_view buf) { + return ExtendCrc32c(crc32c_t{0}, buf); +} + +crc32c_t ExtendCrc32cByZeroes(crc32c_t initial_crc, size_t length) { + uint32_t crc = static_cast<uint32_t>(initial_crc) ^ kCRC32Xor; + CrcEngine()->ExtendByZeroes(&crc, length); + return static_cast<crc32c_t>(crc ^ kCRC32Xor); +} + +crc32c_t ConcatCrc32c(crc32c_t lhs_crc, crc32c_t rhs_crc, size_t rhs_len) { + uint32_t result = static_cast<uint32_t>(lhs_crc); + CrcEngine()->ExtendByZeroes(&result, rhs_len); + return crc32c_t{result ^ static_cast<uint32_t>(rhs_crc)}; +} + +crc32c_t RemoveCrc32cPrefix(crc32c_t crc_a, crc32c_t crc_ab, size_t length_b) { + return ConcatCrc32c(crc_a, crc_ab, length_b); +} + +crc32c_t MemcpyCrc32c(void* dest, const void* src, size_t count, + crc32c_t initial_crc) { + return static_cast<crc32c_t>( + crc_internal::Crc32CAndCopy(dest, src, count, initial_crc, false)); +} + +// Remove a Suffix of given size from a buffer +// +// Given a CRC32C of an existing buffer, `full_string_crc`; the CRC32C of a +// suffix of that buffer to remove, `suffix_crc`; and suffix buffer's length, +// `suffix_len` return the CRC32C of the buffer with suffix removed +// +// This operation has a runtime cost of O(log(`suffix_len`)) +crc32c_t RemoveCrc32cSuffix(crc32c_t full_string_crc, crc32c_t suffix_crc, + size_t suffix_len) { + uint32_t result = static_cast<uint32_t>(full_string_crc) ^ + static_cast<uint32_t>(suffix_crc); + CrcEngine()->UnextendByZeroes(&result, suffix_len); + return crc32c_t{result}; +} + +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/crc/crc32c.h b/absl/crc/crc32c.h new file mode 100644 index 00000000..ba09e52a --- /dev/null +++ b/absl/crc/crc32c.h @@ -0,0 +1,183 @@ +// 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: crc32c.h +// ----------------------------------------------------------------------------- +// +// This header file defines the API for computing CRC32C values as checksums +// for arbitrary sequences of bytes provided as a string buffer. +// +// The API includes the basic functions for computing such CRC32C values and +// some utility functions for performing more efficient mathematical +// computations using an existing checksum. +#ifndef ABSL_CRC_CRC32C_H_ +#define ABSL_CRC_CRC32C_H_ + +#include <cstdint> +#include <ostream> + +#include "absl/crc/internal/crc32c_inline.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +//----------------------------------------------------------------------------- +// crc32c_t +//----------------------------------------------------------------------------- + +// `crc32c_t` defines a strongly-typed integer for holding a CRC32C value. +// +// Some operators are intentionally omitted. Only equality operators are defined +// so that `crc32c_t` can be directly compared. Methods for putting `crc32c_t` +// directly into a set are omitted because this is bug-prone due to checksum +// collisions. Use an explicit conversion to the `uint32_t` space for operations +// that treat `crc32c_t` as an integer. +class crc32c_t final { + public: + crc32c_t() = default; + constexpr explicit crc32c_t(uint32_t crc) : crc_(crc) {} + + crc32c_t(const crc32c_t&) = default; + crc32c_t& operator=(const crc32c_t&) = default; + + explicit operator uint32_t() const { return crc_; } + + friend bool operator==(crc32c_t lhs, crc32c_t rhs) { + return static_cast<uint32_t>(lhs) == static_cast<uint32_t>(rhs); + } + + friend bool operator!=(crc32c_t lhs, crc32c_t rhs) { return !(lhs == rhs); } + + private: + uint32_t crc_; +}; + +namespace crc_internal { +// Non-inline code path for `absl::ExtendCrc32c()`. Do not call directly. +// Call `absl::ExtendCrc32c()` (defined below) instead. +crc32c_t ExtendCrc32cInternal(crc32c_t initial_crc, + absl::string_view buf_to_add); +} // namespace crc_internal + +// ----------------------------------------------------------------------------- +// CRC32C Computation Functions +// ----------------------------------------------------------------------------- + +// ComputeCrc32c() +// +// Returns the CRC32C value of the provided string. +crc32c_t ComputeCrc32c(absl::string_view buf); + +// ExtendCrc32c() +// +// Computes a CRC32C value from an `initial_crc` CRC32C value including the +// `buf_to_add` bytes of an additional buffer. Using this function is more +// efficient than computing a CRC32C value for the combined buffer from +// scratch. +// +// Note: `ExtendCrc32c` with an initial_crc of 0 is equivalent to +// `ComputeCrc32c`. +// +// This operation has a runtime cost of O(`buf_to_add.size()`) +inline crc32c_t ExtendCrc32c(crc32c_t initial_crc, + absl::string_view buf_to_add) { + // Approximately 75% of calls have size <= 64. + if (buf_to_add.size() <= 64) { + uint32_t crc = static_cast<uint32_t>(initial_crc); + if (crc_internal::ExtendCrc32cInline(&crc, buf_to_add.data(), + buf_to_add.size())) { + return crc32c_t{crc}; + } + } + return crc_internal::ExtendCrc32cInternal(initial_crc, buf_to_add); +} + +// ExtendCrc32cByZeroes() +// +// Computes a CRC32C value for a buffer with an `initial_crc` CRC32C value, +// where `length` bytes with a value of 0 are appended to the buffer. Using this +// function is more efficient than computing a CRC32C value for the combined +// buffer from scratch. +// +// This operation has a runtime cost of O(log(`length`)) +crc32c_t ExtendCrc32cByZeroes(crc32c_t initial_crc, size_t length); + +// MemcpyCrc32c() +// +// Copies `src` to `dest` using `memcpy()` semantics, returning the CRC32C +// value of the copied buffer. +// +// Using `MemcpyCrc32c()` is potentially faster than performing the `memcpy()` +// and `ComputeCrc32c()` operations separately. +crc32c_t MemcpyCrc32c(void* dest, const void* src, size_t count, + crc32c_t initial_crc = crc32c_t{0}); + +// ----------------------------------------------------------------------------- +// CRC32C Arithmetic Functions +// ----------------------------------------------------------------------------- + +// The following functions perform arithmetic on CRC32C values, which are +// generally more efficient than recalculating any given result's CRC32C value. + +// ConcatCrc32c() +// +// Calculates the CRC32C value of two buffers with known CRC32C values +// concatenated together. +// +// Given a buffer with CRC32C value `crc1` and a buffer with +// CRC32C value `crc2` and length, `crc2_length`, returns the CRC32C value of +// the concatenation of these two buffers. +// +// This operation has a runtime cost of O(log(`crc2_length`)). +crc32c_t ConcatCrc32c(crc32c_t crc1, crc32c_t crc2, size_t crc2_length); + +// RemoveCrc32cPrefix() +// +// Calculates the CRC32C value of an existing buffer with a series of bytes +// (the prefix) removed from the beginning of that buffer. +// +// Given the CRC32C value of an existing buffer, `full_string_crc`; The CRC32C +// value of a prefix of that buffer, `prefix_crc`; and the length of the buffer +// with the prefix removed, `remaining_string_length` , return the CRC32C +// value of the buffer with the prefix removed. +// +// This operation has a runtime cost of O(log(`remaining_string_length`)). +crc32c_t RemoveCrc32cPrefix(crc32c_t prefix_crc, crc32c_t full_string_crc, + size_t remaining_string_length); +// RemoveCrc32cSuffix() +// +// Calculates the CRC32C value of an existing buffer with a series of bytes +// (the suffix) removed from the end of that buffer. +// +// Given a CRC32C value of an existing buffer `full_string_crc`, the CRC32C +// value of the suffix to remove `suffix_crc`, and the length of that suffix +// `suffix_len`, returns the CRC32C value of the buffer with suffix removed. +// +// This operation has a runtime cost of O(log(`suffix_len`)) +crc32c_t RemoveCrc32cSuffix(crc32c_t full_string_crc, crc32c_t suffix_crc, + size_t suffix_length); + +// operator<< +// +// 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); +} + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CRC_CRC32C_H_ diff --git a/absl/crc/crc32c_benchmark.cc b/absl/crc/crc32c_benchmark.cc new file mode 100644 index 00000000..3b46ef36 --- /dev/null +++ b/absl/crc/crc32c_benchmark.cc @@ -0,0 +1,183 @@ +// 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 <string> + +#include "absl/crc/crc32c.h" +#include "absl/crc/internal/crc32c.h" +#include "absl/memory/memory.h" +#include "absl/strings/string_view.h" +#include "benchmark/benchmark.h" + +namespace { + +std::string TestString(size_t len) { + std::string result; + result.reserve(len); + for (size_t i = 0; i < len; ++i) { + result.push_back(static_cast<char>(i % 256)); + } + return result; +} + +void BM_Calculate(benchmark::State& state) { + int len = state.range(0); + std::string data = TestString(len); + for (auto s : state) { + benchmark::DoNotOptimize(data); + absl::crc32c_t crc = absl::ComputeCrc32c(data); + benchmark::DoNotOptimize(crc); + } +} +BENCHMARK(BM_Calculate)->Arg(0)->Arg(1)->Arg(100)->Arg(10000)->Arg(500000); + +void BM_Extend(benchmark::State& state) { + int len = state.range(0); + std::string extension = TestString(len); + absl::crc32c_t base = absl::crc32c_t{0xC99465AA}; // CRC32C of "Hello World" + for (auto s : state) { + benchmark::DoNotOptimize(base); + benchmark::DoNotOptimize(extension); + absl::crc32c_t crc = absl::ExtendCrc32c(base, extension); + benchmark::DoNotOptimize(crc); + } +} +BENCHMARK(BM_Extend)->Arg(0)->Arg(1)->Arg(100)->Arg(10000)->Arg(500000)->Arg( + 100 * 1000 * 1000); + +// Make working set >> CPU cache size to benchmark prefetches better +void BM_ExtendCacheMiss(benchmark::State& state) { + int len = state.range(0); + constexpr int total = 300 * 1000 * 1000; + std::string extension = TestString(total); + absl::crc32c_t base = absl::crc32c_t{0xC99465AA}; // CRC32C of "Hello World" + for (auto s : state) { + for (int i = 0; i < total; i += len * 2) { + benchmark::DoNotOptimize(base); + benchmark::DoNotOptimize(extension); + absl::crc32c_t crc = + absl::ExtendCrc32c(base, absl::string_view(&extension[i], len)); + benchmark::DoNotOptimize(crc); + } + } + state.SetBytesProcessed(static_cast<int64_t>(state.iterations()) * total / 2); +} +BENCHMARK(BM_ExtendCacheMiss)->Arg(10)->Arg(100)->Arg(1000)->Arg(100000); + +void BM_ExtendByZeroes(benchmark::State& state) { + absl::crc32c_t base = absl::crc32c_t{0xC99465AA}; // CRC32C of "Hello World" + int num_zeroes = state.range(0); + for (auto s : state) { + benchmark::DoNotOptimize(base); + absl::crc32c_t crc = absl::ExtendCrc32cByZeroes(base, num_zeroes); + benchmark::DoNotOptimize(crc); + } +} +BENCHMARK(BM_ExtendByZeroes) + ->RangeMultiplier(10) + ->Range(1, 1000000) + ->RangeMultiplier(32) + ->Range(1, 1 << 20); + +void BM_UnextendByZeroes(benchmark::State& state) { + absl::crc32c_t base = absl::crc32c_t{0xdeadbeef}; + int num_zeroes = state.range(0); + for (auto s : state) { + benchmark::DoNotOptimize(base); + absl::crc32c_t crc = + absl::crc_internal::UnextendCrc32cByZeroes(base, num_zeroes); + benchmark::DoNotOptimize(crc); + } +} +BENCHMARK(BM_UnextendByZeroes) + ->RangeMultiplier(10) + ->Range(1, 1000000) + ->RangeMultiplier(32) + ->Range(1, 1 << 20); + +void BM_Concat(benchmark::State& state) { + int string_b_len = state.range(0); + std::string string_b = TestString(string_b_len); + + // CRC32C of "Hello World" + absl::crc32c_t crc_a = absl::crc32c_t{0xC99465AA}; + absl::crc32c_t crc_b = absl::ComputeCrc32c(string_b); + + for (auto s : state) { + benchmark::DoNotOptimize(crc_a); + benchmark::DoNotOptimize(crc_b); + benchmark::DoNotOptimize(string_b_len); + absl::crc32c_t crc_ab = absl::ConcatCrc32c(crc_a, crc_b, string_b_len); + benchmark::DoNotOptimize(crc_ab); + } +} +BENCHMARK(BM_Concat) + ->RangeMultiplier(10) + ->Range(1, 1000000) + ->RangeMultiplier(32) + ->Range(1, 1 << 20); + +void BM_Memcpy(benchmark::State& state) { + int string_len = state.range(0); + + std::string source = TestString(string_len); + auto dest = absl::make_unique<char[]>(string_len); + + for (auto s : state) { + benchmark::DoNotOptimize(source); + absl::crc32c_t crc = + absl::MemcpyCrc32c(dest.get(), source.data(), source.size()); + benchmark::DoNotOptimize(crc); + benchmark::DoNotOptimize(dest); + benchmark::DoNotOptimize(dest.get()); + benchmark::DoNotOptimize(dest[0]); + } + + state.SetBytesProcessed(static_cast<int64_t>(state.iterations()) * + state.range(0)); +} +BENCHMARK(BM_Memcpy)->Arg(0)->Arg(1)->Arg(100)->Arg(10000)->Arg(500000); + +void BM_RemoveSuffix(benchmark::State& state) { + int full_string_len = state.range(0); + int suffix_len = state.range(1); + + std::string full_string = TestString(full_string_len); + std::string suffix = full_string.substr( + full_string_len - suffix_len, full_string_len); + + absl::crc32c_t full_string_crc = absl::ComputeCrc32c(full_string); + absl::crc32c_t suffix_crc = absl::ComputeCrc32c(suffix); + + for (auto s : state) { + benchmark::DoNotOptimize(full_string_crc); + benchmark::DoNotOptimize(suffix_crc); + benchmark::DoNotOptimize(suffix_len); + absl::crc32c_t crc = absl::RemoveCrc32cSuffix(full_string_crc, suffix_crc, + suffix_len); + benchmark::DoNotOptimize(crc); + } +} +BENCHMARK(BM_RemoveSuffix) + ->ArgPair(1, 1) + ->ArgPair(100, 10) + ->ArgPair(100, 100) + ->ArgPair(10000, 1) + ->ArgPair(10000, 100) + ->ArgPair(10000, 10000) + ->ArgPair(500000, 1) + ->ArgPair(500000, 100) + ->ArgPair(500000, 10000) + ->ArgPair(500000, 500000); +} // namespace diff --git a/absl/crc/crc32c_test.cc b/absl/crc/crc32c_test.cc new file mode 100644 index 00000000..72d422a1 --- /dev/null +++ b/absl/crc/crc32c_test.cc @@ -0,0 +1,194 @@ +// 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/crc/crc32c.h" + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <cstring> +#include <string> + +#include "gtest/gtest.h" +#include "absl/crc/internal/crc32c.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" + +namespace { + +TEST(CRC32C, RFC3720) { + // Test the results of the vectors from + // https://www.rfc-editor.org/rfc/rfc3720#appendix-B.4 + char data[32]; + + // 32 bytes of ones. + memset(data, 0, sizeof(data)); + EXPECT_EQ(absl::ComputeCrc32c(absl::string_view(data, sizeof(data))), + absl::crc32c_t{0x8a9136aa}); + + // 32 bytes of ones. + memset(data, 0xff, sizeof(data)); + EXPECT_EQ(absl::ComputeCrc32c(absl::string_view(data, sizeof(data))), + absl::crc32c_t{0x62a8ab43}); + + // 32 incrementing bytes. + for (int i = 0; i < 32; ++i) data[i] = static_cast<char>(i); + EXPECT_EQ(absl::ComputeCrc32c(absl::string_view(data, sizeof(data))), + absl::crc32c_t{0x46dd794e}); + + // 32 decrementing bytes. + for (int i = 0; i < 32; ++i) data[i] = static_cast<char>(31 - i); + EXPECT_EQ(absl::ComputeCrc32c(absl::string_view(data, sizeof(data))), + absl::crc32c_t{0x113fdb5c}); + + // An iSCSI - SCSI Read (10) Command PDU. + constexpr uint8_t cmd[48] = { + 0x01, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, + 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x18, 0x28, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + }; + EXPECT_EQ(absl::ComputeCrc32c(absl::string_view( + reinterpret_cast<const char*>(cmd), sizeof(cmd))), + absl::crc32c_t{0xd9963a56}); +} + +std::string TestString(size_t len) { + std::string result; + result.reserve(len); + for (size_t i = 0; i < len; ++i) { + result.push_back(static_cast<char>(i % 256)); + } + return result; +} + +TEST(CRC32C, Compute) { + EXPECT_EQ(absl::ComputeCrc32c(""), absl::crc32c_t{0}); + EXPECT_EQ(absl::ComputeCrc32c("hello world"), absl::crc32c_t{0xc99465aa}); +} + +TEST(CRC32C, Extend) { + uint32_t base = 0xC99465AA; // CRC32C of "Hello World" + std::string extension = "Extension String"; + + EXPECT_EQ( + absl::ExtendCrc32c(absl::crc32c_t{base}, extension), + absl::crc32c_t{0xD2F65090}); // CRC32C of "Hello WorldExtension String" +} + +TEST(CRC32C, ExtendByZeroes) { + std::string base = "hello world"; + absl::crc32c_t base_crc = absl::crc32c_t{0xc99465aa}; + + constexpr size_t kExtendByValues[] = {100, 10000, 100000}; + for (const size_t extend_by : kExtendByValues) { + SCOPED_TRACE(extend_by); + absl::crc32c_t crc2 = absl::ExtendCrc32cByZeroes(base_crc, extend_by); + EXPECT_EQ(crc2, absl::ComputeCrc32c(base + std::string(extend_by, '\0'))); + } +} + +TEST(CRC32C, UnextendByZeroes) { + constexpr size_t kExtendByValues[] = {2, 200, 20000, 200000, 20000000}; + constexpr size_t kUnextendByValues[] = {0, 100, 10000, 100000, 10000000}; + + for (auto seed_crc : {absl::crc32c_t{0}, absl::crc32c_t{0xc99465aa}}) { + SCOPED_TRACE(seed_crc); + for (const size_t size_1 : kExtendByValues) { + for (const size_t size_2 : kUnextendByValues) { + size_t extend_size = std::max(size_1, size_2); + size_t unextend_size = std::min(size_1, size_2); + SCOPED_TRACE(extend_size); + SCOPED_TRACE(unextend_size); + + // Extending by A zeroes an unextending by B<A zeros should be identical + // to extending by A-B zeroes. + absl::crc32c_t crc1 = seed_crc; + crc1 = absl::ExtendCrc32cByZeroes(crc1, extend_size); + crc1 = absl::crc_internal::UnextendCrc32cByZeroes(crc1, unextend_size); + + absl::crc32c_t crc2 = seed_crc; + crc2 = absl::ExtendCrc32cByZeroes(crc2, extend_size - unextend_size); + + EXPECT_EQ(crc1, crc2); + } + } + } + + constexpr size_t kSizes[] = {0, 1, 100, 10000}; + for (const size_t size : kSizes) { + SCOPED_TRACE(size); + std::string string_before = TestString(size); + std::string string_after = string_before + std::string(size, '\0'); + + absl::crc32c_t crc_before = absl::ComputeCrc32c(string_before); + absl::crc32c_t crc_after = absl::ComputeCrc32c(string_after); + + EXPECT_EQ(crc_before, + absl::crc_internal::UnextendCrc32cByZeroes(crc_after, size)); + } +} + +TEST(CRC32C, Concat) { + std::string hello = "Hello, "; + std::string world = "world!"; + std::string hello_world = absl::StrCat(hello, world); + + absl::crc32c_t crc_a = absl::ComputeCrc32c(hello); + absl::crc32c_t crc_b = absl::ComputeCrc32c(world); + absl::crc32c_t crc_ab = absl::ComputeCrc32c(hello_world); + + EXPECT_EQ(absl::ConcatCrc32c(crc_a, crc_b, world.size()), crc_ab); +} + +TEST(CRC32C, Memcpy) { + constexpr size_t kBytesSize[] = {0, 1, 20, 500, 100000}; + for (size_t bytes : kBytesSize) { + SCOPED_TRACE(bytes); + std::string sample_string = TestString(bytes); + std::string target_buffer = std::string(bytes, '\0'); + + absl::crc32c_t memcpy_crc = + absl::MemcpyCrc32c(&(target_buffer[0]), sample_string.data(), bytes); + absl::crc32c_t compute_crc = absl::ComputeCrc32c(sample_string); + + EXPECT_EQ(memcpy_crc, compute_crc); + EXPECT_EQ(sample_string, target_buffer); + } +} + +TEST(CRC32C, RemovePrefix) { + std::string hello = "Hello, "; + std::string world = "world!"; + std::string hello_world = absl::StrCat(hello, world); + + absl::crc32c_t crc_a = absl::ComputeCrc32c(hello); + absl::crc32c_t crc_b = absl::ComputeCrc32c(world); + absl::crc32c_t crc_ab = absl::ComputeCrc32c(hello_world); + + EXPECT_EQ(absl::RemoveCrc32cPrefix(crc_a, crc_ab, world.size()), crc_b); +} + +TEST(CRC32C, RemoveSuffix) { + std::string hello = "Hello, "; + std::string world = "world!"; + std::string hello_world = absl::StrCat(hello, world); + + absl::crc32c_t crc_a = absl::ComputeCrc32c(hello); + absl::crc32c_t crc_b = absl::ComputeCrc32c(world); + absl::crc32c_t crc_ab = absl::ComputeCrc32c(hello_world); + + EXPECT_EQ(absl::RemoveCrc32cSuffix(crc_ab, crc_b, world.size()), crc_a); +} +} // namespace diff --git a/absl/crc/internal/cpu_detect.cc b/absl/crc/internal/cpu_detect.cc new file mode 100644 index 00000000..d61b7018 --- /dev/null +++ b/absl/crc/internal/cpu_detect.cc @@ -0,0 +1,256 @@ +// 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/crc/internal/cpu_detect.h" + +#include <cstdint> +#include <string> + +#include "absl/base/config.h" + +#if defined(__aarch64__) && defined(__linux__) +#include <asm/hwcap.h> +#include <sys/auxv.h> +#endif + +#if defined(_WIN32) || defined(_WIN64) +#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) +// 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) { + __asm__ volatile("cpuid \n\t" + : "=a"(cpu_info[0]), "=b"(cpu_info[1]), "=c"(cpu_info[2]), + "=d"(cpu_info[3]) + : "a"(info_type), "c"(0)); +} +#endif // !defined(_WIN32) && !defined(_WIN64) + +enum class Vendor { + kUnknown, + kIntel, + kAmd, +}; + +Vendor GetVendor() { + // Get the vendor string (issue CPUID with eax = 0). + int cpu_info[4]; + __cpuid(cpu_info, 0); + + std::string vendor; + vendor.append(reinterpret_cast<char*>(&cpu_info[1]), 4); + vendor.append(reinterpret_cast<char*>(&cpu_info[3]), 4); + vendor.append(reinterpret_cast<char*>(&cpu_info[2]), 4); + if (vendor == "GenuineIntel") { + return Vendor::kIntel; + } else if (vendor == "AuthenticAMD") { + return Vendor::kAmd; + } else { + return Vendor::kUnknown; + } +} + +CpuType GetIntelCpuType() { + // To get general information and extended features we send eax = 1 and + // ecx = 0 to cpuid. The response is returned in eax, ebx, ecx and edx. + // (See Intel 64 and IA-32 Architectures Software Developer's Manual + // Volume 2A: Instruction Set Reference, A-M CPUID). + // https://www.intel.com/content/www/us/en/architecture-and-technology/64-ia-32-architectures-software-developer-vol-2a-manual.html + // https://learn.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex + int cpu_info[4]; + __cpuid(cpu_info, 1); + + // Response in eax bits as follows: + // 0-3 (stepping id) + // 4-7 (model number), + // 8-11 (family code), + // 12-13 (processor type), + // 16-19 (extended model) + // 20-27 (extended family) + + int family = (cpu_info[0] >> 8) & 0x0f; + int model_num = (cpu_info[0] >> 4) & 0x0f; + int ext_family = (cpu_info[0] >> 20) & 0xff; + int ext_model_num = (cpu_info[0] >> 16) & 0x0f; + + int brand_id = cpu_info[1] & 0xff; + + // Process the extended family and model info if necessary + if (family == 0x0f) { + family += ext_family; + } + + if (family == 0x0f || family == 0x6) { + model_num += (ext_model_num << 4); + } + + switch (brand_id) { + case 0: // no brand ID, so parse CPU family/model + switch (family) { + case 6: // Most PentiumIII processors are in this category + switch (model_num) { + case 0x2c: // Westmere: Gulftown + return CpuType::kIntelWestmere; + case 0x2d: // Sandybridge + return CpuType::kIntelSandybridge; + case 0x3e: // Ivybridge + return CpuType::kIntelIvybridge; + case 0x3c: // Haswell (client) + case 0x3f: // Haswell + return CpuType::kIntelHaswell; + case 0x4f: // Broadwell + case 0x56: // BroadwellDE + return CpuType::kIntelBroadwell; + case 0x55: // Skylake Xeon + if ((cpu_info[0] & 0x0f) < 5) { // stepping < 5 is skylake + return CpuType::kIntelSkylakeXeon; + } else { // stepping >= 5 is cascadelake + return CpuType::kIntelCascadelakeXeon; + } + case 0x5e: // Skylake (client) + return CpuType::kIntelSkylake; + default: + return CpuType::kUnknown; + } + default: + return CpuType::kUnknown; + } + default: + return CpuType::kUnknown; + } +} + +CpuType GetAmdCpuType() { + // To get general information and extended features we send eax = 1 and + // ecx = 0 to cpuid. The response is returned in eax, ebx, ecx and edx. + // (See Intel 64 and IA-32 Architectures Software Developer's Manual + // Volume 2A: Instruction Set Reference, A-M CPUID). + // https://learn.microsoft.com/en-us/cpp/intrinsics/cpuid-cpuidex + int cpu_info[4]; + __cpuid(cpu_info, 1); + + // Response in eax bits as follows: + // 0-3 (stepping id) + // 4-7 (model number), + // 8-11 (family code), + // 12-13 (processor type), + // 16-19 (extended model) + // 20-27 (extended family) + + int family = (cpu_info[0] >> 8) & 0x0f; + int model_num = (cpu_info[0] >> 4) & 0x0f; + int ext_family = (cpu_info[0] >> 20) & 0xff; + int ext_model_num = (cpu_info[0] >> 16) & 0x0f; + + if (family == 0x0f) { + family += ext_family; + model_num += (ext_model_num << 4); + } + + switch (family) { + case 0x17: + switch (model_num) { + case 0x0: // Stepping Ax + case 0x1: // Stepping Bx + return CpuType::kAmdNaples; + case 0x30: // Stepping Ax + case 0x31: // Stepping Bx + return CpuType::kAmdRome; + default: + return CpuType::kUnknown; + } + break; + case 0x19: + switch (model_num) { + case 0x1: // Stepping B0 + return CpuType::kAmdMilan; + default: + return CpuType::kUnknown; + } + break; + default: + return CpuType::kUnknown; + } +} + +} // namespace + +CpuType GetCpuType() { + switch (GetVendor()) { + case Vendor::kIntel: + return GetIntelCpuType(); + case Vendor::kAmd: + return GetAmdCpuType(); + default: + return CpuType::kUnknown; + } +} + +bool SupportsArmCRC32PMULL() { return false; } + +#elif defined(__aarch64__) && defined(__linux__) + +#ifndef HWCAP_CPUID +#define HWCAP_CPUID (1 << 11) +#endif + +#define ABSL_INTERNAL_AARCH64_ID_REG_READ(id, val) \ + asm("mrs %0, " #id : "=r"(val)) + +CpuType GetCpuType() { + // MIDR_EL1 is not visible to EL0, however the access will be emulated by + // linux if AT_HWCAP has HWCAP_CPUID set. + // + // This method will be unreliable on heterogeneous computing systems (ex: + // big.LITTLE) since the value of MIDR_EL1 will change based on the calling + // thread. + uint64_t hwcaps = getauxval(AT_HWCAP); + if (hwcaps & HWCAP_CPUID) { + uint64_t midr = 0; + 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; + } + } + return CpuType::kUnknown; +} + +bool SupportsArmCRC32PMULL() { + uint64_t hwcaps = getauxval(AT_HWCAP); + return (hwcaps & HWCAP_CRC32) && (hwcaps & HWCAP_PMULL); +} + +#else + +CpuType GetCpuType() { return CpuType::kUnknown; } + +bool SupportsArmCRC32PMULL() { return false; } + +#endif + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/crc/internal/cpu_detect.h b/absl/crc/internal/cpu_detect.h new file mode 100644 index 00000000..6054f696 --- /dev/null +++ b/absl/crc/internal/cpu_detect.h @@ -0,0 +1,57 @@ +// Copyright 2022 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_CRC_INTERNAL_CPU_DETECT_H_ +#define ABSL_CRC_INTERNAL_CPU_DETECT_H_ + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +// Enumeration of architectures that we have special-case tuning parameters for. +// This set may change over time. +enum class CpuType { + kUnknown, + kIntelHaswell, + kAmdRome, + kAmdNaples, + kAmdMilan, + kIntelCascadelakeXeon, + kIntelSkylakeXeon, + kIntelBroadwell, + kIntelSkylake, + kIntelIvybridge, + kIntelSandybridge, + kIntelWestmere, + kArmNeoverseN1, +}; + +// Returns the type of host CPU this code is running on. Returns kUnknown if +// the host CPU is of unknown type, or if detection otherwise fails. +CpuType GetCpuType(); + +// Returns whether the host CPU supports the CPU features needed for our +// accelerated implementations. The CpuTypes enumerated above apart from +// kUnknown support the required features. On unknown CPUs, we can use +// this to see if it's safe to use hardware acceleration, though without any +// tuning. +bool SupportsArmCRC32PMULL(); + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CRC_INTERNAL_CPU_DETECT_H_ diff --git a/absl/crc/internal/crc.cc b/absl/crc/internal/crc.cc new file mode 100644 index 00000000..bb8936e3 --- /dev/null +++ b/absl/crc/internal/crc.cc @@ -0,0 +1,468 @@ +// 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. + +// Implementation of CRCs (aka Rabin Fingerprints). +// Treats the input as a polynomial with coefficients in Z(2), +// and finds the remainder when divided by an irreducible polynomial +// of the appropriate length. +// It handles all CRC sizes from 8 to 128 bits. +// It's somewhat complicated by having separate implementations optimized for +// CRC's <=32 bits, <= 64 bits, and <= 128 bits. +// The input string is prefixed with a "1" bit, and has "degree" "0" bits +// appended to it before the remainder is found. This ensures that +// short strings are scrambled somewhat and that strings consisting +// of all nulls have a non-zero CRC. +// +// Uses the "interleaved word-by-word" method from +// "Everything we know about CRC but afraid to forget" by Andrew Kadatch +// and Bob Jenkins, +// http://crcutil.googlecode.com/files/crc-doc.1.0.pdf +// +// The idea is to compute kStride CRCs simultaneously, allowing the +// processor to more effectively use multiple execution units. Each of +// the CRCs is calculated on one word of data followed by kStride - 1 +// words of zeroes; the CRC starting points are staggered by one word. +// Assuming a stride of 4 with data words "ABCDABCDABCD", the first +// CRC is over A000A000A, the second over 0B000B000B, and so on. +// The CRC of the whole data is then calculated by properly aligning the +// CRCs by appending zeroes until the data lengths agree then XORing +// the CRCs. + +#include "absl/crc/internal/crc.h" + +#include <cstdint> + +#include "absl/base/internal/endian.h" +#include "absl/base/internal/prefetch.h" +#include "absl/base/internal/raw_logging.h" +#include "absl/crc/internal/crc_internal.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +namespace { + +// Constants +#if defined(__i386__) || defined(__x86_64__) +constexpr bool kNeedAlignedLoads = false; +#else +constexpr bool kNeedAlignedLoads = true; +#endif + +// We express the number of zeroes as a number in base ZEROES_BASE. By +// pre-computing the zero extensions for all possible components of such an +// expression (numbers in a form a*ZEROES_BASE**b), we can calculate the +// resulting extension by multiplying the extensions for individual components +// using log_{ZEROES_BASE}(num_zeroes) polynomial multiplications. The tables of +// zero extensions contain (ZEROES_BASE - 1) * (log_{ZEROES_BASE}(64)) entries. +constexpr int ZEROES_BASE_LG = 4; // log_2(ZEROES_BASE) +constexpr int ZEROES_BASE = (1 << ZEROES_BASE_LG); // must be a power of 2 + +constexpr uint32_t kCrc32cPoly = 0x82f63b78; + +uint32_t ReverseBits(uint32_t bits) { + bits = (bits & 0xaaaaaaaau) >> 1 | (bits & 0x55555555u) << 1; + bits = (bits & 0xccccccccu) >> 2 | (bits & 0x33333333u) << 2; + bits = (bits & 0xf0f0f0f0u) >> 4 | (bits & 0x0f0f0f0fu) << 4; + return absl::gbswap_32(bits); +} + +// Polynomial long multiplication mod the polynomial of degree 32. +void PolyMultiply(uint32_t* val, uint32_t m, uint32_t poly) { + uint32_t l = *val; + uint32_t result = 0; + auto onebit = uint32_t{0x80000000u}; + for (uint32_t one = onebit; one != 0; one >>= 1) { + if ((l & one) != 0) { + result ^= m; + } + if (m & 1) { + m = (m >> 1) ^ poly; + } else { + m >>= 1; + } + } + *val = result; +} +} // namespace + +void CRCImpl::FillWordTable(uint32_t poly, uint32_t last, int word_size, + Uint32By256* t) { + for (int j = 0; j != word_size; j++) { // for each byte of extension.... + t[j][0] = 0; // a zero has no effect + for (int i = 128; i != 0; i >>= 1) { // fill in entries for powers of 2 + if (j == 0 && i == 128) { + t[j][i] = last; // top bit in last byte is given + } else { + // each successive power of two is derived from the previous + // one, either in this table, or the last table + uint32_t pred; + if (i == 128) { + pred = t[j - 1][1]; + } else { + pred = t[j][i << 1]; + } + // Advance the CRC by one bit (multiply by X, and take remainder + // through one step of polynomial long division) + if (pred & 1) { + t[j][i] = (pred >> 1) ^ poly; + } else { + t[j][i] = pred >> 1; + } + } + } + // CRCs have the property that CRC(a xor b) == CRC(a) xor CRC(b) + // so we can make all the tables for non-powers of two by + // xoring previously created entries. + for (int i = 2; i != 256; i <<= 1) { + for (int k = i + 1; k != (i << 1); k++) { + t[j][k] = t[j][i] ^ t[j][k - i]; + } + } + } +} + +int CRCImpl::FillZeroesTable(uint32_t poly, Uint32By256* t) { + uint32_t inc = 1; + inc <<= 31; + + // Extend by one zero bit. We know degree > 1 so (inc & 1) == 0. + inc >>= 1; + + // Now extend by 2, 4, and 8 bits, so now `inc` is extended by one zero byte. + for (int i = 0; i < 3; ++i) { + PolyMultiply(&inc, inc, poly); + } + + int j = 0; + for (uint64_t inc_len = 1; inc_len != 0; inc_len <<= ZEROES_BASE_LG) { + // Every entry in the table adds an additional inc_len zeroes. + uint32_t v = inc; + for (int a = 1; a != ZEROES_BASE; a++) { + t[0][j] = v; + PolyMultiply(&v, inc, poly); + j++; + } + inc = v; + } + ABSL_RAW_CHECK(j <= 256, ""); + return j; +} + +// Internal version of the "constructor". +CRCImpl* CRCImpl::NewInternal() { + // Find an accelearated implementation first. + CRCImpl* result = TryNewCRC32AcceleratedX86ARMCombined(); + + // Fall back to generic implementions if no acceleration is available. + if (result == nullptr) { + result = new CRC32(); + } + + result->InitTables(); + + 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() { + // Compute the table for extending a CRC by one byte. + Uint32By256* t = new Uint32By256[4]; + FillWordTable(kCrc32cPoly, kCrc32cPoly, 1, t); + for (int i = 0; i != 256; i++) { + this->table0_[i] = t[0][i]; + } + + // Construct a table for updating the CRC by 4 bytes data followed by + // 12 bytes of zeroes. + // + // Note: the data word size could be larger than the CRC size; it might + // be slightly faster to use a 64-bit data word, but doing so doubles the + // table size. + uint32_t last = kCrc32cPoly; + const size_t size = 12; + for (size_t i = 0; i < size; ++i) { + last = (last >> 8) ^ this->table0_[last & 0xff]; + } + FillWordTable(kCrc32cPoly, last, 4, t); + for (size_t b = 0; b < 4; ++b) { + for (int i = 0; i < 256; ++i) { + this->table_[b][i] = t[b][i]; + } + } + + int j = FillZeroesTable(kCrc32cPoly, t); + ABSL_RAW_CHECK(j <= static_cast<int>(ABSL_ARRAYSIZE(this->zeroes_)), ""); + for (int i = 0; i < j; i++) { + this->zeroes_[i] = t[0][i]; + } + + delete[] t; + + // Build up tables for _reversing_ the operation of doing CRC operations on + // zero bytes. + + // In C++, extending `crc` by a single zero bit is done by the following: + // (A) bool low_bit_set = (crc & 1); + // crc >>= 1; + // if (low_bit_set) crc ^= kCrc32cPoly; + // + // In particular note that the high bit of `crc` after this operation will be + // set if and only if the low bit of `crc` was set before it. This means that + // no information is lost, and the operation can be reversed, as follows: + // (B) bool high_bit_set = (crc & 0x80000000u); + // if (high_bit_set) crc ^= kCrc32cPoly; + // crc <<= 1; + // if (high_bit_set) crc ^= 1; + // + // Or, equivalently: + // (C) bool high_bit_set = (crc & 0x80000000u); + // crc <<= 1; + // if (high_bit_set) crc ^= ((kCrc32cPoly << 1) ^ 1); + // + // The last observation is, if we store our checksums in variable `rcrc`, + // with order of the bits reversed, the inverse operation becomes: + // (D) bool low_bit_set = (rcrc & 1); + // rcrc >>= 1; + // if (low_bit_set) rcrc ^= ReverseBits((kCrc32cPoly << 1) ^ 1) + // + // This is the same algorithm (A) that we started with, only with a different + // polynomial bit pattern. This means that by building up our tables with + // this alternate polynomial, we can apply the CRC algorithms to a + // bit-reversed CRC checksum to perform inverse zero-extension. + + const uint32_t kCrc32cUnextendPoly = + ReverseBits(static_cast<uint32_t>((kCrc32cPoly << 1) ^ 1)); + FillWordTable(kCrc32cUnextendPoly, kCrc32cUnextendPoly, 1, &reverse_table0_); + + j = FillZeroesTable(kCrc32cUnextendPoly, &reverse_zeroes_); + ABSL_RAW_CHECK(j <= static_cast<int>(ABSL_ARRAYSIZE(this->reverse_zeroes_)), + ""); +} + +void CRC32::Extend(uint32_t* crc, const void* bytes, size_t length) const { + const uint8_t* p = static_cast<const uint8_t*>(bytes); + const uint8_t* e = p + length; + uint32_t l = *crc; + + auto step_one_byte = [this, &p, &l] () { + int c = (l & 0xff) ^ *p++; + l = this->table0_[c] ^ (l >> 8); + }; + + if (kNeedAlignedLoads) { + // point x at first 4-byte aligned byte in string. this might be past the + // end of the string. + const uint8_t* x = RoundUp<4>(p); + if (x <= e) { + // Process bytes until finished or p is 4-byte aligned + while (p != x) { + step_one_byte(); + } + } + } + + const size_t kSwathSize = 16; + if (static_cast<size_t>(e - p) >= kSwathSize) { + // Load one swath of data into the operating buffers. + uint32_t buf0 = absl::little_endian::Load32(p) ^ l; + uint32_t buf1 = absl::little_endian::Load32(p + 4); + uint32_t buf2 = absl::little_endian::Load32(p + 8); + uint32_t buf3 = absl::little_endian::Load32(p + 12); + p += kSwathSize; + + // Increment a CRC value by a "swath"; this combines the four bytes + // starting at `ptr` and twelve zero bytes, so that four CRCs can be + // built incrementally and combined at the end. + const auto step_swath = [this](uint32_t crc_in, const std::uint8_t* ptr) { + return absl::little_endian::Load32(ptr) ^ + this->table_[3][crc_in & 0xff] ^ + this->table_[2][(crc_in >> 8) & 0xff] ^ + this->table_[1][(crc_in >> 16) & 0xff] ^ + this->table_[0][crc_in >> 24]; + }; + + // Run one CRC calculation step over all swaths in one 16-byte stride + const auto step_stride = [&]() { + buf0 = step_swath(buf0, p); + buf1 = step_swath(buf1, p + 4); + buf2 = step_swath(buf2, p + 8); + buf3 = step_swath(buf3, p + 12); + p += 16; + }; + + // Process kStride interleaved swaths through the data in parallel. + while ((e - p) > kPrefetchHorizon) { + base_internal::PrefetchNta( + reinterpret_cast<const void*>(p + kPrefetchHorizon)); + // Process 64 bytes at a time + step_stride(); + step_stride(); + step_stride(); + step_stride(); + } + while (static_cast<size_t>(e - p) >= kSwathSize) { + step_stride(); + } + + // Now advance one word at a time as far as possible. This isn't worth + // doing if we have word-advance tables. + while (static_cast<size_t>(e - p) >= 4) { + buf0 = step_swath(buf0, p); + uint32_t tmp = buf0; + buf0 = buf1; + buf1 = buf2; + buf2 = buf3; + buf3 = tmp; + p += 4; + } + + // Combine the results from the different swaths. This is just a CRC + // on the data values in the bufX words. + auto combine_one_word = [this](uint32_t crc_in, uint32_t w) { + w ^= crc_in; + for (size_t i = 0; i < 4; ++i) { + w = (w >> 8) ^ this->table0_[w & 0xff]; + } + return w; + }; + + l = combine_one_word(0, buf0); + l = combine_one_word(l, buf1); + l = combine_one_word(l, buf2); + l = combine_one_word(l, buf3); + } + + // Process the last few bytes + while (p != e) { + step_one_byte(); + } + + *crc = l; +} + +void CRC32::ExtendByZeroesImpl(uint32_t* crc, size_t length, + const uint32_t zeroes_table[256], + const uint32_t poly_table[256]) const { + if (length != 0) { + uint32_t l = *crc; + // For each ZEROES_BASE_LG bits in length + // (after the low-order bits have been removed) + // we lookup the appropriate polynomial in the zeroes_ array + // and do a polynomial long multiplication (mod the CRC polynomial) + // to extend the CRC by the appropriate number of bits. + for (int i = 0; length != 0; + i += ZEROES_BASE - 1, length >>= ZEROES_BASE_LG) { + int c = length & (ZEROES_BASE - 1); // pick next ZEROES_BASE_LG bits + if (c != 0) { // if they are not zero, + // multiply by entry in table + // Build a table to aid in multiplying 2 bits at a time. + // It takes too long to build tables for more bits. + uint64_t m = zeroes_table[c + i - 1]; + m <<= 1; + uint64_t m2 = m << 1; + uint64_t mtab[4] = {0, m, m2, m2 ^ m}; + + // Do the multiply one byte at a time. + uint64_t result = 0; + for (int x = 0; x < 32; x += 8) { + // The carry-less multiply. + result ^= mtab[l & 3] ^ (mtab[(l >> 2) & 3] << 2) ^ + (mtab[(l >> 4) & 3] << 4) ^ (mtab[(l >> 6) & 3] << 6); + l >>= 8; + + // Reduce modulo the polynomial + result = (result >> 8) ^ poly_table[result & 0xff]; + } + l = static_cast<uint32_t>(result); + } + } + *crc = l; + } +} + +void CRC32::ExtendByZeroes(uint32_t* crc, size_t length) const { + return CRC32::ExtendByZeroesImpl(crc, length, zeroes_, table0_); +} + +void CRC32::UnextendByZeroes(uint32_t* crc, size_t length) const { + // See the comment in CRC32::InitTables() for an explanation of the algorithm + // below. + *crc = ReverseBits(*crc); + ExtendByZeroesImpl(crc, length, reverse_zeroes_, reverse_table0_); + *crc = ReverseBits(*crc); +} + +void CRC32::Scramble(uint32_t* crc) const { + // Rotate by near half the word size plus 1. See the scramble comment in + // crc_internal.h for an explanation. + constexpr int scramble_rotate = (32 / 2) + 1; + *crc = RotateRight<uint32_t>(static_cast<unsigned int>(*crc + kScrambleLo), + 32, scramble_rotate) & + MaskOfLength<uint32_t>(32); +} + +void CRC32::Unscramble(uint32_t* crc) const { + constexpr int scramble_rotate = (32 / 2) + 1; + uint64_t rotated = RotateRight<uint32_t>(static_cast<unsigned int>(*crc), 32, + 32 - scramble_rotate); + *crc = (rotated - kScrambleLo) & MaskOfLength<uint32_t>(32); +} + +// Constructor and destructor for base class CRC. +CRC::~CRC() {} +CRC::CRC() {} + +// The "constructor" for a CRC32C with a standard polynomial. +CRC* CRC::Crc32c() { + static CRC* singleton = CRCImpl::NewInternal(); + 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 new file mode 100644 index 00000000..72515b06 --- /dev/null +++ b/absl/crc/internal/crc.h @@ -0,0 +1,91 @@ +// 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_CRC_INTERNAL_CRC_H_ +#define ABSL_CRC_INTERNAL_CRC_H_ + +#include <cstdint> + +#include "absl/base/config.h" + +// This class implements CRCs (aka Rabin Fingerprints). +// Treats the input as a polynomial with coefficients in Z(2), +// and finds the remainder when divided by an primitive polynomial +// of the appropriate length. + +// A polynomial is represented by the bit pattern formed by its coefficients, +// but with the highest order bit not stored. +// The highest degree coefficient is stored in the lowest numbered bit +// in the lowest addressed byte. Thus, in what follows, the highest degree +// coefficient that is stored is in the low order bit of "lo" or "*lo". + +// Hardware acceleration is used when available. + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +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". + virtual void Extend(uint32_t* crc, const void* bytes, + size_t length) const = 0; + + // Equivalent to Extend(crc, bytes, length) where "bytes" + // 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 + // 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. + virtual void Scramble(uint32_t* crc) const = 0; + virtual void Unscramble(uint32_t* crc) const = 0; + + // Crc32c() returns the singleton implementation of CRC for the CRC32C + // polynomial. Returns a handle that MUST NOT be destroyed with delete. + static CRC* Crc32c(); + + protected: + CRC(); // Clients may not call constructor; use Crc32c() instead. + + private: + CRC(const CRC&) = delete; + CRC& operator=(const CRC&) = delete; +}; + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CRC_INTERNAL_CRC_H_ diff --git a/absl/crc/internal/crc32_x86_arm_combined_simd.h b/absl/crc/internal/crc32_x86_arm_combined_simd.h new file mode 100644 index 00000000..fb643986 --- /dev/null +++ b/absl/crc/internal/crc32_x86_arm_combined_simd.h @@ -0,0 +1,269 @@ +// 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_CRC_INTERNAL_CRC32_X86_ARM_COMBINED_SIMD_H_ +#define ABSL_CRC_INTERNAL_CRC32_X86_ARM_COMBINED_SIMD_H_ + +#include <cstdint> + +#include "absl/base/config.h" + +// ------------------------------------------------------------------------- +// Many x86 and ARM machines have CRC acceleration hardware. +// We can do a faster version of Extend() on such machines. +// We define a translation layer for both x86 and ARM for the ease of use and +// most performance gains. + +// This implementation requires 64-bit CRC instructions (part of SSE 4.2) and +// PCLMULQDQ instructions. 32-bit builds with SSE 4.2 do exist, so the +// __x86_64__ condition is necessary. +#if defined(__x86_64__) && defined(__SSE4_2__) && defined(__PCLMUL__) + +#include <x86intrin.h> +#define ABSL_CRC_INTERNAL_HAVE_X86_SIMD + +#elif defined(_MSC_VER) && defined(__AVX__) + +// MSVC AVX (/arch:AVX) implies SSE 4.2 and PCLMULQDQ. +#include <intrin.h> +#define ABSL_CRC_INTERNAL_HAVE_X86_SIMD + +#elif defined(__aarch64__) && defined(__LITTLE_ENDIAN__) && \ + defined(__ARM_FEATURE_CRC32) && defined(ABSL_INTERNAL_HAVE_ARM_NEON) && \ + defined(__ARM_FEATURE_CRYPTO) + +#include <arm_acle.h> +#include <arm_neon.h> +#define ABSL_CRC_INTERNAL_HAVE_ARM_SIMD + +#endif + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +#if defined(ABSL_CRC_INTERNAL_HAVE_ARM_SIMD) || \ + defined(ABSL_CRC_INTERNAL_HAVE_X86_SIMD) + +#if defined(ABSL_CRC_INTERNAL_HAVE_ARM_SIMD) +using V128 = uint64x2_t; +#else +using V128 = __m128i; +#endif + +// Starting with the initial value in |crc|, accumulates a CRC32 value for +// unsigned integers of different sizes. +uint32_t CRC32_u8(uint32_t crc, uint8_t v); + +uint32_t CRC32_u16(uint32_t crc, uint16_t v); + +uint32_t CRC32_u32(uint32_t crc, uint32_t v); + +uint32_t CRC32_u64(uint32_t crc, uint64_t v); + +// Loads 128 bits of integer data. |src| must be 16-byte aligned. +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); + +// Polynomially multiplies the high 64 bits of |l| and |r|. +V128 V128_PMulHi(const V128 l, const V128 r); + +// Polynomially multiplies the low 64 bits of |l| and |r|. +V128 V128_PMulLow(const V128 l, const V128 r); + +// Polynomially multiplies the low 64 bits of |r| and high 64 bits of |l|. +V128 V128_PMul01(const V128 l, const V128 r); + +// Polynomially multiplies the low 64 bits of |l| and high 64 bits of |r|. +V128 V128_PMul10(const V128 l, const V128 r); + +// Produces a XOR operation of |l| and |r|. +V128 V128_Xor(const V128 l, const V128 r); + +// Produces an AND operation of |l| and |r|. +V128 V128_And(const V128 l, const V128 r); + +// Sets two 64 bit integers to one 128 bit vector. The order is reverse. +// dst[63:0] := |r| +// dst[127:64] := |l| +V128 V128_From2x64(const uint64_t l, const uint64_t r); + +// Shift |l| right by |imm| bytes while shifting in zeros. +template <int imm> +V128 V128_ShiftRight(const V128 l); + +// Extracts a 32-bit integer from |l|, selected with |imm|. +template <int imm> +int V128_Extract32(const V128 l); + +// Extracts the low 64 bits from V128. +int64_t V128_Low64(const V128 l); + +// Left-shifts packed 64-bit integers in l by r. +V128 V128_ShiftLeft64(const V128 l, const V128 r); + +#endif + +#if defined(ABSL_CRC_INTERNAL_HAVE_X86_SIMD) + +inline uint32_t CRC32_u8(uint32_t crc, uint8_t v) { + return _mm_crc32_u8(crc, v); +} + +inline uint32_t CRC32_u16(uint32_t crc, uint16_t v) { + return _mm_crc32_u16(crc, v); +} + +inline uint32_t CRC32_u32(uint32_t crc, uint32_t v) { + return _mm_crc32_u32(crc, v); +} + +inline uint32_t CRC32_u64(uint32_t crc, uint64_t v) { + return static_cast<uint32_t>(_mm_crc32_u64(crc, v)); +} + +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 V128 V128_PMulHi(const V128 l, const V128 r) { + return _mm_clmulepi64_si128(l, r, 0x11); +} + +inline V128 V128_PMulLow(const V128 l, const V128 r) { + return _mm_clmulepi64_si128(l, r, 0x00); +} + +inline V128 V128_PMul01(const V128 l, const V128 r) { + return _mm_clmulepi64_si128(l, r, 0x01); +} + +inline V128 V128_PMul10(const V128 l, const V128 r) { + return _mm_clmulepi64_si128(l, r, 0x10); +} + +inline V128 V128_Xor(const V128 l, const V128 r) { return _mm_xor_si128(l, r); } + +inline V128 V128_And(const V128 l, const V128 r) { return _mm_and_si128(l, r); } + +inline V128 V128_From2x64(const uint64_t l, const uint64_t r) { + return _mm_set_epi64x(static_cast<int64_t>(l), static_cast<int64_t>(r)); +} + +template <int imm> +inline V128 V128_ShiftRight(const V128 l) { + return _mm_srli_si128(l, imm); +} + +template <int imm> +inline int V128_Extract32(const V128 l) { + return _mm_extract_epi32(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) { + return _mm_sll_epi64(l, r); +} + +#elif defined(ABSL_CRC_INTERNAL_HAVE_ARM_SIMD) + +inline uint32_t CRC32_u8(uint32_t crc, uint8_t v) { return __crc32cb(crc, v); } + +inline uint32_t CRC32_u16(uint32_t crc, uint16_t v) { + return __crc32ch(crc, v); +} + +inline uint32_t CRC32_u32(uint32_t crc, uint32_t v) { + return __crc32cw(crc, v); +} + +inline uint32_t CRC32_u64(uint32_t crc, uint64_t v) { + return __crc32cd(crc, v); +} + +inline V128 V128_Load(const V128* src) { + return vld1q_u64(reinterpret_cast<const uint64_t*>(src)); +} + +inline V128 V128_LoadU(const V128* src) { + return vld1q_u64(reinterpret_cast<const uint64_t*>(src)); +} + +// 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. +inline V128 V128_PMulHi(const V128 l, const V128 r) { + uint64x2_t res; + __asm__ __volatile__("pmull2 %0.1q, %1.2d, %2.2d \n\t" + : "=w"(res) + : "w"(l), "w"(r)); + return res; +} + +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))))); +} + +inline V128 V128_PMul01(const V128 l, const V128 r) { + return reinterpret_cast<V128>(vmull_p64( + reinterpret_cast<poly64_t>(vget_high_p64(vreinterpretq_p64_u64(l))), + reinterpret_cast<poly64_t>(vget_low_p64(vreinterpretq_p64_u64(r))))); +} + +inline V128 V128_PMul10(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_high_p64(vreinterpretq_p64_u64(r))))); +} + +inline V128 V128_Xor(const V128 l, const V128 r) { return veorq_u64(l, r); } + +inline V128 V128_And(const V128 l, const V128 r) { return vandq_u64(l, r); } + +inline V128 V128_From2x64(const uint64_t l, const uint64_t r) { + return vcombine_u64(vcreate_u64(r), vcreate_u64(l)); +} + +template <int imm> +inline V128 V128_ShiftRight(const V128 l) { + return vreinterpretq_u64_s8( + vextq_s8(vreinterpretq_s8_u64(l), vdupq_n_s8(0), imm)); +} + +template <int imm> +inline int V128_Extract32(const V128 l) { + return vgetq_lane_s32(vreinterpretq_s32_u64(l), imm); +} + +inline int64_t V128_Low64(const V128 l) { + return vgetq_lane_s64(vreinterpretq_s64_u64(l), 0); +} + +inline V128 V128_ShiftLeft64(const V128 l, const V128 r) { + return vshlq_u64(l, vreinterpretq_s64_u64(r)); +} + +#endif + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CRC_INTERNAL_CRC32_X86_ARM_COMBINED_SIMD_H_ diff --git a/absl/crc/internal/crc32c.h b/absl/crc/internal/crc32c.h new file mode 100644 index 00000000..34027c55 --- /dev/null +++ b/absl/crc/internal/crc32c.h @@ -0,0 +1,39 @@ +// 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_CRC_INTERNAL_CRC32C_H_ +#define ABSL_CRC_INTERNAL_CRC32C_H_ + +#include "absl/base/config.h" +#include "absl/crc/crc32c.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +// Modifies a CRC32 value by removing `length` bytes with a value of 0 from +// the end of the string. +// +// This is the inverse operation of ExtendCrc32cByZeroes(). +// +// This operation has a runtime cost of O(log(`length`)) +// +// Internal implementation detail, exposed for testing only. +crc32c_t UnextendCrc32cByZeroes(crc32c_t initial_crc, size_t length); + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CRC_INTERNAL_CRC32C_H_ diff --git a/absl/crc/internal/crc32c_inline.h b/absl/crc/internal/crc32c_inline.h new file mode 100644 index 00000000..6236c10b --- /dev/null +++ b/absl/crc/internal/crc32c_inline.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. + +#ifndef ABSL_CRC_INTERNAL_CRC32C_INLINE_H_ +#define ABSL_CRC_INTERNAL_CRC32C_INLINE_H_ + +#include <cstdint> + +#include "absl/base/config.h" +#include "absl/base/internal/endian.h" +#include "absl/crc/internal/crc32_x86_arm_combined_simd.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +// CRC32C implementation optimized for small inputs. +// Either computes crc and return true, or if there is +// no hardware support does nothing and returns false. +inline bool ExtendCrc32cInline(uint32_t* crc, const char* p, size_t n) { +#if defined(ABSL_CRC_INTERNAL_HAVE_ARM_SIMD) || \ + defined(ABSL_CRC_INTERNAL_HAVE_X86_SIMD) + constexpr uint32_t kCrc32Xor = 0xffffffffU; + *crc ^= kCrc32Xor; + if (n & 1) { + *crc = CRC32_u8(*crc, static_cast<uint8_t>(*p)); + n--; + p++; + } + if (n & 2) { + *crc = CRC32_u16(*crc, absl::little_endian::Load16(p)); + n -= 2; + p += 2; + } + if (n & 4) { + *crc = CRC32_u32(*crc, absl::little_endian::Load32(p)); + n -= 4; + p += 4; + } + while (n) { + *crc = CRC32_u64(*crc, absl::little_endian::Load64(p)); + n -= 8; + p += 8; + } + *crc ^= kCrc32Xor; + return true; +#else + // No hardware support, signal the need to fallback. + static_cast<void>(crc); + static_cast<void>(p); + static_cast<void>(n); + return false; +#endif // defined(ABSL_CRC_INTERNAL_HAVE_ARM_SIMD) || + // defined(ABSL_CRC_INTERNAL_HAVE_X86_SIMD) +} + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CRC_INTERNAL_CRC32C_INLINE_H_ diff --git a/absl/crc/internal/crc_cord_state.cc b/absl/crc/internal/crc_cord_state.cc new file mode 100644 index 00000000..d0be0ddd --- /dev/null +++ b/absl/crc/internal/crc_cord_state.cc @@ -0,0 +1,130 @@ +// 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/crc/internal/crc_cord_state.h" + +#include <cassert> + +#include "absl/base/config.h" +#include "absl/numeric/bits.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +CrcCordState::RefcountedRep* CrcCordState::RefSharedEmptyRep() { + static CrcCordState::RefcountedRep* empty = new CrcCordState::RefcountedRep; + + assert(empty->count.load(std::memory_order_relaxed) >= 1); + assert(empty->rep.removed_prefix.length == 0); + assert(empty->rep.prefix_crc.empty()); + + Ref(empty); + return empty; +} + +CrcCordState::CrcCordState() : refcounted_rep_(new RefcountedRep) {} + +CrcCordState::CrcCordState(const CrcCordState& other) + : refcounted_rep_(other.refcounted_rep_) { + Ref(refcounted_rep_); +} + +CrcCordState::CrcCordState(CrcCordState&& other) + : refcounted_rep_(other.refcounted_rep_) { + // Make `other` valid for use after move. + other.refcounted_rep_ = RefSharedEmptyRep(); +} + +CrcCordState& CrcCordState::operator=(const CrcCordState& other) { + if (this != &other) { + Unref(refcounted_rep_); + refcounted_rep_ = other.refcounted_rep_; + Ref(refcounted_rep_); + } + return *this; +} + +CrcCordState& CrcCordState::operator=(CrcCordState&& other) { + if (this != &other) { + Unref(refcounted_rep_); + refcounted_rep_ = other.refcounted_rep_; + // Make `other` valid for use after move. + other.refcounted_rep_ = RefSharedEmptyRep(); + } + return *this; +} + +CrcCordState::~CrcCordState() { + Unref(refcounted_rep_); +} + +crc32c_t CrcCordState::Checksum() const { + if (rep().prefix_crc.empty()) { + return absl::crc32c_t{0}; + } + if (IsNormalized()) { + return rep().prefix_crc.back().crc; + } + return absl::RemoveCrc32cPrefix( + rep().removed_prefix.crc, rep().prefix_crc.back().crc, + rep().prefix_crc.back().length - rep().removed_prefix.length); +} + +CrcCordState::PrefixCrc CrcCordState::NormalizedPrefixCrcAtNthChunk( + size_t n) const { + assert(n < NumChunks()); + if (IsNormalized()) { + return rep().prefix_crc[n]; + } + size_t length = rep().prefix_crc[n].length - rep().removed_prefix.length; + return PrefixCrc(length, + absl::RemoveCrc32cPrefix(rep().removed_prefix.crc, + rep().prefix_crc[n].crc, length)); +} + +void CrcCordState::Normalize() { + if (IsNormalized() || rep().prefix_crc.empty()) { + return; + } + + Rep* r = mutable_rep(); + for (auto& prefix_crc : r->prefix_crc) { + size_t remaining = prefix_crc.length - r->removed_prefix.length; + prefix_crc.crc = absl::RemoveCrc32cPrefix(r->removed_prefix.crc, + prefix_crc.crc, remaining); + prefix_crc.length = remaining; + } + r->removed_prefix = PrefixCrc(); +} + +void CrcCordState::Poison() { + Rep* rep = mutable_rep(); + if (NumChunks() > 0) { + for (auto& prefix_crc : rep->prefix_crc) { + // This is basically CRC32::Scramble(). + uint32_t crc = static_cast<uint32_t>(prefix_crc.crc); + crc += 0x2e76e41b; + crc = absl::rotr(crc, 17); + prefix_crc.crc = crc32c_t{crc}; + } + } else { + // Add a fake corrupt chunk. + rep->prefix_crc.push_back(PrefixCrc(0, crc32c_t{1})); + } +} + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/crc/internal/crc_cord_state.h b/absl/crc/internal/crc_cord_state.h new file mode 100644 index 00000000..d305424c --- /dev/null +++ b/absl/crc/internal/crc_cord_state.h @@ -0,0 +1,159 @@ +// 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_CRC_INTERNAL_CRC_CORD_STATE_H_ +#define ABSL_CRC_INTERNAL_CRC_CORD_STATE_H_ + +#include <atomic> +#include <cstddef> +#include <deque> + +#include "absl/base/config.h" +#include "absl/crc/crc32c.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +// CrcCordState is a copy-on-write class that holds the chunked CRC32C data +// that allows CrcCord to perform efficient substring operations. CrcCordState +// is used as a member variable in CrcCord. When a CrcCord is converted to a +// Cord, the CrcCordState is shallow-copied into the root node of the Cord. If +// the converted Cord is modified outside of CrcCord, the CrcCordState is +// discarded from the Cord. If the Cord is converted back to a CrcCord, and the +// Cord is still carrying the CrcCordState in its root node, the CrcCord can +// re-use the CrcCordState, making the construction of the CrcCord cheap. +// +// CrcCordState does not try to encapsulate the CRC32C state (CrcCord requires +// knowledge of how CrcCordState represents the CRC32C state). It does +// encapsulate the copy-on-write nature of the state. +class CrcCordState { + public: + // Constructors. + CrcCordState(); + CrcCordState(const CrcCordState&); + CrcCordState(CrcCordState&&); + + // Destructor. Atomically unreferences the data. + ~CrcCordState(); + + // Copy and move operators. + CrcCordState& operator=(const CrcCordState&); + CrcCordState& operator=(CrcCordState&&); + + // A (length, crc) pair. + struct PrefixCrc { + PrefixCrc() = default; + PrefixCrc(size_t length_arg, absl::crc32c_t crc_arg) + : length(length_arg), crc(crc_arg) {} + + size_t length = 0; + + // TODO(absl-team): Memory stomping often zeros out memory. If this struct + // gets overwritten, we could end up with {0, 0}, which is the correct CRC + // for a string of length 0. Consider storing a scrambled value and + // unscrambling it before verifying it. + absl::crc32c_t crc = absl::crc32c_t{0}; + }; + + // The representation of the chunked CRC32C data. + 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. + // + // CrcCordState is said to be "normalized" if removed_prefix.length == 0. + PrefixCrc removed_prefix; + + // A deque of (length, crc) pairs, representing length and crc of a prefix + // of the Cord, before removed_prefix has been subtracted. The lengths of + // the prefixes are stored in increasing order. If the Cord is not empty, + // the last value in deque is the contains the CRC32C of the entire Cord + // when removed_prefix is subtracted from it. + std::deque<PrefixCrc> prefix_crc; + }; + + // Returns a reference to the representation of the chunked CRC32C data. + const Rep& rep() const { return refcounted_rep_->rep; } + + // Returns a mutable reference to the representation of the chunked CRC32C + // data. Calling this function will copy the data if another instance also + // holds a reference to the data, so it is important to call rep() instead if + // the data may not be mutated. + Rep* mutable_rep() { + if (refcounted_rep_->count.load(std::memory_order_acquire) != 1) { + RefcountedRep* copy = new RefcountedRep; + copy->rep = refcounted_rep_->rep; + Unref(refcounted_rep_); + refcounted_rep_ = copy; + } + return &refcounted_rep_->rep; + } + + // Returns the CRC32C of the entire Cord. + absl::crc32c_t Checksum() const; + + // 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 + // prefix from the chunks. + void Normalize(); + + // Returns the number of cached chunks. + size_t NumChunks() const { return rep().prefix_crc.size(); } + + // Helper that returns the (length, crc) of the `n`-th cached chunked. + PrefixCrc NormalizedPrefixCrcAtNthChunk(size_t n) const; + + // Poisons all chunks to so that Checksum() will likely be incorrect with high + // probability. + void Poison(); + + private: + struct RefcountedRep { + std::atomic<int32_t> count{1}; + Rep rep; + }; + + // Adds a reference to the shared global empty `RefcountedRep`, and returns a + // pointer to the `RefcountedRep`. This is an optimization to avoid unneeded + // allocations when the allocation is unlikely to ever be used. The returned + // pointer can be `Unref()`ed when it is no longer needed. Since the returned + // instance will always have a reference counter greater than 1, attempts to + // modify it (by calling `mutable_rep()`) will create a new unshared copy. + static RefcountedRep* RefSharedEmptyRep(); + + static void Ref(RefcountedRep* r) { + assert(r != nullptr); + r->count.fetch_add(1, std::memory_order_relaxed); + } + + static void Unref(RefcountedRep* r) { + assert(r != nullptr); + if (r->count.fetch_sub(1, std::memory_order_acq_rel) == 1) { + delete r; + } + } + + RefcountedRep* refcounted_rep_; +}; + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CRC_INTERNAL_CRC_CORD_STATE_H_ diff --git a/absl/crc/internal/crc_cord_state_test.cc b/absl/crc/internal/crc_cord_state_test.cc new file mode 100644 index 00000000..e2c8e3cd --- /dev/null +++ b/absl/crc/internal/crc_cord_state_test.cc @@ -0,0 +1,124 @@ +// 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/crc/internal/crc_cord_state.h" + +#include <algorithm> +#include <cstdint> +#include <string> +#include <utility> + +#include "gtest/gtest.h" +#include "absl/crc/crc32c.h" + +namespace { + +TEST(CrcCordState, Default) { + absl::crc_internal::CrcCordState state; + EXPECT_TRUE(state.IsNormalized()); + EXPECT_EQ(state.Checksum(), absl::crc32c_t{0}); + state.Normalize(); + EXPECT_EQ(state.Checksum(), absl::crc32c_t{0}); +} + +TEST(CrcCordState, Normalize) { + absl::crc_internal::CrcCordState state; + auto* rep = state.mutable_rep(); + rep->prefix_crc.push_back( + absl::crc_internal::CrcCordState::PrefixCrc(1000, absl::crc32c_t{1000})); + rep->prefix_crc.push_back( + absl::crc_internal::CrcCordState::PrefixCrc(2000, absl::crc32c_t{2000})); + rep->removed_prefix = + absl::crc_internal::CrcCordState::PrefixCrc(500, absl::crc32c_t{500}); + + // The removed_prefix means state is not normalized. + EXPECT_FALSE(state.IsNormalized()); + + absl::crc32c_t crc = state.Checksum(); + state.Normalize(); + EXPECT_TRUE(state.IsNormalized()); + + // The checksum should not change as a result of calling Normalize(). + EXPECT_EQ(state.Checksum(), crc); + EXPECT_EQ(rep->removed_prefix.length, 0); +} + +TEST(CrcCordState, Copy) { + absl::crc_internal::CrcCordState state; + auto* rep = state.mutable_rep(); + rep->prefix_crc.push_back( + absl::crc_internal::CrcCordState::PrefixCrc(1000, absl::crc32c_t{1000})); + + absl::crc_internal::CrcCordState copy = state; + + EXPECT_EQ(state.Checksum(), absl::crc32c_t{1000}); + EXPECT_EQ(copy.Checksum(), absl::crc32c_t{1000}); +} + +TEST(CrcCordState, UnsharedSelfCopy) { + absl::crc_internal::CrcCordState state; + auto* rep = state.mutable_rep(); + rep->prefix_crc.push_back( + absl::crc_internal::CrcCordState::PrefixCrc(1000, absl::crc32c_t{1000})); + + const absl::crc_internal::CrcCordState& ref = state; + state = ref; + + EXPECT_EQ(state.Checksum(), absl::crc32c_t{1000}); +} + +TEST(CrcCordState, Move) { + absl::crc_internal::CrcCordState state; + auto* rep = state.mutable_rep(); + rep->prefix_crc.push_back( + absl::crc_internal::CrcCordState::PrefixCrc(1000, absl::crc32c_t{1000})); + + absl::crc_internal::CrcCordState moved = std::move(state); + EXPECT_EQ(moved.Checksum(), absl::crc32c_t{1000}); +} + +TEST(CrcCordState, UnsharedSelfMove) { + absl::crc_internal::CrcCordState state; + auto* rep = state.mutable_rep(); + rep->prefix_crc.push_back( + absl::crc_internal::CrcCordState::PrefixCrc(1000, absl::crc32c_t{1000})); + + absl::crc_internal::CrcCordState& ref = state; + state = std::move(ref); + + EXPECT_EQ(state.Checksum(), absl::crc32c_t{1000}); +} + +TEST(CrcCordState, PoisonDefault) { + absl::crc_internal::CrcCordState state; + state.Poison(); + EXPECT_NE(state.Checksum(), absl::crc32c_t{0}); +} + +TEST(CrcCordState, PoisonData) { + absl::crc_internal::CrcCordState state; + auto* rep = state.mutable_rep(); + rep->prefix_crc.push_back( + absl::crc_internal::CrcCordState::PrefixCrc(1000, absl::crc32c_t{1000})); + rep->prefix_crc.push_back( + absl::crc_internal::CrcCordState::PrefixCrc(2000, absl::crc32c_t{2000})); + rep->removed_prefix = + absl::crc_internal::CrcCordState::PrefixCrc(500, absl::crc32c_t{500}); + + absl::crc32c_t crc = state.Checksum(); + state.Poison(); + EXPECT_NE(state.Checksum(), crc); +} + +} // namespace diff --git a/absl/crc/internal/crc_internal.h b/absl/crc/internal/crc_internal.h new file mode 100644 index 00000000..0611b383 --- /dev/null +++ b/absl/crc/internal/crc_internal.h @@ -0,0 +1,179 @@ +// 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_CRC_INTERNAL_CRC_INTERNAL_H_ +#define ABSL_CRC_INTERNAL_CRC_INTERNAL_H_ + +#include <cstdint> +#include <memory> +#include <vector> + +#include "absl/base/internal/raw_logging.h" +#include "absl/crc/internal/crc.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +namespace crc_internal { + +// Prefetch constants used in some Extend() implementations +constexpr int kPrefetchHorizon = ABSL_CACHELINE_SIZE * 4; // Prefetch this far +// Shorter prefetch distance for smaller buffers +constexpr int kPrefetchHorizonMedium = ABSL_CACHELINE_SIZE * 1; +static_assert(kPrefetchHorizon >= 64, "CRCPrefetchHorizon less than loop len"); + +// We require the Scramble() function: +// - to be reversible (Unscramble() must exist) +// - to be non-linear in the polynomial's Galois field (so the CRC of a +// scrambled CRC is not linearly affected by the scrambled CRC, even if +// using the same polynomial) +// - not to be its own inverse. Preferably, if X=Scramble^N(X) and N!=0, then +// N is large. +// - to be fast. +// - not to change once defined. +// We introduce non-linearity in two ways: +// Addition of a constant. +// - The carries introduce non-linearity; we use bits of an irrational +// (phi) to make it unlikely that we introduce no carries. +// Rotate by a constant number of bits. +// - We use floor(degree/2)+1, which does not divide the degree, and +// splits the bits nearly evenly, which makes it less likely the +// halves will be the same or one will be all zeroes. +// We do both things to improve the chances of non-linearity in the face of +// bit patterns with low numbers of bits set, while still being fast. +// Below is the constant that we add. The bits are the first 128 bits of the +// fractional part of phi, with a 1 ored into the bottom bit to maximize the +// cycle length of repeated adds. +constexpr uint64_t kScrambleHi = (static_cast<uint64_t>(0x4f1bbcdcU) << 32) | + static_cast<uint64_t>(0xbfa53e0aU); +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 + public: + using Uint32By256 = uint32_t[256]; + + CRCImpl() {} + ~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. + static void FillWordTable(uint32_t poly, uint32_t last, int word_size, + Uint32By256* t); + + // Build the table for extending by zeroes, returning the number of entries. + // For a in {1, 2, ..., ZEROES_BASE-1}, b in {0, 1, 2, 3, ...}, + // entry j=a-1+(ZEROES_BASE-1)*b + // contains a polynomial Pi such that multiplying + // a CRC by Pi mod P, where P is the CRC polynomial, is equivalent to + // appending a*2**(ZEROES_BASE_LG*b) zero bytes to the original string. + static int FillZeroesTable(uint32_t poly, Uint32By256* t); + + virtual void InitTables() = 0; + + private: + CRCImpl(const CRCImpl&) = delete; + CRCImpl& operator=(const CRCImpl&) = delete; +}; + +// This is the 32-bit implementation. It handles all sizes from 8 to 32. +class CRC32 : public CRCImpl { + public: + CRC32() {} + ~CRC32() override {} + + void Extend(uint32_t* crc, const void* bytes, size_t length) const override; + void ExtendByZeroes(uint32_t* crc, size_t length) const override; + void Scramble(uint32_t* crc) const override; + void Unscramble(uint32_t* crc) const override; + void UnextendByZeroes(uint32_t* crc, size_t length) const override; + + void InitTables() override; + + private: + // 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, + // 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; + + uint32_t table0_[256]; // table of byte extensions + uint32_t zeroes_[256]; // table of zero extensions + + // table of 4-byte extensions shifted by 12 bytes of zeroes + uint32_t table_[4][256]; + + // Reverse lookup tables, using the alternate polynomial used by + // UnextendByZeroes(). + uint32_t reverse_table0_[256]; // table of reverse byte extensions + uint32_t reverse_zeroes_[256]; // table of reverse zero extensions + + CRC32(const CRC32&) = delete; + CRC32& operator=(const CRC32&) = delete; +}; + +// Helpers + +// Return a bit mask containing len 1-bits. +// Requires 0 < len <= sizeof(T) +template <typename T> +T MaskOfLength(int len) { + // shift 2 by len-1 rather than 1 by len because shifts of wordsize + // are undefined. + return (T(2) << (len - 1)) - 1; +} + +// Rotate low-order "width" bits of "in" right by "r" bits, +// setting other bits in word to arbitrary values. +template <typename T> +T RotateRight(T in, int width, int r) { + return (in << (width - r)) | ((in >> r) & MaskOfLength<T>(width - r)); +} + +// RoundUp<N>(p) returns the lowest address >= p aligned to an N-byte +// boundary. Requires that N is a power of 2. +template <int alignment> +const uint8_t* RoundUp(const uint8_t* p) { + static_assert((alignment & (alignment - 1)) == 0, "alignment is not 2^n"); + constexpr uintptr_t mask = alignment - 1; + const uintptr_t as_uintptr = reinterpret_cast<uintptr_t>(p); + return reinterpret_cast<const uint8_t*>((as_uintptr + mask) & ~mask); +} + +// Return a newly created CRC32AcceleratedX86ARMCombined if we can use Intel's +// or ARM's CRC acceleration for a given polynomial. Return nullptr otherwise. +CRCImpl* TryNewCRC32AcceleratedX86ARMCombined(); + +// Return all possible hardware accelerated implementations. For testing only. +std::vector<std::unique_ptr<CRCImpl>> NewCRC32AcceleratedX86ARMCombinedAll(); + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CRC_INTERNAL_CRC_INTERNAL_H_ diff --git a/absl/crc/internal/crc_memcpy.h b/absl/crc/internal/crc_memcpy.h new file mode 100644 index 00000000..4909d433 --- /dev/null +++ b/absl/crc/internal/crc_memcpy.h @@ -0,0 +1,119 @@ +// 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_CRC_INTERNAL_CRC_MEMCPY_H_ +#define ABSL_CRC_INTERNAL_CRC_MEMCPY_H_ + +#include <cstddef> +#include <memory> + +#include "absl/base/config.h" +#include "absl/crc/crc32c.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__) +#define ABSL_INTERNAL_HAVE_X86_64_ACCELERATED_CRC_MEMCPY_ENGINE 1 +#endif + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +class CrcMemcpyEngine { + public: + virtual ~CrcMemcpyEngine() = default; + + virtual crc32c_t Compute(void* __restrict dst, const void* __restrict src, + std::size_t length, crc32c_t initial_crc) const = 0; + + protected: + CrcMemcpyEngine() = default; +}; + +class CrcMemcpy { + public: + static crc32c_t CrcAndCopy(void* __restrict dst, const void* __restrict src, + std::size_t length, + crc32c_t initial_crc = crc32c_t{0}, + bool non_temporal = false) { + static const ArchSpecificEngines engines = GetArchSpecificEngines(); + auto* engine = non_temporal ? engines.non_temporal : engines.temporal; + return engine->Compute(dst, src, length, initial_crc); + } + + // For testing only: get an architecture-specific engine for tests. + static std::unique_ptr<CrcMemcpyEngine> GetTestEngine(int vector, + int integer); + + private: + struct ArchSpecificEngines { + CrcMemcpyEngine* temporal; + CrcMemcpyEngine* non_temporal; + }; + + static ArchSpecificEngines GetArchSpecificEngines(); +}; + +// Fallback CRC-memcpy engine. +class FallbackCrcMemcpyEngine : public CrcMemcpyEngine { + public: + FallbackCrcMemcpyEngine() = default; + FallbackCrcMemcpyEngine(const FallbackCrcMemcpyEngine&) = delete; + FallbackCrcMemcpyEngine operator=(const FallbackCrcMemcpyEngine&) = delete; + + crc32c_t Compute(void* __restrict dst, const void* __restrict src, + std::size_t length, crc32c_t initial_crc) const override; +}; + +// CRC Non-Temporal-Memcpy engine. +class CrcNonTemporalMemcpyEngine : public CrcMemcpyEngine { + public: + CrcNonTemporalMemcpyEngine() = default; + CrcNonTemporalMemcpyEngine(const CrcNonTemporalMemcpyEngine&) = delete; + CrcNonTemporalMemcpyEngine operator=(const CrcNonTemporalMemcpyEngine&) = + delete; + + crc32c_t Compute(void* __restrict dst, const void* __restrict src, + std::size_t length, crc32c_t initial_crc) const override; +}; + +// CRC Non-Temporal-Memcpy AVX engine. +class CrcNonTemporalMemcpyAVXEngine : public CrcMemcpyEngine { + public: + CrcNonTemporalMemcpyAVXEngine() = default; + CrcNonTemporalMemcpyAVXEngine(const CrcNonTemporalMemcpyAVXEngine&) = delete; + CrcNonTemporalMemcpyAVXEngine operator=( + const CrcNonTemporalMemcpyAVXEngine&) = delete; + + crc32c_t Compute(void* __restrict dst, const void* __restrict src, + std::size_t length, crc32c_t initial_crc) const override; +}; + +// Copy source to destination and return the CRC32C of the data copied. If an +// accelerated version is available, use the accelerated version, otherwise use +// the generic fallback version. +inline crc32c_t Crc32CAndCopy(void* __restrict dst, const void* __restrict src, + std::size_t length, + crc32c_t initial_crc = crc32c_t{0}, + bool non_temporal = false) { + return CrcMemcpy::CrcAndCopy(dst, src, length, initial_crc, non_temporal); +} + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CRC_INTERNAL_CRC_MEMCPY_H_ diff --git a/absl/crc/internal/crc_memcpy_fallback.cc b/absl/crc/internal/crc_memcpy_fallback.cc new file mode 100644 index 00000000..15b4b055 --- /dev/null +++ b/absl/crc/internal/crc_memcpy_fallback.cc @@ -0,0 +1,75 @@ +// 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 <cstdint> +#include <memory> + +#include "absl/base/config.h" +#include "absl/crc/crc32c.h" +#include "absl/crc/internal/crc_memcpy.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +absl::crc32c_t FallbackCrcMemcpyEngine::Compute(void* __restrict dst, + const void* __restrict src, + std::size_t length, + crc32c_t initial_crc) const { + constexpr size_t kBlockSize = 8192; + absl::crc32c_t crc = initial_crc; + + const char* src_bytes = reinterpret_cast<const char*>(src); + char* dst_bytes = reinterpret_cast<char*>(dst); + + // Copy + CRC loop - run 8k chunks until we are out of full chunks. CRC + // then copy was found to be slightly more efficient in our test cases. + std::size_t offset = 0; + for (; offset + kBlockSize < length; offset += kBlockSize) { + crc = absl::ExtendCrc32c(crc, + absl::string_view(src_bytes + offset, kBlockSize)); + memcpy(dst_bytes + offset, src_bytes + offset, kBlockSize); + } + + // Save some work if length is 0. + if (offset < length) { + std::size_t final_copy_size = length - offset; + crc = absl::ExtendCrc32c( + crc, absl::string_view(src_bytes + offset, final_copy_size)); + memcpy(dst_bytes + offset, src_bytes + offset, final_copy_size); + } + + return crc; +} + +// Compile the following only if we don't have +#ifndef ABSL_INTERNAL_HAVE_X86_64_ACCELERATED_CRC_MEMCPY_ENGINE + +CrcMemcpy::ArchSpecificEngines CrcMemcpy::GetArchSpecificEngines() { + CrcMemcpy::ArchSpecificEngines engines; + engines.temporal = new FallbackCrcMemcpyEngine(); + engines.non_temporal = new FallbackCrcMemcpyEngine(); + return engines; +} + +std::unique_ptr<CrcMemcpyEngine> CrcMemcpy::GetTestEngine(int /*vector*/, + int /*integer*/) { + return std::make_unique<FallbackCrcMemcpyEngine>(); +} + +#endif // ABSL_INTERNAL_HAVE_X86_64_ACCELERATED_CRC_MEMCPY_ENGINE + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/crc/internal/crc_memcpy_test.cc b/absl/crc/internal/crc_memcpy_test.cc new file mode 100644 index 00000000..bbdcd205 --- /dev/null +++ b/absl/crc/internal/crc_memcpy_test.cc @@ -0,0 +1,169 @@ +// 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/crc/internal/crc_memcpy.h" + +#include <cstddef> +#include <cstdint> +#include <cstring> +#include <limits> +#include <memory> +#include <string> +#include <utility> + +#include "gtest/gtest.h" +#include "absl/crc/crc32c.h" +#include "absl/memory/memory.h" +#include "absl/random/distributions.h" +#include "absl/random/random.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" + +namespace { + +enum CrcEngine { + X86 = 0, + NONTEMPORAL = 1, + FALLBACK = 2, +}; + +// Correctness tests: +// - Every source/destination byte alignment 0-15, every size 0-511 bytes +// - Arbitrarily aligned source, large size +template <size_t max_size> +class CrcMemcpyTest : public testing::Test { + protected: + CrcMemcpyTest() { + source_ = std::make_unique<char[]>(kSize); + destination_ = std::make_unique<char[]>(kSize); + } + static constexpr size_t kAlignment = 16; + static constexpr size_t kMaxCopySize = max_size; + static constexpr size_t kSize = kAlignment + kMaxCopySize; + std::unique_ptr<char[]> source_; + std::unique_ptr<char[]> destination_; + + absl::BitGen gen_; +}; + +// Small test is slightly larger 4096 bytes to allow coverage of the "large" +// copy function. The minimum size to exercise all code paths in that function +// would be around 256 consecutive tests (getting every possible tail value +// and 0-2 small copy loops after the main block), so testing from 4096-4500 +// will cover all of those code paths multiple times. +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> { + protected: + x86ParamTestTemplate() { + if (GetParam().crc_engine_selector == FALLBACK) { + engine_ = std::make_unique<absl::crc_internal::FallbackCrcMemcpyEngine>(); + } else if (GetParam().crc_engine_selector == NONTEMPORAL) { + engine_ = + std::make_unique<absl::crc_internal::CrcNonTemporalMemcpyEngine>(); + } else { + engine_ = absl::crc_internal::CrcMemcpy::GetTestEngine( + GetParam().vector_lanes, GetParam().integer_lanes); + } + } + + // Convenience method. + ParamsT GetParam() const { + return ::testing::WithParamInterface<ParamsT>::GetParam(); + } + + std::unique_ptr<absl::crc_internal::CrcMemcpyEngine> engine_; +}; +struct TestParams { + CrcEngine crc_engine_selector = X86; + int vector_lanes = 0; + int integer_lanes = 0; +}; +using x86ParamTest = x86ParamTestTemplate<TestParams>; +// SmallCorrectness is designed to exercise every possible set of code paths +// in the memcpy code, not including the loop. +TEST_P(x86ParamTest, SmallCorrectnessCheckSourceAlignment) { + constexpr size_t kTestSizes[] = {0, 100, 255, 512, 1024, 4000, kMaxCopySize}; + + for (size_t source_alignment = 0; source_alignment < kAlignment; + source_alignment++) { + for (auto size : kTestSizes) { + char* base_data = static_cast<char*>(source_.get()) + source_alignment; + for (size_t i = 0; i < size; i++) { + *(base_data + i) = + static_cast<char>(absl::Uniform<unsigned char>(gen_)); + } + absl::crc32c_t initial_crc = + absl::crc32c_t{absl::Uniform<uint32_t>(gen_)}; + absl::crc32c_t experiment_crc = + engine_->Compute(destination_.get(), source_.get() + source_alignment, + size, initial_crc); + // Check the memory region to make sure it is the same + int mem_comparison = + memcmp(destination_.get(), source_.get() + source_alignment, size); + SCOPED_TRACE(absl::StrCat("Error in memcpy of size: ", size, + " with source alignment: ", source_alignment)); + ASSERT_EQ(mem_comparison, 0); + absl::crc32c_t baseline_crc = absl::ExtendCrc32c( + initial_crc, + absl::string_view( + static_cast<char*>(source_.get()) + source_alignment, size)); + ASSERT_EQ(baseline_crc, experiment_crc); + } + } +} + +TEST_P(x86ParamTest, SmallCorrectnessCheckDestAlignment) { + constexpr size_t kTestSizes[] = {0, 100, 255, 512, 1024, 4000, kMaxCopySize}; + + for (size_t dest_alignment = 0; dest_alignment < kAlignment; + dest_alignment++) { + for (auto size : kTestSizes) { + char* base_data = static_cast<char*>(source_.get()); + for (size_t i = 0; i < size; i++) { + *(base_data + i) = + static_cast<char>(absl::Uniform<unsigned char>(gen_)); + } + absl::crc32c_t initial_crc = + absl::crc32c_t{absl::Uniform<uint32_t>(gen_)}; + absl::crc32c_t experiment_crc = + engine_->Compute(destination_.get() + dest_alignment, source_.get(), + size, initial_crc); + // Check the memory region to make sure it is the same + int mem_comparison = + memcmp(destination_.get() + dest_alignment, source_.get(), size); + SCOPED_TRACE(absl::StrCat("Error in memcpy of size: ", size, + " with dest alignment: ", dest_alignment)); + ASSERT_EQ(mem_comparison, 0); + absl::crc32c_t baseline_crc = absl::ExtendCrc32c( + initial_crc, + absl::string_view(static_cast<char*>(source_.get()), size)); + ASSERT_EQ(baseline_crc, experiment_crc); + } + } +} + +INSTANTIATE_TEST_SUITE_P(x86ParamTest, x86ParamTest, + ::testing::Values( + // Tests for configurations that may occur in prod. + TestParams{X86, 3, 0}, TestParams{X86, 1, 2}, + // Fallback test. + TestParams{FALLBACK, 0, 0}, + // Non Temporal + TestParams{NONTEMPORAL, 0, 0})); + +} // namespace diff --git a/absl/crc/internal/crc_memcpy_x86_64.cc b/absl/crc/internal/crc_memcpy_x86_64.cc new file mode 100644 index 00000000..66f784de --- /dev/null +++ b/absl/crc/internal/crc_memcpy_x86_64.cc @@ -0,0 +1,434 @@ +// 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. + +// 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. +// +// 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. +// +// The CRC instruction has a 3-byte latency, and we are stressing the ALU ports +// here (unlike a traditional memcopy, which has almost no ALU use), so we will +// need to copy in such a way that the CRC unit is used efficiently. We have two +// regimes in this code: +// 1. For operations of size < kCrcSmallSize, do the CRC then the memcpy +// 2. For operations of size > kCrcSmallSize: +// a) compute an initial CRC + copy on a small amount of data to align the +// destination pointer on a 16-byte boundary. +// b) Split the data into 3 main regions and a tail (smaller than 48 bytes) +// c) Do the copy and CRC of the 3 main regions, interleaving (start with +// full cache line copies for each region, then move to single 16 byte +// pieces per region). +// d) Combine the CRCs with CRC32C::Concat. +// e) Copy the tail and extend the CRC with the CRC of the tail. +// This method is not ideal for op sizes between ~1k and ~8k because CRC::Concat +// takes a significant amount of time. A medium-sized approach could be added +// using 3 CRCs over fixed-size blocks where the zero-extensions required for +// CRC32C::Concat can be precomputed. + +#ifdef __SSE4_2__ +#include <immintrin.h> +#endif + +#ifdef _MSC_VER +#include <intrin.h> +#endif + +#include <array> +#include <cstddef> +#include <cstdint> +#include <type_traits> + +#include "absl/base/dynamic_annotations.h" +#include "absl/base/internal/prefetch.h" +#include "absl/base/optimization.h" +#include "absl/crc/crc32c.h" +#include "absl/crc/internal/cpu_detect.h" +#include "absl/crc/internal/crc_memcpy.h" +#include "absl/strings/string_view.h" + +#ifdef ABSL_INTERNAL_HAVE_X86_64_ACCELERATED_CRC_MEMCPY_ENGINE + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +namespace { + +inline crc32c_t ShortCrcCopy(char* dst, const char* src, std::size_t length, + crc32c_t crc) { + // Small copy: just go 1 byte at a time: being nice to the branch predictor + // is more important here than anything else + 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); + *reinterpret_cast<uint8_t*>(dst) = data; + ++src; + ++dst; + } + return crc32c_t{crc_uint32}; +} + +constexpr size_t kIntLoadsPerVec = sizeof(__m128i) / 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<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); + + // Load the blocks, unaligned + data[i] = _mm_loadu_si128(vsrc); + + // Store the blocks, aligned + _mm_store_si128(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))))}; + 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))))}; + } + + for (size_t i = 0; i < int_regions; i++) { + size_t region = vec_regions + i; + + auto* usrc = + reinterpret_cast<const uint64_t*>(*src + region_size * region); + auto* udst = reinterpret_cast<uint64_t*>(*dst + region_size * region); + + for (size_t j = 0; j < kIntLoadsPerVec; j++) { + size_t data_index = i * kIntLoadsPerVec + j; + + int_data[data_index] = *(usrc + j); + crcs[region] = crc32c_t{static_cast<uint32_t>(_mm_crc32_u64( + static_cast<uint32_t>(crcs[region]), int_data[data_index]))}; + + *(udst + j) = int_data[data_index]; + } + } + + // Increment pointers + *src += sizeof(__m128i); + *dst += sizeof(__m128i); + --copy_rounds; + } +} + +} // namespace + +template <size_t vec_regions, size_t int_regions> +class AcceleratedCrcMemcpyEngine : public CrcMemcpyEngine { + public: + AcceleratedCrcMemcpyEngine() = default; + AcceleratedCrcMemcpyEngine(const AcceleratedCrcMemcpyEngine&) = delete; + AcceleratedCrcMemcpyEngine operator=(const AcceleratedCrcMemcpyEngine&) = + delete; + + crc32c_t Compute(void* __restrict dst, const void* __restrict src, + std::size_t length, crc32c_t initial_crc) const override; +}; + +template <size_t vec_regions, size_t int_regions> +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; + constexpr uint32_t kCrcDataXor = uint32_t{0xffffffff}; + constexpr std::size_t kBlockSize = sizeof(__m128i); + constexpr std::size_t kCopyRoundSize = kRegions * kBlockSize; + + // Number of blocks per cacheline. + constexpr std::size_t kBlocksPerCacheLine = ABSL_CACHELINE_SIZE / kBlockSize; + + char* dst_bytes = static_cast<char*>(dst); + const char* src_bytes = static_cast<const char*>(src); + + // Make sure that one prefetch per big block is enough to cover the whole + // dataset, and we don't prefetch too much. + static_assert(ABSL_CACHELINE_SIZE % kBlockSize == 0, + "Cache lines are not divided evenly into blocks, may have " + "unintended behavior!"); + + // Experimentally-determined boundary between a small and large copy. + // Below this number, spin-up and concatenation of CRCs takes enough time that + // it kills the throughput gains of using 3 regions and wide vectors. + constexpr size_t kCrcSmallSize = 256; + + // Experimentally-determined prefetch distance. Main loop copies will + // prefeth data 2 cache lines ahead. + constexpr std::size_t kPrefetchAhead = 2 * ABSL_CACHELINE_SIZE; + + // Small-size CRC-memcpy : just do CRC + memcpy + if (length < kCrcSmallSize) { + crc32c_t crc = + ExtendCrc32c(initial_crc, absl::string_view(src_bytes, length)); + memcpy(dst, src, length); + return crc; + } + + // Start work on the CRC: undo the XOR from the previous calculation or set up + // the initial value of the CRC. + // initial_crc ^= kCrcDataXor; + initial_crc = crc32c_t{static_cast<uint32_t>(initial_crc) ^ kCrcDataXor}; + + // Do an initial alignment copy, so we can use aligned store instructions to + // the destination pointer. We align the destination pointer because the + // penalty for an unaligned load is small compared to the penalty of an + // unaligned store on modern CPUs. + std::size_t bytes_from_last_aligned = + reinterpret_cast<uintptr_t>(dst) & (kBlockSize - 1); + if (bytes_from_last_aligned != 0) { + std::size_t bytes_for_alignment = kBlockSize - bytes_from_last_aligned; + + // Do the short-sized copy and CRC. + initial_crc = + ShortCrcCopy(dst_bytes, src_bytes, bytes_for_alignment, initial_crc); + src_bytes += bytes_for_alignment; + dst_bytes += bytes_for_alignment; + length -= bytes_for_alignment; + } + + // We are going to do the copy and CRC in kRegions regions to make sure that + // we can saturate the CRC unit. The CRCs will be combined at the end of the + // run. Copying will use the SSE registers, and we will extract words from + // the SSE registers to add to the CRC. Initially, we run the loop one full + // cache line per region at a time, in order to insert prefetches. + + // Initialize CRCs for kRegions regions. + crc32c_t crcs[kRegions]; + crcs[0] = initial_crc; + for (size_t i = 1; i < kRegions; i++) { + crcs[i] = crc32c_t{kCrcDataXor}; + } + + // Find the number of rounds to copy and the region size. Also compute the + // tail size here. + size_t copy_rounds = length / kCopyRoundSize; + + // Find the size of each region and the size of the tail. + const std::size_t region_size = copy_rounds * kBlockSize; + 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<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); + } + + // Load and store data, computing CRC on the way. + for (size_t i = 0; i < kBlocksPerCacheLine; i++) { + // Copy and CRC the data for the CRC regions. + for (size_t j = 0; j < vec_regions; j++) { + // Cycle which regions get vector load/store and integer load/store, to + // engage prefetching logic around vector load/stores and save issue + // slots by using the integer registers. + 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); + + // 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))))}; + + // Store the data. + _mm_store_si128(vdst + i, vec_data[j]); + } + + // Preload the partial CRCs for the CLMUL subregions. + for (size_t j = 0; j < int_regions; j++) { + // Cycle which regions get vector load/store and integer load/store, to + // engage prefetching logic around vector load/stores and save issue + // slots by using the integer registers. + size_t region = (j + vec_regions + i) % kRegions; + + auto* usrc = + reinterpret_cast<const uint64_t*>(src_bytes + region_size * region); + auto* udst = + reinterpret_cast<uint64_t*>(dst_bytes + region_size * region); + + for (size_t k = 0; k < kIntLoadsPerVec; k++) { + size_t data_index = j * kIntLoadsPerVec + k; + + // Load and CRC the data. + int_data[data_index] = *(usrc + i * kIntLoadsPerVec + k); + crcs[region] = crc32c_t{static_cast<uint32_t>(_mm_crc32_u64( + static_cast<uint32_t>(crcs[region]), int_data[data_index]))}; + + // Store the data. + *(udst + i * kIntLoadsPerVec + k) = int_data[data_index]; + } + } + } + + // Increment pointers + src_bytes += kBlockSize * kBlocksPerCacheLine; + dst_bytes += kBlockSize * kBlocksPerCacheLine; + copy_rounds -= kBlocksPerCacheLine; + } + + // Copy and CRC the tails of each region. + LargeTailCopy<vec_regions, int_regions>(crcs, &dst_bytes, &src_bytes, + region_size, copy_rounds); + + // Move the source and destination pointers to the end of the region + src_bytes += region_size * (kRegions - 1); + dst_bytes += region_size * (kRegions - 1); + + // 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++) { + crcs[i] = crc32c_t{static_cast<uint32_t>(crcs[i]) ^ kCrcDataXor}; + } + + // Build a CRC of the first kRegions - 1 regions. + crc32c_t full_crc = crcs[0]; + for (size_t i = 1; i + 1 < kRegions; i++) { + 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}; + return ConcatCrc32c(full_crc, crcs[kRegions - 1], region_size + tail_size); +} + +CrcMemcpy::ArchSpecificEngines CrcMemcpy::GetArchSpecificEngines() { +#ifdef UNDEFINED_BEHAVIOR_SANITIZER + // UBSAN does not play nicely with unaligned loads (which we use a lot). + // Get the underlying architecture. + CpuType cpu_type = GetCpuType(); + switch (cpu_type) { + case CpuType::kUnknown: + case CpuType::kAmdRome: + case CpuType::kAmdNaples: + case CpuType::kIntelCascadelakeXeon: + case CpuType::kIntelSkylakeXeon: + case CpuType::kIntelSkylake: + case CpuType::kIntelBroadwell: + case CpuType::kIntelHaswell: + case CpuType::kIntelIvybridge: + return { + .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(), + }; + default: + return {.temporal = new FallbackCrcMemcpyEngine(), + .non_temporal = new FallbackCrcMemcpyEngine()}; + } +#else + // Get the underlying architecture. + CpuType cpu_type = GetCpuType(); + switch (cpu_type) { + // On Zen 2, PEXTRQ uses 2 micro-ops, including one on the vector store port + // which data movement from the vector registers to the integer registers + // (where CRC32C happens) to crowd the same units as vector stores. As a + // result, using that path exclusively causes bottlenecking on this port. + // We can avoid this bottleneck by using the integer side of the CPU for + // most operations rather than the vector side. We keep a vector region to + // engage some of the prefetching logic in the cache hierarchy which seems + // to give vector instructions special treatment. These prefetch units see + // strided access to each region, and do the right thing. + case CpuType::kAmdRome: + case CpuType::kAmdNaples: + return { + .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. + case CpuType::kIntelCascadelakeXeon: + case CpuType::kIntelSkylakeXeon: + case CpuType::kIntelSkylake: + case CpuType::kIntelBroadwell: + case CpuType::kIntelHaswell: + case CpuType::kIntelIvybridge: + return { + .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(), + }; + default: + return {.temporal = new FallbackCrcMemcpyEngine(), + .non_temporal = new FallbackCrcMemcpyEngine()}; + } +#endif // UNDEFINED_BEHAVIOR_SANITIZER +} + +// For testing, allow the user to specify which engine they want. +std::unique_ptr<CrcMemcpyEngine> CrcMemcpy::GetTestEngine(int vector, + int integer) { + if (vector == 3 && integer == 0) { + return std::make_unique<AcceleratedCrcMemcpyEngine<3, 0>>(); + } else if (vector == 1 && integer == 2) { + return std::make_unique<AcceleratedCrcMemcpyEngine<1, 2>>(); + } + return nullptr; +} + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_INTERNAL_HAVE_X86_64_ACCELERATED_CRC_MEMCPY_ENGINE diff --git a/absl/crc/internal/crc_non_temporal_memcpy.cc b/absl/crc/internal/crc_non_temporal_memcpy.cc new file mode 100644 index 00000000..adc867f6 --- /dev/null +++ b/absl/crc/internal/crc_non_temporal_memcpy.cc @@ -0,0 +1,93 @@ +// Copyright 2022 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <cstdint> + +#include "absl/base/config.h" +#include "absl/crc/crc32c.h" +#include "absl/crc/internal/crc_memcpy.h" +#include "absl/crc/internal/non_temporal_memcpy.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +crc32c_t CrcNonTemporalMemcpyEngine::Compute(void* __restrict dst, + const void* __restrict src, + std::size_t length, + crc32c_t initial_crc) const { + constexpr size_t kBlockSize = 8192; + crc32c_t crc = initial_crc; + + const char* src_bytes = reinterpret_cast<const char*>(src); + char* dst_bytes = reinterpret_cast<char*>(dst); + + // Copy + CRC loop - run 8k chunks until we are out of full chunks. + std::size_t offset = 0; + for (; offset + kBlockSize < length; offset += kBlockSize) { + crc = absl::ExtendCrc32c(crc, + absl::string_view(src_bytes + offset, kBlockSize)); + non_temporal_store_memcpy(dst_bytes + offset, src_bytes + offset, + kBlockSize); + } + + // Save some work if length is 0. + if (offset < length) { + std::size_t final_copy_size = length - offset; + crc = ExtendCrc32c(crc, + absl::string_view(src_bytes + offset, final_copy_size)); + + non_temporal_store_memcpy(dst_bytes + offset, src_bytes + offset, + final_copy_size); + } + + return crc; +} + +crc32c_t CrcNonTemporalMemcpyAVXEngine::Compute(void* __restrict dst, + const void* __restrict src, + std::size_t length, + crc32c_t initial_crc) const { + constexpr size_t kBlockSize = 8192; + crc32c_t crc = initial_crc; + + const char* src_bytes = reinterpret_cast<const char*>(src); + char* dst_bytes = reinterpret_cast<char*>(dst); + + // Copy + CRC loop - run 8k chunks until we are out of full chunks. + std::size_t offset = 0; + for (; offset + kBlockSize < length; offset += kBlockSize) { + crc = ExtendCrc32c(crc, absl::string_view(src_bytes + offset, kBlockSize)); + + non_temporal_store_memcpy_avx(dst_bytes + offset, src_bytes + offset, + kBlockSize); + } + + // Save some work if length is 0. + if (offset < length) { + std::size_t final_copy_size = length - offset; + crc = ExtendCrc32c(crc, + absl::string_view(src_bytes + offset, final_copy_size)); + + non_temporal_store_memcpy_avx(dst_bytes + offset, src_bytes + offset, + final_copy_size); + } + + return crc; +} + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/crc/internal/crc_x86_arm_combined.cc b/absl/crc/internal/crc_x86_arm_combined.cc new file mode 100644 index 00000000..d71191e3 --- /dev/null +++ b/absl/crc/internal/crc_x86_arm_combined.cc @@ -0,0 +1,725 @@ +// 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. + +// Hardware accelerated CRC32 computation on Intel and ARM architecture. + +#include <cstddef> +#include <cstdint> + +#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/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" +#include "absl/numeric/bits.h" + +#if defined(ABSL_CRC_INTERNAL_HAVE_ARM_SIMD) || \ + defined(ABSL_CRC_INTERNAL_HAVE_X86_SIMD) +#define ABSL_INTERNAL_CAN_USE_SIMD_CRC32C +#endif + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +#if defined(ABSL_INTERNAL_CAN_USE_SIMD_CRC32C) + +// Implementation details not exported outside of file +namespace { + +// Some machines have CRC acceleration hardware. +// We can do a faster version of Extend() on such machines. +class CRC32AcceleratedX86ARMCombined : public CRC32 { + public: + CRC32AcceleratedX86ARMCombined() {} + ~CRC32AcceleratedX86ARMCombined() override {} + void ExtendByZeroes(uint32_t* crc, size_t length) const override; + uint32_t ComputeZeroConstant(size_t length) const; + + private: + CRC32AcceleratedX86ARMCombined(const CRC32AcceleratedX86ARMCombined&) = + delete; + CRC32AcceleratedX86ARMCombined& operator=( + const CRC32AcceleratedX86ARMCombined&) = delete; +}; + +// Constants for switching between algorithms. +// Chosen by comparing speed at different powers of 2. +constexpr size_t kSmallCutoff = 256; +constexpr size_t kMediumCutoff = 2048; + +#define ABSL_INTERNAL_STEP1(crc) \ + do { \ + crc = CRC32_u8(static_cast<uint32_t>(crc), *p++); \ + } while (0) +#define ABSL_INTERNAL_STEP2(crc) \ + do { \ + crc = \ + CRC32_u16(static_cast<uint32_t>(crc), absl::little_endian::Load16(p)); \ + p += 2; \ + } while (0) +#define ABSL_INTERNAL_STEP4(crc) \ + do { \ + crc = \ + CRC32_u32(static_cast<uint32_t>(crc), absl::little_endian::Load32(p)); \ + p += 4; \ + } while (0) +#define ABSL_INTERNAL_STEP8(crc, data) \ + do { \ + crc = CRC32_u64(static_cast<uint32_t>(crc), \ + absl::little_endian::Load64(data)); \ + data += 8; \ + } while (0) +#define ABSL_INTERNAL_STEP8BY2(crc0, crc1, p0, p1) \ + do { \ + ABSL_INTERNAL_STEP8(crc0, p0); \ + ABSL_INTERNAL_STEP8(crc1, p1); \ + } while (0) +#define ABSL_INTERNAL_STEP8BY3(crc0, crc1, crc2, p0, p1, p2) \ + do { \ + ABSL_INTERNAL_STEP8(crc0, p0); \ + ABSL_INTERNAL_STEP8(crc1, p1); \ + ABSL_INTERNAL_STEP8(crc2, p2); \ + } while (0) + +namespace { + +uint32_t multiply(uint32_t a, uint32_t b) { + V128 shifts = V128_From2x64(0, 1); + V128 power = V128_From2x64(0, a); + V128 crc = V128_From2x64(0, b); + V128 res = V128_PMulLow(power, crc); + + // Combine crc values + res = V128_ShiftLeft64(res, shifts); + return static_cast<uint32_t>(V128_Extract32<1>(res)) ^ + CRC32_u32(0, static_cast<uint32_t>(V128_Low64(res))); +} + +// Powers of crc32c polynomial, for faster ExtendByZeros. +// Verified against folly: +// folly/hash/detail/Crc32CombineDetail.cpp +constexpr uint32_t kCRC32CPowers[] = { + 0x82f63b78, 0x6ea2d55c, 0x18b8ea18, 0x510ac59a, 0xb82be955, 0xb8fdb1e7, + 0x88e56f72, 0x74c360a4, 0xe4172b16, 0x0d65762a, 0x35d73a62, 0x28461564, + 0xbf455269, 0xe2ea32dc, 0xfe7740e6, 0xf946610b, 0x3c204f8f, 0x538586e3, + 0x59726915, 0x734d5309, 0xbc1ac763, 0x7d0722cc, 0xd289cabe, 0xe94ca9bc, + 0x05b74f3f, 0xa51e1f42, 0x40000000, 0x20000000, 0x08000000, 0x00800000, + 0x00008000, 0x82f63b78, 0x6ea2d55c, 0x18b8ea18, 0x510ac59a, 0xb82be955, + 0xb8fdb1e7, 0x88e56f72, 0x74c360a4, 0xe4172b16, 0x0d65762a, 0x35d73a62, + 0x28461564, 0xbf455269, 0xe2ea32dc, 0xfe7740e6, 0xf946610b, 0x3c204f8f, + 0x538586e3, 0x59726915, 0x734d5309, 0xbc1ac763, 0x7d0722cc, 0xd289cabe, + 0xe94ca9bc, 0x05b74f3f, 0xa51e1f42, 0x40000000, 0x20000000, 0x08000000, + 0x00800000, 0x00008000, +}; + +} // namespace + +// Compute a magic constant, so that multiplying by it is the same as +// extending crc by length zeros. +uint32_t CRC32AcceleratedX86ARMCombined::ComputeZeroConstant( + size_t length) const { + // Lowest 2 bits are handled separately in ExtendByZeroes + length >>= 2; + + int index = absl::countr_zero(length); + uint32_t prev = kCRC32CPowers[index]; + length &= length - 1; + + while (length) { + // For each bit of length, extend by 2**n zeros. + index = absl::countr_zero(length); + prev = multiply(prev, kCRC32CPowers[index]); + length &= length - 1; + } + return prev; +} + +void CRC32AcceleratedX86ARMCombined::ExtendByZeroes(uint32_t* crc, + size_t length) const { + uint32_t val = *crc; + // Don't bother with multiplication for small length. + switch (length & 3) { + case 0: + break; + case 1: + val = CRC32_u8(val, 0); + break; + case 2: + val = CRC32_u16(val, 0); + break; + case 3: + val = CRC32_u8(val, 0); + val = CRC32_u16(val, 0); + break; + } + if (length > 3) { + val = multiply(val, ComputeZeroConstant(length)); + } + *crc = val; +} + +// Taken from Intel paper "Fast CRC Computation for iSCSI Polynomial Using CRC32 +// Instruction" +// https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/crc-iscsi-polynomial-crc32-instruction-paper.pdf +// We only need every 4th value, because we unroll loop by 4. +constexpr uint64_t kClmulConstants[] = { + 0x09e4addf8, 0x0ba4fc28e, 0x00d3b6092, 0x09e4addf8, 0x0ab7aff2a, + 0x102f9b8a2, 0x0b9e02b86, 0x00d3b6092, 0x1bf2e8b8a, 0x18266e456, + 0x0d270f1a2, 0x0ab7aff2a, 0x11eef4f8e, 0x083348832, 0x0dd7e3b0c, + 0x0b9e02b86, 0x0271d9844, 0x1b331e26a, 0x06b749fb2, 0x1bf2e8b8a, + 0x0e6fc4e6a, 0x0ce7f39f4, 0x0d7a4825c, 0x0d270f1a2, 0x026f6a60a, + 0x12ed0daac, 0x068bce87a, 0x11eef4f8e, 0x1329d9f7e, 0x0b3e32c28, + 0x0170076fa, 0x0dd7e3b0c, 0x1fae1cc66, 0x010746f3c, 0x086d8e4d2, + 0x0271d9844, 0x0b3af077a, 0x093a5f730, 0x1d88abd4a, 0x06b749fb2, + 0x0c9c8b782, 0x0cec3662e, 0x1ddffc5d4, 0x0e6fc4e6a, 0x168763fa6, + 0x0b0cd4768, 0x19b1afbc4, 0x0d7a4825c, 0x123888b7a, 0x00167d312, + 0x133d7a042, 0x026f6a60a, 0x000bcf5f6, 0x19d34af3a, 0x1af900c24, + 0x068bce87a, 0x06d390dec, 0x16cba8aca, 0x1f16a3418, 0x1329d9f7e, + 0x19fb2a8b0, 0x02178513a, 0x1a0f717c4, 0x0170076fa, +}; + +enum class CutoffStrategy { + // Use 3 CRC streams to fold into 1. + Fold3, + // Unroll CRC instructions for 64 bytes. + Unroll64CRC, +}; + +// Base class for CRC32AcceleratedX86ARMCombinedMultipleStreams containing the +// methods and data that don't need the template arguments. +class CRC32AcceleratedX86ARMCombinedMultipleStreamsBase + : public CRC32AcceleratedX86ARMCombined { + protected: + // Update partialCRC with crc of 64 byte block. Calling FinalizePclmulStream + // would produce a single crc checksum, but it is expensive. PCLMULQDQ has a + // high latency, so we run 4 128-bit partial checksums that can be reduced to + // a single value by FinalizePclmulStream later. Computing crc for arbitrary + // polynomialas with PCLMULQDQ is described in Intel paper "Fast CRC + // Computation for Generic Polynomials Using PCLMULQDQ Instruction" + // https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf + // We are applying it to CRC32C polynomial. + ABSL_ATTRIBUTE_ALWAYS_INLINE void Process64BytesPclmul( + const uint8_t* p, V128* partialCRC) const { + V128 loopMultiplicands = V128_Load(reinterpret_cast<const V128*>(k1k2)); + + V128 partialCRC1 = partialCRC[0]; + V128 partialCRC2 = partialCRC[1]; + V128 partialCRC3 = partialCRC[2]; + V128 partialCRC4 = partialCRC[3]; + + V128 tmp1 = V128_PMulHi(partialCRC1, loopMultiplicands); + V128 tmp2 = V128_PMulHi(partialCRC2, loopMultiplicands); + V128 tmp3 = V128_PMulHi(partialCRC3, loopMultiplicands); + V128 tmp4 = V128_PMulHi(partialCRC4, loopMultiplicands); + V128 data1 = V128_LoadU(reinterpret_cast<const V128*>(p + 16 * 0)); + V128 data2 = V128_LoadU(reinterpret_cast<const V128*>(p + 16 * 1)); + V128 data3 = V128_LoadU(reinterpret_cast<const V128*>(p + 16 * 2)); + V128 data4 = V128_LoadU(reinterpret_cast<const V128*>(p + 16 * 3)); + partialCRC1 = V128_PMulLow(partialCRC1, loopMultiplicands); + partialCRC2 = V128_PMulLow(partialCRC2, loopMultiplicands); + partialCRC3 = V128_PMulLow(partialCRC3, loopMultiplicands); + partialCRC4 = V128_PMulLow(partialCRC4, loopMultiplicands); + partialCRC1 = V128_Xor(tmp1, partialCRC1); + partialCRC2 = V128_Xor(tmp2, partialCRC2); + partialCRC3 = V128_Xor(tmp3, partialCRC3); + partialCRC4 = V128_Xor(tmp4, partialCRC4); + partialCRC1 = V128_Xor(partialCRC1, data1); + partialCRC2 = V128_Xor(partialCRC2, data2); + partialCRC3 = V128_Xor(partialCRC3, data3); + partialCRC4 = V128_Xor(partialCRC4, data4); + partialCRC[0] = partialCRC1; + partialCRC[1] = partialCRC2; + partialCRC[2] = partialCRC3; + partialCRC[3] = partialCRC4; + } + + // Reduce partialCRC produced by Process64BytesPclmul into a single value, + // that represents crc checksum of all the processed bytes. + ABSL_ATTRIBUTE_ALWAYS_INLINE uint64_t + FinalizePclmulStream(V128* partialCRC) const { + V128 partialCRC1 = partialCRC[0]; + V128 partialCRC2 = partialCRC[1]; + V128 partialCRC3 = partialCRC[2]; + V128 partialCRC4 = partialCRC[3]; + + // Combine 4 vectors of partial crc into a single vector. + V128 reductionMultiplicands = + V128_Load(reinterpret_cast<const V128*>(k5k6)); + + V128 low = V128_PMulLow(reductionMultiplicands, partialCRC1); + V128 high = V128_PMulHi(reductionMultiplicands, partialCRC1); + + partialCRC1 = V128_Xor(low, high); + partialCRC1 = V128_Xor(partialCRC1, partialCRC2); + + low = V128_PMulLow(reductionMultiplicands, partialCRC3); + high = V128_PMulHi(reductionMultiplicands, partialCRC3); + + partialCRC3 = V128_Xor(low, high); + partialCRC3 = V128_Xor(partialCRC3, partialCRC4); + + reductionMultiplicands = V128_Load(reinterpret_cast<const V128*>(k3k4)); + + low = V128_PMulLow(reductionMultiplicands, partialCRC1); + high = V128_PMulHi(reductionMultiplicands, partialCRC1); + V128 fullCRC = V128_Xor(low, high); + fullCRC = V128_Xor(fullCRC, partialCRC3); + + // Reduce fullCRC into scalar value. + reductionMultiplicands = V128_Load(reinterpret_cast<const V128*>(k5k6)); + + V128 mask = V128_Load(reinterpret_cast<const V128*>(kMask)); + + V128 tmp = V128_PMul01(reductionMultiplicands, fullCRC); + fullCRC = V128_ShiftRight<8>(fullCRC); + fullCRC = V128_Xor(fullCRC, tmp); + + reductionMultiplicands = V128_Load(reinterpret_cast<const V128*>(k7k0)); + + tmp = V128_ShiftRight<4>(fullCRC); + fullCRC = V128_And(fullCRC, mask); + fullCRC = V128_PMulLow(reductionMultiplicands, fullCRC); + fullCRC = V128_Xor(tmp, fullCRC); + + reductionMultiplicands = V128_Load(reinterpret_cast<const V128*>(kPoly)); + + tmp = V128_And(fullCRC, mask); + tmp = V128_PMul01(reductionMultiplicands, tmp); + tmp = V128_And(tmp, mask); + tmp = V128_PMulLow(reductionMultiplicands, tmp); + + fullCRC = V128_Xor(tmp, fullCRC); + + return static_cast<uint64_t>(V128_Extract32<1>(fullCRC)); + } + + // Update crc with 64 bytes of data from p. + ABSL_ATTRIBUTE_ALWAYS_INLINE uint64_t Process64BytesCRC(const uint8_t* p, + uint64_t crc) const { + for (int i = 0; i < 8; i++) { + crc = + CRC32_u64(static_cast<uint32_t>(crc), absl::little_endian::Load64(p)); + p += 8; + } + return crc; + } + + // Generated by crc32c_x86_test --crc32c_generate_constants=true + // and verified against constants in linux kernel for S390: + // https://github.com/torvalds/linux/blob/master/arch/s390/crypto/crc32le-vx.S + alignas(16) static constexpr uint64_t k1k2[2] = {0x0740eef02, 0x09e4addf8}; + alignas(16) static constexpr uint64_t k3k4[2] = {0x1384aa63a, 0x0ba4fc28e}; + alignas(16) static constexpr uint64_t k5k6[2] = {0x0f20c0dfe, 0x14cd00bd6}; + alignas(16) static constexpr uint64_t k7k0[2] = {0x0dd45aab8, 0x000000000}; + alignas(16) static constexpr uint64_t kPoly[2] = {0x105ec76f0, 0x0dea713f1}; + alignas(16) static constexpr uint32_t kMask[4] = {~0u, 0u, ~0u, 0u}; + + // Medium runs of bytes are broken into groups of kGroupsSmall blocks of same + // size. Each group is CRCed in parallel then combined at the end of the + // block. + static constexpr size_t kGroupsSmall = 3; + // For large runs we use up to kMaxStreams blocks computed with CRC + // instruction, and up to kMaxStreams blocks computed with PCLMULQDQ, which + // are combined in the end. + static constexpr size_t kMaxStreams = 3; +}; + +#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL +alignas(16) constexpr uint64_t + CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::k1k2[2]; +alignas(16) constexpr uint64_t + CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::k3k4[2]; +alignas(16) constexpr uint64_t + CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::k5k6[2]; +alignas(16) constexpr uint64_t + CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::k7k0[2]; +alignas(16) constexpr uint64_t + CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::kPoly[2]; +alignas(16) constexpr uint32_t + CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::kMask[4]; +constexpr size_t + CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::kGroupsSmall; +constexpr size_t CRC32AcceleratedX86ARMCombinedMultipleStreamsBase::kMaxStreams; +#endif // ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL + +template <size_t num_crc_streams, size_t num_pclmul_streams, + CutoffStrategy strategy> +class CRC32AcceleratedX86ARMCombinedMultipleStreams + : public CRC32AcceleratedX86ARMCombinedMultipleStreamsBase { + ABSL_ATTRIBUTE_HOT + void Extend(uint32_t* crc, const void* bytes, size_t length) const override { + static_assert(num_crc_streams >= 1 && num_crc_streams <= kMaxStreams, + "Invalid number of crc streams"); + static_assert(num_pclmul_streams >= 0 && num_pclmul_streams <= kMaxStreams, + "Invalid number of pclmul streams"); + const uint8_t* p = static_cast<const uint8_t*>(bytes); + const uint8_t* e = p + length; + uint32_t l = *crc; + uint64_t l64; + + // We have dedicated instruction for 1,2,4 and 8 bytes. + if (length & 8) { + ABSL_INTERNAL_STEP8(l, p); + length &= ~size_t{8}; + } + if (length & 4) { + ABSL_INTERNAL_STEP4(l); + length &= ~size_t{4}; + } + if (length & 2) { + ABSL_INTERNAL_STEP2(l); + length &= ~size_t{2}; + } + if (length & 1) { + ABSL_INTERNAL_STEP1(l); + length &= ~size_t{1}; + } + if (length == 0) { + *crc = l; + return; + } + // length is now multiple of 16. + + // For small blocks just run simple loop, because cost of combining multiple + // streams is significant. + if (strategy != CutoffStrategy::Unroll64CRC) { + if (length < kSmallCutoff) { + while (length >= 16) { + ABSL_INTERNAL_STEP8(l, p); + ABSL_INTERNAL_STEP8(l, p); + length -= 16; + } + *crc = l; + return; + } + } + + // For medium blocks we run 3 crc streams and combine them as described in + // Intel paper above. Running 4th stream doesn't help, because crc + // instruction has latency 3 and throughput 1. + if (length < kMediumCutoff) { + l64 = l; + if (strategy == CutoffStrategy::Fold3) { + uint64_t l641 = 0; + uint64_t l642 = 0; + const size_t blockSize = 32; + size_t bs = static_cast<size_t>(e - p) / kGroupsSmall / blockSize; + const uint8_t* p1 = p + bs * blockSize; + const uint8_t* p2 = p1 + bs * blockSize; + + for (size_t i = 0; i + 1 < bs; ++i) { + 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); + ABSL_INTERNAL_STEP8BY3(l64, l641, l642, p, p1, p2); + base_internal::PrefetchT0( + reinterpret_cast<const char*>(p + kPrefetchHorizonMedium)); + base_internal::PrefetchT0( + reinterpret_cast<const char*>(p1 + kPrefetchHorizonMedium)); + base_internal::PrefetchT0( + reinterpret_cast<const char*>(p2 + kPrefetchHorizonMedium)); + } + // Don't run crc on last 8 bytes. + 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); + ABSL_INTERNAL_STEP8BY2(l64, l641, p, p1); + + V128 magic = *(reinterpret_cast<const V128*>(kClmulConstants) + bs - 1); + + V128 tmp = V128_From2x64(0, l64); + + V128 res1 = V128_PMulLow(tmp, magic); + + tmp = V128_From2x64(0, l641); + + V128 res2 = V128_PMul10(tmp, magic); + V128 x = V128_Xor(res1, res2); + l64 = static_cast<uint64_t>(V128_Low64(x)) ^ + absl::little_endian::Load64(p2); + l64 = CRC32_u64(static_cast<uint32_t>(l642), l64); + + p = p2 + 8; + } else if (strategy == CutoffStrategy::Unroll64CRC) { + while ((e - p) >= 64) { + l64 = Process64BytesCRC(p, l64); + p += 64; + } + } + } else { + // There is a lot of data, we can ignore combine costs and run all + // requested streams (num_crc_streams + num_pclmul_streams), + // using prefetch. CRC and PCLMULQDQ use different cpu execution units, + // so on some cpus it makes sense to execute both of them for different + // streams. + + // Point x at first 8-byte aligned byte in string. + const uint8_t* x = RoundUp<8>(p); + // Process bytes until p is 8-byte aligned, if that isn't past the end. + while (p != x) { + ABSL_INTERNAL_STEP1(l); + } + + size_t bs = static_cast<size_t>(e - p) / + (num_crc_streams + num_pclmul_streams) / 64; + const uint8_t* crc_streams[kMaxStreams]; + const uint8_t* pclmul_streams[kMaxStreams]; + // We are guaranteed to have at least one crc stream. + crc_streams[0] = p; + for (size_t i = 1; i < num_crc_streams; i++) { + crc_streams[i] = crc_streams[i - 1] + bs * 64; + } + pclmul_streams[0] = crc_streams[num_crc_streams - 1] + bs * 64; + for (size_t i = 1; i < num_pclmul_streams; i++) { + pclmul_streams[i] = pclmul_streams[i - 1] + bs * 64; + } + + // Per stream crc sums. + uint64_t l64_crc[kMaxStreams] = {l}; + uint64_t l64_pclmul[kMaxStreams] = {0}; + + // Peel first iteration, because PCLMULQDQ stream, needs setup. + for (size_t i = 0; i < num_crc_streams; i++) { + l64_crc[i] = Process64BytesCRC(crc_streams[i], l64_crc[i]); + crc_streams[i] += 16 * 4; + } + + V128 partialCRC[kMaxStreams][4]; + for (size_t i = 0; i < num_pclmul_streams; i++) { + partialCRC[i][0] = V128_LoadU( + reinterpret_cast<const V128*>(pclmul_streams[i] + 16 * 0)); + partialCRC[i][1] = V128_LoadU( + reinterpret_cast<const V128*>(pclmul_streams[i] + 16 * 1)); + partialCRC[i][2] = V128_LoadU( + reinterpret_cast<const V128*>(pclmul_streams[i] + 16 * 2)); + partialCRC[i][3] = V128_LoadU( + reinterpret_cast<const V128*>(pclmul_streams[i] + 16 * 3)); + pclmul_streams[i] += 16 * 4; + } + + for (size_t i = 1; i < bs; i++) { + // Prefetch data for next itterations. + for (size_t j = 0; j < num_crc_streams; j++) { + base_internal::PrefetchT0( + 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)); + } + + // We process each stream in 64 byte blocks. This can be written as + // for (int i = 0; i < num_pclmul_streams; i++) { + // Process64BytesPclmul(pclmul_streams[i], partialCRC[i]); + // pclmul_streams[i] += 16 * 4; + // } + // for (int i = 0; i < num_crc_streams; i++) { + // l64_crc[i] = Process64BytesCRC(crc_streams[i], l64_crc[i]); + // crc_streams[i] += 16*4; + // } + // But unrolling and interleaving PCLMULQDQ and CRC blocks manually + // gives ~2% performance boost. + l64_crc[0] = Process64BytesCRC(crc_streams[0], l64_crc[0]); + crc_streams[0] += 16 * 4; + if (num_pclmul_streams > 0) { + Process64BytesPclmul(pclmul_streams[0], partialCRC[0]); + pclmul_streams[0] += 16 * 4; + } + if (num_crc_streams > 1) { + l64_crc[1] = Process64BytesCRC(crc_streams[1], l64_crc[1]); + crc_streams[1] += 16 * 4; + } + if (num_pclmul_streams > 1) { + Process64BytesPclmul(pclmul_streams[1], partialCRC[1]); + pclmul_streams[1] += 16 * 4; + } + if (num_crc_streams > 2) { + l64_crc[2] = Process64BytesCRC(crc_streams[2], l64_crc[2]); + crc_streams[2] += 16 * 4; + } + if (num_pclmul_streams > 2) { + Process64BytesPclmul(pclmul_streams[2], partialCRC[2]); + pclmul_streams[2] += 16 * 4; + } + } + + // PCLMULQDQ based streams require special final step; + // CRC based don't. + for (size_t i = 0; i < num_pclmul_streams; i++) { + l64_pclmul[i] = FinalizePclmulStream(partialCRC[i]); + } + + // Combine all streams into single result. + uint32_t magic = ComputeZeroConstant(bs * 64); + l64 = l64_crc[0]; + for (size_t i = 1; i < num_crc_streams; i++) { + l64 = multiply(static_cast<uint32_t>(l64), magic); + l64 ^= l64_crc[i]; + } + for (size_t i = 0; i < num_pclmul_streams; i++) { + l64 = multiply(static_cast<uint32_t>(l64), magic); + l64 ^= l64_pclmul[i]; + } + + // Update p. + if (num_pclmul_streams > 0) { + p = pclmul_streams[num_pclmul_streams - 1]; + } else { + p = crc_streams[num_crc_streams - 1]; + } + } + l = static_cast<uint32_t>(l64); + + while ((e - p) >= 16) { + ABSL_INTERNAL_STEP8(l, p); + ABSL_INTERNAL_STEP8(l, p); + } + // Process the last few bytes + while (p != e) { + ABSL_INTERNAL_STEP1(l); + } + +#undef ABSL_INTERNAL_STEP8BY3 +#undef ABSL_INTERNAL_STEP8BY2 +#undef ABSL_INTERNAL_STEP8 +#undef ABSL_INTERNAL_STEP4 +#undef ABSL_INTERNAL_STEP2 +#undef ABSL_INTERNAL_STEP1 + + *crc = l; + } +}; + +} // namespace + +// Intel processors with SSE4.2 have an instruction for one particular +// 32-bit CRC polynomial: crc32c +CRCImpl* TryNewCRC32AcceleratedX86ARMCombined() { + CpuType type = GetCpuType(); + switch (type) { + case CpuType::kIntelHaswell: + case CpuType::kAmdRome: + case CpuType::kAmdNaples: + case CpuType::kAmdMilan: + return new CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 1, CutoffStrategy::Fold3>(); + // PCLMULQDQ is fast, use combined PCLMULQDQ + CRC implementation. + case CpuType::kIntelCascadelakeXeon: + case CpuType::kIntelSkylakeXeon: + case CpuType::kIntelBroadwell: + case CpuType::kIntelSkylake: + return new CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 2, CutoffStrategy::Fold3>(); + // PCLMULQDQ is slow, don't use it. + case CpuType::kIntelIvybridge: + case CpuType::kIntelSandybridge: + case CpuType::kIntelWestmere: + return new CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 0, CutoffStrategy::Fold3>(); + case CpuType::kArmNeoverseN1: + return new CRC32AcceleratedX86ARMCombinedMultipleStreams< + 1, 1, CutoffStrategy::Unroll64CRC>(); +#if defined(__aarch64__) + default: + // Not all ARM processors support the needed instructions, so check here + // before trying to use an accelerated implementation. + if (SupportsArmCRC32PMULL()) { + return new CRC32AcceleratedX86ARMCombinedMultipleStreams< + 1, 1, CutoffStrategy::Unroll64CRC>(); + } else { + return nullptr; + } +#else + default: + // Something else, play it safe and assume slow PCLMULQDQ. + return new CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 0, CutoffStrategy::Fold3>(); +#endif + } +} + +std::vector<std::unique_ptr<CRCImpl>> NewCRC32AcceleratedX86ARMCombinedAll() { + auto ret = std::vector<std::unique_ptr<CRCImpl>>(); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 1, 0, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 1, 1, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 1, 2, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 1, 3, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 2, 0, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 2, 1, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 2, 2, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 2, 3, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 0, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 1, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 2, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 3, CutoffStrategy::Fold3>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 1, 0, CutoffStrategy::Unroll64CRC>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 1, 1, CutoffStrategy::Unroll64CRC>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 1, 2, CutoffStrategy::Unroll64CRC>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 1, 3, CutoffStrategy::Unroll64CRC>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 2, 0, CutoffStrategy::Unroll64CRC>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 2, 1, CutoffStrategy::Unroll64CRC>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 2, 2, CutoffStrategy::Unroll64CRC>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 2, 3, CutoffStrategy::Unroll64CRC>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 0, CutoffStrategy::Unroll64CRC>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 1, CutoffStrategy::Unroll64CRC>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 2, CutoffStrategy::Unroll64CRC>>()); + ret.push_back(absl::make_unique<CRC32AcceleratedX86ARMCombinedMultipleStreams< + 3, 3, CutoffStrategy::Unroll64CRC>>()); + + return ret; +} + +#else // !ABSL_INTERNAL_CAN_USE_SIMD_CRC32C + +std::vector<std::unique_ptr<CRCImpl>> NewCRC32AcceleratedX86ARMCombinedAll() { + return std::vector<std::unique_ptr<CRCImpl>>(); +} + +// no hardware acceleration available +CRCImpl* TryNewCRC32AcceleratedX86ARMCombined() { return nullptr; } + +#endif + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/crc/internal/non_temporal_arm_intrinsics.h b/absl/crc/internal/non_temporal_arm_intrinsics.h new file mode 100644 index 00000000..9e5ccfc4 --- /dev/null +++ b/absl/crc/internal/non_temporal_arm_intrinsics.h @@ -0,0 +1,79 @@ +// 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_CRC_INTERNAL_NON_TEMPORAL_ARM_INTRINSICS_H_ +#define ABSL_CRC_INTERNAL_NON_TEMPORAL_ARM_INTRINSICS_H_ + +#include "absl/base/config.h" + +#ifdef __aarch64__ +#include <arm_neon.h> + +typedef int64x2_t __m128i; /* 128-bit vector containing integers */ +#define vreinterpretq_m128i_s32(x) vreinterpretq_s64_s32(x) +#define vreinterpretq_s64_m128i(x) (x) + +// Guarantees that every preceding store is globally visible before any +// subsequent store. +// https://msdn.microsoft.com/en-us/library/5h2w73d1%28v=vs.90%29.aspx +static inline __attribute__((always_inline)) void _mm_sfence(void) { + __sync_synchronize(); +} + +// Load 128-bits of integer data from unaligned memory into dst. This intrinsic +// may perform better than _mm_loadu_si128 when the data crosses a cache line +// boundary. +// +// dst[127:0] := MEM[mem_addr+127:mem_addr] +// +// https://software.intel.com/sites/landingpage/IntrinsicsGuide/#text=_mm_lddqu_si128 +#define _mm_lddqu_si128 _mm_loadu_si128 + +// Loads 128-bit value. : +// https://msdn.microsoft.com/zh-cn/library/f4k12ae8(v=vs.90).aspx +static inline __attribute__((always_inline)) __m128i _mm_loadu_si128( + const __m128i *p) { + return vreinterpretq_m128i_s32(vld1q_s32((const int32_t *)p)); +} + +// Stores the data in a to the address p without polluting the caches. If the +// cache line containing address p is already in the cache, the cache will be +// updated. +// https://msdn.microsoft.com/en-us/library/ba08y07y%28v=vs.90%29.aspx +static inline __attribute__((always_inline)) void _mm_stream_si128(__m128i *p, + __m128i a) { +#if ABSL_HAVE_BUILTIN(__builtin_nontemporal_store) + __builtin_nontemporal_store(a, p); +#else + vst1q_s64((int64_t *)p, vreinterpretq_s64_m128i(a)); +#endif +} + +// Sets the 16 signed 8-bit integer values. +// https://msdn.microsoft.com/en-us/library/x0cx8zd3(v=vs.90).aspx +static inline __attribute__((always_inline)) __m128i _mm_set_epi8( + signed char b15, signed char b14, signed char b13, signed char b12, + signed char b11, signed char b10, signed char b9, signed char b8, + signed char b7, signed char b6, signed char b5, signed char b4, + signed char b3, signed char b2, signed char b1, signed char b0) { + int8_t __attribute__((aligned(16))) + data[16] = {(int8_t)b0, (int8_t)b1, (int8_t)b2, (int8_t)b3, + (int8_t)b4, (int8_t)b5, (int8_t)b6, (int8_t)b7, + (int8_t)b8, (int8_t)b9, (int8_t)b10, (int8_t)b11, + (int8_t)b12, (int8_t)b13, (int8_t)b14, (int8_t)b15}; + return (__m128i)vld1q_s8(data); +} +#endif // __aarch64__ + +#endif // ABSL_CRC_INTERNAL_NON_TEMPORAL_ARM_INTRINSICS_H_ diff --git a/absl/crc/internal/non_temporal_memcpy.h b/absl/crc/internal/non_temporal_memcpy.h new file mode 100644 index 00000000..b3d94bad --- /dev/null +++ b/absl/crc/internal/non_temporal_memcpy.h @@ -0,0 +1,180 @@ +// 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_CRC_INTERNAL_NON_TEMPORAL_MEMCPY_H_ +#define ABSL_CRC_INTERNAL_NON_TEMPORAL_MEMCPY_H_ + +#ifdef _MSC_VER +#include <intrin.h> +#endif + +#ifdef __SSE__ +#include <xmmintrin.h> +#endif + +#ifdef __SSE2__ +#include <emmintrin.h> +#endif + +#ifdef __SSE3__ +#include <pmmintrin.h> +#endif + +#ifdef __AVX__ +#include <immintrin.h> +#endif + +#ifdef __aarch64__ +#include "absl/crc/internal/non_temporal_arm_intrinsics.h" +#endif + +#include <algorithm> +#include <cassert> +#include <cstdint> +#include <cstring> + +#include "absl/base/config.h" +#include "absl/base/optimization.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace crc_internal { + +// This non-temporal memcpy does regular load and non-temporal store memory +// copy. It is compatible to both 16-byte aligned and unaligned addresses. If +// data at the destination is not immediately accessed, using non-temporal +// memcpy can save 1 DRAM load of the destination cacheline. +constexpr size_t kCacheLineSize = ABSL_CACHELINE_SIZE; + +// If the objects overlap, the behavior is undefined. +inline void *non_temporal_store_memcpy(void *__restrict dst, + const void *__restrict src, size_t len) { +#if defined(__SSE3__) || defined(__aarch64__) || \ + (defined(_MSC_VER) && defined(__AVX__)) + // This implementation requires SSE3. + // MSVC cannot target SSE3 directly, but when MSVC targets AVX, + // SSE3 support is implied. + uint8_t *d = reinterpret_cast<uint8_t *>(dst); + const uint8_t *s = reinterpret_cast<const uint8_t *>(src); + + // memcpy() the misaligned header. At the end of this if block, <d> is + // aligned to a 64-byte cacheline boundary or <len> == 0. + if (reinterpret_cast<uintptr_t>(d) & (kCacheLineSize - 1)) { + uintptr_t bytes_before_alignment_boundary = + kCacheLineSize - + (reinterpret_cast<uintptr_t>(d) & (kCacheLineSize - 1)); + size_t header_len = (std::min)(bytes_before_alignment_boundary, len); + assert(bytes_before_alignment_boundary < kCacheLineSize); + memcpy(d, s, header_len); + d += header_len; + s += header_len; + len -= header_len; + } + + if (len >= kCacheLineSize) { + _mm_sfence(); + __m128i *dst_cacheline = reinterpret_cast<__m128i *>(d); + const __m128i *src_cacheline = reinterpret_cast<const __m128i *>(s); + constexpr int kOpsPerCacheLine = kCacheLineSize / sizeof(__m128i); + size_t loops = len / kCacheLineSize; + + while (len >= kCacheLineSize) { + __m128i temp1, temp2, temp3, temp4; + temp1 = _mm_lddqu_si128(src_cacheline + 0); + temp2 = _mm_lddqu_si128(src_cacheline + 1); + temp3 = _mm_lddqu_si128(src_cacheline + 2); + temp4 = _mm_lddqu_si128(src_cacheline + 3); + _mm_stream_si128(dst_cacheline + 0, temp1); + _mm_stream_si128(dst_cacheline + 1, temp2); + _mm_stream_si128(dst_cacheline + 2, temp3); + _mm_stream_si128(dst_cacheline + 3, temp4); + src_cacheline += kOpsPerCacheLine; + dst_cacheline += kOpsPerCacheLine; + len -= kCacheLineSize; + } + d += loops * kCacheLineSize; + s += loops * kCacheLineSize; + _mm_sfence(); + } + + // memcpy the tail. + if (len) { + memcpy(d, s, len); + } + return dst; +#else + // Fallback to regular memcpy. + return memcpy(dst, src, len); +#endif // __SSE3__ || __aarch64__ || (_MSC_VER && __AVX__) +} + +inline void *non_temporal_store_memcpy_avx(void *__restrict dst, + const void *__restrict src, + size_t len) { +#ifdef __AVX__ + uint8_t *d = reinterpret_cast<uint8_t *>(dst); + const uint8_t *s = reinterpret_cast<const uint8_t *>(src); + + // memcpy() the misaligned header. At the end of this if block, <d> is + // aligned to a 64-byte cacheline boundary or <len> == 0. + if (reinterpret_cast<uintptr_t>(d) & (kCacheLineSize - 1)) { + uintptr_t bytes_before_alignment_boundary = + kCacheLineSize - + (reinterpret_cast<uintptr_t>(d) & (kCacheLineSize - 1)); + size_t header_len = (std::min)(bytes_before_alignment_boundary, len); + assert(bytes_before_alignment_boundary < kCacheLineSize); + memcpy(d, s, header_len); + d += header_len; + s += header_len; + len -= header_len; + } + + if (len >= kCacheLineSize) { + _mm_sfence(); + __m256i *dst_cacheline = reinterpret_cast<__m256i *>(d); + const __m256i *src_cacheline = reinterpret_cast<const __m256i *>(s); + constexpr int kOpsPerCacheLine = kCacheLineSize / sizeof(__m256i); + size_t loops = len / kCacheLineSize; + + while (len >= kCacheLineSize) { + __m256i temp1, temp2; + temp1 = _mm256_lddqu_si256(src_cacheline + 0); + temp2 = _mm256_lddqu_si256(src_cacheline + 1); + _mm256_stream_si256(dst_cacheline + 0, temp1); + _mm256_stream_si256(dst_cacheline + 1, temp2); + src_cacheline += kOpsPerCacheLine; + dst_cacheline += kOpsPerCacheLine; + len -= kCacheLineSize; + } + d += loops * kCacheLineSize; + s += loops * kCacheLineSize; + _mm_sfence(); + } + + // memcpy the tail. + if (len) { + memcpy(d, s, len); + } + return dst; +#else + // Fallback to regular memcpy when AVX is not available. + return memcpy(dst, src, len); +#endif // __AVX__ +} + +} // namespace crc_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CRC_INTERNAL_NON_TEMPORAL_MEMCPY_H_ diff --git a/absl/crc/internal/non_temporal_memcpy_test.cc b/absl/crc/internal/non_temporal_memcpy_test.cc new file mode 100644 index 00000000..eb07a559 --- /dev/null +++ b/absl/crc/internal/non_temporal_memcpy_test.cc @@ -0,0 +1,88 @@ +// 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/crc/internal/non_temporal_memcpy.h" + +#include <algorithm> +#include <cstdint> +#include <iostream> +#include <vector> + +#include "gtest/gtest.h" + +namespace { + +struct TestParam { + size_t copy_size; + uint32_t src_offset; + uint32_t dst_offset; +}; + +class NonTemporalMemcpyTest : public testing::TestWithParam<TestParam> { + protected: + void SetUp() override { + // Make buf_size multiple of 16 bytes. + size_t buf_size = ((std::max(GetParam().src_offset, GetParam().dst_offset) + + GetParam().copy_size) + + 15) / + 16 * 16; + a_.resize(buf_size); + b_.resize(buf_size); + for (size_t i = 0; i < buf_size; i++) { + a_[i] = static_cast<uint8_t>(i % 256); + b_[i] = ~a_[i]; + } + } + + std::vector<uint8_t> a_, b_; +}; + +TEST_P(NonTemporalMemcpyTest, SSEEquality) { + uint8_t *src = a_.data() + GetParam().src_offset; + uint8_t *dst = b_.data() + GetParam().dst_offset; + absl::crc_internal::non_temporal_store_memcpy(dst, src, GetParam().copy_size); + for (size_t i = 0; i < GetParam().copy_size; i++) { + EXPECT_EQ(src[i], dst[i]); + } +} + +TEST_P(NonTemporalMemcpyTest, AVXEquality) { + uint8_t* src = a_.data() + GetParam().src_offset; + uint8_t* dst = b_.data() + GetParam().dst_offset; + + absl::crc_internal::non_temporal_store_memcpy_avx(dst, src, + GetParam().copy_size); + for (size_t i = 0; i < GetParam().copy_size; i++) { + EXPECT_EQ(src[i], dst[i]); + } +} + +// 63B is smaller than one cacheline operation thus the non-temporal routine +// will not be called. +// 4352B is sufficient for testing 4092B data copy with room for offsets. +constexpr TestParam params[] = { + {63, 0, 0}, {58, 5, 5}, {61, 2, 0}, {61, 0, 2}, + {58, 5, 2}, {4096, 0, 0}, {4096, 0, 1}, {4096, 0, 2}, + {4096, 0, 3}, {4096, 0, 4}, {4096, 0, 5}, {4096, 0, 6}, + {4096, 0, 7}, {4096, 0, 8}, {4096, 0, 9}, {4096, 0, 10}, + {4096, 0, 11}, {4096, 0, 12}, {4096, 0, 13}, {4096, 0, 14}, + {4096, 0, 15}, {4096, 7, 7}, {4096, 3, 0}, {4096, 1, 0}, + {4096, 9, 3}, {4096, 9, 11}, {8192, 0, 0}, {8192, 5, 2}, + {1024768, 7, 11}, {1, 0, 0}, {1, 0, 1}, {1, 1, 0}, + {1, 1, 1}}; + +INSTANTIATE_TEST_SUITE_P(ParameterizedNonTemporalMemcpyTest, + NonTemporalMemcpyTest, testing::ValuesIn(params)); + +} // namespace diff --git a/absl/debugging/BUILD.bazel b/absl/debugging/BUILD.bazel index 932a8e9f..edbb3698 100644 --- a/absl/debugging/BUILD.bazel +++ b/absl/debugging/BUILD.bazel @@ -49,6 +49,19 @@ cc_library( ":debugging_internal", "//absl/base:config", "//absl/base:core_headers", + "//absl/base:raw_logging_internal", + ], +) + +cc_test( + name = "stacktrace_test", + srcs = ["stacktrace_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":stacktrace", + "//absl/base:core_headers", + "@com_google_googletest//:gtest_main", ], ) @@ -121,7 +134,7 @@ cc_library( ], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, - visibility = ["//visibility:private"], + visibility = ["//absl/log/internal:__pkg__"], deps = [ ":stacktrace", ":symbolize", diff --git a/absl/debugging/CMakeLists.txt b/absl/debugging/CMakeLists.txt index d8207d6a..8f29cc07 100644 --- a/absl/debugging/CMakeLists.txt +++ b/absl/debugging/CMakeLists.txt @@ -41,9 +41,23 @@ absl_cc_library( absl::debugging_internal absl::config absl::core_headers + absl::raw_logging_internal PUBLIC ) +absl_cc_test( + NAME + stacktrace_test + SRCS + "stacktrace_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::stacktrace + absl::core_headers + GTest::gmock_main +) + absl_cc_library( NAME symbolize @@ -61,7 +75,7 @@ absl_cc_library( ${ABSL_DEFAULT_COPTS} LINKOPTS ${ABSL_DEFAULT_LINKOPTS} - $<$<BOOL:${MINGW}>:"dbghelp"> + $<$<BOOL:${MINGW}>:-ldbghelp> DEPS absl::debugging_internal absl::demangle_internal diff --git a/absl/debugging/failure_signal_handler.cc b/absl/debugging/failure_signal_handler.cc index affade3b..ef8ab9e5 100644 --- a/absl/debugging/failure_signal_handler.cc +++ b/absl/debugging/failure_signal_handler.cc @@ -50,7 +50,9 @@ #ifndef _WIN32 #define ABSL_HAVE_SIGACTION // Apple WatchOS and TVOS don't allow sigaltstack -#if !(defined(TARGET_OS_WATCH) && TARGET_OS_WATCH) && \ +// Apple macOS has sigaltstack, but using it makes backtrace() unusable. +#if !(defined(TARGET_OS_OSX) && TARGET_OS_OSX) && \ + !(defined(TARGET_OS_WATCH) && TARGET_OS_WATCH) && \ !(defined(TARGET_OS_TV) && TARGET_OS_TV) && !defined(__QNX__) #define ABSL_HAVE_SIGALTSTACK #endif @@ -133,10 +135,11 @@ static bool SetupAlternateStackOnce() { #if defined(__wasm__) || defined (__asjms__) const size_t page_mask = getpagesize() - 1; #else - const size_t page_mask = sysconf(_SC_PAGESIZE) - 1; + const size_t page_mask = static_cast<size_t>(sysconf(_SC_PAGESIZE)) - 1; #endif size_t stack_size = - (std::max<size_t>(SIGSTKSZ, 65536) + page_mask) & ~page_mask; + (std::max(static_cast<size_t>(SIGSTKSZ), size_t{65536}) + page_mask) & + ~page_mask; #if defined(ABSL_HAVE_ADDRESS_SANITIZER) || \ defined(ABSL_HAVE_MEMORY_SANITIZER) || defined(ABSL_HAVE_THREAD_SANITIZER) // Account for sanitizer instrumentation requiring additional stack space. @@ -216,7 +219,7 @@ static void InstallOneFailureHandler(FailureSignalData* data, #endif static void WriteToStderr(const char* data) { - absl::raw_logging_internal::AsyncSignalSafeWriteToStderr(data, strlen(data)); + absl::raw_log_internal::AsyncSignalSafeWriteToStderr(data, strlen(data)); } static void WriteSignalMessage(int signo, int cpu, @@ -289,7 +292,7 @@ static void WriteFailureInfo(int signo, void* ucontext, int cpu, // some platforms. static void PortableSleepForSeconds(int seconds) { #ifdef _WIN32 - Sleep(seconds * 1000); + Sleep(static_cast<DWORD>(seconds * 1000)); #else struct timespec sleep_time; sleep_time.tv_sec = seconds; @@ -323,9 +326,9 @@ static void AbslFailureSignalHandler(int signo, siginfo_t*, void* ucontext) { const GetTidType this_tid = absl::base_internal::GetTID(); GetTidType previous_failed_tid = 0; - if (!failed_tid.compare_exchange_strong( - previous_failed_tid, static_cast<intptr_t>(this_tid), - std::memory_order_acq_rel, std::memory_order_relaxed)) { + if (!failed_tid.compare_exchange_strong(previous_failed_tid, this_tid, + std::memory_order_acq_rel, + std::memory_order_relaxed)) { ABSL_RAW_LOG( ERROR, "Signal %d raised at PC=%p while already in AbslFailureSignalHandler()", @@ -354,7 +357,7 @@ static void AbslFailureSignalHandler(int signo, siginfo_t*, void* ucontext) { if (fsh_options.alarm_on_failure_secs > 0) { alarm(0); // Cancel any existing alarms. signal(SIGALRM, ImmediateAbortSignalHandler); - alarm(fsh_options.alarm_on_failure_secs); + alarm(static_cast<unsigned int>(fsh_options.alarm_on_failure_secs)); } #endif diff --git a/absl/debugging/internal/address_is_readable.cc b/absl/debugging/internal/address_is_readable.cc index 4be6256b..91eaa76f 100644 --- a/absl/debugging/internal/address_is_readable.cc +++ b/absl/debugging/internal/address_is_readable.cc @@ -52,7 +52,7 @@ namespace debugging_internal { bool AddressIsReadable(const void *addr) { // Align address on 8-byte boundary. On aarch64, checking last // byte before inaccessible page returned unexpected EFAULT. - const uintptr_t u_addr = reinterpret_cast<uintptr_t>(addr) & ~7; + const uintptr_t u_addr = reinterpret_cast<uintptr_t>(addr) & ~uintptr_t{7}; addr = reinterpret_cast<const void *>(u_addr); // rt_sigprocmask below will succeed for this input. diff --git a/absl/debugging/internal/demangle.cc b/absl/debugging/internal/demangle.cc index 93ae3279..f2832915 100644 --- a/absl/debugging/internal/demangle.cc +++ b/absl/debugging/internal/demangle.cc @@ -151,12 +151,12 @@ static const AbbrevPair kSubstitutionList[] = { // State needed for demangling. This struct is copied in almost every stack // frame, so every byte counts. typedef struct { - int mangled_idx; // Cursor of mangled name. - int out_cur_idx; // Cursor of output string. - int prev_name_idx; // For constructors/destructors. - signed int prev_name_length : 16; // For constructors/destructors. - signed int nest_level : 15; // For nested names. - unsigned int append : 1; // Append flag. + int mangled_idx; // Cursor of mangled name. + int out_cur_idx; // Cursor of output string. + int prev_name_idx; // For constructors/destructors. + unsigned int prev_name_length : 16; // For constructors/destructors. + signed int nest_level : 15; // For nested names. + unsigned int append : 1; // Append flag. // Note: for some reason MSVC can't pack "bool append : 1" into the same int // with the above two fields, so we use an int instead. Amusingly it can pack // "signed bool" as expected, but relying on that to continue to be a legal @@ -235,8 +235,8 @@ static size_t StrLen(const char *str) { } // Returns true if "str" has at least "n" characters remaining. -static bool AtLeastNumCharsRemaining(const char *str, int n) { - for (int i = 0; i < n; ++i) { +static bool AtLeastNumCharsRemaining(const char *str, size_t n) { + for (size_t i = 0; i < n; ++i) { if (str[i] == '\0') { return false; } @@ -253,18 +253,20 @@ static bool StrPrefix(const char *str, const char *prefix) { return prefix[i] == '\0'; // Consumed everything in "prefix". } -static void InitState(State *state, const char *mangled, char *out, - int out_size) { +static void InitState(State* state, + const char* mangled, + char* out, + size_t out_size) { state->mangled_begin = mangled; state->out = out; - state->out_end_idx = out_size; + state->out_end_idx = static_cast<int>(out_size); state->recursion_depth = 0; state->steps = 0; state->parse_state.mangled_idx = 0; state->parse_state.out_cur_idx = 0; state->parse_state.prev_name_idx = 0; - state->parse_state.prev_name_length = -1; + state->parse_state.prev_name_length = 0; state->parse_state.nest_level = -1; state->parse_state.append = true; } @@ -356,8 +358,8 @@ static bool ZeroOrMore(ParseFunc parse_func, State *state) { // Append "str" at "out_cur_idx". If there is an overflow, out_cur_idx is // set to out_end_idx+1. The output string is ensured to // always terminate with '\0' as long as there is no overflow. -static void Append(State *state, const char *const str, const int length) { - for (int i = 0; i < length; ++i) { +static void Append(State *state, const char *const str, const size_t length) { + for (size_t i = 0; i < length; ++i) { if (state->parse_state.out_cur_idx + 1 < state->out_end_idx) { // +1 for '\0' state->out[state->parse_state.out_cur_idx++] = str[i]; @@ -420,7 +422,7 @@ static bool EndsWith(State *state, const char chr) { // Append "str" with some tweaks, iff "append" state is true. static void MaybeAppendWithLength(State *state, const char *const str, - const int length) { + const size_t length) { if (state->parse_state.append && length > 0) { // Append a space if the output buffer ends with '<' and "str" // starts with '<' to avoid <<<. @@ -432,14 +434,14 @@ static void MaybeAppendWithLength(State *state, const char *const str, if (state->parse_state.out_cur_idx < state->out_end_idx && (IsAlpha(str[0]) || str[0] == '_')) { state->parse_state.prev_name_idx = state->parse_state.out_cur_idx; - state->parse_state.prev_name_length = length; + state->parse_state.prev_name_length = static_cast<unsigned int>(length); } Append(state, str, length); } } // Appends a positive decimal number to the output if appending is enabled. -static bool MaybeAppendDecimal(State *state, unsigned int val) { +static bool MaybeAppendDecimal(State *state, int val) { // Max {32-64}-bit unsigned int is 20 digits. constexpr size_t kMaxLength = 20; char buf[kMaxLength]; @@ -451,12 +453,12 @@ static bool MaybeAppendDecimal(State *state, unsigned int val) { // one-past-the-end and manipulate one character before the pointer. char *p = &buf[kMaxLength]; do { // val=0 is the only input that should write a leading zero digit. - *--p = (val % 10) + '0'; + *--p = static_cast<char>((val % 10) + '0'); val /= 10; } while (p > buf && val != 0); // 'p' landed on the last character we set. How convenient. - Append(state, p, kMaxLength - (p - buf)); + Append(state, p, kMaxLength - static_cast<size_t>(p - buf)); } return true; @@ -466,7 +468,7 @@ static bool MaybeAppendDecimal(State *state, unsigned int val) { // Returns true so that it can be placed in "if" conditions. static bool MaybeAppend(State *state, const char *const str) { if (state->parse_state.append) { - int length = StrLen(str); + size_t length = StrLen(str); MaybeAppendWithLength(state, str, length); } return true; @@ -521,10 +523,10 @@ static void MaybeCancelLastSeparator(State *state) { // Returns true if the identifier of the given length pointed to by // "mangled_cur" is anonymous namespace. -static bool IdentifierIsAnonymousNamespace(State *state, int length) { +static bool IdentifierIsAnonymousNamespace(State *state, size_t length) { // Returns true if "anon_prefix" is a proper prefix of "mangled_cur". static const char anon_prefix[] = "_GLOBAL__N_"; - return (length > static_cast<int>(sizeof(anon_prefix) - 1) && + return (length > (sizeof(anon_prefix) - 1) && StrPrefix(RemainingInput(state), anon_prefix)); } @@ -542,12 +544,13 @@ static bool ParseUnnamedTypeName(State *state); static bool ParseNumber(State *state, int *number_out); static bool ParseFloatNumber(State *state); static bool ParseSeqId(State *state); -static bool ParseIdentifier(State *state, int length); +static bool ParseIdentifier(State *state, size_t length); static bool ParseOperatorName(State *state, int *arity); static bool ParseSpecialName(State *state); static bool ParseCallOffset(State *state); static bool ParseNVOffset(State *state); static bool ParseVOffset(State *state); +static bool ParseAbiTags(State *state); static bool ParseCtorDtorName(State *state); static bool ParseDecltype(State *state); static bool ParseType(State *state); @@ -601,7 +604,7 @@ static bool ParseSubstitution(State *state, bool accept_std); // // Reference: // - Itanium C++ ABI -// <https://mentorembedded.github.io/cxx-abi/abi.html#mangling> +// <https://itanium-cxx-abi.github.io/cxx-abi/abi.html#mangling> // <mangled-name> ::= _Z <encoding> static bool ParseMangledName(State *state) { @@ -741,17 +744,42 @@ static bool ParsePrefix(State *state) { return true; } -// <unqualified-name> ::= <operator-name> -// ::= <ctor-dtor-name> -// ::= <source-name> -// ::= <local-source-name> // GCC extension; see below. -// ::= <unnamed-type-name> +// <unqualified-name> ::= <operator-name> [<abi-tags>] +// ::= <ctor-dtor-name> [<abi-tags>] +// ::= <source-name> [<abi-tags>] +// ::= <local-source-name> [<abi-tags>] +// ::= <unnamed-type-name> [<abi-tags>] +// +// <local-source-name> is a GCC extension; see below. static bool ParseUnqualifiedName(State *state) { ComplexityGuard guard(state); if (guard.IsTooComplex()) return false; - return (ParseOperatorName(state, nullptr) || ParseCtorDtorName(state) || - ParseSourceName(state) || ParseLocalSourceName(state) || - ParseUnnamedTypeName(state)); + if (ParseOperatorName(state, nullptr) || ParseCtorDtorName(state) || + ParseSourceName(state) || ParseLocalSourceName(state) || + ParseUnnamedTypeName(state)) { + return ParseAbiTags(state); + } + return false; +} + +// <abi-tags> ::= <abi-tag> [<abi-tags>] +// <abi-tag> ::= B <source-name> +static bool ParseAbiTags(State *state) { + ComplexityGuard guard(state); + if (guard.IsTooComplex()) return false; + + while (ParseOneCharToken(state, 'B')) { + ParseState copy = state->parse_state; + MaybeAppend(state, "[abi:"); + + if (!ParseSourceName(state)) { + state->parse_state = copy; + return false; + } + MaybeAppend(state, "]"); + } + + return true; } // <source-name> ::= <positive length number> <identifier> @@ -760,7 +788,8 @@ static bool ParseSourceName(State *state) { if (guard.IsTooComplex()) return false; ParseState copy = state->parse_state; int length = -1; - if (ParseNumber(state, &length) && ParseIdentifier(state, length)) { + if (ParseNumber(state, &length) && + ParseIdentifier(state, static_cast<size_t>(length))) { return true; } state->parse_state = copy; @@ -838,7 +867,7 @@ static bool ParseNumber(State *state, int *number_out) { uint64_t number = 0; for (; *p != '\0'; ++p) { if (IsDigit(*p)) { - number = number * 10 + (*p - '0'); + number = number * 10 + static_cast<uint64_t>(*p - '0'); } else { break; } @@ -853,7 +882,7 @@ static bool ParseNumber(State *state, int *number_out) { state->parse_state.mangled_idx += p - RemainingInput(state); if (number_out != nullptr) { // Note: possibly truncate "number". - *number_out = number; + *number_out = static_cast<int>(number); } return true; } @@ -897,10 +926,10 @@ static bool ParseSeqId(State *state) { } // <identifier> ::= <unqualified source code identifier> (of given length) -static bool ParseIdentifier(State *state, int length) { +static bool ParseIdentifier(State *state, size_t length) { ComplexityGuard guard(state); if (guard.IsTooComplex()) return false; - if (length < 0 || !AtLeastNumCharsRemaining(RemainingInput(state), length)) { + if (!AtLeastNumCharsRemaining(RemainingInput(state), length)) { return false; } if (IdentifierIsAnonymousNamespace(state, length)) { @@ -1947,7 +1976,7 @@ static bool Overflowed(const State *state) { } // The demangler entry point. -bool Demangle(const char *mangled, char *out, int out_size) { +bool Demangle(const char* mangled, char* out, size_t out_size) { State state; InitState(&state, mangled, out, out_size); return ParseTopLevelMangledName(&state) && !Overflowed(&state) && diff --git a/absl/debugging/internal/demangle.h b/absl/debugging/internal/demangle.h index c314d9bc..e1f15698 100644 --- a/absl/debugging/internal/demangle.h +++ b/absl/debugging/internal/demangle.h @@ -62,7 +62,7 @@ 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, int out_size); +bool Demangle(const char* mangled, char* out, size_t out_size); } // namespace debugging_internal ABSL_NAMESPACE_END diff --git a/absl/debugging/internal/demangle_test.cc b/absl/debugging/internal/demangle_test.cc index 6b142902..8463a2b7 100644 --- a/absl/debugging/internal/demangle_test.cc +++ b/absl/debugging/internal/demangle_test.cc @@ -102,6 +102,30 @@ TEST(Demangle, Clones) { EXPECT_FALSE(Demangle("_ZL3Foov.isra.2.constprop.", tmp, sizeof(tmp))); } +// Test the GNU abi_tag extension. +TEST(Demangle, AbiTags) { + char tmp[80]; + + // Mangled name generated via: + // struct [[gnu::abi_tag("abc")]] A{}; + // A a; + EXPECT_TRUE(Demangle("_Z1aB3abc", tmp, sizeof(tmp))); + EXPECT_STREQ("a[abi:abc]", tmp); + + // Mangled name generated via: + // struct B { + // B [[gnu::abi_tag("xyz")]] (){}; + // }; + // B b; + EXPECT_TRUE(Demangle("_ZN1BC2B3xyzEv", tmp, sizeof(tmp))); + EXPECT_STREQ("B::B[abi:xyz]()", tmp); + + // Mangled name generated via: + // [[gnu::abi_tag("foo", "bar")]] void C() {} + EXPECT_TRUE(Demangle("_Z1CB3barB3foov", tmp, sizeof(tmp))); + EXPECT_STREQ("C[abi:bar][abi:foo]()", tmp); +} + // Tests that verify that Demangle footprint is within some limit. // They are not to be run under sanitizers as the sanitizers increase // stack consumption by about 4x. diff --git a/absl/debugging/internal/elf_mem_image.cc b/absl/debugging/internal/elf_mem_image.cc index a9d66714..42dcd3cd 100644 --- a/absl/debugging/internal/elf_mem_image.cc +++ b/absl/debugging/internal/elf_mem_image.cc @@ -91,7 +91,7 @@ int ElfMemImage::GetNumSymbols() const { return 0; } // See http://www.caldera.com/developers/gabi/latest/ch5.dynamic.html#hash - return hash_[1]; + return static_cast<int>(hash_[1]); } const ElfW(Sym) *ElfMemImage::GetDynsym(int index) const { @@ -105,11 +105,9 @@ const ElfW(Versym) *ElfMemImage::GetVersym(int index) const { } const ElfW(Phdr) *ElfMemImage::GetPhdr(int index) const { - ABSL_RAW_CHECK(index < ehdr_->e_phnum, "index out of range"); - return GetTableElement<ElfW(Phdr)>(ehdr_, - ehdr_->e_phoff, - ehdr_->e_phentsize, - index); + ABSL_RAW_CHECK(index >= 0 && index < ehdr_->e_phnum, "index out of range"); + return GetTableElement<ElfW(Phdr)>(ehdr_, ehdr_->e_phoff, ehdr_->e_phentsize, + static_cast<size_t>(index)); } const char *ElfMemImage::GetDynstr(ElfW(Word) offset) const { @@ -159,7 +157,8 @@ void ElfMemImage::Init(const void *base) { hash_ = nullptr; strsize_ = 0; verdefnum_ = 0; - link_base_ = ~0L; // Sentinel: PT_LOAD .p_vaddr can't possibly be this. + // Sentinel: PT_LOAD .p_vaddr can't possibly be this. + link_base_ = ~ElfW(Addr){0}; // NOLINT(readability/braces) if (!base) { return; } @@ -218,11 +217,11 @@ void ElfMemImage::Init(const void *base) { } ptrdiff_t relocation = base_as_char - reinterpret_cast<const char *>(link_base_); - ElfW(Dyn) *dynamic_entry = - reinterpret_cast<ElfW(Dyn) *>(dynamic_program_header->p_vaddr + - relocation); + ElfW(Dyn)* dynamic_entry = reinterpret_cast<ElfW(Dyn)*>( + static_cast<intptr_t>(dynamic_program_header->p_vaddr) + relocation); for (; dynamic_entry->d_tag != DT_NULL; ++dynamic_entry) { - const auto value = dynamic_entry->d_un.d_val + relocation; + const auto value = + static_cast<intptr_t>(dynamic_entry->d_un.d_val) + relocation; switch (dynamic_entry->d_tag) { case DT_HASH: hash_ = reinterpret_cast<ElfW(Word) *>(value); @@ -240,10 +239,10 @@ void ElfMemImage::Init(const void *base) { verdef_ = reinterpret_cast<ElfW(Verdef) *>(value); break; case DT_VERDEFNUM: - verdefnum_ = dynamic_entry->d_un.d_val; + verdefnum_ = static_cast<size_t>(dynamic_entry->d_un.d_val); break; case DT_STRSZ: - strsize_ = dynamic_entry->d_un.d_val; + strsize_ = static_cast<size_t>(dynamic_entry->d_un.d_val); break; default: // Unrecognized entries explicitly ignored. diff --git a/absl/debugging/internal/examine_stack.cc b/absl/debugging/internal/examine_stack.cc index 5bdd341e..57863228 100644 --- a/absl/debugging/internal/examine_stack.cc +++ b/absl/debugging/internal/examine_stack.cc @@ -278,13 +278,14 @@ void DumpStackTrace(int min_dropped_frames, int max_num_frames, void* stack_buf[kDefaultDumpStackFramesLimit]; void** stack = stack_buf; int num_stack = kDefaultDumpStackFramesLimit; - int allocated_bytes = 0; + size_t allocated_bytes = 0; if (num_stack >= max_num_frames) { // User requested fewer frames than we already have space for. num_stack = max_num_frames; } else { - const size_t needed_bytes = max_num_frames * sizeof(stack[0]); + const size_t needed_bytes = + static_cast<size_t>(max_num_frames) * sizeof(stack[0]); void* p = Allocate(needed_bytes); if (p != nullptr) { // We got the space. num_stack = max_num_frames; @@ -293,12 +294,13 @@ void DumpStackTrace(int min_dropped_frames, int max_num_frames, } } - size_t depth = absl::GetStackTrace(stack, num_stack, min_dropped_frames + 1); - for (size_t i = 0; i < depth; i++) { + int depth = absl::GetStackTrace(stack, num_stack, min_dropped_frames + 1); + for (int i = 0; i < depth; i++) { if (symbolize_stacktrace) { - DumpPCAndSymbol(writer, writer_arg, stack[i], " "); + DumpPCAndSymbol(writer, writer_arg, stack[static_cast<size_t>(i)], + " "); } else { - DumpPC(writer, writer_arg, stack[i], " "); + DumpPC(writer, writer_arg, stack[static_cast<size_t>(i)], " "); } } diff --git a/absl/debugging/internal/stacktrace_aarch64-inl.inc b/absl/debugging/internal/stacktrace_aarch64-inl.inc index 4f9db9d6..71cdaf09 100644 --- a/absl/debugging/internal/stacktrace_aarch64-inl.inc +++ b/absl/debugging/internal/stacktrace_aarch64-inl.inc @@ -19,7 +19,7 @@ #include "absl/debugging/internal/vdso_support.h" // a no-op on non-elf or non-glibc systems #include "absl/debugging/stacktrace.h" -static const uintptr_t kUnknownFrameSize = 0; +static const size_t kUnknownFrameSize = 0; #if defined(__linux__) // Returns the address of the VDSO __kernel_rt_sigreturn function, if present. @@ -65,11 +65,12 @@ static const unsigned char* GetKernelRtSigreturnAddress() { // Compute the size of a stack frame in [low..high). We assume that // low < high. Return size of kUnknownFrameSize. template<typename T> -static inline uintptr_t ComputeStackFrameSize(const T* low, - const T* high) { +static inline size_t ComputeStackFrameSize(const T* low, + const T* high) { const char* low_char_ptr = reinterpret_cast<const char *>(low); const char* high_char_ptr = reinterpret_cast<const char *>(high); - return low < high ? high_char_ptr - low_char_ptr : kUnknownFrameSize; + return low < high ? static_cast<size_t>(high_char_ptr - low_char_ptr) + : kUnknownFrameSize; } // Given a pointer to a stack frame, locate and return the calling @@ -110,15 +111,15 @@ static void **NextStackFrame(void **old_frame_pointer, const void *uc) { } #endif - // aarch64 ABI requires stack pointer to be 16-byte-aligned. - if ((reinterpret_cast<uintptr_t>(new_frame_pointer) & 15) != 0) + // The frame pointer should be 8-byte aligned. + 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) { - const uintptr_t max_size = STRICT_UNWINDING ? 100000 : 1000000; - const uintptr_t frame_size = + const size_t max_size = STRICT_UNWINDING ? 100000 : 1000000; + const size_t frame_size = ComputeStackFrameSize(old_frame_pointer, new_frame_pointer); if (frame_size == kUnknownFrameSize || frame_size > max_size) return nullptr; @@ -165,7 +166,8 @@ static int UnwindImpl(void** result, int* sizes, int max_depth, int skip_count, } else { result[n] = prev_return_address; if (IS_STACK_FRAMES) { - sizes[n] = ComputeStackFrameSize(frame_pointer, next_frame_pointer); + sizes[n] = static_cast<int>( + ComputeStackFrameSize(frame_pointer, next_frame_pointer)); } n++; } diff --git a/absl/debugging/internal/stacktrace_generic-inl.inc b/absl/debugging/internal/stacktrace_generic-inl.inc index b2792a1f..5fa169a7 100644 --- a/absl/debugging/internal/stacktrace_generic-inl.inc +++ b/absl/debugging/internal/stacktrace_generic-inl.inc @@ -80,7 +80,7 @@ static int UnwindImpl(void** result, int* sizes, int max_depth, int skip_count, if (IS_STACK_FRAMES) { // No implementation for finding out the stack frame sizes yet. - memset(sizes, 0, sizeof(*sizes) * result_count); + memset(sizes, 0, sizeof(*sizes) * static_cast<size_t>(result_count)); } if (min_dropped_frames != nullptr) { if (size - skip_count - max_depth > 0) { diff --git a/absl/debugging/internal/stacktrace_riscv-inl.inc b/absl/debugging/internal/stacktrace_riscv-inl.inc index 7123b71b..20183fa3 100644 --- a/absl/debugging/internal/stacktrace_riscv-inl.inc +++ b/absl/debugging/internal/stacktrace_riscv-inl.inc @@ -30,56 +30,14 @@ #include <cassert> #include <cstdint> #include <iostream> +#include <limits> +#include <utility> #include "absl/base/attributes.h" -#include "absl/debugging/internal/address_is_readable.h" -#include "absl/debugging/internal/vdso_support.h" #include "absl/debugging/stacktrace.h" static const uintptr_t kUnknownFrameSize = 0; -#if defined(__linux__) -// Returns the address of the VDSO __kernel_rt_sigreturn function, if present. -static const unsigned char *GetKernelRtSigreturnAddress() { - constexpr uintptr_t kImpossibleAddress = 0; - ABSL_CONST_INIT static std::atomic<uintptr_t> memoized(kImpossibleAddress); - uintptr_t address = memoized.load(std::memory_order_relaxed); - if (address != kImpossibleAddress) { - return reinterpret_cast<const unsigned char *>(address); - } - - address = reinterpret_cast<uintptr_t>(nullptr); - -#if ABSL_HAVE_VDSO_SUPPORT - absl::debugging_internal::VDSOSupport vdso; - if (vdso.IsPresent()) { - absl::debugging_internal::VDSOSupport::SymbolInfo symbol_info; - // Symbol versioning pulled from arch/riscv/kernel/vdso/vdso.lds at v5.10. - auto lookup = [&](int type) { - return vdso.LookupSymbol("__vdso_rt_sigreturn", "LINUX_4.15", type, - &symbol_info); - }; - if ((!lookup(STT_FUNC) && !lookup(STT_NOTYPE)) || - symbol_info.address == nullptr) { - // Unexpected: VDSO is present, yet the expected symbol is missing or - // null. - assert(false && "VDSO is present, but doesn't have expected symbol"); - } else { - if (reinterpret_cast<uintptr_t>(symbol_info.address) != - kImpossibleAddress) { - address = reinterpret_cast<uintptr_t>(symbol_info.address); - } else { - assert(false && "VDSO returned invalid address"); - } - } - } -#endif - - memoized.store(address, std::memory_order_relaxed); - return reinterpret_cast<const unsigned char *>(address); -} -#endif // __linux__ - // Compute the size of a stack frame in [low..high). We assume that low < high. // Return size of kUnknownFrameSize. template <typename T> @@ -96,7 +54,8 @@ static inline uintptr_t ComputeStackFrameSize(const T *low, const T *high) { 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) { +static void ** NextStackFrame(void **old_frame_pointer, const void *uc, + const std::pair<size_t, size_t> range) { // . // . // . @@ -114,55 +73,43 @@ static void ** NextStackFrame(void **old_frame_pointer, const void *uc) { // $sp ->| ... | // +----------------+ void **new_frame_pointer = reinterpret_cast<void **>(old_frame_pointer[-2]); - bool check_frame_size = true; - -#if defined(__linux__) - if (WITH_CONTEXT && uc != nullptr) { - // Check to see if next frame's return address is __kernel_rt_sigreturn. - if (old_frame_pointer[-1] == GetKernelRtSigreturnAddress()) { - const ucontext_t *ucv = static_cast<const ucontext_t *>(uc); - // old_frame_pointer is not suitable for unwinding, look at ucontext to - // discover frame pointer before signal. - // - // RISCV ELF psABI has the frame pointer at x8/fp/s0. - // -- RISCV psABI Table 18.2 - void **const pre_signal_frame_pointer = - reinterpret_cast<void **>(ucv->uc_mcontext.__gregs[8]); - - // Check the alleged frame pointer is actually readable. This is to - // prevent "double fault" in case we hit the first fault due to stack - // corruption. - if (!absl::debugging_internal::AddressIsReadable( - pre_signal_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 an - // alterate stack for signals. - check_frame_size = false; - } - } -#endif + uintptr_t frame_pointer = reinterpret_cast<uintptr_t>(new_frame_pointer); // The RISCV ELF psABI mandates that the stack pointer is always 16-byte // aligned. - // FIXME(abdulras) this doesn't hold for ILP32E which only mandates a 4-byte + // TODO(#1236) this doesn't hold for ILP32E which only mandates a 4-byte // alignment. - if ((reinterpret_cast<uintptr_t>(new_frame_pointer) & 15) != 0) + if (frame_pointer & 15) return nullptr; + // If the new frame pointer matches the signal context, avoid terminating + // early to deal with alternate signal stacks. + if (WITH_CONTEXT) + if (const ucontext_t *ucv = static_cast<const ucontext_t *>(uc)) + // RISCV ELF psABI has the frame pointer at x8/fp/s0. + // -- RISCV psABI Table 18.2 + if (ucv->uc_mcontext.__gregs[8] == frame_pointer) + return new_frame_pointer; + // Check frame size. In strict mode, we assume frames to be under 100,000 // bytes. In non-strict mode, we relax the limit to 1MB. - if (check_frame_size) { - const uintptr_t max_size = STRICT_UNWINDING ? 100000 : 1000000; - const uintptr_t frame_size = - ComputeStackFrameSize(old_frame_pointer, new_frame_pointer); - if (frame_size == kUnknownFrameSize || frame_size > max_size) + const uintptr_t max_size = STRICT_UNWINDING ? 100000 : 1000000; + const uintptr_t frame_size = + ComputeStackFrameSize(old_frame_pointer, new_frame_pointer); + if (frame_size == kUnknownFrameSize) { + if (STRICT_UNWINDING) + return nullptr; + + // In non-strict mode permit non-contiguous stacks (e.g. alternate signal + // frame handling). + if (reinterpret_cast<uintptr_t>(new_frame_pointer) < range.first || + reinterpret_cast<uintptr_t>(new_frame_pointer) > range.second) return nullptr; } + if (frame_size > max_size) + return nullptr; + return new_frame_pointer; } @@ -180,6 +127,12 @@ static int UnwindImpl(void **result, int *sizes, int max_depth, int skip_count, #error reading stack pointer not yet supported on this platform #endif + std::pair<size_t, size_t> stack = { + // assume that the first page is not the stack. + static_cast<size_t>(sysconf(_SC_PAGESIZE)), + std::numeric_limits<size_t>::max() - sizeof(void *) + }; + int n = 0; void *return_address = nullptr; while (frame_pointer && n < max_depth) { @@ -190,7 +143,8 @@ static int UnwindImpl(void **result, int *sizes, int max_depth, int skip_count, // 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); + NextStackFrame<!IS_STACK_FRAMES, IS_WITH_CONTEXT>(frame_pointer, ucp, + stack); if (skip_count > 0) { skip_count--; @@ -217,7 +171,8 @@ static int UnwindImpl(void **result, int *sizes, int max_depth, int skip_count, num_dropped_frames++; } frame_pointer = - NextStackFrame<!IS_STACK_FRAMES, IS_WITH_CONTEXT>(frame_pointer, ucp); + NextStackFrame<!IS_STACK_FRAMES, IS_WITH_CONTEXT>(frame_pointer, ucp, + stack); } *min_dropped_frames = num_dropped_frames; } diff --git a/absl/debugging/internal/stacktrace_win32-inl.inc b/absl/debugging/internal/stacktrace_win32-inl.inc index 1c666c8b..ef2b973e 100644 --- a/absl/debugging/internal/stacktrace_win32-inl.inc +++ b/absl/debugging/internal/stacktrace_win32-inl.inc @@ -63,11 +63,12 @@ static RtlCaptureStackBackTrace_Function* const RtlCaptureStackBackTrace_fn = template <bool IS_STACK_FRAMES, bool IS_WITH_CONTEXT> static int UnwindImpl(void** result, int* sizes, int max_depth, int skip_count, const void*, int* min_dropped_frames) { - int n = 0; - if (!RtlCaptureStackBackTrace_fn) { - // can't find a stacktrace with no function to call + USHORT n = 0; + if (!RtlCaptureStackBackTrace_fn || skip_count < 0 || max_depth < 0) { + // can't get a stacktrace with no function/invalid args } else { - n = (int)RtlCaptureStackBackTrace_fn(skip_count + 2, max_depth, result, 0); + n = RtlCaptureStackBackTrace_fn(static_cast<ULONG>(skip_count) + 2, + static_cast<ULONG>(max_depth), result, 0); } if (IS_STACK_FRAMES) { // No implementation for finding out the stack frame sizes yet. diff --git a/absl/debugging/internal/stacktrace_x86-inl.inc b/absl/debugging/internal/stacktrace_x86-inl.inc index 1b5d8235..7b26464e 100644 --- a/absl/debugging/internal/stacktrace_x86-inl.inc +++ b/absl/debugging/internal/stacktrace_x86-inl.inc @@ -29,14 +29,13 @@ #include <cstdint> #include <limits> +#include "absl/base/attributes.h" #include "absl/base/macros.h" #include "absl/base/port.h" #include "absl/debugging/internal/address_is_readable.h" #include "absl/debugging/internal/vdso_support.h" // a no-op on non-elf or non-glibc systems #include "absl/debugging/stacktrace.h" -#include "absl/base/internal/raw_logging.h" - using absl::debugging_internal::AddressIsReadable; #if defined(__linux__) && defined(__i386__) @@ -113,6 +112,10 @@ static int CountPushInstructions(const unsigned char *const addr) { // Assume stack frames larger than 100,000 bytes are bogus. static const int kMaxFrameBytes = 100000; +// 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 *); // Returns the stack frame pointer from signal context, 0 if unknown. // vuc is a ucontext_t *. We use void* to avoid the use @@ -140,13 +143,14 @@ static uintptr_t GetFP(const void *vuc) { // TODO(bcmills): -momit-leaf-frame-pointer is currently the default // behavior when building with clang. Talk to the C++ toolchain team about // fixing that. - if (bp >= sp && bp - sp <= kMaxFrameBytes) return bp; + if (bp >= sp && bp - sp <= kMaxFrameBytes) + return static_cast<uintptr_t>(bp); // If bp isn't a plausible frame pointer, return the stack pointer instead. // If we're lucky, it points to the start of a stack frame; otherwise, we'll // get one frame of garbage in the stack trace and fail the sanity check on // the next iteration. - return sp; + return static_cast<uintptr_t>(sp); } #endif return 0; @@ -258,8 +262,26 @@ static void **NextStackFrame(void **old_fp, const void *uc, // With the stack growing downwards, older stack frame must be // at a greater address that the current one. if (new_fp_u <= old_fp_u) return nullptr; - if (new_fp_u - old_fp_u > kMaxFrameBytes) return nullptr; + // If we get a very large frame size, it may be an indication that we + // guessed frame pointers incorrectly and now risk a paging fault + // dereferencing a wrong frame pointer. Or maybe not because large frames + // are possible as well. The main stack is assumed to be readable, + // so we assume the large frame is legit if we know the real stack bounds + // and are within the stack. + if (new_fp_u - old_fp_u > kMaxFrameBytes) { + if (stack_high < kUnknownStackEnd && + static_cast<size_t>(getpagesize()) < stack_low) { + // Stack bounds are known. + if (!(stack_low < new_fp_u && new_fp_u <= stack_high)) { + // new_fp_u is not within the known stack. + return nullptr; + } + } else { + // Stack bounds are unknown, prefer truncated stack to possible crash. + return nullptr; + } + } if (stack_low < old_fp_u && old_fp_u <= stack_high) { // Old BP was in the expected stack region... if (!(stack_low < new_fp_u && new_fp_u <= stack_high)) { @@ -310,8 +332,9 @@ static int UnwindImpl(void **result, int *sizes, int max_depth, int skip_count, int n = 0; void **fp = reinterpret_cast<void **>(__builtin_frame_address(0)); - size_t stack_low = getpagesize(); // Assume that the first page is not stack. - size_t stack_high = std::numeric_limits<size_t>::max() - sizeof(void *); + // Assume that the first page is not stack. + size_t stack_low = static_cast<size_t>(getpagesize()); + size_t stack_high = kUnknownStackEnd; while (fp && n < max_depth) { if (*(fp + 1) == reinterpret_cast<void *>(0)) { @@ -327,7 +350,9 @@ static int UnwindImpl(void **result, int *sizes, int max_depth, int skip_count, result[n] = *(fp + 1); if (IS_STACK_FRAMES) { if (next_fp > fp) { - sizes[n] = (uintptr_t)next_fp - (uintptr_t)fp; + sizes[n] = static_cast<int>( + reinterpret_cast<uintptr_t>(next_fp) - + reinterpret_cast<uintptr_t>(fp)); } else { // A frame-size of 0 is used to indicate unknown frame size. sizes[n] = 0; diff --git a/absl/debugging/internal/vdso_support.cc b/absl/debugging/internal/vdso_support.cc index 40eb055f..8a588eaf 100644 --- a/absl/debugging/internal/vdso_support.cc +++ b/absl/debugging/internal/vdso_support.cc @@ -193,8 +193,9 @@ long VDSOSupport::InitAndGetCPU(unsigned *cpu, // NOLINT(runtime/int) ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY int GetCPU() { unsigned cpu; - int ret_code = (*VDSOSupport::getcpu_fn_)(&cpu, nullptr, nullptr); - return ret_code == 0 ? cpu : ret_code; + long ret_code = // NOLINT(runtime/int) + (*VDSOSupport::getcpu_fn_)(&cpu, nullptr, nullptr); + return ret_code == 0 ? static_cast<int>(cpu) : static_cast<int>(ret_code); } } // namespace debugging_internal diff --git a/absl/debugging/leak_check.h b/absl/debugging/leak_check.h index eff162f6..6bd79406 100644 --- a/absl/debugging/leak_check.h +++ b/absl/debugging/leak_check.h @@ -37,7 +37,7 @@ // not also use AddressSanitizer). To use the mode, simply pass // `-fsanitize=leak` to both the compiler and linker. Since GCC does not // currently provide a way of detecting this mode at compile-time, GCC users -// must also pass -DLEAK_SANIITIZER to the compiler. An example Bazel command +// must also pass -DLEAK_SANITIZER to the compiler. An example Bazel command // could be // // $ bazel test --copt=-DLEAK_SANITIZER --copt=-fsanitize=leak diff --git a/absl/debugging/stacktrace_test.cc b/absl/debugging/stacktrace_test.cc new file mode 100644 index 00000000..78ce7ad0 --- /dev/null +++ b/absl/debugging/stacktrace_test.cc @@ -0,0 +1,47 @@ +// 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/debugging/stacktrace.h" + +#include "gtest/gtest.h" +#include "absl/base/macros.h" +#include "absl/base/optimization.h" + +namespace { + +// This test is currently only known to pass on linux/x86_64. +#if defined(__linux__) && defined(__x86_64__) +ABSL_ATTRIBUTE_NOINLINE void Unwind(void* p) { + ABSL_ATTRIBUTE_UNUSED static void* volatile sink = p; + constexpr int kSize = 16; + void* stack[kSize]; + int frames[kSize]; + absl::GetStackTrace(stack, kSize, 0); + absl::GetStackFrames(stack, frames, kSize, 0); +} + +ABSL_ATTRIBUTE_NOINLINE void HugeFrame() { + char buffer[1 << 20]; + Unwind(buffer); + ABSL_BLOCK_TAIL_CALL_OPTIMIZATION(); +} + +TEST(StackTrace, HugeFrame) { + // Ensure that the unwinder is not confused by very large stack frames. + HugeFrame(); + ABSL_BLOCK_TAIL_CALL_OPTIMIZATION(); +} +#endif + +} // namespace diff --git a/absl/debugging/symbolize_darwin.inc b/absl/debugging/symbolize_darwin.inc index 443ce9ef..cf63d191 100644 --- a/absl/debugging/symbolize_darwin.inc +++ b/absl/debugging/symbolize_darwin.inc @@ -83,13 +83,14 @@ bool Symbolize(const void* pc, char* out, int out_size) { memmove(out, tmp_buf, len + 1); } } else { - strncpy(out, symbol.c_str(), out_size); + strncpy(out, symbol.c_str(), static_cast<size_t>(out_size)); } if (out[out_size - 1] != '\0') { // strncpy() does not '\0' terminate when it truncates. static constexpr char kEllipsis[] = "..."; - int ellipsis_size = std::min<int>(sizeof(kEllipsis) - 1, out_size - 1); + size_t ellipsis_size = + std::min(sizeof(kEllipsis) - 1, static_cast<size_t>(out_size) - 1); memcpy(out + out_size - ellipsis_size - 1, kEllipsis, ellipsis_size); out[out_size - 1] = '\0'; } diff --git a/absl/debugging/symbolize_elf.inc b/absl/debugging/symbolize_elf.inc index 9bfdd915..ffb4eecf 100644 --- a/absl/debugging/symbolize_elf.inc +++ b/absl/debugging/symbolize_elf.inc @@ -205,7 +205,8 @@ struct ObjFile { // PT_LOAD program header describing executable code. // Normally we expect just one, but SWIFT binaries have two. - std::array<ElfW(Phdr), 2> phdr; + // CUDA binaries have 3 (see cr/473913254 description). + std::array<ElfW(Phdr), 4> phdr; }; // Build 4-way associative cache for symbols. Within each cache line, symbols @@ -252,21 +253,21 @@ class AddrMap { public: AddrMap() : size_(0), allocated_(0), obj_(nullptr) {} ~AddrMap() { base_internal::LowLevelAlloc::Free(obj_); } - int Size() const { return size_; } - ObjFile *At(int i) { return &obj_[i]; } + size_t Size() const { return size_; } + ObjFile *At(size_t i) { return &obj_[i]; } ObjFile *Add(); void Clear(); private: - int size_; // count of valid elements (<= allocated_) - int allocated_; // count of allocated elements - ObjFile *obj_; // array of allocated_ elements + size_t size_; // count of valid elements (<= allocated_) + size_t allocated_; // count of allocated elements + ObjFile *obj_; // array of allocated_ elements AddrMap(const AddrMap &) = delete; AddrMap &operator=(const AddrMap &) = delete; }; void AddrMap::Clear() { - for (int i = 0; i != size_; i++) { + for (size_t i = 0; i != size_; i++) { At(i)->~ObjFile(); } size_ = 0; @@ -274,7 +275,7 @@ void AddrMap::Clear() { ObjFile *AddrMap::Add() { if (size_ == allocated_) { - int new_allocated = allocated_ * 2 + 50; + size_t new_allocated = allocated_ * 2 + 50; ObjFile *new_obj_ = static_cast<ObjFile *>(base_internal::LowLevelAlloc::AllocWithArena( new_allocated * sizeof(*new_obj_), SigSafeArena())); @@ -300,7 +301,7 @@ class Symbolizer { private: char *CopyString(const char *s) { - int len = strlen(s); + size_t len = strlen(s); char *dst = static_cast<char *>( base_internal::LowLevelAlloc::AllocWithArena(len + 1, SigSafeArena())); ABSL_RAW_CHECK(dst != nullptr, "out of memory"); @@ -321,8 +322,8 @@ class Symbolizer { FindSymbolResult GetSymbolFromObjectFile(const ObjFile &obj, const void *const pc, const ptrdiff_t relocation, - char *out, int out_size, - char *tmp_buf, int tmp_buf_size); + char *out, size_t out_size, + char *tmp_buf, size_t tmp_buf_size); const char *GetUncachedSymbol(const void *pc); enum { @@ -353,11 +354,11 @@ static std::atomic<Symbolizer *> g_cached_symbolizer; } // namespace -static int SymbolizerSize() { +static size_t SymbolizerSize() { #if defined(__wasm__) || defined(__asmjs__) - int pagesize = getpagesize(); + auto pagesize = static_cast<size_t>(getpagesize()); #else - int pagesize = sysconf(_SC_PAGESIZE); + auto pagesize = static_cast<size_t>(sysconf(_SC_PAGESIZE)); #endif return ((sizeof(Symbolizer) - 1) / pagesize + 1) * pagesize; } @@ -429,7 +430,7 @@ static ssize_t ReadPersistent(int fd, void *buf, size_t count) { if (len == 0) { // Reached EOF. break; } - num_bytes += len; + num_bytes += static_cast<size_t>(len); } SAFE_ASSERT(num_bytes <= count); return static_cast<ssize_t>(num_bytes); @@ -442,8 +443,8 @@ 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, %ju, SEEK_SET) failed: errno=%d", fd, - static_cast<uintmax_t>(offset), errno); + ABSL_RAW_LOG(WARNING, "lseek(%d, %jd, SEEK_SET) failed: errno=%d", fd, + static_cast<intmax_t>(offset), errno); return -1; } return ReadPersistent(fd, buf, count); @@ -478,29 +479,37 @@ static int FileGetElfType(const int fd) { // 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, int tmp_buf_size) { + ElfW(Shdr) * out, char *tmp_buf, size_t tmp_buf_size) { ElfW(Shdr) *buf = reinterpret_cast<ElfW(Shdr) *>(tmp_buf); - const int buf_entries = tmp_buf_size / sizeof(buf[0]); - const int buf_bytes = buf_entries * sizeof(buf[0]); + const size_t buf_entries = tmp_buf_size / sizeof(buf[0]); + const size_t buf_bytes = buf_entries * sizeof(buf[0]); - for (int i = 0; i < sh_num;) { - const ssize_t num_bytes_left = (sh_num - i) * sizeof(buf[0]); - const ssize_t num_bytes_to_read = + for (size_t i = 0; static_cast<int>(i) < sh_num;) { + const size_t num_bytes_left = + (static_cast<size_t>(sh_num) - i) * sizeof(buf[0]); + const size_t num_bytes_to_read = (buf_bytes > num_bytes_left) ? num_bytes_left : buf_bytes; - const off_t offset = sh_offset + i * sizeof(buf[0]); + 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); - if (len % sizeof(buf[0]) != 0) { + if (len < 0) { ABSL_RAW_LOG( WARNING, - "Reading %zd bytes from offset %ju returned %zd which is not a " + "Reading %zu bytes from offset %ju returned %zd which is negative.", + num_bytes_to_read, static_cast<intmax_t>(offset), len); + return false; + } + if (static_cast<size_t>(len) % sizeof(buf[0]) != 0) { + ABSL_RAW_LOG( + WARNING, + "Reading %zu bytes from offset %jd returned %zd which is not a " "multiple of %zu.", - num_bytes_to_read, static_cast<uintmax_t>(offset), len, + num_bytes_to_read, static_cast<intmax_t>(offset), len, sizeof(buf[0])); return false; } - const ssize_t num_headers_in_buf = len / sizeof(buf[0]); + const size_t num_headers_in_buf = static_cast<size_t>(len) / sizeof(buf[0]); SAFE_ASSERT(num_headers_in_buf <= buf_entries); - for (int j = 0; j < num_headers_in_buf; ++j) { + for (size_t j = 0; j < num_headers_in_buf; ++j) { if (buf[j].sh_type == type) { *out = buf[j]; return true; @@ -524,8 +533,8 @@ bool ForEachSection(int fd, } ElfW(Shdr) shstrtab; - off_t shstrtab_offset = - (elf_header.e_shoff + elf_header.e_shentsize * elf_header.e_shstrndx); + 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)) { return false; } @@ -533,22 +542,23 @@ bool ForEachSection(int fd, for (int i = 0; i < elf_header.e_shnum; ++i) { ElfW(Shdr) out; off_t section_header_offset = - (elf_header.e_shoff + elf_header.e_shentsize * i); + static_cast<off_t>(elf_header.e_shoff) + elf_header.e_shentsize * i; if (!ReadFromOffsetExact(fd, &out, sizeof(out), section_header_offset)) { return false; } - off_t name_offset = shstrtab.sh_offset + out.sh_name; + 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); - if (n_read == -1) { + if (n_read < 0) { return false; } else if (n_read > kMaxSectionNameLen) { // Long read? return false; } - absl::string_view name(header_name, strnlen(header_name, n_read)); + absl::string_view name(header_name, + strnlen(header_name, static_cast<size_t>(n_read))); if (!callback(name, out)) { break; } @@ -575,19 +585,19 @@ bool GetSectionHeaderByName(int fd, const char *name, size_t name_len, } ElfW(Shdr) shstrtab; - off_t shstrtab_offset = - (elf_header.e_shoff + elf_header.e_shentsize * elf_header.e_shstrndx); + 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)) { return false; } for (int i = 0; i < elf_header.e_shnum; ++i) { off_t section_header_offset = - (elf_header.e_shoff + elf_header.e_shentsize * i); + static_cast<off_t>(elf_header.e_shoff) + elf_header.e_shentsize * i; if (!ReadFromOffsetExact(fd, out, sizeof(*out), section_header_offset)) { return false; } - off_t name_offset = shstrtab.sh_offset + out->sh_name; + 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); if (n_read < 0) { return false; @@ -645,10 +655,10 @@ static bool InSection(const void *address, const ElfW(Shdr) * section) { } static const char *ComputeOffset(const char *base, ptrdiff_t offset) { - // Note: cast to uintptr_t to avoid undefined behavior when base evaluates to + // Note: cast to intptr_t to avoid undefined behavior when base evaluates to // zero and offset is non-zero. - return reinterpret_cast<const char *>( - reinterpret_cast<uintptr_t>(base) + offset); + return reinterpret_cast<const char *>(reinterpret_cast<intptr_t>(base) + + offset); } // Read a symbol table and look for the symbol containing the @@ -661,18 +671,18 @@ 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, int out_size, + const void *const pc, const int fd, char *out, size_t out_size, ptrdiff_t relocation, const ElfW(Shdr) * strtab, const ElfW(Shdr) * symtab, - const ElfW(Shdr) * opd, char *tmp_buf, int tmp_buf_size) { + const ElfW(Shdr) * opd, char *tmp_buf, size_t tmp_buf_size) { if (symtab == nullptr) { return SYMBOL_NOT_FOUND; } // Read multiple symbols at once to save read() calls. ElfW(Sym) *buf = reinterpret_cast<ElfW(Sym) *>(tmp_buf); - const int buf_entries = tmp_buf_size / sizeof(buf[0]); + const size_t buf_entries = tmp_buf_size / sizeof(buf[0]); - const int num_symbols = symtab->sh_size / symtab->sh_entsize; + const size_t num_symbols = symtab->sh_size / symtab->sh_entsize; // On platforms using an .opd section (PowerPC & IA64), a function symbol // has the address of a function descriptor, which contains the real @@ -687,16 +697,19 @@ static ABSL_ATTRIBUTE_NOINLINE FindSymbolResult FindSymbol( ElfW(Sym) best_match; SafeMemZero(&best_match, sizeof(best_match)); bool found_match = false; - for (int i = 0; i < num_symbols;) { - off_t offset = symtab->sh_offset + i * symtab->sh_entsize; - const int num_remaining_symbols = num_symbols - i; - const int entries_in_chunk = std::min(num_remaining_symbols, buf_entries); - const int bytes_in_chunk = entries_in_chunk * sizeof(buf[0]); + for (size_t i = 0; i < num_symbols;) { + off_t offset = + static_cast<off_t>(symtab->sh_offset + i * symtab->sh_entsize); + const size_t num_remaining_symbols = num_symbols - i; + 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); - SAFE_ASSERT(len % sizeof(buf[0]) == 0); - const ssize_t num_symbols_in_buf = len / sizeof(buf[0]); + 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]); SAFE_ASSERT(num_symbols_in_buf <= entries_in_chunk); - for (int j = 0; j < num_symbols_in_buf; ++j) { + for (size_t j = 0; j < num_symbols_in_buf; ++j) { const ElfW(Sym) &symbol = buf[j]; // For a DSO, a symbol address is relocated by the loading address. @@ -713,7 +726,7 @@ static ABSL_ATTRIBUTE_NOINLINE FindSymbolResult FindSymbol( // about what encoding is being used; we just want the real start address // of the function. start_address = reinterpret_cast<const char *>( - reinterpret_cast<uintptr_t>(start_address) & ~1); + reinterpret_cast<uintptr_t>(start_address) & ~1u); #endif if (deref_function_descriptor_pointer && @@ -726,7 +739,8 @@ static ABSL_ATTRIBUTE_NOINLINE FindSymbolResult FindSymbol( // If pc is inside the .opd section, it points to a function descriptor. const size_t size = pc_in_opd ? kFunctionDescriptorSize : symbol.st_size; - const void *const end_address = ComputeOffset(start_address, size); + const void *const end_address = + ComputeOffset(start_address, static_cast<ptrdiff_t>(size)); if (symbol.st_value != 0 && // Skip null value symbols. symbol.st_shndx != 0 && // Skip undefined symbols. #ifdef STT_TLS @@ -744,16 +758,18 @@ static ABSL_ATTRIBUTE_NOINLINE FindSymbolResult FindSymbol( } if (found_match) { - const size_t off = strtab->sh_offset + best_match.st_name; + 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); if (n_read <= 0) { // This should never happen. ABSL_RAW_LOG(WARNING, - "Unable to read from fd %d at offset %zu: n_read = %zd", fd, - off, n_read); + "Unable to read from fd %d at offset %lld: n_read = %zd", fd, + static_cast<long long>(off), n_read); return SYMBOL_NOT_FOUND; } - ABSL_RAW_CHECK(n_read <= out_size, "ReadFromOffset read too much data."); + ABSL_RAW_CHECK(static_cast<size_t>(n_read) <= out_size, + "ReadFromOffset read too much data."); // strtab->sh_offset points into .strtab-like section that contains // NUL-terminated strings: '\0foo\0barbaz\0...". @@ -761,7 +777,7 @@ static ABSL_ATTRIBUTE_NOINLINE FindSymbolResult FindSymbol( // sh_offset+st_name points to the start of symbol name, but we don't know // how long the symbol is, so we try to read as much as we have space for, // and usually over-read (i.e. there is a NUL somewhere before n_read). - if (memchr(out, '\0', n_read) == nullptr) { + if (memchr(out, '\0', static_cast<size_t>(n_read)) == nullptr) { // Either out_size was too small (n_read == out_size and no NUL), or // we tried to read past the EOF (n_read < out_size) and .strtab is // corrupt (missing terminating NUL; should never happen for valid ELF). @@ -779,7 +795,7 @@ static ABSL_ATTRIBUTE_NOINLINE FindSymbolResult FindSymbol( // See FindSymbol() comment for description of return value. FindSymbolResult Symbolizer::GetSymbolFromObjectFile( const ObjFile &obj, const void *const pc, const ptrdiff_t relocation, - char *out, int out_size, char *tmp_buf, int tmp_buf_size) { + char *out, size_t out_size, char *tmp_buf, size_t tmp_buf_size) { ElfW(Shdr) symtab; ElfW(Shdr) strtab; ElfW(Shdr) opd; @@ -802,13 +818,15 @@ FindSymbolResult Symbolizer::GetSymbolFromObjectFile( // 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, - obj.elf_header.e_shoff, symbol_table_type, + 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), - obj.elf_header.e_shoff + symtab.sh_link * sizeof(symtab))) { + static_cast<off_t>(obj.elf_header.e_shoff + + symtab.sh_link * sizeof(symtab)))) { continue; } const FindSymbolResult rc = @@ -833,7 +851,7 @@ class FileDescriptor { ~FileDescriptor() { if (fd_ >= 0) { - NO_INTR(close(fd_)); + close(fd_); } } @@ -850,7 +868,7 @@ class FileDescriptor { // and snprintf(). class LineReader { public: - explicit LineReader(int fd, char *buf, int buf_len) + explicit LineReader(int fd, char *buf, size_t buf_len) : fd_(fd), buf_len_(buf_len), buf_(buf), @@ -878,12 +896,12 @@ class LineReader { bol_ = eol_ + 1; // Advance to the next line in the buffer. SAFE_ASSERT(bol_ <= eod_); // "bol_" can point to "eod_". if (!HasCompleteLine()) { - const int incomplete_line_length = eod_ - bol_; + const auto incomplete_line_length = static_cast<size_t>(eod_ - bol_); // Move the trailing incomplete line to the beginning. memmove(buf_, bol_, incomplete_line_length); // Read text from file and append it. char *const append_pos = buf_ + incomplete_line_length; - const int capacity_left = buf_len_ - incomplete_line_length; + const size_t capacity_left = buf_len_ - incomplete_line_length; const ssize_t num_bytes = ReadPersistent(fd_, append_pos, capacity_left); if (num_bytes <= 0) { // EOF or error. @@ -906,7 +924,8 @@ class LineReader { private: char *FindLineFeed() const { - return reinterpret_cast<char *>(memchr(bol_, '\n', eod_ - bol_)); + return reinterpret_cast<char *>( + memchr(bol_, '\n', static_cast<size_t>(eod_ - bol_))); } bool BufferIsEmpty() const { return buf_ == eod_; } @@ -916,7 +935,7 @@ class LineReader { } const int fd_; - const int buf_len_; + const size_t buf_len_; char *const buf_; char *bol_; char *eol_; @@ -934,7 +953,8 @@ static const char *GetHex(const char *start, const char *end, int ch = *p; if ((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'F') || (ch >= 'a' && ch <= 'f')) { - hex = (hex << 4) | (ch < 'A' ? ch - '0' : (ch & 0xF) + 9); + hex = (hex << 4) | + static_cast<uint64_t>(ch < 'A' ? ch - '0' : (ch & 0xF) + 9); } else { // Encountered the first non-hex character. break; } @@ -966,7 +986,7 @@ static bool ShouldUseMapping(const char *const flags) { static ABSL_ATTRIBUTE_NOINLINE bool ReadAddrMap( bool (*callback)(const char *filename, const void *const start_addr, const void *const end_addr, uint64_t offset, void *arg), - void *arg, void *tmp_buf, int tmp_buf_size) { + void *arg, void *tmp_buf, size_t tmp_buf_size) { // Use /proc/self/task/<pid>/maps instead of /proc/self/maps. The latter // requires kernel to stop all threads, and is significantly slower when there // are 1000s of threads. @@ -1081,10 +1101,10 @@ ObjFile *Symbolizer::FindObjFile(const void *const addr, size_t len) { } } - int lo = 0; - int hi = addr_map_.Size(); + size_t lo = 0; + size_t hi = addr_map_.Size(); while (lo < hi) { - int mid = (lo + hi) / 2; + size_t mid = (lo + hi) / 2; if (addr < addr_map_.At(mid)->end_addr) { hi = mid; } else { @@ -1106,11 +1126,11 @@ ObjFile *Symbolizer::FindObjFile(const void *const addr, size_t len) { } void Symbolizer::ClearAddrMap() { - for (int i = 0; i != addr_map_.Size(); i++) { + for (size_t i = 0; i != addr_map_.Size(); i++) { ObjFile *o = addr_map_.At(i); base_internal::LowLevelAlloc::Free(o->filename); if (o->fd >= 0) { - NO_INTR(close(o->fd)); + close(o->fd); } } addr_map_.Clear(); @@ -1126,7 +1146,7 @@ bool Symbolizer::RegisterObjFile(const char *filename, // Files are supposed to be added in the increasing address order. Make // sure that's the case. - int addr_map_size = impl->addr_map_.Size(); + size_t addr_map_size = impl->addr_map_.Size(); if (addr_map_size != 0) { ObjFile *old = impl->addr_map_.At(addr_map_size - 1); if (old->end_addr > end_addr) { @@ -1170,12 +1190,12 @@ bool Symbolizer::RegisterObjFile(const char *filename, // where the input symbol is demangled in-place. // To keep stack consumption low, we would like this function to not // get inlined. -static ABSL_ATTRIBUTE_NOINLINE void DemangleInplace(char *out, int out_size, +static ABSL_ATTRIBUTE_NOINLINE void DemangleInplace(char *out, size_t out_size, char *tmp_buf, - int tmp_buf_size) { + size_t tmp_buf_size) { if (Demangle(out, tmp_buf, tmp_buf_size)) { // Demangling succeeded. Copy to out if the space allows. - int len = strlen(tmp_buf); + size_t len = strlen(tmp_buf); if (len + 1 <= out_size) { // +1 for '\0'. SAFE_ASSERT(len < tmp_buf_size); memmove(out, tmp_buf, len + 1); @@ -1218,7 +1238,8 @@ const char *Symbolizer::InsertSymbolInCache(const void *const pc, SymbolCacheLine *line = GetCacheLine(pc); uint32_t max_age = 0; - int oldest_index = -1; + size_t oldest_index = 0; + bool found_oldest_index = false; for (size_t i = 0; i < ABSL_ARRAYSIZE(line->pc); ++i) { if (line->pc[i] == nullptr) { AgeSymbols(line); @@ -1230,11 +1251,12 @@ const char *Symbolizer::InsertSymbolInCache(const void *const pc, if (line->age[i] >= max_age) { max_age = line->age[i]; oldest_index = i; + found_oldest_index = true; } } AgeSymbols(line); - ABSL_RAW_CHECK(oldest_index >= 0, "Corrupt cache"); + ABSL_RAW_CHECK(found_oldest_index, "Corrupt cache"); base_internal::LowLevelAlloc::Free(line->name[oldest_index]); line->pc[oldest_index] = pc; line->name[oldest_index] = CopyString(name); @@ -1303,7 +1325,7 @@ static bool MaybeInitializeObjFile(ObjFile *obj) { } const int phnum = obj->elf_header.e_phnum; const int phentsize = obj->elf_header.e_phentsize; - size_t phoff = obj->elf_header.e_phoff; + auto phoff = static_cast<off_t>(obj->elf_header.e_phoff); size_t num_executable_load_segments = 0; for (int j = 0; j < phnum; j++) { ElfW(Phdr) phdr; @@ -1321,8 +1343,9 @@ static bool MaybeInitializeObjFile(ObjFile *obj) { if (num_executable_load_segments < obj->phdr.size()) { memcpy(&obj->phdr[num_executable_load_segments++], &phdr, sizeof(phdr)); } else { - ABSL_RAW_LOG(WARNING, "%s: too many executable LOAD segments", - obj->filename); + ABSL_RAW_LOG( + WARNING, "%s: too many executable LOAD segments: %zu >= %zu", + obj->filename, num_executable_load_segments, obj->phdr.size()); break; } } @@ -1354,7 +1377,7 @@ const char *Symbolizer::GetUncachedSymbol(const void *pc) { // // For obj->offset > 0, adjust the relocation since a mapping at offset // X in the file will have a start address of [true relocation]+X. - relocation = start_addr - obj->offset; + relocation = static_cast<ptrdiff_t>(start_addr - obj->offset); // Note: some binaries have multiple "rx" LOAD segments. We must // find the right one. @@ -1529,7 +1552,7 @@ bool RegisterFileMappingHint(const void *start, const void *end, uint64_t offset ret = false; } else { // TODO(ckennelly): Move this into a string copy routine. - int len = strlen(filename); + size_t len = strlen(filename); char *dst = static_cast<char *>( base_internal::LowLevelAlloc::AllocWithArena(len + 1, SigSafeArena())); ABSL_RAW_CHECK(dst != nullptr, "out of memory"); @@ -1585,16 +1608,17 @@ bool Symbolize(const void *pc, char *out, int out_size) { const char *name = s->GetSymbol(pc); bool ok = false; if (name != nullptr && out_size > 0) { - strncpy(out, name, out_size); + strncpy(out, name, static_cast<size_t>(out_size)); ok = true; - if (out[out_size - 1] != '\0') { + if (out[static_cast<size_t>(out_size) - 1] != '\0') { // strncpy() does not '\0' terminate when it truncates. Do so, with // trailing ellipsis. static constexpr char kEllipsis[] = "..."; - int ellipsis_size = - std::min(implicit_cast<int>(strlen(kEllipsis)), out_size - 1); - memcpy(out + out_size - ellipsis_size - 1, kEllipsis, ellipsis_size); - out[out_size - 1] = '\0'; + size_t ellipsis_size = + std::min(strlen(kEllipsis), static_cast<size_t>(out_size) - 1); + memcpy(out + static_cast<size_t>(out_size) - ellipsis_size - 1, kEllipsis, + ellipsis_size); + out[static_cast<size_t>(out_size) - 1] = '\0'; } } debugging_internal::FreeSymbolizer(s); diff --git a/absl/debugging/symbolize_win32.inc b/absl/debugging/symbolize_win32.inc index c3df46f6..53a099a1 100644 --- a/absl/debugging/symbolize_win32.inc +++ b/absl/debugging/symbolize_win32.inc @@ -65,14 +65,15 @@ bool Symbolize(const void* pc, char* out, int out_size) { if (!SymFromAddr(process, reinterpret_cast<DWORD64>(pc), nullptr, symbol)) { return false; } - strncpy(out, symbol->Name, out_size); - if (out[out_size - 1] != '\0') { + const size_t out_size_t = static_cast<size_t>(out_size); + strncpy(out, symbol->Name, out_size_t); + if (out[out_size_t - 1] != '\0') { // strncpy() does not '\0' terminate when it truncates. static constexpr char kEllipsis[] = "..."; - int ellipsis_size = - std::min<int>(sizeof(kEllipsis) - 1, out_size - 1); - memcpy(out + out_size - ellipsis_size - 1, kEllipsis, ellipsis_size); - out[out_size - 1] = '\0'; + size_t ellipsis_size = + std::min(sizeof(kEllipsis) - 1, out_size_t - 1); + memcpy(out + out_size_t - ellipsis_size - 1, kEllipsis, ellipsis_size); + out[out_size_t - 1] = '\0'; } return true; } diff --git a/absl/flags/BUILD.bazel b/absl/flags/BUILD.bazel index 4ca687ee..f5b3e033 100644 --- a/absl/flags/BUILD.bazel +++ b/absl/flags/BUILD.bazel @@ -53,6 +53,7 @@ cc_library( linkopts = ABSL_DEFAULT_LINKOPTS, visibility = [ "//absl/flags:__pkg__", + "//absl/log:__pkg__", ], deps = [ ":path_util", @@ -308,6 +309,7 @@ cc_library( ":reflection", ":usage", ":usage_internal", + "//absl/algorithm:container", "//absl/base:config", "//absl/base:core_headers", "//absl/strings", @@ -327,6 +329,8 @@ cc_test( copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, tags = [ + "no_test:os:android", + "no_test:os:ios", "no_test_android", "no_test_ios", "no_test_wasm", @@ -368,6 +372,8 @@ cc_test( copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, tags = [ + "no_test:os:android", + "no_test:os:ios", "no_test_android", "no_test_ios", "no_test_wasm", @@ -434,6 +440,8 @@ cc_test( copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, tags = [ + "no_test:os:android", + "no_test:os:ios", "no_test_android", "no_test_ios", "no_test_wasm", @@ -490,6 +498,8 @@ cc_test( copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, tags = [ + "no_test:os:android", + "no_test:os:ios", "no_test_android", "no_test_ios", "no_test_wasm", @@ -552,6 +562,8 @@ cc_test( copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, tags = [ + "no_test:os:android", + "no_test:os:ios", "no_test_android", "no_test_ios", "no_test_wasm", diff --git a/absl/flags/CMakeLists.txt b/absl/flags/CMakeLists.txt index 3e9d5adf..2f234aa4 100644 --- a/absl/flags/CMakeLists.txt +++ b/absl/flags/CMakeLists.txt @@ -279,6 +279,7 @@ absl_cc_library( LINKOPTS ${ABSL_DEFAULT_LINKOPTS} DEPS + absl::algorithm_container absl::config absl::core_headers absl::flags_config diff --git a/absl/flags/flag_benchmark.cc b/absl/flags/flag_benchmark.cc index fc572d9c..758a6a55 100644 --- a/absl/flags/flag_benchmark.cc +++ b/absl/flags/flag_benchmark.cc @@ -241,10 +241,11 @@ BENCHMARK(BM_ThreadedFindCommandLineFlag)->ThreadRange(1, 16); } // namespace +#ifdef __llvm__ +// To view disassembly use: gdb ${BINARY} -batch -ex "disassemble /s $FUNC" #define InvokeGetFlag(T) \ T AbslInvokeGetFlag##T() { return absl::GetFlag(SINGLE_FLAG(T)); } \ int odr##T = (benchmark::DoNotOptimize(AbslInvokeGetFlag##T), 1); BENCHMARKED_TYPES(InvokeGetFlag) - -// To veiw disassembly use: gdb ${BINARY} -batch -ex "disassemble /s $FUNC" +#endif // __llvm__ diff --git a/absl/flags/internal/flag.cc b/absl/flags/internal/flag.cc index 55892d77..cc656f9d 100644 --- a/absl/flags/internal/flag.cc +++ b/absl/flags/internal/flag.cc @@ -406,7 +406,7 @@ template <typename StorageT> StorageT* FlagImpl::OffsetValue() const { char* p = reinterpret_cast<char*>(const_cast<FlagImpl*>(this)); // The offset is deduced via Flag value type specific op_. - size_t offset = flags_internal::ValueOffset(op_); + ptrdiff_t offset = flags_internal::ValueOffset(op_); return reinterpret_cast<StorageT*>(p + offset); } @@ -486,7 +486,7 @@ bool FlagImpl::ReadOneBool() const { } void FlagImpl::ReadSequenceLockedData(void* dst) const { - int size = Sizeof(op_); + size_t size = Sizeof(op_); // Attempt to read using the sequence lock. if (ABSL_PREDICT_TRUE(seq_lock_.TryRead(dst, AtomicBufferValue(), size))) { return; diff --git a/absl/flags/internal/parse.h b/absl/flags/internal/parse.h index de706c89..0a7012fc 100644 --- a/absl/flags/internal/parse.h +++ b/absl/flags/internal/parse.h @@ -52,6 +52,10 @@ std::vector<char*> ParseCommandLineImpl(int argc, char* argv[], // command line or specified in flag file present on the original command line. bool WasPresentOnCommandLine(absl::string_view flag_name); +// Return existing flags similar to the parameter, in order to help in case of +// misspellings. +std::vector<std::string> GetMisspellingHints(absl::string_view flag); + } // namespace flags_internal ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/flags/internal/usage.cc b/absl/flags/internal/usage.cc index 949709e8..5efc7b07 100644 --- a/absl/flags/internal/usage.cc +++ b/absl/flags/internal/usage.cc @@ -17,7 +17,9 @@ #include <stdint.h> +#include <algorithm> #include <functional> +#include <iterator> #include <map> #include <ostream> #include <string> @@ -33,6 +35,7 @@ #include "absl/flags/internal/program_name.h" #include "absl/flags/internal/registry.h" #include "absl/flags/usage_config.h" +#include "absl/strings/match.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_split.h" #include "absl/strings/string_view.h" @@ -148,8 +151,7 @@ class FlagHelpPrettyPrinter { } // Write the token, ending the string first if necessary/possible. - if (!new_line && - (line_len_ + static_cast<int>(token.size()) >= max_line_len_)) { + if (!new_line && (line_len_ + token.size() >= max_line_len_)) { EndLine(); new_line = true; } @@ -344,7 +346,7 @@ void FlagHelp(std::ostream& out, const CommandLineFlag& flag, void FlagsHelp(std::ostream& out, absl::string_view filter, HelpFormat format, absl::string_view program_usage_message) { flags_internal::FlagKindFilter filter_cb = [&](absl::string_view filename) { - return filter.empty() || filename.find(filter) != absl::string_view::npos; + return filter.empty() || absl::StrContains(filename, filter); }; flags_internal::FlagsHelpImpl(out, filter_cb, format, program_usage_message); } @@ -466,7 +468,7 @@ void SetFlagsHelpFormat(HelpFormat format) { // function. bool DeduceUsageFlags(absl::string_view name, absl::string_view value) { if (absl::ConsumePrefix(&name, "help")) { - if (name == "") { + if (name.empty()) { if (value.empty()) { SetFlagsHelpMode(HelpMode::kImportant); } else { diff --git a/absl/flags/marshalling.h b/absl/flags/marshalling.h index b1e2ffa5..325e75e5 100644 --- a/absl/flags/marshalling.h +++ b/absl/flags/marshalling.h @@ -86,7 +86,7 @@ // } // // Using an optional flag in this manner avoids common workarounds for -// indicating such an unset flag (such as using sentinal values to indicate this +// indicating such an unset flag (such as using sentinel values to indicate this // state). // // An optional flag also allows a developer to pass a flag in an "unset" diff --git a/absl/flags/parse.cc b/absl/flags/parse.cc index dd1a6796..fa953f55 100644 --- a/absl/flags/parse.cc +++ b/absl/flags/parse.cc @@ -18,6 +18,7 @@ #include <stdlib.h> #include <algorithm> +#include <cstdint> #include <fstream> #include <iostream> #include <iterator> @@ -30,6 +31,7 @@ #include <windows.h> #endif +#include "absl/algorithm/container.h" #include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/const_init.h" @@ -47,7 +49,9 @@ #include "absl/flags/usage.h" #include "absl/flags/usage_config.h" #include "absl/strings/ascii.h" +#include "absl/strings/internal/damerau_levenshtein_distance.h" #include "absl/strings/str_cat.h" +#include "absl/strings/str_join.h" #include "absl/strings/string_view.h" #include "absl/strings/strip.h" #include "absl/synchronization/mutex.h" @@ -72,6 +76,11 @@ ABSL_CONST_INIT absl::Mutex specified_flags_guard(absl::kConstInit); ABSL_CONST_INIT std::vector<const CommandLineFlag*>* specified_flags ABSL_GUARDED_BY(specified_flags_guard) = nullptr; +// Suggesting at most kMaxHints flags in case of misspellings. +ABSL_CONST_INIT const size_t kMaxHints = 100; +// Suggesting only flags which have a smaller distance than kMaxDistance. +ABSL_CONST_INIT const size_t kMaxDistance = 3; + struct SpecifiedFlagsCompare { bool operator()(const CommandLineFlag* a, const CommandLineFlag* b) const { return a->Name() < b->Name(); @@ -159,14 +168,14 @@ class ArgsList { // Returns success status: true if parsing successful, false otherwise. bool ReadFromFlagfile(const std::string& flag_file_name); - int Size() const { return args_.size() - next_arg_; } - int FrontIndex() const { return next_arg_; } + size_t Size() const { return args_.size() - next_arg_; } + size_t FrontIndex() const { return next_arg_; } absl::string_view Front() const { return args_[next_arg_]; } void PopFront() { next_arg_++; } private: std::vector<std::string> args_; - int next_arg_; + size_t next_arg_; }; bool ArgsList::ReadFromFlagfile(const std::string& flag_file_name) { @@ -605,6 +614,55 @@ bool WasPresentOnCommandLine(absl::string_view flag_name) { // -------------------------------------------------------------------- +struct BestHints { + explicit BestHints(uint8_t _max) : best_distance(_max + 1) {} + bool AddHint(absl::string_view hint, uint8_t distance) { + if (hints.size() >= kMaxHints) return false; + if (distance == best_distance) { + hints.emplace_back(hint); + } + if (distance < best_distance) { + best_distance = distance; + hints = std::vector<std::string>{std::string(hint)}; + } + return true; + } + + uint8_t best_distance; + std::vector<std::string> hints; +}; + +// Return the list of flags with the smallest Damerau-Levenshtein distance to +// the given flag. +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) { + if (best_hints.hints.size() >= kMaxHints) return; + uint8_t distance = strings_internal::CappedDamerauLevenshteinDistance( + flag, f.Name(), best_hints.best_distance); + best_hints.AddHint(f.Name(), distance); + // For boolean flags, also calculate distance to the negated form. + if (f.IsOfType<bool>()) { + const std::string negated_flag = absl::StrCat("no", f.Name()); + distance = strings_internal::CappedDamerauLevenshteinDistance( + flag, negated_flag, best_hints.best_distance); + best_hints.AddHint(negated_flag, distance); + } + }); + // Finally calculate distance to flags in "undefok". + absl::c_for_each(undefok, [&](const absl::string_view f) { + if (best_hints.hints.size() >= kMaxHints) return; + uint8_t distance = strings_internal::CappedDamerauLevenshteinDistance( + flag, f, best_hints.best_distance); + best_hints.AddHint(absl::StrCat(f, " (undefok)"), distance); + }); + return best_hints.hints; +} + +// -------------------------------------------------------------------- + std::vector<char*> ParseCommandLineImpl(int argc, char* argv[], ArgvListAction arg_list_act, UsageFlagsAction usage_flag_act, @@ -626,7 +684,7 @@ std::vector<char*> ParseCommandLineImpl(int argc, char* argv[], std::vector<char*> output_args; std::vector<char*> positional_args; - output_args.reserve(argc); + output_args.reserve(static_cast<size_t>(argc)); // 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 @@ -755,10 +813,19 @@ std::vector<char*> ParseCommandLineImpl(int argc, char* argv[], for (const auto& flag_name : undefined_flag_names) { if (CanIgnoreUndefinedFlag(flag_name.second)) continue; - - flags_internal::ReportUsageError( - absl::StrCat("Unknown command line flag '", flag_name.second, "'"), - true); + // 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); + } success = false; } @@ -795,8 +862,8 @@ std::vector<char*> ParseCommandLineImpl(int argc, char* argv[], // All the remaining arguments are positional. if (!input_args.empty()) { - for (int arg_index = input_args.back().FrontIndex(); arg_index < argc; - ++arg_index) { + 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]); } } diff --git a/absl/flags/parse_test.cc b/absl/flags/parse_test.cc index 8dc91db2..418b0e55 100644 --- a/absl/flags/parse_test.cc +++ b/absl/flags/parse_test.cc @@ -17,6 +17,7 @@ #include <stdlib.h> +#include <cstddef> #include <fstream> #include <string> #include <vector> @@ -39,6 +40,36 @@ #include <windows.h> #endif +// Define 125 similar flags to test kMaxHints for flag suggestions. +#define FLAG_MULT(x) F3(x) +#define TEST_FLAG_HEADER FLAG_HEADER_ + +#define F(name) ABSL_FLAG(int, name, 0, ""); + +#define F1(name) \ + F(name##1); \ + F(name##2); \ + F(name##3); \ + F(name##4); \ + F(name##5); +/**/ +#define F2(name) \ + F1(name##1); \ + F1(name##2); \ + F1(name##3); \ + F1(name##4); \ + F1(name##5); +/**/ +#define F3(name) \ + F2(name##1); \ + F2(name##2); \ + F2(name##3); \ + F2(name##4); \ + F2(name##5); +/**/ + +FLAG_MULT(TEST_FLAG_HEADER) + namespace { using absl::base_internal::ScopedSetEnv; @@ -565,6 +596,49 @@ TEST_F(ParseDeathTest, TestInvalidUDTFlagFormat) { // -------------------------------------------------------------------- +TEST_F(ParseDeathTest, TestFlagSuggestions) { + const char* in_args1[] = { + "testbin", + "--legacy_boo", + }; + EXPECT_DEATH_IF_SUPPORTED( + InvokeParse(in_args1), + "Unknown command line flag 'legacy_boo'. Did you mean: legacy_bool ?"); + + const char* in_args2[] = {"testbin", "--foo", "--undefok=foo1"}; + EXPECT_DEATH_IF_SUPPORTED( + InvokeParse(in_args2), + "Unknown command line flag 'foo'. Did you mean: foo1 \\(undefok\\)?"); + + const char* in_args3[] = { + "testbin", + "--nolegacy_ino", + }; + EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args3), + "Unknown command line flag 'nolegacy_ino'. Did " + "you mean: nolegacy_bool, legacy_int ?"); +} + +// -------------------------------------------------------------------- + +TEST_F(ParseTest, GetHints) { + EXPECT_THAT(absl::flags_internal::GetMisspellingHints("legacy_boo"), + testing::ContainerEq(std::vector<std::string>{"legacy_bool"})); + EXPECT_THAT(absl::flags_internal::GetMisspellingHints("nolegacy_itn"), + testing::ContainerEq(std::vector<std::string>{"legacy_int"})); + EXPECT_THAT(absl::flags_internal::GetMisspellingHints("nolegacy_int1"), + testing::ContainerEq(std::vector<std::string>{"legacy_int"})); + EXPECT_THAT(absl::flags_internal::GetMisspellingHints("nolegacy_int"), + testing::ContainerEq(std::vector<std::string>{"legacy_int"})); + EXPECT_THAT(absl::flags_internal::GetMisspellingHints("nolegacy_ino"), + testing::ContainerEq( + std::vector<std::string>{"nolegacy_bool", "legacy_int"})); + EXPECT_THAT( + absl::flags_internal::GetMisspellingHints("FLAG_HEADER_000").size(), 100); +} + +// -------------------------------------------------------------------- + TEST_F(ParseTest, TestLegacyFlags) { const char* in_args1[] = { "testbin", diff --git a/absl/functional/any_invocable.h b/absl/functional/any_invocable.h index 0c5faca0..3e783c87 100644 --- a/absl/functional/any_invocable.h +++ b/absl/functional/any_invocable.h @@ -26,6 +26,9 @@ // NOTE: `absl::AnyInvocable` is similar to the C++23 `std::move_only_function` // abstraction, but has a slightly different API and is not designed to be a // drop-in replacement or C++11-compatible backfill of that type. +// +// Credits to Matt Calabrese (https://github.com/mattcalabrese) for the original +// implementation. #ifndef ABSL_FUNCTIONAL_ANY_INVOCABLE_H_ #define ABSL_FUNCTIONAL_ANY_INVOCABLE_H_ @@ -146,8 +149,8 @@ ABSL_NAMESPACE_BEGIN // std::move(continuation)(result_of_foo); // } // -// Credits to Matt Calabrese (https://github.com/mattcalabrese) for the original -// implementation. +// Attempting to call `absl::AnyInvocable` multiple times in such a case +// results in undefined behavior. template <class Sig> class AnyInvocable : private internal_any_invocable::Impl<Sig> { private: diff --git a/absl/functional/any_invocable_test.cc b/absl/functional/any_invocable_test.cc index fb5e7792..1ed85407 100644 --- a/absl/functional/any_invocable_test.cc +++ b/absl/functional/any_invocable_test.cc @@ -16,6 +16,7 @@ #include <cstddef> #include <initializer_list> +#include <memory> #include <numeric> #include <type_traits> @@ -1156,9 +1157,6 @@ TYPED_TEST_P(AnyInvTestMovable, ConversionConstructionUserDefinedType) { EXPECT_TRUE(static_cast<bool>(fun)); EXPECT_EQ(29, TypeParam::ToThisParam(fun)(7, 8, 9).value); - - EXPECT_TRUE(static_cast<bool>(fun)); - EXPECT_EQ(38, TypeParam::ToThisParam(fun)(10, 11, 12).value); } TYPED_TEST_P(AnyInvTestMovable, ConversionConstructionVoidCovariance) { @@ -1179,9 +1177,6 @@ TYPED_TEST_P(AnyInvTestMovable, ConversionAssignUserDefinedTypeEmptyLhs) { EXPECT_TRUE(static_cast<bool>(fun)); EXPECT_EQ(29, TypeParam::ToThisParam(fun)(7, 8, 9).value); - - EXPECT_TRUE(static_cast<bool>(fun)); - EXPECT_EQ(38, TypeParam::ToThisParam(fun)(10, 11, 12).value); } TYPED_TEST_P(AnyInvTestMovable, ConversionAssignUserDefinedTypeNonemptyLhs) { @@ -1193,9 +1188,6 @@ TYPED_TEST_P(AnyInvTestMovable, ConversionAssignUserDefinedTypeNonemptyLhs) { EXPECT_TRUE(static_cast<bool>(fun)); EXPECT_EQ(29, TypeParam::ToThisParam(fun)(7, 8, 9).value); - - EXPECT_TRUE(static_cast<bool>(fun)); - EXPECT_EQ(38, TypeParam::ToThisParam(fun)(10, 11, 12).value); } TYPED_TEST_P(AnyInvTestMovable, ConversionAssignVoidCovariance) { @@ -1246,12 +1238,9 @@ TYPED_TEST_P(AnyInvTestNoexceptTrue, ConversionConstructionConstraints) { #else using AnyInvType = typename TypeParam::AnyInvType; -// TODO(b/217761454): Fix this and re-enable for MSVC. -#ifndef _MSC_VER EXPECT_FALSE((std::is_constructible< AnyInvType, typename TypeParam::AnyInvocableFunTypeNotNoexcept*>::value)); -#endif EXPECT_FALSE(( std::is_constructible<AnyInvType, typename TypeParam::IncompatibleInvocable>::value)); @@ -1264,12 +1253,9 @@ TYPED_TEST_P(AnyInvTestNoexceptTrue, ConversionAssignConstraints) { #else using AnyInvType = typename TypeParam::AnyInvType; -// TODO(b/217761454): Fix this and re-enable for MSVC. -#ifndef _MSC_VER EXPECT_FALSE((std::is_assignable< AnyInvType&, typename TypeParam::AnyInvocableFunTypeNotNoexcept*>::value)); -#endif EXPECT_FALSE( (std::is_assignable<AnyInvType&, typename TypeParam::IncompatibleInvocable>::value)); @@ -1420,6 +1406,41 @@ TYPED_TEST_P(AnyInvTestRvalue, ConversionAssignReferenceWrapper) { std::is_assignable<AnyInvType&, std::reference_wrapper<AddType>>::value)); } +TYPED_TEST_P(AnyInvTestRvalue, NonConstCrashesOnSecondCall) { + using AnyInvType = typename TypeParam::AnyInvType; + using AddType = typename TypeParam::AddType; + + AnyInvType fun(absl::in_place_type<AddType>, 5); + + EXPECT_TRUE(static_cast<bool>(fun)); + std::move(fun)(7, 8, 9); + + // Ensure we're still valid + EXPECT_TRUE(static_cast<bool>(fun)); // NOLINT(bugprone-use-after-move) + +#if !defined(NDEBUG) || ABSL_OPTION_HARDENED == 1 + EXPECT_DEATH_IF_SUPPORTED(std::move(fun)(7, 8, 9), ""); +#endif +} + +// Ensure that any qualifiers (in particular &&-qualifiers) do not affect +// when the destructor is actually run. +TYPED_TEST_P(AnyInvTestRvalue, QualifierIndependentObjectLifetime) { + using AnyInvType = typename TypeParam::AnyInvType; + + auto refs = std::make_shared<std::nullptr_t>(); + { + AnyInvType fun([refs](auto&&...) noexcept { return 0; }); + EXPECT_FALSE(refs.unique()); + + std::move(fun)(7, 8, 9); + + // Ensure destructor hasn't run even if rref-qualified + EXPECT_FALSE(refs.unique()); + } + EXPECT_TRUE(refs.unique()); +} + // NOTE: This test suite originally attempted to enumerate all possible // combinations of type properties but the build-time started getting too large. // Instead, it is now assumed that certain parameters are orthogonal and so @@ -1676,7 +1697,9 @@ INSTANTIATE_TYPED_TEST_SUITE_P(NonRvalueCallNothrow, AnyInvTestNonRvalue, REGISTER_TYPED_TEST_SUITE_P(AnyInvTestRvalue, ConversionConstructionReferenceWrapper, NonMoveableResultType, - ConversionAssignReferenceWrapper); + ConversionAssignReferenceWrapper, + NonConstCrashesOnSecondCall, + QualifierIndependentObjectLifetime); INSTANTIATE_TYPED_TEST_SUITE_P(RvalueCallMayThrow, AnyInvTestRvalue, TestParameterListRvalueQualifiersCallMayThrow); diff --git a/absl/functional/internal/any_invocable.h b/absl/functional/internal/any_invocable.h index f353139c..6bfbda18 100644 --- a/absl/functional/internal/any_invocable.h +++ b/absl/functional/internal/any_invocable.h @@ -66,6 +66,7 @@ #include "absl/base/config.h" #include "absl/base/internal/invoke.h" #include "absl/base/macros.h" +#include "absl/base/optimization.h" #include "absl/meta/type_traits.h" #include "absl/utility/utility.h" @@ -281,7 +282,7 @@ void LocalManagerNontrivial(FunctionToCall operation, from_object.~T(); // Must not throw. // NOLINT return; } - ABSL_INTERNAL_UNREACHABLE; + ABSL_UNREACHABLE(); } // The invoker that is used when a target function is in local storage @@ -319,7 +320,7 @@ inline void RemoteManagerTrivial(FunctionToCall operation, #endif // __cpp_sized_deallocation return; } - ABSL_INTERNAL_UNREACHABLE; + ABSL_UNREACHABLE(); } // The manager that is used when a target function is in remote storage and the @@ -341,7 +342,7 @@ void RemoteManagerNontrivial(FunctionToCall operation, ::delete static_cast<T*>(from->remote.target); // Must not throw. return; } - ABSL_INTERNAL_UNREACHABLE; + ABSL_UNREACHABLE(); } // The invoker that is used when a target function is in remote storage @@ -683,23 +684,23 @@ using UnwrapStdReferenceWrapper = // An alias that always yields std::true_type (used with constraints) where // substitution failures happen when forming the template arguments. template <class... T> -using True = +using TrueAlias = std::integral_constant<bool, sizeof(absl::void_t<T...>*) != 0>; /*SFINAE constraints for the conversion-constructor.*/ template <class Sig, class F, class = absl::enable_if_t< !std::is_same<RemoveCVRef<F>, AnyInvocable<Sig>>::value>> -using CanConvert = - True<absl::enable_if_t<!IsInPlaceType<RemoveCVRef<F>>::value>, - absl::enable_if_t<Impl<Sig>::template CallIsValid<F>::value>, - absl::enable_if_t< - Impl<Sig>::template CallIsNoexceptIfSigIsNoexcept<F>::value>, - absl::enable_if_t<std::is_constructible<absl::decay_t<F>, F>::value>>; +using CanConvert = TrueAlias< + absl::enable_if_t<!IsInPlaceType<RemoveCVRef<F>>::value>, + absl::enable_if_t<Impl<Sig>::template CallIsValid<F>::value>, + absl::enable_if_t< + Impl<Sig>::template CallIsNoexceptIfSigIsNoexcept<F>::value>, + absl::enable_if_t<std::is_constructible<absl::decay_t<F>, F>::value>>; /*SFINAE constraints for the std::in_place constructors.*/ template <class Sig, class F, class... Args> -using CanEmplace = True< +using CanEmplace = TrueAlias< absl::enable_if_t<Impl<Sig>::template CallIsValid<F>::value>, absl::enable_if_t< Impl<Sig>::template CallIsNoexceptIfSigIsNoexcept<F>::value>, @@ -709,19 +710,19 @@ using CanEmplace = True< template <class Sig, class F, class = absl::enable_if_t< !std::is_same<RemoveCVRef<F>, AnyInvocable<Sig>>::value>> -using CanAssign = - True<absl::enable_if_t<Impl<Sig>::template CallIsValid<F>::value>, - absl::enable_if_t< - Impl<Sig>::template CallIsNoexceptIfSigIsNoexcept<F>::value>, - absl::enable_if_t<std::is_constructible<absl::decay_t<F>, F>::value>>; +using CanAssign = TrueAlias< + absl::enable_if_t<Impl<Sig>::template CallIsValid<F>::value>, + absl::enable_if_t< + Impl<Sig>::template CallIsNoexceptIfSigIsNoexcept<F>::value>, + absl::enable_if_t<std::is_constructible<absl::decay_t<F>, F>::value>>; /*SFINAE constraints for the reference-wrapper conversion-assign operator.*/ template <class Sig, class F> -using CanAssignReferenceWrapper = - True<absl::enable_if_t< - Impl<Sig>::template CallIsValid<std::reference_wrapper<F>>::value>, - absl::enable_if_t<Impl<Sig>::template CallIsNoexceptIfSigIsNoexcept< - std::reference_wrapper<F>>::value>>; +using CanAssignReferenceWrapper = TrueAlias< + absl::enable_if_t< + Impl<Sig>::template CallIsValid<std::reference_wrapper<F>>::value>, + absl::enable_if_t<Impl<Sig>::template CallIsNoexceptIfSigIsNoexcept< + std::reference_wrapper<F>>::value>>; //////////////////////////////////////////////////////////////////////////////// // @@ -778,7 +779,7 @@ using CanAssignReferenceWrapper = \ /*SFINAE constraint to check if F is invocable with the proper signature*/ \ template <class F> \ - using CallIsValid = True<absl::enable_if_t<absl::disjunction< \ + using CallIsValid = TrueAlias<absl::enable_if_t<absl::disjunction< \ absl::base_internal::is_invocable_r<ReturnType, \ absl::decay_t<F> inv_quals, P...>, \ std::is_same<ReturnType, \ @@ -788,8 +789,8 @@ using CanAssignReferenceWrapper = /*SFINAE constraint to check if F is nothrow-invocable when necessary*/ \ template <class F> \ using CallIsNoexceptIfSigIsNoexcept = \ - True<ABSL_INTERNAL_ANY_INVOCABLE_NOEXCEPT_CONSTRAINT(inv_quals, \ - noex)>; \ + TrueAlias<ABSL_INTERNAL_ANY_INVOCABLE_NOEXCEPT_CONSTRAINT(inv_quals, \ + noex)>; \ \ /*Put the AnyInvocable into an empty state.*/ \ Impl() = default; \ @@ -809,11 +810,31 @@ using CanAssignReferenceWrapper = : Core(absl::in_place_type<absl::decay_t<T> inv_quals>, \ std::forward<Args>(args)...) {} \ \ + InvokerType<noex, ReturnType, P...>* ExtractInvoker() cv { \ + using QualifiedTestType = int cv ref; \ + auto* invoker = this->invoker_; \ + if (!std::is_const<QualifiedTestType>::value && \ + std::is_rvalue_reference<QualifiedTestType>::value) { \ + ABSL_HARDENING_ASSERT([this]() { \ + /* We checked that this isn't const above, so const_cast is safe */ \ + const_cast<Impl*>(this)->invoker_ = \ + [](TypeErasedState*, \ + ForwardedParameterType<P>...) noexcept(noex) -> ReturnType { \ + ABSL_HARDENING_ASSERT(false && "AnyInvocable use-after-move"); \ + std::terminate(); \ + }; \ + return this->HasValue(); \ + }()); \ + } \ + return invoker; \ + } \ + \ /*The actual invocation operation with the proper signature*/ \ ReturnType operator()(P... args) cv ref noexcept(noex) { \ assert(this->invoker_ != nullptr); \ - return this->invoker_(const_cast<TypeErasedState*>(&this->state_), \ - static_cast<ForwardedParameterType<P>>(args)...); \ + return this->ExtractInvoker()( \ + const_cast<TypeErasedState*>(&this->state_), \ + static_cast<ForwardedParameterType<P>>(args)...); \ } \ } diff --git a/absl/hash/BUILD.bazel b/absl/hash/BUILD.bazel index bcc316f9..a0db919b 100644 --- a/absl/hash/BUILD.bazel +++ b/absl/hash/BUILD.bazel @@ -43,6 +43,7 @@ cc_library( "//absl/container:fixed_array", "//absl/functional:function_ref", "//absl/meta:type_traits", + "//absl/numeric:bits", "//absl/numeric:int128", "//absl/strings", "//absl/types:optional", @@ -157,7 +158,6 @@ cc_library( deps = [ "//absl/base:config", "//absl/base:endian", - "//absl/numeric:bits", "//absl/numeric:int128", ], ) diff --git a/absl/hash/CMakeLists.txt b/absl/hash/CMakeLists.txt index 423b74b5..f99f35bc 100644 --- a/absl/hash/CMakeLists.txt +++ b/absl/hash/CMakeLists.txt @@ -24,7 +24,8 @@ absl_cc_library( "internal/hash.h" COPTS ${ABSL_DEFAULT_COPTS} - DEPS + DEPS + absl::bits absl::city absl::config absl::core_headers @@ -55,6 +56,7 @@ absl_cc_library( absl::variant GTest::gmock TESTONLY + PUBLIC ) absl_cc_test( @@ -81,6 +83,10 @@ absl_cc_test( ) # Internal-only target, do not depend on directly. +# +# Note: Even though external code should not depend on this target +# directly, it must be marked PUBLIC since it is a dependency of +# hash_testing. absl_cc_library( NAME spy_hash_state @@ -93,6 +99,7 @@ absl_cc_library( absl::strings absl::str_format TESTONLY + PUBLIC ) # Internal-only target, do not depend on directly. @@ -134,7 +141,6 @@ absl_cc_library( COPTS ${ABSL_DEFAULT_COPTS} DEPS - absl::bits absl::config absl::endian absl::int128 diff --git a/absl/hash/hash_test.cc b/absl/hash/hash_test.cc index ffa45e6e..5b556180 100644 --- a/absl/hash/hash_test.cc +++ b/absl/hash/hash_test.cc @@ -222,7 +222,7 @@ TEST(HashValueTest, PointerAlignment) { // Limit the scope to the bits we would be using for Swisstable. constexpr size_t kMask = (1 << (kLog2NumValues + 7)) - 1; size_t stuck_bits = (~bits_or | bits_and) & kMask; - EXPECT_EQ(stuck_bits, 0) << "0x" << std::hex << stuck_bits; + EXPECT_EQ(stuck_bits, 0u) << "0x" << std::hex << stuck_bits; } } @@ -737,10 +737,10 @@ TEST(HashValueTest, CombinePiecewiseBuffer) { // // This test is run on a buffer that is a multiple of the stride size, and one // that isn't. - for (size_t big_buffer_size : {1024 * 2 + 512, 1024 * 3}) { + for (size_t big_buffer_size : {1024u * 2 + 512u, 1024u * 3}) { SCOPED_TRACE(big_buffer_size); std::string big_buffer; - for (int i = 0; i < big_buffer_size; ++i) { + for (size_t i = 0; i < big_buffer_size; ++i) { // Arbitrary string big_buffer.push_back(32 + (i * (i / 3)) % 64); } @@ -904,8 +904,8 @@ TEST(IsHashableTest, PoisonHash) { EXPECT_FALSE(absl::is_copy_assignable<absl::Hash<X>>::value); EXPECT_FALSE(absl::is_move_assignable<absl::Hash<X>>::value); EXPECT_FALSE(IsHashCallable<X>::value); -#if !defined(__GNUC__) || __GNUC__ < 9 - // This doesn't compile on GCC 9. +#if !defined(__GNUC__) || defined(__clang__) + // TODO(b/144368551): As of GCC 8.4 this does not compile. EXPECT_FALSE(IsAggregateInitializable<absl::Hash<X>>::value); #endif } @@ -1135,10 +1135,10 @@ TEST(HashTest, HashNonUniquelyRepresentedType) { unsigned char buffer2[kNumStructs * sizeof(StructWithPadding)]; std::memset(buffer2, 255, sizeof(buffer2)); auto* s2 = reinterpret_cast<StructWithPadding*>(buffer2); - for (int i = 0; i < kNumStructs; ++i) { + for (size_t i = 0; i < kNumStructs; ++i) { SCOPED_TRACE(i); - s1[i].c = s2[i].c = '0' + i; - s1[i].i = s2[i].i = i; + s1[i].c = s2[i].c = static_cast<char>('0' + i); + s1[i].i = s2[i].i = static_cast<int>(i); ASSERT_FALSE(memcmp(buffer1 + i * sizeof(StructWithPadding), buffer2 + i * sizeof(StructWithPadding), sizeof(StructWithPadding)) == 0) @@ -1226,7 +1226,9 @@ struct ValueWithBoolConversion { namespace std { template <> struct hash<ValueWithBoolConversion> { - size_t operator()(ValueWithBoolConversion v) { return v.i; } + size_t operator()(ValueWithBoolConversion v) { + return static_cast<size_t>(v.i); + } }; } // namespace std diff --git a/absl/hash/internal/city.cc b/absl/hash/internal/city.cc index 5460134e..f0d31964 100644 --- a/absl/hash/internal/city.cc +++ b/absl/hash/internal/city.cc @@ -97,7 +97,7 @@ static uint32_t Hash32Len13to24(const char *s, size_t len) { uint32_t d = Fetch32(s + (len >> 1)); uint32_t e = Fetch32(s); uint32_t f = Fetch32(s + len - 4); - uint32_t h = len; + uint32_t h = static_cast<uint32_t>(len); return fmix(Mur(f, Mur(e, Mur(d, Mur(c, Mur(b, Mur(a, h))))))); } @@ -106,15 +106,15 @@ static uint32_t Hash32Len0to4(const char *s, size_t len) { uint32_t b = 0; uint32_t c = 9; for (size_t i = 0; i < len; i++) { - signed char v = s[i]; - b = b * c1 + v; + signed char v = static_cast<signed char>(s[i]); + b = b * c1 + static_cast<uint32_t>(v); c ^= b; } - return fmix(Mur(b, Mur(len, c))); + return fmix(Mur(b, Mur(static_cast<uint32_t>(len), c))); } static uint32_t Hash32Len5to12(const char *s, size_t len) { - uint32_t a = len, b = len * 5, c = 9, d = b; + uint32_t a = static_cast<uint32_t>(len), b = a * 5, c = 9, d = b; a += Fetch32(s); b += Fetch32(s + len - 4); c += Fetch32(s + ((len >> 1) & 4)); @@ -129,7 +129,7 @@ uint32_t CityHash32(const char *s, size_t len) { } // len > 24 - uint32_t h = len, g = c1 * len, f = g; + uint32_t h = static_cast<uint32_t>(len), g = c1 * h, f = g; uint32_t a0 = Rotate32(Fetch32(s + len - 4) * c1, 17) * c2; uint32_t a1 = Rotate32(Fetch32(s + len - 8) * c1, 17) * c2; @@ -230,11 +230,11 @@ static uint64_t HashLen0to16(const char *s, size_t len) { return HashLen16(len + (a << 3), Fetch32(s + len - 4), mul); } if (len > 0) { - uint8_t a = s[0]; - uint8_t b = s[len >> 1]; - uint8_t c = s[len - 1]; + uint8_t a = static_cast<uint8_t>(s[0]); + uint8_t b = static_cast<uint8_t>(s[len >> 1]); + uint8_t c = static_cast<uint8_t>(s[len - 1]); uint32_t y = static_cast<uint32_t>(a) + (static_cast<uint32_t>(b) << 8); - uint32_t z = len + (static_cast<uint32_t>(c) << 2); + uint32_t z = static_cast<uint32_t>(len) + (static_cast<uint32_t>(c) << 2); return ShiftMix(y * k2 ^ z * k0) * k2; } return k2; diff --git a/absl/hash/internal/hash.h b/absl/hash/internal/hash.h index 45dfdd46..ccf4cc1a 100644 --- a/absl/hash/internal/hash.h +++ b/absl/hash/internal/hash.h @@ -49,6 +49,7 @@ #include "absl/hash/internal/city.h" #include "absl/hash/internal/low_level_hash.h" #include "absl/meta/type_traits.h" +#include "absl/numeric/bits.h" #include "absl/numeric/int128.h" #include "absl/strings/string_view.h" #include "absl/types/optional.h" @@ -444,7 +445,7 @@ H AbslHashValue(H hash_state, T C::* ptr) { // 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_v<T C::*>); + static_assert(std::has_unique_object_representations<T C::*>::value); #endif // __cpp_lib_has_unique_object_representations return n; #endif @@ -1052,7 +1053,7 @@ class ABSL_DLL MixingHashState : public HashStateBase<MixingHashState> { uint64_t most_significant = low_mem; uint64_t least_significant = high_mem; #endif - return {least_significant, most_significant >> (128 - len * 8)}; + return {least_significant, most_significant}; } // Reads 4 to 8 bytes from p. Zero pads to fill uint64_t. @@ -1183,9 +1184,22 @@ inline uint64_t MixingHashState::CombineContiguousImpl( } v = Hash64(first, len); } else if (len > 8) { + // This hash function was constructed by the ML-driven algorithm discovery + // using reinforcement learning. We fed the agent lots of inputs from + // microbenchmarks, SMHasher, low hamming distance from generated inputs and + // picked up the one that was good on micro and macrobenchmarks. auto p = Read9To16(first, len); - state = Mix(state, p.first); - v = p.second; + uint64_t lo = p.first; + uint64_t hi = p.second; + // Rotation by 53 was found to be most often useful when discovering these + // hashing algorithms with ML techniques. + lo = absl::rotr(lo, 53); + state += kMul; + lo += state; + state ^= hi; + uint128 m = state; + m *= lo; + return static_cast<uint64_t>(m ^ (m >> 64)); } else if (len >= 4) { v = Read4To8(first, len); } else if (len > 0) { diff --git a/absl/hash/internal/low_level_hash.cc b/absl/hash/internal/low_level_hash.cc index 6f9cb9c7..c917457a 100644 --- a/absl/hash/internal/low_level_hash.cc +++ b/absl/hash/internal/low_level_hash.cc @@ -15,7 +15,6 @@ #include "absl/hash/internal/low_level_hash.h" #include "absl/base/internal/unaligned_access.h" -#include "absl/numeric/bits.h" #include "absl/numeric/int128.h" namespace absl { @@ -23,24 +22,13 @@ ABSL_NAMESPACE_BEGIN namespace hash_internal { static uint64_t Mix(uint64_t v0, uint64_t v1) { -#if !defined(__aarch64__) - // The default bit-mixer uses 64x64->128-bit multiplication. absl::uint128 p = v0; p *= v1; return absl::Uint128Low64(p) ^ absl::Uint128High64(p); -#else - // The default bit-mixer above would perform poorly on some ARM microarchs, - // where calculating a 128-bit product requires a sequence of two - // instructions with a high combined latency and poor throughput. - // Instead, we mix bits using only 64-bit arithmetic, which is faster. - uint64_t p = v0 ^ absl::rotl(v1, 40); - p *= v1 ^ absl::rotl(v0, 39); - return p ^ (p >> 11); -#endif } uint64_t LowLevelHash(const void* data, size_t len, uint64_t seed, - const uint64_t salt[]) { + const uint64_t salt[5]) { 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]; @@ -106,7 +94,8 @@ uint64_t LowLevelHash(const void* data, size_t len, uint64_t seed, } else if (len > 0) { // If we have at least 1 and at most 3 bytes, read all of the provided // bits into A, with some adjustments. - a = ((ptr[0] << 16) | (ptr[len >> 1] << 8) | ptr[len - 1]); + a = static_cast<uint64_t>((ptr[0] << 16) | (ptr[len >> 1] << 8) | + ptr[len - 1]); b = 0; } else { a = 0; diff --git a/absl/hash/internal/low_level_hash_test.cc b/absl/hash/internal/low_level_hash_test.cc index ae930b34..589a3d8f 100644 --- a/absl/hash/internal/low_level_hash_test.cc +++ b/absl/hash/internal/low_level_hash_test.cc @@ -452,54 +452,6 @@ TEST(LowLevelHashTest, VerifyGolden) { 0xdd497891465a2cc1, 0x6f1fe8c57a33072e, 0x2c9f4ec078c460c0, 0x9a725bde8f6a1437, 0x6ce545fa3ef61e4d, }; -#elif defined(__aarch64__) - constexpr uint64_t kGolden[kNumGoldenOutputs] = { - 0x45c0aadee165dcbe, 0x25ed8587f6f20d06, 0x5f23ae668ce7926d, - 0xfef74d1da0846719, 0x54478408e68cb7d4, 0xee27ddaf88c6fe68, - 0xb7ac7031e81867ca, 0xf1168f818ec6c36d, 0x1dd0b734a83b019a, - 0xd6ae30d4142b54fe, 0xcd860c721ccb80fb, 0x068acf8493794756, - 0xd4ada0be58681307, 0x13ffe0f64ca540ed, 0xffc1d7a3401aec02, - 0xd81c4d865cf95fb9, 0x1dd0793acede62e0, 0xa6722abbca8fe4cf, - 0x5453d3e4111a7e40, 0xf29b3e3204c9dcd2, 0x23be2980e43117f7, - 0x74e2ccbc286f08eb, 0x19ef7c0f9496003a, 0xbfbf1c3e49b27987, - 0x6e6c179eb4a82c70, 0x07f4e184216bc4fc, 0xf17fbc4254927554, - 0xe57696b70a45b1b6, 0x6d3b144631b320e8, 0xccf8729792c75a2d, - 0xe832495b41fa980b, 0x5c96cfdc7b227d34, 0xc4dca234ef4e43f4, - 0x5fc801abf9abe307, 0xe41e3c5076d88f4d, 0x522346200ddec3c3, - 0x72bed1946fd7aaa4, 0x0ac1f84dcc335f96, 0x3af78db5e0a47670, - 0x6100ebf1481f1caf, 0xf5fd10037fc651a3, 0xa01227d8944665f3, - 0x7217681c4bbc9420, 0x4adee538e3eb10d1, 0x35e1761ad96de9a7, - 0x8b370aef9613bfba, 0x824506f749eeaf59, 0x85e805fa04423991, - 0xb61e9c33283c3de7, 0xc79721bbcb039ed6, 0x04e1c19a3a1e6639, - 0x6aaf6346b68dd638, 0x601a4b496be6d0c4, 0x3ece355f91c41787, - 0xd2fc8998448d7888, 0xd7529804f843efa9, 0xabdcc38a288536aa, - 0xdd323e48a9718648, 0x2090279c0030a52a, 0xe2f90faca88a3cd1, - 0x3e0c4e92fc50e4aa, 0xa26d308798e801dd, 0x432eefeedee8c02e, - 0xca4ce494614b77df, 0xbba82911e838066d, 0x4b00821016adee4b, - 0x4cf6e526dfb5a20f, 0x5b8466495142cba2, 0xe28ac1406e88a68c, - 0x8511e5f9d3100999, 0x05acbfe02798890b, 0x74c249c7ce4a8425, - 0xdbe7468d09bc34bc, 0x11079ab10e3b9b58, 0xb7788dec9032035a, - 0xb7e8daa786513f80, 0x34c3288831f46b45, 0x014cce5f0c21ecc6, - 0xc6a8f7b024551a28, 0x49784e902e207fd8, 0x4720d32af0b55158, - 0x8df3ec5de0c1da00, 0xf4db677b2c9e6853, 0xaa419abea78d312d, - 0x181e0f91bd757443, 0xa8c45136fada083b, 0x91303b93f5f0582c, - 0x883b95c6ddc62a08, 0x93186a8875fe952b, 0xd94f533928e957e2, - 0x6ba343003e10c172, 0xc8623b620c715d6a, 0x8ca0c512e180e244, - 0xdc9b74c2536b6216, 0x8eb5fdc61b295d96, 0x2ad83966b37c95ba, - 0xb90bf154ac5edec9, 0x902cf847b326cfb3, 0x7b02d0c0ca7808ca, - 0x492f310d003ea15f, 0x3eb6497a47c95990, 0x5d46b0ced31428b7, - 0x081afa67d1986157, 0x043482ec286b20eb, 0xc103c8f18c1a2a53, - 0xe8e9995a81481e83, 0x6bb3295822bc90b5, 0xeec75297a3fa5672, - 0x591c8440c4857412, 0x74947f455aaf24ad, 0xcf0e571586ec77a9, - 0x0c2553ea8c0400ad, 0x380219118066255f, 0x7595adb88b15ebe2, - 0xb33c00696c64ae23, 0xa143516ddd7c9857, 0x39179af229248d26, - 0x65d387a6f2ee2079, 0x89f8a9b21cd2f195, 0xbfef032d25df92e6, - 0x6b7e18a36c69da71, 0x4b3b15f6c28974e6, 0x032a75917f6c544c, - 0xe3b97ecca6d287cd, 0xa4a563110d3cda81, 0x35e09e8134f4e7f1, - 0xc9419dd03a9a390e, 0x7b86fae9000fd329, 0x1e044f8d54fe74c3, - 0x9c4991d7a47e9666, 0xfb485f3a1df4fdb6, 0xb11519969eeb94ff, - 0x3224ea1c44caeb8d, 0x86570bbd7cc6b80d, - }; #else constexpr uint64_t kGolden[kNumGoldenOutputs] = { 0xe5a40d39ab796423, 0x1766974bf7527d81, 0x5c3bbbe230db17a8, diff --git a/absl/log/BUILD.bazel b/absl/log/BUILD.bazel new file mode 100644 index 00000000..e9e411ff --- /dev/null +++ b/absl/log/BUILD.bazel @@ -0,0 +1,596 @@ +# +# 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. +# + +load( + "//absl:copts/configure_copts.bzl", + "ABSL_DEFAULT_COPTS", + "ABSL_DEFAULT_LINKOPTS", + "ABSL_TEST_COPTS", +) + +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +# Public targets + +cc_library( + name = "absl_check", + hdrs = ["absl_check.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/log/internal:check_impl", + ], +) + +cc_library( + name = "absl_log", + hdrs = ["absl_log.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/log/internal:log_impl", + ], +) + +cc_library( + name = "check", + hdrs = ["check.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/log/internal:check_impl", + "//absl/log/internal:check_op", + "//absl/log/internal:conditions", + "//absl/log/internal:log_message", + "//absl/log/internal:strip", + ], +) + +cc_library( + name = "die_if_null", + srcs = ["die_if_null.cc"], + hdrs = ["die_if_null.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/strings", + ], +) + +cc_library( + name = "flags", + srcs = ["flags.cc"], + hdrs = ["flags.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:public"], + deps = [ + ":globals", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/flags:flag", + "//absl/flags:marshalling", + "//absl/log/internal:config", + "//absl/log/internal:flags", + "//absl/strings", + ], + # Binaries which do not access these flags from C++ still want this library linked in. + alwayslink = True, +) + +cc_library( + name = "globals", + srcs = ["globals.cc"], + hdrs = ["globals.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:public"], + deps = [ + "//absl/base:atomic_hook", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/hash", + "//absl/strings", + ], +) + +cc_library( + name = "initialize", + srcs = ["initialize.cc"], + hdrs = ["initialize.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:public"], + deps = [ + ":globals", + "//absl/base:config", + "//absl/log/internal:globals", + "//absl/time", + ], +) + +cc_library( + name = "log", + hdrs = ["log.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/log/internal:log_impl", + ], +) + +cc_library( + name = "log_entry", + srcs = ["log_entry.cc"], + hdrs = ["log_entry.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/log/internal:config", + "//absl/strings", + "//absl/time", + "//absl/types:span", + ], +) + +cc_library( + name = "log_sink", + srcs = ["log_sink.cc"], + hdrs = ["log_sink.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log_entry", + "//absl/base:config", + ], +) + +cc_library( + name = "log_sink_registry", + hdrs = ["log_sink_registry.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log_sink", + "//absl/base:config", + "//absl/log/internal:log_sink_set", + ], +) + +cc_library( + name = "log_streamer", + hdrs = ["log_streamer.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":absl_log", + "//absl/base:config", + "//absl/base:log_severity", + "//absl/strings", + "//absl/strings:internal", + "//absl/types:optional", + "//absl/utility", + ], +) + +cc_library( + name = "scoped_mock_log", + testonly = True, + srcs = ["scoped_mock_log.cc"], + hdrs = ["scoped_mock_log.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log_entry", + ":log_sink", + ":log_sink_registry", + "//absl/base:config", + "//absl/base:log_severity", + "//absl/base:raw_logging_internal", + "//absl/strings", + "@com_google_googletest//:gtest", + ], +) + +cc_library( + name = "structured", + hdrs = ["structured.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base:config", + "//absl/log/internal:structured", + "//absl/strings", + ], +) + +# Test targets + +cc_test( + name = "absl_check_test", + size = "small", + srcs = ["absl_check_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = [ + "no_test:os:ios", + "no_test_ios", + "no_test_wasm", + ], + deps = [ + ":absl_check", + ":check_test_impl", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "absl_log_basic_test", + size = "small", + srcs = ["absl_log_basic_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":absl_log", + ":log_basic_test_impl", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "check_test", + size = "small", + srcs = ["check_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = [ + "no_test:os:ios", + "no_test_ios", + "no_test_wasm", + ], + deps = [ + ":check", + ":check_test_impl", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "check_test_impl", + testonly = True, + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = [ + "no_test:os:ios", + "no_test_ios", + "no_test_wasm", + ], + textual_hdrs = ["check_test_impl.h"], + visibility = ["//visibility:private"], + deps = [ + "//absl/base:config", + "//absl/base:core_headers", + "//absl/log/internal:test_helpers", + "//absl/status", + "@com_google_googletest//:gtest", + ], +) + +cc_test( + name = "die_if_null_test", + size = "small", + srcs = ["die_if_null_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":die_if_null", + "//absl/base:core_headers", + "//absl/log/internal:test_helpers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "flags_test", + size = "small", + srcs = ["flags_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":flags", + ":globals", + ":log", + ":scoped_mock_log", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/flags:flag", + "//absl/flags:reflection", + "//absl/log/internal:flags", + "//absl/log/internal:test_helpers", + "//absl/log/internal:test_matchers", + "//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "globals_test", + size = "small", + srcs = ["globals_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":globals", + ":log", + ":scoped_mock_log", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/log/internal:globals", + "//absl/log/internal:test_helpers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "log_basic_test", + size = "small", + srcs = ["log_basic_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log", + ":log_basic_test_impl", + "@com_google_googletest//:gtest_main", + ], +) + +cc_library( + name = "log_basic_test_impl", + testonly = True, + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + textual_hdrs = ["log_basic_test_impl.h"], + visibility = ["//visibility:private"], + deps = [ + "//absl/base", + "//absl/base:log_severity", + "//absl/log:globals", + "//absl/log:log_entry", + "//absl/log:scoped_mock_log", + "//absl/log/internal:test_actions", + "//absl/log/internal:test_helpers", + "//absl/log/internal:test_matchers", + "@com_google_googletest//:gtest", + ], +) + +cc_test( + name = "log_entry_test", + size = "small", + srcs = ["log_entry_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log_entry", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/log/internal:append_truncated", + "//absl/log/internal:format", + "//absl/log/internal:test_helpers", + "//absl/strings", + "//absl/time", + "//absl/types:span", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "log_format_test", + size = "small", + srcs = ["log_format_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":check", + ":log", + ":scoped_mock_log", + "//absl/log/internal:test_matchers", + "//absl/strings", + "//absl/strings:str_format", + "//absl/types:optional", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "log_macro_hygiene_test", + size = "small", + srcs = ["log_macro_hygiene_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log", + ":scoped_mock_log", + "//absl/base:core_headers", + "//absl/base:log_severity", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "log_sink_test", + size = "medium", + srcs = ["log_sink_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = [ + "no_test:os:ios", + "no_test_ios", + "no_test_wasm", + ], + deps = [ + ":log", + ":log_sink", + ":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_main", + ], +) + +cc_test( + name = "log_streamer_test", + size = "medium", + srcs = ["log_streamer_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log", + ":log_streamer", + ":scoped_mock_log", + "//absl/base", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/log/internal:test_actions", + "//absl/log/internal:test_helpers", + "//absl/log/internal:test_matchers", + "//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "log_modifier_methods_test", + size = "small", + srcs = ["log_modifier_methods_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log", + ":log_sink", + ":scoped_mock_log", + "//absl/log/internal:test_actions", + "//absl/log/internal:test_helpers", + "//absl/log/internal:test_matchers", + "//absl/strings", + "//absl/time", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "scoped_mock_log_test", + size = "small", + srcs = ["scoped_mock_log_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + linkstatic = 1, + tags = [ + "no_test:os:ios", + "no_test_ios", + "no_test_wasm", + ], + deps = [ + ":globals", + ":log", + ":scoped_mock_log", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/log/internal:test_helpers", + "//absl/log/internal:test_matchers", + "//absl/memory", + "//absl/strings", + "//absl/synchronization", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "stripping_test", + size = "small", + srcs = ["stripping_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + # This test requires all code live in the binary (instead of shared libraries) + # because we test for the existence of specific literals in the binary. + linkstatic = 1, + deps = [ + ":check", + ":log", + "//absl/base:strerror", + "//absl/flags:program_name", + "//absl/log/internal:test_helpers", + "//absl/strings", + "//absl/strings:str_format", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( + name = "structured_test", + size = "small", + srcs = ["structured_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log", + ":scoped_mock_log", + ":structured", + "//absl/base:core_headers", + "//absl/log/internal:test_helpers", + "//absl/log/internal:test_matchers", + "@com_google_googletest//:gtest_main", + ], +) + +cc_binary( + name = "log_benchmark", + testonly = 1, + srcs = ["log_benchmark.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = ["benchmark"], + deps = [ + ":check", + ":flags", + ":globals", + ":log", + ":log_entry", + ":log_sink", + ":log_sink_registry", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/flags:flag", + "//absl/log/internal:flags", + "@com_github_google_benchmark//:benchmark_main", + ], +) diff --git a/absl/log/CMakeLists.txt b/absl/log/CMakeLists.txt new file mode 100644 index 00000000..fb1b59f5 --- /dev/null +++ b/absl/log/CMakeLists.txt @@ -0,0 +1,1042 @@ +# +# 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. +# + +# Internal targets +absl_cc_library( + NAME + log_internal_check_impl + SRCS + HDRS + "internal/check_impl.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::core_headers + absl::log_internal_check_op + absl::log_internal_conditions + absl::log_internal_message + absl::log_internal_strip +) + +absl_cc_library( + NAME + log_internal_check_op + SRCS + "internal/check_op.cc" + HDRS + "internal/check_op.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::core_headers + absl::log_internal_nullguard + absl::log_internal_nullstream + absl::log_internal_strip + absl::strings +) + +absl_cc_library( + NAME + log_internal_conditions + SRCS + "internal/conditions.cc" + HDRS + "internal/conditions.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::base + absl::config + absl::core_headers + absl::log_internal_voidify +) + +absl_cc_library( + NAME + log_internal_config + SRCS + HDRS + "internal/config.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::core_headers +) + +absl_cc_library( + NAME + log_internal_flags + SRCS + HDRS + "internal/flags.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::flags +) + +absl_cc_library( + NAME + log_internal_format + SRCS + "internal/log_format.cc" + HDRS + "internal/log_format.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::core_headers + absl::log_internal_append_truncated + absl::log_internal_config + absl::log_internal_globals + absl::log_severity + absl::strings + absl::str_format + absl::time + absl::span +) + +absl_cc_library( + NAME + log_internal_globals + SRCS + "internal/globals.cc" + HDRS + "internal/globals.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::core_headers + absl::log_severity + absl::raw_logging_internal + absl::strings + absl::time +) + +absl_cc_library( + NAME + log_internal_log_impl + SRCS + HDRS + "internal/log_impl.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::log_internal_conditions + absl::log_internal_message + absl::log_internal_strip +) + +absl_cc_library( + NAME + log_internal_proto + SRCS + "internal/proto.cc" + HDRS + "internal/proto.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::base + absl::config + absl::core_headers + absl::strings + absl::span +) + +absl_cc_library( + NAME + log_internal_message + SRCS + "internal/log_message.cc" + HDRS + "internal/log_message.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::base + absl::config + absl::core_headers + absl::errno_saver + absl::inlined_vector + absl::examine_stack + absl::log_internal_append_truncated + absl::log_internal_format + absl::log_internal_globals + absl::log_internal_proto + absl::log_internal_log_sink_set + absl::log_internal_nullguard + absl::log_globals + absl::log_entry + absl::log_severity + absl::log_sink + absl::log_sink_registry + absl::memory + absl::raw_logging_internal + absl::strings + absl::strerror + absl::time + absl::span +) + +absl_cc_library( + NAME + log_internal_log_sink_set + SRCS + "internal/log_sink_set.cc" + HDRS + "internal/log_sink_set.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + $<$<BOOL:${ANDROID}>:-llog> + DEPS + absl::base + absl::cleanup + absl::config + absl::core_headers + absl::log_internal_config + absl::log_internal_globals + absl::log_globals + absl::log_entry + absl::log_severity + absl::log_sink + absl::raw_logging_internal + absl::synchronization + absl::span + absl::strings +) + +absl_cc_library( + NAME + log_internal_nullguard + SRCS + "internal/nullguard.cc" + HDRS + "internal/nullguard.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::core_headers +) + +absl_cc_library( + NAME + log_internal_nullstream + SRCS + HDRS + "internal/nullstream.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::core_headers + absl::log_severity + absl::strings +) + +absl_cc_library( + NAME + log_internal_strip + SRCS + HDRS + "internal/strip.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::log_internal_message + absl::log_internal_nullstream + absl::log_severity +) + +absl_cc_library( + NAME + log_internal_test_actions + SRCS + "internal/test_actions.cc" + HDRS + "internal/test_actions.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::core_headers + absl::log_entry + absl::log_severity + absl::strings + absl::time + TESTONLY +) + +absl_cc_library( + NAME + log_internal_test_helpers + SRCS + "internal/test_helpers.cc" + HDRS + "internal/test_helpers.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::log_globals + absl::log_initialize + absl::log_internal_globals + absl::log_severity + GTest::gtest + TESTONLY +) + +absl_cc_library( + NAME + log_internal_test_matchers + SRCS + "internal/test_matchers.cc" + HDRS + "internal/test_matchers.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::core_headers + absl::log_entry + absl::log_internal_test_helpers + absl::log_severity + absl::strings + absl::time + GTest::gtest + GTest::gmock + TESTONLY +) + +absl_cc_library( + NAME + log_internal_voidify + SRCS + HDRS + "internal/voidify.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config +) + +absl_cc_library( + NAME + log_internal_append_truncated + SRCS + HDRS + "internal/append_truncated.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::strings + absl::span +) + +# Public targets +absl_cc_library( + NAME + absl_check + SRCS + HDRS + "absl_check.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::log_internal_check_impl + PUBLIC +) + +absl_cc_library( + NAME + absl_log + SRCS + HDRS + "absl_log.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::log_internal_log_impl + PUBLIC +) + +absl_cc_library( + NAME + check + SRCS + HDRS + "check.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::log_internal_check_impl + absl::core_headers + absl::log_internal_check_op + absl::log_internal_conditions + absl::log_internal_message + absl::log_internal_strip + PUBLIC +) + +absl_cc_library( + NAME + die_if_null + SRCS + "die_if_null.cc" + HDRS + "die_if_null.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::core_headers + absl::log + absl::strings + PUBLIC +) + +absl_cc_library( + NAME + log_flags + SRCS + "flags.cc" + HDRS + "flags.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::core_headers + absl::log_globals + absl::log_severity + absl::log_internal_config + absl::log_internal_flags + absl::flags + absl::flags_marshalling + absl::strings + PUBLIC +) + +absl_cc_library( + NAME + log_globals + SRCS + "globals.cc" + HDRS + "globals.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::atomic_hook + absl::config + absl::core_headers + absl::hash + absl::log_severity + absl::strings +) + +absl_cc_library( + NAME + log_initialize + SRCS + "initialize.cc" + HDRS + "initialize.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::log_globals + absl::log_internal_globals + absl::time + PUBLIC +) + +absl_cc_library( + NAME + log + SRCS + HDRS + "log.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::log_internal_log_impl + PUBLIC +) + +absl_cc_library( + NAME + log_entry + SRCS + "log_entry.cc" + HDRS + "log_entry.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::core_headers + absl::log_internal_config + absl::log_severity + absl::span + absl::strings + absl::time + PUBLIC +) + +absl_cc_library( + NAME + log_sink + SRCS + "log_sink.cc" + HDRS + "log_sink.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::log_entry + PUBLIC +) + +absl_cc_library( + NAME + log_sink_registry + SRCS + HDRS + "log_sink_registry.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::log_sink + absl::log_internal_log_sink_set + PUBLIC +) + +absl_cc_library( + NAME + log_streamer + SRCS + HDRS + "log_streamer.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::absl_log + absl::log_severity + absl::optional + absl::strings + absl::strings_internal + absl::utility + PUBLIC +) + +absl_cc_library( + NAME + scoped_mock_log + SRCS + "scoped_mock_log.cc" + HDRS + "scoped_mock_log.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::log_entry + absl::log_severity + absl::log_sink + absl::log_sink_registry + absl::raw_logging_internal + absl::strings + GTest::gmock + GTest::gtest + PUBLIC + TESTONLY +) + +absl_cc_library( + NAME + log_internal_structured + HDRS + "internal/structured.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::log_internal_message + absl::strings +) + +absl_cc_library( + NAME + log_structured + HDRS + "structured.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::log_internal_structured + absl::strings + PUBLIC +) + +# Test targets + +absl_cc_test( + NAME + absl_check_test + SRCS + "absl_check_test.cc" + "check_test_impl.h" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::absl_check + absl::config + absl::core_headers + absl::log_internal_test_helpers + absl::status + GTest::gmock + GTest::gtest_main +) + +absl_cc_test( + NAME + absl_log_basic_test + SRCS + "log_basic_test.cc" + "log_basic_test_impl.h" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::base + absl::absl_log + absl::log_entry + absl::log_globals + absl::log_severity + absl::log_internal_test_actions + absl::log_internal_test_helpers + absl::log_internal_test_matchers + absl::scoped_mock_log + GTest::gmock + GTest::gtest_main +) + +absl_cc_test( + NAME + check_test + SRCS + "check_test.cc" + "check_test_impl.h" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::check + absl::config + absl::core_headers + absl::log_internal_test_helpers + absl::status + GTest::gmock + GTest::gtest_main +) + +absl_cc_test( + NAME + die_if_null_test + SRCS + "die_if_null_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::core_headers + absl::die_if_null + absl::log_internal_test_helpers + GTest::gtest_main +) + +absl_cc_test( + NAME + log_basic_test + SRCS + "log_basic_test.cc" + "log_basic_test_impl.h" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::base + absl::log + absl::log_entry + absl::log_globals + absl::log_severity + absl::log_internal_test_actions + absl::log_internal_test_helpers + absl::log_internal_test_matchers + absl::scoped_mock_log + GTest::gmock + GTest::gtest_main +) + +absl_cc_test( + NAME + log_entry_test + SRCS + "log_entry_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::core_headers + absl::log_entry + absl::log_internal_append_truncated + absl::log_internal_format + absl::log_internal_globals + absl::log_internal_test_helpers + absl::log_severity + absl::span + absl::strings + absl::time + GTest::gmock + GTest::gtest_main +) + +absl_cc_test( + NAME + log_flags_test + SRCS + "flags_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::core_headers + absl::log + absl::log_flags + absl::log_globals + absl::log_internal_flags + absl::log_internal_test_helpers + absl::log_internal_test_matchers + absl::log_severity + absl::flags + absl::flags_reflection + absl::scoped_mock_log + absl::strings + GTest::gmock + GTest::gtest_main +) + +absl_cc_test( + NAME + log_globals_test + SRCS + "globals_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::core_headers + absl::log + absl::log_globals + absl::log_internal_globals + absl::log_internal_test_helpers + absl::log_severity + absl::scoped_mock_log + GTest::gtest_main +) + +absl_cc_test( + NAME + log_format_test + SRCS + "log_format_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::check + absl::log + absl::log_internal_test_matchers + absl::optional + absl::scoped_mock_log + absl::str_format + absl::strings + GTest::gmock + GTest::gtest_main +) + +absl_cc_test( + NAME + log_macro_hygiene_test + SRCS + "log_macro_hygiene_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::core_headers + absl::log + absl::log_severity + absl::scoped_mock_log + GTest::gmock + GTest::gtest_main +) + +absl_cc_test( + NAME + log_sink_test + SRCS + "log_sink_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::core_headers + absl::log + absl::log_internal_test_actions + absl::log_internal_test_helpers + absl::log_internal_test_matchers + absl::log_sink + absl::log_sink_registry + absl::log_severity + absl::raw_logging_internal + absl::scoped_mock_log + absl::strings + GTest::gtest_main +) + +absl_cc_test( + NAME + log_streamer_test + SRCS + "log_streamer_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::base + absl::core_headers + absl::log + absl::log_internal_test_actions + absl::log_internal_test_helpers + absl::log_internal_test_matchers + absl::log_streamer + absl::log_severity + absl::scoped_mock_log + absl::strings + GTest::gtest_main +) + +absl_cc_test( + NAME + log_modifier_methods_test + SRCS + "log_modifier_methods_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::log + absl::log_internal_test_actions + absl::log_internal_test_helpers + absl::log_internal_test_matchers + absl::log_sink + absl::scoped_mock_log + absl::strings + absl::time + GTest::gmock + GTest::gtest_main +) + +absl_cc_test( + NAME + scoped_mock_log_test + SRCS + "scoped_mock_log_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::core_headers + absl::log + absl::log_globals + absl::log_internal_globals + absl::log_internal_test_helpers + absl::log_internal_test_matchers + absl::log_severity + absl::memory + absl::scoped_mock_log + absl::strings + absl::synchronization + GTest::gmock + GTest::gtest_main +) + +absl_cc_test( + NAME + log_internal_stderr_log_sink_test + SRCS + "internal/stderr_log_sink_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::core_headers + absl::log + absl::log_globals + absl::log_internal_test_helpers + absl::log_severity + GTest::gmock + GTest::gtest_main +) + +absl_cc_test( + NAME + log_stripping_test + SRCS + "stripping_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::check + absl::flags_program_name + absl::log + absl::log_internal_test_helpers + absl::strerror + absl::strings + absl::str_format + GTest::gmock + GTest::gtest_main +) + +absl_cc_test( + NAME + log_structured_test + SRCS + "structured_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::core_headers + absl::log + absl::log_internal_test_helpers + absl::log_internal_test_matchers + absl::log_structured + absl::scoped_mock_log + GTest::gmock + GTest::gtest_main +) diff --git a/absl/log/absl_check.h b/absl/log/absl_check.h new file mode 100644 index 00000000..14a2307f --- /dev/null +++ b/absl/log/absl_check.h @@ -0,0 +1,105 @@ +// 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_check.h +// ----------------------------------------------------------------------------- +// +// This header declares a family of `ABSL_CHECK` macros as alternative spellings +// for `CHECK` macros in `check.h`. +// +// Except for those whose names begin with `ABSL_DCHECK`, these macros are not +// controlled by `NDEBUG` (cf. `assert`), so the check will be executed +// regardless of compilation mode. `ABSL_CHECK` and friends are thus useful for +// confirming invariants in situations where continuing to run would be worse +// than terminating, e.g., due to risk of data corruption or security +// compromise. It is also more robust and portable to deliberately terminate +// at a particular place with a useful message and backtrace than to assume some +// ultimately unspecified and unreliable crashing behavior (such as a +// "segmentation fault"). +// +// For full documentation of each macro, see comments in `check.h`, which has an +// identical set of macros without the ABSL_* prefix. + +#ifndef ABSL_LOG_ABSL_CHECK_H_ +#define ABSL_LOG_ABSL_CHECK_H_ + +#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_EQ(val1, val2) \ + ABSL_CHECK_EQ_IMPL((val1), #val1, (val2), #val2) +#define ABSL_CHECK_NE(val1, val2) \ + ABSL_CHECK_NE_IMPL((val1), #val1, (val2), #val2) +#define ABSL_CHECK_LE(val1, val2) \ + ABSL_CHECK_LE_IMPL((val1), #val1, (val2), #val2) +#define ABSL_CHECK_LT(val1, val2) \ + ABSL_CHECK_LT_IMPL((val1), #val1, (val2), #val2) +#define ABSL_CHECK_GE(val1, val2) \ + ABSL_CHECK_GE_IMPL((val1), #val1, (val2), #val2) +#define ABSL_CHECK_GT(val1, val2) \ + ABSL_CHECK_GT_IMPL((val1), #val1, (val2), #val2) +#define ABSL_QCHECK_EQ(val1, val2) \ + ABSL_QCHECK_EQ_IMPL((val1), #val1, (val2), #val2) +#define ABSL_QCHECK_NE(val1, val2) \ + ABSL_QCHECK_NE_IMPL((val1), #val1, (val2), #val2) +#define ABSL_QCHECK_LE(val1, val2) \ + ABSL_QCHECK_LE_IMPL((val1), #val1, (val2), #val2) +#define ABSL_QCHECK_LT(val1, val2) \ + ABSL_QCHECK_LT_IMPL((val1), #val1, (val2), #val2) +#define ABSL_QCHECK_GE(val1, val2) \ + ABSL_QCHECK_GE_IMPL((val1), #val1, (val2), #val2) +#define ABSL_QCHECK_GT(val1, val2) \ + ABSL_QCHECK_GT_IMPL((val1), #val1, (val2), #val2) +#define ABSL_DCHECK_EQ(val1, val2) \ + ABSL_DCHECK_EQ_IMPL((val1), #val1, (val2), #val2) +#define ABSL_DCHECK_NE(val1, val2) \ + ABSL_DCHECK_NE_IMPL((val1), #val1, (val2), #val2) +#define ABSL_DCHECK_LE(val1, val2) \ + ABSL_DCHECK_LE_IMPL((val1), #val1, (val2), #val2) +#define ABSL_DCHECK_LT(val1, val2) \ + ABSL_DCHECK_LT_IMPL((val1), #val1, (val2), #val2) +#define ABSL_DCHECK_GE(val1, val2) \ + ABSL_DCHECK_GE_IMPL((val1), #val1, (val2), #val2) +#define ABSL_DCHECK_GT(val1, val2) \ + ABSL_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_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_STRCASEEQ(s1, s2) \ + ABSL_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) +#define ABSL_QCHECK_STRCASEEQ(s1, s2) \ + ABSL_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) +#define ABSL_DCHECK_STRCASEEQ(s1, s2) \ + ABSL_DCHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2) +#define ABSL_DCHECK_STRCASENE(s1, s2) \ + ABSL_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 new file mode 100644 index 00000000..8ddacdb1 --- /dev/null +++ b/absl/log/absl_check_test.cc @@ -0,0 +1,58 @@ +// +// 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/absl_check.h" + +#define ABSL_TEST_CHECK ABSL_CHECK +#define ABSL_TEST_CHECK_OK ABSL_CHECK_OK +#define ABSL_TEST_CHECK_EQ ABSL_CHECK_EQ +#define ABSL_TEST_CHECK_NE ABSL_CHECK_NE +#define ABSL_TEST_CHECK_GE ABSL_CHECK_GE +#define ABSL_TEST_CHECK_LE ABSL_CHECK_LE +#define ABSL_TEST_CHECK_GT ABSL_CHECK_GT +#define ABSL_TEST_CHECK_LT ABSL_CHECK_LT +#define ABSL_TEST_CHECK_STREQ ABSL_CHECK_STREQ +#define ABSL_TEST_CHECK_STRNE ABSL_CHECK_STRNE +#define ABSL_TEST_CHECK_STRCASEEQ ABSL_CHECK_STRCASEEQ +#define ABSL_TEST_CHECK_STRCASENE ABSL_CHECK_STRCASENE + +#define ABSL_TEST_DCHECK ABSL_DCHECK +#define ABSL_TEST_DCHECK_OK ABSL_DCHECK_OK +#define ABSL_TEST_DCHECK_EQ ABSL_DCHECK_EQ +#define ABSL_TEST_DCHECK_NE ABSL_DCHECK_NE +#define ABSL_TEST_DCHECK_GE ABSL_DCHECK_GE +#define ABSL_TEST_DCHECK_LE ABSL_DCHECK_LE +#define ABSL_TEST_DCHECK_GT ABSL_DCHECK_GT +#define ABSL_TEST_DCHECK_LT ABSL_DCHECK_LT +#define ABSL_TEST_DCHECK_STREQ ABSL_DCHECK_STREQ +#define ABSL_TEST_DCHECK_STRNE ABSL_DCHECK_STRNE +#define ABSL_TEST_DCHECK_STRCASEEQ ABSL_DCHECK_STRCASEEQ +#define ABSL_TEST_DCHECK_STRCASENE ABSL_DCHECK_STRCASENE + +#define ABSL_TEST_QCHECK ABSL_QCHECK +#define ABSL_TEST_QCHECK_OK ABSL_QCHECK_OK +#define ABSL_TEST_QCHECK_EQ ABSL_QCHECK_EQ +#define ABSL_TEST_QCHECK_NE ABSL_QCHECK_NE +#define ABSL_TEST_QCHECK_GE ABSL_QCHECK_GE +#define ABSL_TEST_QCHECK_LE ABSL_QCHECK_LE +#define ABSL_TEST_QCHECK_GT ABSL_QCHECK_GT +#define ABSL_TEST_QCHECK_LT ABSL_QCHECK_LT +#define ABSL_TEST_QCHECK_STREQ ABSL_QCHECK_STREQ +#define ABSL_TEST_QCHECK_STRNE ABSL_QCHECK_STRNE +#define ABSL_TEST_QCHECK_STRCASEEQ ABSL_QCHECK_STRCASEEQ +#define ABSL_TEST_QCHECK_STRCASENE ABSL_QCHECK_STRCASENE + +#include "gtest/gtest.h" +#include "absl/log/check_test_impl.h" diff --git a/absl/log/absl_log.h b/absl/log/absl_log.h new file mode 100644 index 00000000..1c6cf263 --- /dev/null +++ b/absl/log/absl_log.h @@ -0,0 +1,94 @@ +// 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_log.h +// ----------------------------------------------------------------------------- +// +// This header declares a family of `ABSL_LOG` macros as alternative spellings +// for macros in `log.h`. +// +// Basic invocation looks like this: +// +// ABSL_LOG(INFO) << "Found " << num_cookies << " cookies"; +// +// Most `ABSL_LOG` macros take a severity level argument. The severity levels +// are `INFO`, `WARNING`, `ERROR`, and `FATAL`. +// +// For full documentation, see comments in `log.h`, which includes full +// reference documentation on use of the equivalent `LOG` macro and has an +// identical set of macros without the ABSL_* prefix. + +#ifndef ABSL_LOG_ABSL_LOG_H_ +#define ABSL_LOG_ABSL_LOG_H_ + +#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_IF(severity, condition) \ + ABSL_LOG_IF_IMPL(_##severity, condition) +#define ABSL_PLOG_IF(severity, condition) \ + ABSL_PLOG_IF_IMPL(_##severity, condition) +#define ABSL_DLOG_IF(severity, condition) \ + ABSL_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_SEC(severity, n_seconds) \ + ABSL_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_SEC(severity, n_seconds) \ + ABSL_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_SEC(severity, n_seconds) \ + ABSL_DLOG_EVERY_N_SEC_IMPL(_##severity, n_seconds) + +#define ABSL_LOG_IF_EVERY_N(severity, condition, n) \ + ABSL_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) +#define ABSL_LOG_IF_EVERY_POW_2(severity, condition) \ + ABSL_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) + +#define ABSL_PLOG_IF_EVERY_N(severity, condition, n) \ + ABSL_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) +#define ABSL_PLOG_IF_EVERY_POW_2(severity, condition) \ + ABSL_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) + +#define ABSL_DLOG_IF_EVERY_N(severity, condition, n) \ + ABSL_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) +#define ABSL_DLOG_IF_EVERY_POW_2(severity, condition) \ + ABSL_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) + +#endif // ABSL_LOG_ABSL_LOG_H_ diff --git a/absl/log/absl_log_basic_test.cc b/absl/log/absl_log_basic_test.cc new file mode 100644 index 00000000..bc8a787d --- /dev/null +++ b/absl/log/absl_log_basic_test.cc @@ -0,0 +1,21 @@ +// +// 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/absl_log.h" + +#define ABSL_TEST_LOG ABSL_LOG + +#include "gtest/gtest.h" +#include "absl/log/log_basic_test_impl.h" diff --git a/absl/log/check.h b/absl/log/check.h new file mode 100644 index 00000000..33145a57 --- /dev/null +++ b/absl/log/check.h @@ -0,0 +1,183 @@ +// 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/check.h +// ----------------------------------------------------------------------------- +// +// This header declares a family of `CHECK` macros. +// +// `CHECK` macros terminate the program with a fatal error if the specified +// condition is not true. +// +// Except for those whose names begin with `DCHECK`, these macros are not +// controlled by `NDEBUG` (cf. `assert`), so the check will be executed +// regardless of compilation mode. `CHECK` and friends are thus useful for +// confirming invariants in situations where continuing to run would be worse +// than terminating, e.g., due to risk of data corruption or security +// compromise. It is also more robust and portable to deliberately terminate +// at a particular place with a useful message and backtrace than to assume some +// ultimately unspecified and unreliable crashing behavior (such as a +// "segmentation fault"). + +#ifndef ABSL_LOG_CHECK_H_ +#define ABSL_LOG_CHECK_H_ + +#include "absl/log/internal/check_impl.h" +#include "absl/log/internal/check_op.h" // IWYU pragma: export +#include "absl/log/internal/conditions.h" // IWYU pragma: export +#include "absl/log/internal/log_message.h" // IWYU pragma: export +#include "absl/log/internal/strip.h" // IWYU pragma: export + +// CHECK() +// +// `CHECK` terminates the program with a fatal error if `condition` is not true. +// +// The message may include additional information such as stack traces, when +// available. +// +// Example: +// +// CHECK(!cheese.empty()) << "Out of Cheese"; +// +// Might produce a message like: +// +// Check failed: !cheese.empty() Out of Cheese +#define CHECK(condition) ABSL_CHECK_IMPL((condition), #condition) + +// QCHECK() +// +// `QCHECK` behaves like `CHECK` but does not print a full stack trace and does +// 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) + +// PCHECK() +// +// `PCHECK` behaves like `CHECK` but appends a description of the current state +// of `errno` to the failure message. +// +// Example: +// +// int fd = open("/var/empty/missing", O_RDONLY); +// PCHECK(fd != -1) << "posix is difficult"; +// +// 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) + +// DCHECK() +// +// `DCHECK` behaves like `CHECK` in debug mode and does nothing otherwise (as +// `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) + +// `CHECK_EQ` and friends are syntactic sugar for `CHECK(x == y)` that +// automatically output the expression being tested and the evaluated values on +// either side. +// +// Example: +// +// int x = 3, y = 5; +// CHECK_EQ(2 * x, y) << "oops!"; +// +// Might produce a message like: +// +// Check failed: 2 * x == y (6 vs. 5) oops! +// +// The values must implement the appropriate comparison operator as well as +// `operator<<(std::ostream&, ...)`. Care is taken to ensure that each +// argument is evaluated exactly once, and that anything which is legal to pass +// as a function argument is legal here. In particular, the arguments may be +// temporary expressions which will end up being destroyed at the end of the +// statement, +// +// Example: +// +// CHECK_EQ(std::string("abc")[1], 'b'); +// +// 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) + +// `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 +// includes the actual status and terminate the program. +// +// As with all `DCHECK` variants, `DCHECK_OK` has no effect (not even +// evaluating its argument) if `NDEBUG` is enabled. +// +// Example: +// +// CHECK_OK(FunctionReturnsStatus(x, y, z)) << "oops!"; +// +// 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) + +// `CHECK_STREQ` and friends provide `CHECK_EQ` functionality for C strings, +// i.e., nul-terminated char arrays. The `CASE` versions are case-insensitive. +// +// Example: +// +// CHECK_STREQ(argv[0], "./skynet"); +// +// Note that both arguments may be temporary strings which are destroyed by the +// compiler at the end of the current full expression. +// +// 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 QCHECK_STRCASEEQ(s1, s2) \ + ABSL_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) +#define DCHECK_STRCASEEQ(s1, s2) \ + ABSL_DCHECK_STRCASEEQ_IMPL((s1), #s1, (s2), #s2) +#define DCHECK_STRCASENE(s1, s2) \ + ABSL_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 new file mode 100644 index 00000000..f44a686e --- /dev/null +++ b/absl/log/check_test.cc @@ -0,0 +1,58 @@ +// +// 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/check.h" + +#define ABSL_TEST_CHECK CHECK +#define ABSL_TEST_CHECK_OK CHECK_OK +#define ABSL_TEST_CHECK_EQ CHECK_EQ +#define ABSL_TEST_CHECK_NE CHECK_NE +#define ABSL_TEST_CHECK_GE CHECK_GE +#define ABSL_TEST_CHECK_LE CHECK_LE +#define ABSL_TEST_CHECK_GT CHECK_GT +#define ABSL_TEST_CHECK_LT CHECK_LT +#define ABSL_TEST_CHECK_STREQ CHECK_STREQ +#define ABSL_TEST_CHECK_STRNE CHECK_STRNE +#define ABSL_TEST_CHECK_STRCASEEQ CHECK_STRCASEEQ +#define ABSL_TEST_CHECK_STRCASENE CHECK_STRCASENE + +#define ABSL_TEST_DCHECK DCHECK +#define ABSL_TEST_DCHECK_OK DCHECK_OK +#define ABSL_TEST_DCHECK_EQ DCHECK_EQ +#define ABSL_TEST_DCHECK_NE DCHECK_NE +#define ABSL_TEST_DCHECK_GE DCHECK_GE +#define ABSL_TEST_DCHECK_LE DCHECK_LE +#define ABSL_TEST_DCHECK_GT DCHECK_GT +#define ABSL_TEST_DCHECK_LT DCHECK_LT +#define ABSL_TEST_DCHECK_STREQ DCHECK_STREQ +#define ABSL_TEST_DCHECK_STRNE DCHECK_STRNE +#define ABSL_TEST_DCHECK_STRCASEEQ DCHECK_STRCASEEQ +#define ABSL_TEST_DCHECK_STRCASENE DCHECK_STRCASENE + +#define ABSL_TEST_QCHECK QCHECK +#define ABSL_TEST_QCHECK_OK QCHECK_OK +#define ABSL_TEST_QCHECK_EQ QCHECK_EQ +#define ABSL_TEST_QCHECK_NE QCHECK_NE +#define ABSL_TEST_QCHECK_GE QCHECK_GE +#define ABSL_TEST_QCHECK_LE QCHECK_LE +#define ABSL_TEST_QCHECK_GT QCHECK_GT +#define ABSL_TEST_QCHECK_LT QCHECK_LT +#define ABSL_TEST_QCHECK_STREQ QCHECK_STREQ +#define ABSL_TEST_QCHECK_STRNE QCHECK_STRNE +#define ABSL_TEST_QCHECK_STRCASEEQ QCHECK_STRCASEEQ +#define ABSL_TEST_QCHECK_STRCASENE QCHECK_STRCASENE + +#include "gtest/gtest.h" +#include "absl/log/check_test_impl.h" diff --git a/absl/log/check_test_impl.h b/absl/log/check_test_impl.h new file mode 100644 index 00000000..d5c0aee4 --- /dev/null +++ b/absl/log/check_test_impl.h @@ -0,0 +1,528 @@ +// +// 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_LOG_CHECK_TEST_IMPL_H_ +#define ABSL_LOG_CHECK_TEST_IMPL_H_ + +// Verify that both sets of macros behave identically by parameterizing the +// entire test file. +#ifndef ABSL_TEST_CHECK +#error ABSL_TEST_CHECK must be defined for these tests to work. +#endif + +#include <ostream> +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/status/status.h" + +// NOLINTBEGIN(misc-definitions-in-headers) + +namespace absl_log_internal { + +using ::testing::AllOf; +using ::testing::HasSubstr; +using ::testing::Not; + +auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( + new absl::log_internal::LogTestEnvironment); + +#if GTEST_HAS_DEATH_TEST + +TEST(CHECKDeathTest, TestBasicValues) { + ABSL_TEST_CHECK(true); + + EXPECT_DEATH(ABSL_TEST_CHECK(false), "Check failed: false"); + + int i = 2; + ABSL_TEST_CHECK(i != 3); // NOLINT +} + +#endif // GTEST_HAS_DEATH_TEST + +TEST(CHECKTest, TestLogicExpressions) { + int i = 5; + ABSL_TEST_CHECK(i > 0 && i < 10); + ABSL_TEST_CHECK(i < 0 || i > 3); +} + +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L +ABSL_CONST_INIT const auto global_var_check = [](int i) { + ABSL_TEST_CHECK(i > 0); // NOLINT + return i + 1; +}(3); + +ABSL_CONST_INIT const auto global_var = [](int i) { + ABSL_TEST_CHECK_GE(i, 0); // NOLINT + return i + 1; +}(global_var_check); +#endif // ABSL_INTERNAL_CPLUSPLUS_LANG + +TEST(CHECKTest, TestPlacementsInCompoundStatements) { + // check placement inside if/else clauses + if (true) ABSL_TEST_CHECK(true); + + if (false) + ; // NOLINT + else + ABSL_TEST_CHECK(true); + + switch (0) + case 0: + ABSL_TEST_CHECK(true); // NOLINT + +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L + constexpr auto var = [](int i) { + ABSL_TEST_CHECK(i > 0); // NOLINT + return i + 1; + }(global_var); + (void)var; +#endif // ABSL_INTERNAL_CPLUSPLUS_LANG +} + +TEST(CHECKTest, TestBoolConvertible) { + struct Tester { + } tester; + ABSL_TEST_CHECK([&]() { return &tester; }()); +} + +#if GTEST_HAS_DEATH_TEST + +TEST(CHECKDeathTest, TestChecksWithSideEffects) { + int var = 0; + ABSL_TEST_CHECK([&var]() { + ++var; + return true; + }()); + EXPECT_EQ(var, 1); + + EXPECT_DEATH(ABSL_TEST_CHECK([&var]() { + ++var; + return false; + }()) << var, + "Check failed: .* 2"); +} + +#endif // GTEST_HAS_DEATH_TEST + +template <int a, int b> +constexpr int sum() { + return a + b; +} +#define MACRO_ONE 1 +#define TEMPLATE_SUM(a, b) sum<a, b>() +#define CONCAT(a, b) a b +#define IDENTITY(x) x + +TEST(CHECKTest, TestPassingMacroExpansion) { + ABSL_TEST_CHECK(IDENTITY(true)); + ABSL_TEST_CHECK_EQ(TEMPLATE_SUM(MACRO_ONE, 2), 3); + ABSL_TEST_CHECK_STREQ(CONCAT("x", "y"), "xy"); +} + +#if GTEST_HAS_DEATH_TEST + +TEST(CHECKTest, TestMacroExpansionInMessage) { + auto MessageGen = []() { ABSL_TEST_CHECK(IDENTITY(false)); }; + EXPECT_DEATH(MessageGen(), HasSubstr("IDENTITY(false)")); +} + +TEST(CHECKTest, TestNestedMacroExpansionInMessage) { + EXPECT_DEATH(ABSL_TEST_CHECK(IDENTITY(false)), HasSubstr("IDENTITY(false)")); +} + +TEST(CHECKTest, TestMacroExpansionCompare) { + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(IDENTITY(false), IDENTITY(true)), + HasSubstr("IDENTITY(false) == IDENTITY(true)")); + EXPECT_DEATH(ABSL_TEST_CHECK_GT(IDENTITY(1), IDENTITY(2)), + HasSubstr("IDENTITY(1) > IDENTITY(2)")); +} + +TEST(CHECKTest, TestMacroExpansionStrCompare) { + EXPECT_DEATH(ABSL_TEST_CHECK_STREQ(IDENTITY("x"), IDENTITY("y")), + HasSubstr("IDENTITY(\"x\") == IDENTITY(\"y\")")); + EXPECT_DEATH(ABSL_TEST_CHECK_STRCASENE(IDENTITY("a"), IDENTITY("A")), + HasSubstr("IDENTITY(\"a\") != IDENTITY(\"A\")")); +} + +TEST(CHECKTest, TestMacroExpansionStatus) { + EXPECT_DEATH( + ABSL_TEST_CHECK_OK(IDENTITY(absl::FailedPreconditionError("message"))), + HasSubstr("IDENTITY(absl::FailedPreconditionError(\"message\"))")); +} + +TEST(CHECKTest, TestMacroExpansionComma) { + EXPECT_DEATH(ABSL_TEST_CHECK(TEMPLATE_SUM(MACRO_ONE, 2) == 4), + HasSubstr("TEMPLATE_SUM(MACRO_ONE, 2) == 4")); +} + +TEST(CHECKTest, TestMacroExpansionCommaCompare) { + EXPECT_DEATH( + ABSL_TEST_CHECK_EQ(TEMPLATE_SUM(2, MACRO_ONE), TEMPLATE_SUM(3, 2)), + HasSubstr("TEMPLATE_SUM(2, MACRO_ONE) == TEMPLATE_SUM(3, 2)")); + EXPECT_DEATH( + ABSL_TEST_CHECK_GT(TEMPLATE_SUM(2, MACRO_ONE), TEMPLATE_SUM(3, 2)), + HasSubstr("TEMPLATE_SUM(2, MACRO_ONE) > TEMPLATE_SUM(3, 2)")); +} + +TEST(CHECKTest, TestMacroExpansionCommaStrCompare) { + EXPECT_DEATH(ABSL_TEST_CHECK_STREQ(CONCAT("x", "y"), "z"), + HasSubstr("CONCAT(\"x\", \"y\") == \"z\"")); + EXPECT_DEATH(ABSL_TEST_CHECK_STRNE(CONCAT("x", "y"), "xy"), + HasSubstr("CONCAT(\"x\", \"y\") != \"xy\"")); +} + +#endif // GTEST_HAS_DEATH_TEST + +#undef TEMPLATE_SUM +#undef CONCAT +#undef MACRO +#undef ONE + +#if GTEST_HAS_DEATH_TEST + +TEST(CHECKDeachTest, TestOrderOfInvocationsBetweenCheckAndMessage) { + int counter = 0; + + auto GetStr = [&counter]() -> std::string { + return counter++ == 0 ? "" : "non-empty"; + }; + + EXPECT_DEATH(ABSL_TEST_CHECK(!GetStr().empty()) << GetStr(), + HasSubstr("non-empty")); +} + +TEST(CHECKTest, TestSecondaryFailure) { + auto FailingRoutine = []() { + ABSL_TEST_CHECK(false) << "Secondary"; + return false; + }; + EXPECT_DEATH(ABSL_TEST_CHECK(FailingRoutine()) << "Primary", + AllOf(HasSubstr("Secondary"), Not(HasSubstr("Primary")))); +} + +TEST(CHECKTest, TestSecondaryFailureInMessage) { + auto MessageGen = []() { + ABSL_TEST_CHECK(false) << "Secondary"; + return "Primary"; + }; + EXPECT_DEATH(ABSL_TEST_CHECK(false) << MessageGen(), + AllOf(HasSubstr("Secondary"), Not(HasSubstr("Primary")))); +} + +#endif // GTEST_HAS_DEATH_TEST + +TEST(CHECKTest, TestBinaryChecksWithPrimitives) { + ABSL_TEST_CHECK_EQ(1, 1); + ABSL_TEST_CHECK_NE(1, 2); + ABSL_TEST_CHECK_GE(1, 1); + ABSL_TEST_CHECK_GE(2, 1); + ABSL_TEST_CHECK_LE(1, 1); + ABSL_TEST_CHECK_LE(1, 2); + ABSL_TEST_CHECK_GT(2, 1); + ABSL_TEST_CHECK_LT(1, 2); +} + +// For testing using CHECK*() on anonymous enums. +enum { CASE_A, CASE_B }; + +TEST(CHECKTest, TestBinaryChecksWithEnumValues) { + // Tests using CHECK*() on anonymous enums. + ABSL_TEST_CHECK_EQ(CASE_A, CASE_A); + ABSL_TEST_CHECK_NE(CASE_A, CASE_B); + ABSL_TEST_CHECK_GE(CASE_A, CASE_A); + ABSL_TEST_CHECK_GE(CASE_B, CASE_A); + ABSL_TEST_CHECK_LE(CASE_A, CASE_A); + ABSL_TEST_CHECK_LE(CASE_A, CASE_B); + ABSL_TEST_CHECK_GT(CASE_B, CASE_A); + ABSL_TEST_CHECK_LT(CASE_A, CASE_B); +} + +TEST(CHECKTest, TestBinaryChecksWithNullptr) { + const void* p_null = nullptr; + const void* p_not_null = &p_null; + ABSL_TEST_CHECK_EQ(p_null, nullptr); + ABSL_TEST_CHECK_EQ(nullptr, p_null); + ABSL_TEST_CHECK_NE(p_not_null, nullptr); + ABSL_TEST_CHECK_NE(nullptr, p_not_null); +} + +#if GTEST_HAS_DEATH_TEST + +// Test logging of various char-typed values by failing CHECK*(). +TEST(CHECKDeathTest, TestComparingCharsValues) { + { + char a = ';'; + char b = 'b'; + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b), + "Check failed: a == b \\(';' vs. 'b'\\)"); + b = 1; + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b), + "Check failed: a == b \\(';' vs. char value 1\\)"); + } + { + signed char a = ';'; + signed char b = 'b'; + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b), + "Check failed: a == b \\(';' vs. 'b'\\)"); + b = -128; + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b), + "Check failed: a == b \\(';' vs. signed char value -128\\)"); + } + { + unsigned char a = ';'; + unsigned char b = 'b'; + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b), + "Check failed: a == b \\(';' vs. 'b'\\)"); + b = 128; + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b), + "Check failed: a == b \\(';' vs. unsigned char value 128\\)"); + } +} + +TEST(CHECKDeathTest, TestNullValuesAreReportedCleanly) { + const char* a = nullptr; + const char* b = nullptr; + EXPECT_DEATH(ABSL_TEST_CHECK_NE(a, b), + "Check failed: a != b \\(\\(null\\) vs. \\(null\\)\\)"); + + a = "xx"; + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b), + "Check failed: a == b \\(xx vs. \\(null\\)\\)"); + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(b, a), + "Check failed: b == a \\(\\(null\\) vs. xx\\)"); + + std::nullptr_t n{}; + EXPECT_DEATH(ABSL_TEST_CHECK_NE(n, nullptr), + "Check failed: n != nullptr \\(\\(null\\) vs. \\(null\\)\\)"); +} + +#endif // GTEST_HAS_DEATH_TEST + +TEST(CHECKTest, TestSTREQ) { + ABSL_TEST_CHECK_STREQ("this", "this"); + ABSL_TEST_CHECK_STREQ(nullptr, nullptr); + ABSL_TEST_CHECK_STRCASEEQ("this", "tHiS"); + ABSL_TEST_CHECK_STRCASEEQ(nullptr, nullptr); + ABSL_TEST_CHECK_STRNE("this", "tHiS"); + ABSL_TEST_CHECK_STRNE("this", nullptr); + ABSL_TEST_CHECK_STRCASENE("this", "that"); + ABSL_TEST_CHECK_STRCASENE(nullptr, "that"); + ABSL_TEST_CHECK_STREQ((std::string("a") + "b").c_str(), "ab"); + ABSL_TEST_CHECK_STREQ(std::string("test").c_str(), + (std::string("te") + std::string("st")).c_str()); +} + +TEST(CHECKTest, TestComparisonPlacementsInCompoundStatements) { + // check placement inside if/else clauses + if (true) ABSL_TEST_CHECK_EQ(1, 1); + if (true) ABSL_TEST_CHECK_STREQ("c", "c"); + + if (false) + ; // NOLINT + else + ABSL_TEST_CHECK_LE(0, 1); + + if (false) + ; // NOLINT + else + ABSL_TEST_CHECK_STRNE("a", "b"); + + switch (0) + case 0: + ABSL_TEST_CHECK_NE(1, 0); + + switch (0) + case 0: + ABSL_TEST_CHECK_STRCASEEQ("A", "a"); + +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L + constexpr auto var = [](int i) { + ABSL_TEST_CHECK_GT(i, 0); + return i + 1; + }(global_var); + (void)var; + + // CHECK_STR... checks are not supported in constexpr routines. + // constexpr auto var2 = [](int i) { + // ABSL_TEST_CHECK_STRNE("c", "d"); + // return i + 1; + // }(global_var); + +#if defined(__GNUC__) + int var3 = (({ ABSL_TEST_CHECK_LE(1, 2); }), global_var < 10) ? 1 : 0; + (void)var3; + + int var4 = (({ ABSL_TEST_CHECK_STREQ("a", "a"); }), global_var < 10) ? 1 : 0; + (void)var4; +#endif // __GNUC__ +#endif // ABSL_INTERNAL_CPLUSPLUS_LANG +} + +TEST(CHECKTest, TestDCHECK) { +#ifdef NDEBUG + ABSL_TEST_DCHECK(1 == 2) << " DCHECK's shouldn't be compiled in normal mode"; +#endif + ABSL_TEST_DCHECK(1 == 1); // NOLINT(readability/check) + ABSL_TEST_DCHECK_EQ(1, 1); + ABSL_TEST_DCHECK_NE(1, 2); + ABSL_TEST_DCHECK_GE(1, 1); + ABSL_TEST_DCHECK_GE(2, 1); + ABSL_TEST_DCHECK_LE(1, 1); + ABSL_TEST_DCHECK_LE(1, 2); + ABSL_TEST_DCHECK_GT(2, 1); + ABSL_TEST_DCHECK_LT(1, 2); + + // Test DCHECK on std::nullptr_t + const void* p_null = nullptr; + const void* p_not_null = &p_null; + ABSL_TEST_DCHECK_EQ(p_null, nullptr); + ABSL_TEST_DCHECK_EQ(nullptr, p_null); + ABSL_TEST_DCHECK_NE(p_not_null, nullptr); + ABSL_TEST_DCHECK_NE(nullptr, p_not_null); +} + +TEST(CHECKTest, TestQCHECK) { + // The tests that QCHECK does the same as CHECK + ABSL_TEST_QCHECK(1 == 1); // NOLINT(readability/check) + ABSL_TEST_QCHECK_EQ(1, 1); + ABSL_TEST_QCHECK_NE(1, 2); + ABSL_TEST_QCHECK_GE(1, 1); + ABSL_TEST_QCHECK_GE(2, 1); + ABSL_TEST_QCHECK_LE(1, 1); + ABSL_TEST_QCHECK_LE(1, 2); + ABSL_TEST_QCHECK_GT(2, 1); + ABSL_TEST_QCHECK_LT(1, 2); + + // Tests using QCHECK*() on anonymous enums. + ABSL_TEST_QCHECK_EQ(CASE_A, CASE_A); + ABSL_TEST_QCHECK_NE(CASE_A, CASE_B); + ABSL_TEST_QCHECK_GE(CASE_A, CASE_A); + ABSL_TEST_QCHECK_GE(CASE_B, CASE_A); + ABSL_TEST_QCHECK_LE(CASE_A, CASE_A); + ABSL_TEST_QCHECK_LE(CASE_A, CASE_B); + ABSL_TEST_QCHECK_GT(CASE_B, CASE_A); + ABSL_TEST_QCHECK_LT(CASE_A, CASE_B); +} + +TEST(CHECKTest, TestQCHECKPlacementsInCompoundStatements) { + // check placement inside if/else clauses + if (true) ABSL_TEST_QCHECK(true); + + if (false) + ; // NOLINT + else + ABSL_TEST_QCHECK(true); + + if (false) + ; // NOLINT + else + ABSL_TEST_QCHECK(true); + + switch (0) + case 0: + ABSL_TEST_QCHECK(true); + +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 201703L + constexpr auto var = [](int i) { + ABSL_TEST_QCHECK(i > 0); // NOLINT + return i + 1; + }(global_var); + (void)var; + +#if defined(__GNUC__) + int var2 = (({ ABSL_TEST_CHECK_LE(1, 2); }), global_var < 10) ? 1 : 0; + (void)var2; +#endif // __GNUC__ +#endif // ABSL_INTERNAL_CPLUSPLUS_LANG +} + +class ComparableType { + public: + explicit ComparableType(int v) : v_(v) {} + + void MethodWithCheck(int i) { + ABSL_TEST_CHECK_EQ(*this, i); + ABSL_TEST_CHECK_EQ(i, *this); + } + + int Get() const { return v_; } + + private: + friend bool operator==(const ComparableType& lhs, const ComparableType& rhs) { + return lhs.v_ == rhs.v_; + } + friend bool operator!=(const ComparableType& lhs, const ComparableType& rhs) { + return lhs.v_ != rhs.v_; + } + friend bool operator<(const ComparableType& lhs, const ComparableType& rhs) { + return lhs.v_ < rhs.v_; + } + friend bool operator<=(const ComparableType& lhs, const ComparableType& rhs) { + return lhs.v_ <= rhs.v_; + } + friend bool operator>(const ComparableType& lhs, const ComparableType& rhs) { + return lhs.v_ > rhs.v_; + } + friend bool operator>=(const ComparableType& lhs, const ComparableType& rhs) { + return lhs.v_ >= rhs.v_; + } + friend bool operator==(const ComparableType& lhs, int rhs) { + return lhs.v_ == rhs; + } + friend bool operator==(int lhs, const ComparableType& rhs) { + return lhs == rhs.v_; + } + + friend std::ostream& operator<<(std::ostream& out, const ComparableType& v) { + return out << "ComparableType{" << v.Get() << "}"; + } + + int v_; +}; + +TEST(CHECKTest, TestUserDefinedCompOp) { + ABSL_TEST_CHECK_EQ(ComparableType{0}, ComparableType{0}); + ABSL_TEST_CHECK_NE(ComparableType{1}, ComparableType{2}); + ABSL_TEST_CHECK_LT(ComparableType{1}, ComparableType{2}); + ABSL_TEST_CHECK_LE(ComparableType{1}, ComparableType{2}); + ABSL_TEST_CHECK_GT(ComparableType{2}, ComparableType{1}); + ABSL_TEST_CHECK_GE(ComparableType{2}, ComparableType{2}); +} + +TEST(CHECKTest, TestCheckInMethod) { + ComparableType v{1}; + v.MethodWithCheck(1); +} + +TEST(CHECKDeathTest, TestUserDefinedStreaming) { + ComparableType v1{1}; + ComparableType v2{2}; + + EXPECT_DEATH( + ABSL_TEST_CHECK_EQ(v1, v2), + HasSubstr( + "Check failed: v1 == v2 (ComparableType{1} vs. ComparableType{2})")); +} + +} // namespace absl_log_internal + +// NOLINTEND(misc-definitions-in-headers) + +#endif // ABSL_LOG_CHECK_TEST_IMPL_H_ diff --git a/absl/log/die_if_null.cc b/absl/log/die_if_null.cc new file mode 100644 index 00000000..19c6a28e --- /dev/null +++ b/absl/log/die_if_null.cc @@ -0,0 +1,32 @@ +// 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/die_if_null.h" + +#include "absl/base/config.h" +#include "absl/log/log.h" +#include "absl/strings/str_cat.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +void DieBecauseNull(const char* file, int line, const char* exprtext) { + LOG(FATAL).AtLocation(file, line) + << absl::StrCat("Check failed: '", exprtext, "' Must be non-null"); +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/die_if_null.h b/absl/log/die_if_null.h new file mode 100644 index 00000000..127a9ac8 --- /dev/null +++ b/absl/log/die_if_null.h @@ -0,0 +1,76 @@ +// 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/die_if_null.h +// ----------------------------------------------------------------------------- +// +// This header declares macro `ABSL_DIE_IF_NULL`. + +#ifndef ABSL_LOG_DIE_IF_NULL_H_ +#define ABSL_LOG_DIE_IF_NULL_H_ + +#include <stdint.h> + +#include <utility> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/optimization.h" + +// ABSL_DIE_IF_NULL() +// +// `ABSL_DIE_IF_NULL` behaves as `CHECK_NE` against `nullptr` but *also* +// "returns" its argument. It is useful in initializers where statements (like +// `CHECK_NE`) can't be used. Outside initializers, prefer `CHECK` or +// `CHECK_NE`. `ABSL_DIE_IF_NULL` works for both raw pointers and (compatible) +// smart pointers including `std::unique_ptr` and `std::shared_ptr`; more +// generally, it works for any type that can be compared to nullptr_t. For +// types that aren't raw pointers, `ABSL_DIE_IF_NULL` returns a reference to +// its argument, preserving the value category. Example: +// +// Foo() : bar_(ABSL_DIE_IF_NULL(MethodReturningUniquePtr())) {} +// +// Use `CHECK(ptr)` or `CHECK(ptr != nullptr)` if the returned pointer is +// unused. +#define ABSL_DIE_IF_NULL(val) \ + ::absl::log_internal::DieIfNull(__FILE__, __LINE__, #val, (val)) + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +// Crashes the process after logging `exprtext` annotated at the `file` and +// `line` location. Called when `ABSL_DIE_IF_NULL` fails. Calling this function +// generates less code than its implementation would if inlined, for a slight +// code size reduction each time `ABSL_DIE_IF_NULL` is called. +ABSL_ATTRIBUTE_NORETURN ABSL_ATTRIBUTE_NOINLINE void DieBecauseNull( + const char* file, int line, const char* exprtext); + +// Helper for `ABSL_DIE_IF_NULL`. +template <typename T> +ABSL_MUST_USE_RESULT T DieIfNull(const char* file, int line, + const char* exprtext, T&& t) { + if (ABSL_PREDICT_FALSE(t == nullptr)) { + // Call a non-inline helper function for a small code size improvement. + DieBecauseNull(file, line, exprtext); + } + return std::forward<T>(t); +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_DIE_IF_NULL_H_ diff --git a/absl/log/die_if_null_test.cc b/absl/log/die_if_null_test.cc new file mode 100644 index 00000000..b0aab781 --- /dev/null +++ b/absl/log/die_if_null_test.cc @@ -0,0 +1,107 @@ +// +// 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/die_if_null.h" + +#include <stdint.h> + +#include <memory> +#include <utility> + +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/log/internal/test_helpers.h" + +namespace { + +auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( + new absl::log_internal::LogTestEnvironment); + +// TODO(b/69907837): Revisit these tests with the goal of making them less +// convoluted. +TEST(AbslDieIfNull, Simple) { + int64_t t; + void* ptr = static_cast<void*>(&t); + void* ref = ABSL_DIE_IF_NULL(ptr); + ASSERT_EQ(ptr, ref); + + char* t_as_char; + t_as_char = ABSL_DIE_IF_NULL(reinterpret_cast<char*>(&t)); + (void)t_as_char; + + unsigned char* t_as_uchar; + t_as_uchar = ABSL_DIE_IF_NULL(reinterpret_cast<unsigned char*>(&t)); + (void)t_as_uchar; + + int* t_as_int; + t_as_int = ABSL_DIE_IF_NULL(reinterpret_cast<int*>(&t)); + (void)t_as_int; + + int64_t* t_as_int64_t; + t_as_int64_t = ABSL_DIE_IF_NULL(reinterpret_cast<int64_t*>(&t)); + (void)t_as_int64_t; + + std::unique_ptr<int64_t> sptr(new int64_t); + EXPECT_EQ(sptr.get(), ABSL_DIE_IF_NULL(sptr).get()); + ABSL_DIE_IF_NULL(sptr).reset(); + + int64_t* int_ptr = new int64_t(); + EXPECT_EQ(int_ptr, ABSL_DIE_IF_NULL(std::unique_ptr<int64_t>(int_ptr)).get()); +} + +#if GTEST_HAS_DEATH_TEST +TEST(DeathCheckAbslDieIfNull, Simple) { + void* ptr; + ASSERT_DEATH({ ptr = ABSL_DIE_IF_NULL(nullptr); }, ""); + (void)ptr; + + std::unique_ptr<int64_t> sptr; + ASSERT_DEATH(ptr = ABSL_DIE_IF_NULL(sptr).get(), ""); +} +#endif + +// Ensures that ABSL_DIE_IF_NULL works with C++11's std::unique_ptr and +// std::shared_ptr. +TEST(AbslDieIfNull, DoesNotCompareSmartPointerToNULL) { + std::unique_ptr<int> up(new int); + EXPECT_EQ(&up, &ABSL_DIE_IF_NULL(up)); + ABSL_DIE_IF_NULL(up).reset(); + + std::shared_ptr<int> sp(new int); + EXPECT_EQ(&sp, &ABSL_DIE_IF_NULL(sp)); + ABSL_DIE_IF_NULL(sp).reset(); +} + +// Verifies that ABSL_DIE_IF_NULL returns an rvalue reference if its argument is +// an rvalue reference. +TEST(AbslDieIfNull, PreservesRValues) { + int64_t* ptr = new int64_t(); + auto uptr = ABSL_DIE_IF_NULL(std::unique_ptr<int64_t>(ptr)); + EXPECT_EQ(ptr, uptr.get()); +} + +// Verifies that ABSL_DIE_IF_NULL returns an lvalue if its argument is an +// lvalue. +TEST(AbslDieIfNull, PreservesLValues) { + int64_t array[2] = {0}; + int64_t* a = array + 0; + int64_t* b = array + 1; + using std::swap; + swap(ABSL_DIE_IF_NULL(a), ABSL_DIE_IF_NULL(b)); + EXPECT_EQ(array + 1, a); + EXPECT_EQ(array + 0, b); +} + +} // namespace diff --git a/absl/log/flags.cc b/absl/log/flags.cc new file mode 100644 index 00000000..b5308881 --- /dev/null +++ b/absl/log/flags.cc @@ -0,0 +1,112 @@ +// +// 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/flags.h" + +#include <stddef.h> + +#include <algorithm> +#include <cstdlib> +#include <string> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/flags/flag.h" +#include "absl/flags/marshalling.h" +#include "absl/log/globals.h" +#include "absl/log/internal/config.h" +#include "absl/strings/numbers.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { +namespace { + +void SyncLoggingFlags() { + absl::SetFlag(&FLAGS_minloglevel, static_cast<int>(absl::MinLogLevel())); + absl::SetFlag(&FLAGS_log_prefix, absl::ShouldPrependLogPrefix()); +} + +bool RegisterSyncLoggingFlags() { + log_internal::SetLoggingGlobalsListener(&SyncLoggingFlags); + return true; +} + +ABSL_ATTRIBUTE_UNUSED const bool unused = RegisterSyncLoggingFlags(); + +template <typename T> +T GetFromEnv(const char* varname, T dflt) { + const char* val = ::getenv(varname); + if (val != nullptr) { + std::string err; + ABSL_INTERNAL_CHECK(absl::ParseFlag(val, &dflt, &err), err.c_str()); + } + return dflt; +} + +constexpr absl::LogSeverityAtLeast StderrThresholdDefault() { + return absl::LogSeverityAtLeast::kError; +} + +} // namespace +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +ABSL_FLAG(int, stderrthreshold, + static_cast<int>(absl::log_internal::StderrThresholdDefault()), + "Log messages at or above this threshold level are copied to stderr.") + .OnUpdate([] { + absl::log_internal::RawSetStderrThreshold( + static_cast<absl::LogSeverityAtLeast>( + absl::GetFlag(FLAGS_stderrthreshold))); + }); + +ABSL_FLAG(int, minloglevel, static_cast<int>(absl::LogSeverityAtLeast::kInfo), + "Messages logged at a lower level than this don't actually " + "get logged anywhere") + .OnUpdate([] { + absl::log_internal::RawSetMinLogLevel( + static_cast<absl::LogSeverityAtLeast>( + absl::GetFlag(FLAGS_minloglevel))); + }); + +ABSL_FLAG(std::string, log_backtrace_at, "", + "Emit a backtrace when logging at file:linenum.") + .OnUpdate([] { + const std::string log_backtrace_at = + absl::GetFlag(FLAGS_log_backtrace_at); + if (log_backtrace_at.empty()) return; + + const size_t last_colon = log_backtrace_at.rfind(':'); + if (last_colon == log_backtrace_at.npos) return; + + const absl::string_view file = + absl::string_view(log_backtrace_at).substr(0, last_colon); + int line; + if (absl::SimpleAtoi( + absl::string_view(log_backtrace_at).substr(last_colon + 1), + &line)) { + absl::SetLogBacktraceLocation(file, line); + } + }); + +ABSL_FLAG(bool, log_prefix, true, + "Prepend the log prefix to the start of each log line") + .OnUpdate([] { + absl::log_internal::RawEnableLogPrefix(absl::GetFlag(FLAGS_log_prefix)); + }); diff --git a/absl/log/flags.h b/absl/log/flags.h new file mode 100644 index 00000000..146cfdd6 --- /dev/null +++ b/absl/log/flags.h @@ -0,0 +1,43 @@ +// 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/flags.h +// ----------------------------------------------------------------------------- +// + +#ifndef ABSL_LOG_FLAGS_H_ +#define ABSL_LOG_FLAGS_H_ + +// The Abseil Logging library supports the following command line flags to +// configure logging behavior at runtime: +// +// --stderrthreshold=<value> +// Log messages at or above this threshold level are copied to stderr. +// +// --minloglevel=<value> +// Messages logged at a lower level than this are discarded and don't actually +// get logged anywhere. +// +// --log_backtrace_at=<file:linenum> +// Emit a backtrace (stack trace) when logging at file:linenum. +// +// To use these commandline flags, the //absl/log:flags library must be +// explicitly linked, and absl::ParseCommandLine() must be called before the +// call to absl::InitializeLog(). +// +// To configure the Log library programmatically, use the interfaces defined in +// absl/log/globals.h. + +#endif // ABSL_LOG_FLAGS_H_ diff --git a/absl/log/flags_test.cc b/absl/log/flags_test.cc new file mode 100644 index 00000000..a0f6d763 --- /dev/null +++ b/absl/log/flags_test.cc @@ -0,0 +1,184 @@ +// +// 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/flags.h" + +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/base/log_severity.h" +#include "absl/flags/flag.h" +#include "absl/flags/reflection.h" +#include "absl/log/globals.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/log/internal/test_matchers.h" +#include "absl/log/log.h" +#include "absl/log/scoped_mock_log.h" +#include "absl/strings/str_cat.h" + +namespace { +using ::absl::log_internal::TextMessage; + +using ::testing::HasSubstr; +using ::testing::Not; + +auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( + new absl::log_internal::LogTestEnvironment); + +constexpr static absl::LogSeverityAtLeast DefaultStderrThreshold() { + return absl::LogSeverityAtLeast::kError; +} + +class LogFlagsTest : public ::testing::Test { + protected: + absl::FlagSaver flag_saver_; +}; + +// This test is disabled because it adds order dependency to the test suite. +// This order dependency is currently not fixable due to the way the +// stderrthreshold global value is out of sync with the stderrthreshold flag. +TEST_F(LogFlagsTest, DISABLED_StderrKnobsDefault) { + EXPECT_EQ(absl::StderrThreshold(), DefaultStderrThreshold()); +} + +TEST_F(LogFlagsTest, SetStderrThreshold) { + absl::SetFlag(&FLAGS_stderrthreshold, + static_cast<int>(absl::LogSeverityAtLeast::kInfo)); + + EXPECT_EQ(absl::StderrThreshold(), absl::LogSeverityAtLeast::kInfo); + + absl::SetFlag(&FLAGS_stderrthreshold, + static_cast<int>(absl::LogSeverityAtLeast::kError)); + + EXPECT_EQ(absl::StderrThreshold(), absl::LogSeverityAtLeast::kError); +} + +TEST_F(LogFlagsTest, SetMinLogLevel) { + absl::SetFlag(&FLAGS_minloglevel, + static_cast<int>(absl::LogSeverityAtLeast::kError)); + + EXPECT_EQ(absl::MinLogLevel(), absl::LogSeverityAtLeast::kError); + + absl::log_internal::ScopedMinLogLevel scoped_min_log_level( + absl::LogSeverityAtLeast::kWarning); + + EXPECT_EQ(absl::GetFlag(FLAGS_minloglevel), + static_cast<int>(absl::LogSeverityAtLeast::kWarning)); +} + +TEST_F(LogFlagsTest, PrependLogPrefix) { + absl::SetFlag(&FLAGS_log_prefix, false); + + EXPECT_EQ(absl::ShouldPrependLogPrefix(), false); + + absl::EnableLogPrefix(true); + + EXPECT_EQ(absl::GetFlag(FLAGS_log_prefix), true); +} + +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(); + 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(); + LOG(INFO) << "hello world"; +} + +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(); + do_log(); +} + +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(); + do_log(); +} + +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(); + do_log(); +} + +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(); + do_log(); +} + +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); + + EXPECT_CALL(test_sink, Send(TextMessage(HasSubstr("(stacktrace:")))); + + test_sink.StartCapturingLogs(); + do_log(); +} + +} // namespace diff --git a/absl/log/globals.cc b/absl/log/globals.cc new file mode 100644 index 00000000..6dfe81f0 --- /dev/null +++ b/absl/log/globals.cc @@ -0,0 +1,148 @@ +// 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/globals.h" + +#include <stddef.h> +#include <stdint.h> + +#include <atomic> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/internal/atomic_hook.h" +#include "absl/base/log_severity.h" +#include "absl/hash/hash.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace { + +// These atomics represent logging library configuration. +// Integer types are used instead of absl::LogSeverity to ensure that a +// lock-free std::atomic is used when possible. +ABSL_CONST_INIT std::atomic<int> min_log_level{ + static_cast<int>(absl::LogSeverityAtLeast::kInfo)}; +ABSL_CONST_INIT std::atomic<int> stderrthreshold{ + static_cast<int>(absl::LogSeverityAtLeast::kError)}; +// We evaluate this value as a hash comparison to avoid having to +// hold a mutex or make a copy (to access the value of a string-typed flag) in +// very hot codepath. +ABSL_CONST_INIT std::atomic<size_t> log_backtrace_at_hash{0}; +ABSL_CONST_INIT std::atomic<bool> prepend_log_prefix{true}; + +ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES +absl::base_internal::AtomicHook<log_internal::LoggingGlobalsListener> + logging_globals_listener; + +size_t HashSiteForLogBacktraceAt(absl::string_view file, int line) { + return absl::HashOf(file, line); +} + +void TriggerLoggingGlobalsListener() { + auto* listener = logging_globals_listener.Load(); + if (listener != nullptr) listener(); +} + +} // namespace + +namespace log_internal { + +void RawSetMinLogLevel(absl::LogSeverityAtLeast severity) { + min_log_level.store(static_cast<int>(severity), std::memory_order_release); +} + +void RawSetStderrThreshold(absl::LogSeverityAtLeast severity) { + stderrthreshold.store(static_cast<int>(severity), std::memory_order_release); +} + +void RawEnableLogPrefix(bool on_off) { + prepend_log_prefix.store(on_off, std::memory_order_release); +} + +void SetLoggingGlobalsListener(LoggingGlobalsListener l) { + logging_globals_listener.Store(l); +} + +} // namespace log_internal + +absl::LogSeverityAtLeast MinLogLevel() { + return static_cast<absl::LogSeverityAtLeast>( + min_log_level.load(std::memory_order_acquire)); +} + +void SetMinLogLevel(absl::LogSeverityAtLeast severity) { + log_internal::RawSetMinLogLevel(severity); + TriggerLoggingGlobalsListener(); +} + +namespace log_internal { + +ScopedMinLogLevel::ScopedMinLogLevel(absl::LogSeverityAtLeast severity) + : saved_severity_(absl::MinLogLevel()) { + absl::SetMinLogLevel(severity); +} +ScopedMinLogLevel::~ScopedMinLogLevel() { + absl::SetMinLogLevel(saved_severity_); +} + +} // namespace log_internal + +absl::LogSeverityAtLeast StderrThreshold() { + return static_cast<absl::LogSeverityAtLeast>( + stderrthreshold.load(std::memory_order_acquire)); +} + +void SetStderrThreshold(absl::LogSeverityAtLeast severity) { + log_internal::RawSetStderrThreshold(severity); + TriggerLoggingGlobalsListener(); +} + +ScopedStderrThreshold::ScopedStderrThreshold(absl::LogSeverityAtLeast severity) + : saved_severity_(absl::StderrThreshold()) { + absl::SetStderrThreshold(severity); +} + +ScopedStderrThreshold::~ScopedStderrThreshold() { + absl::SetStderrThreshold(saved_severity_); +} + +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); + + return flag_hash != 0 && flag_hash == HashSiteForLogBacktraceAt(file, line); +} + +} // namespace log_internal + +void SetLogBacktraceLocation(absl::string_view file, int line) { + log_backtrace_at_hash.store(HashSiteForLogBacktraceAt(file, line), + std::memory_order_release); +} + +bool ShouldPrependLogPrefix() { + return prepend_log_prefix.load(std::memory_order_acquire); +} + +void EnableLogPrefix(bool on_off) { + log_internal::RawEnableLogPrefix(on_off); + TriggerLoggingGlobalsListener(); +} + +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/globals.h b/absl/log/globals.h new file mode 100644 index 00000000..32b87db0 --- /dev/null +++ b/absl/log/globals.h @@ -0,0 +1,165 @@ +// 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/globals.h +// ----------------------------------------------------------------------------- +// +// This header declares global logging library configuration knobs. + +#ifndef ABSL_LOG_GLOBALS_H_ +#define ABSL_LOG_GLOBALS_H_ + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +//------------------------------------------------------------------------------ +// Minimum Log Level +//------------------------------------------------------------------------------ +// +// Messages logged at or above this severity are directed to all registered log +// sinks or skipped otherwise. This parameter can also be modified using +// command line flag --minloglevel. +// See absl/base/log_severity.h for descriptions of severity levels. + +// MinLogLevel() +// +// Returns the value of the Minimum Log Level parameter. +// This function is async-signal-safe. +ABSL_MUST_USE_RESULT absl::LogSeverityAtLeast MinLogLevel(); + +// SetMinLogLevel() +// +// Updates the value of Minimum Log Level parameter. +// This function is async-signal-safe. +void SetMinLogLevel(absl::LogSeverityAtLeast severity); + +namespace log_internal { + +// ScopedMinLogLevel +// +// RAII type used to temporarily update the Min Log Level parameter. +class ScopedMinLogLevel final { + public: + explicit ScopedMinLogLevel(absl::LogSeverityAtLeast severity); + ScopedMinLogLevel(const ScopedMinLogLevel&) = delete; + ScopedMinLogLevel& operator=(const ScopedMinLogLevel&) = delete; + ~ScopedMinLogLevel(); + + private: + absl::LogSeverityAtLeast saved_severity_; +}; + +} // namespace log_internal + +//------------------------------------------------------------------------------ +// Stderr Threshold +//------------------------------------------------------------------------------ +// +// Messages logged at or above this level are directed to stderr in +// addition to other registered log sinks. This parameter can also be modified +// using command line flag --stderrthreshold. +// See absl/base/log_severity.h for descriptions of severity levels. + +// StderrThreshold() +// +// Returns the value of the Stderr Threshold parameter. +// This function is async-signal-safe. +ABSL_MUST_USE_RESULT absl::LogSeverityAtLeast StderrThreshold(); + +// SetStderrThreshold() +// +// Updates the Stderr Threshold parameter. +// This function is async-signal-safe. +void SetStderrThreshold(absl::LogSeverityAtLeast severity); +inline void SetStderrThreshold(absl::LogSeverity severity) { + absl::SetStderrThreshold(static_cast<absl::LogSeverityAtLeast>(severity)); +} + +// ScopedStderrThreshold +// +// RAII type used to temporarily update the Stderr Threshold parameter. +class ScopedStderrThreshold final { + public: + explicit ScopedStderrThreshold(absl::LogSeverityAtLeast severity); + ScopedStderrThreshold(const ScopedStderrThreshold&) = delete; + ScopedStderrThreshold& operator=(const ScopedStderrThreshold&) = delete; + ~ScopedStderrThreshold(); + + private: + absl::LogSeverityAtLeast saved_severity_; +}; + +//------------------------------------------------------------------------------ +// Log Backtrace At +//------------------------------------------------------------------------------ +// +// Users can request backtrace to be logged at specific locations, specified +// by file and line number. + +// ShouldLogBacktraceAt() +// +// Returns true if we should log a backtrace at the specified location. +namespace log_internal { +ABSL_MUST_USE_RESULT bool ShouldLogBacktraceAt(absl::string_view file, + int line); +} // namespace log_internal + +// SetLogBacktraceLocation() +// +// Sets the location the backtrace should be logged at. +void SetLogBacktraceLocation(absl::string_view file, int line); + +//------------------------------------------------------------------------------ +// Prepend Log Prefix +//------------------------------------------------------------------------------ +// +// This option tells the logging library that every logged message +// should include the prefix (severity, date, time, PID, etc.) + +// ShouldPrependLogPrefix() +// +// Returns the value of the Prepend Log Prefix option. +// This function is async-signal-safe. +ABSL_MUST_USE_RESULT bool ShouldPrependLogPrefix(); + +// EnableLogPrefix() +// +// Updates the value of the Prepend Log Prefix option. +// This function is async-signal-safe. +void EnableLogPrefix(bool on_off); + +namespace log_internal { + +using LoggingGlobalsListener = void (*)(); +void SetLoggingGlobalsListener(LoggingGlobalsListener l); + +// Internal implementation for the setter routines. These are used +// to break circular dependencies between flags and globals. Each "Raw" +// routine corresponds to the non-"Raw" counterpart and used to set the +// configuration parameter directly without calling back to the listener. +void RawSetMinLogLevel(absl::LogSeverityAtLeast severity); +void RawSetStderrThreshold(absl::LogSeverityAtLeast severity); +void RawEnableLogPrefix(bool on_off); + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_GLOBALS_H_ diff --git a/absl/log/globals_test.cc b/absl/log/globals_test.cc new file mode 100644 index 00000000..6710c5aa --- /dev/null +++ b/absl/log/globals_test.cc @@ -0,0 +1,91 @@ +// +// 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/globals.h" + +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/base/log_severity.h" +#include "absl/log/internal/globals.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/log/log.h" +#include "absl/log/scoped_mock_log.h" + +namespace { + +auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( + new absl::log_internal::LogTestEnvironment); + +constexpr static absl::LogSeverityAtLeast DefaultMinLogLevel() { + return absl::LogSeverityAtLeast::kInfo; +} +constexpr static absl::LogSeverityAtLeast DefaultStderrThreshold() { + return absl::LogSeverityAtLeast::kError; +} + +TEST(TestGlobals, MinLogLevel) { + EXPECT_EQ(absl::MinLogLevel(), DefaultMinLogLevel()); + absl::SetMinLogLevel(absl::LogSeverityAtLeast::kError); + EXPECT_EQ(absl::MinLogLevel(), absl::LogSeverityAtLeast::kError); + absl::SetMinLogLevel(DefaultMinLogLevel()); +} + +TEST(TestGlobals, ScopedMinLogLevel) { + EXPECT_EQ(absl::MinLogLevel(), DefaultMinLogLevel()); + { + absl::log_internal::ScopedMinLogLevel scoped_stderr_threshold( + absl::LogSeverityAtLeast::kError); + EXPECT_EQ(absl::MinLogLevel(), absl::LogSeverityAtLeast::kError); + } + EXPECT_EQ(absl::MinLogLevel(), DefaultMinLogLevel()); +} + +TEST(TestGlobals, StderrThreshold) { + EXPECT_EQ(absl::StderrThreshold(), DefaultStderrThreshold()); + absl::SetStderrThreshold(absl::LogSeverityAtLeast::kError); + EXPECT_EQ(absl::StderrThreshold(), absl::LogSeverityAtLeast::kError); + absl::SetStderrThreshold(DefaultStderrThreshold()); +} + +TEST(TestGlobals, ScopedStderrThreshold) { + EXPECT_EQ(absl::StderrThreshold(), DefaultStderrThreshold()); + { + absl::ScopedStderrThreshold scoped_stderr_threshold( + absl::LogSeverityAtLeast::kError); + EXPECT_EQ(absl::StderrThreshold(), absl::LogSeverityAtLeast::kError); + } + EXPECT_EQ(absl::StderrThreshold(), DefaultStderrThreshold()); +} + +TEST(TestGlobals, LogBacktraceAt) { + EXPECT_FALSE(absl::log_internal::ShouldLogBacktraceAt("some_file.cc", 111)); + absl::SetLogBacktraceLocation("some_file.cc", 111); + EXPECT_TRUE(absl::log_internal::ShouldLogBacktraceAt("some_file.cc", 111)); + EXPECT_FALSE( + absl::log_internal::ShouldLogBacktraceAt("another_file.cc", 222)); +} + +TEST(TestGlobals, LogPrefix) { + EXPECT_TRUE(absl::ShouldPrependLogPrefix()); + absl::EnableLogPrefix(false); + EXPECT_FALSE(absl::ShouldPrependLogPrefix()); + absl::EnableLogPrefix(true); + EXPECT_TRUE(absl::ShouldPrependLogPrefix()); +} + +} // namespace diff --git a/absl/log/initialize.cc b/absl/log/initialize.cc new file mode 100644 index 00000000..a3f6d6c1 --- /dev/null +++ b/absl/log/initialize.cc @@ -0,0 +1,34 @@ +// 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/initialize.h" + +#include "absl/base/config.h" +#include "absl/log/internal/globals.h" +#include "absl/time/time.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +void InitializeLog() { + // This comes first since it is used by RAW_LOG. + absl::log_internal::SetTimeZone(absl::LocalTimeZone()); + + // Note that initialization is complete, so logs can now be sent to their + // proper destinations rather than stderr. + log_internal::SetInitialized(); +} + +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/initialize.h b/absl/log/initialize.h new file mode 100644 index 00000000..f600eb60 --- /dev/null +++ b/absl/log/initialize.h @@ -0,0 +1,45 @@ +// 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/initialize.h +// ----------------------------------------------------------------------------- +// +// This header declares the Abseil Log initialization routine InitializeLog(). + +#ifndef ABSL_LOG_INITIALIZE_H_ +#define ABSL_LOG_INITIALIZE_H_ + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +// InitializeLog() +// +// Initializes the Abseil logging library. +// +// Before this function is called, all log messages are directed only to stderr. +// After initialization is finished, log messages are directed to all registered +// `LogSink`s. +// +// It is an error to call this function twice. +// +// There is no corresponding function to shut down the logging library. +void InitializeLog(); + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INITIALIZE_H_ diff --git a/absl/log/internal/BUILD.bazel b/absl/log/internal/BUILD.bazel new file mode 100644 index 00000000..a1f1a67c --- /dev/null +++ b/absl/log/internal/BUILD.bazel @@ -0,0 +1,383 @@ +# +# 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. +# + +load( + "//absl:copts/configure_copts.bzl", + "ABSL_DEFAULT_COPTS", + "ABSL_DEFAULT_LINKOPTS", + "ABSL_TEST_COPTS", +) + +package(default_visibility = [ + "//absl/log:__pkg__", +]) + +licenses(["notice"]) + +cc_library( + name = "check_impl", + hdrs = ["check_impl.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":check_op", + ":conditions", + ":log_message", + ":strip", + "//absl/base:core_headers", + ], +) + +cc_library( + name = "check_op", + srcs = ["check_op.cc"], + hdrs = ["check_op.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/log:__pkg__", + ], + deps = [ + ":nullguard", + ":nullstream", + ":strip", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/strings", + ], +) + +cc_library( + name = "conditions", + srcs = ["conditions.cc"], + hdrs = ["conditions.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":voidify", + "//absl/base", + "//absl/base:config", + "//absl/base:core_headers", + ], +) + +cc_library( + name = "config", + hdrs = ["config.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/log:__pkg__", + ], + deps = [ + "//absl/base:config", + "//absl/base:core_headers", + ], +) + +cc_library( + name = "flags", + hdrs = ["flags.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/flags:flag", + ], +) + +cc_library( + name = "format", + srcs = ["log_format.cc"], + hdrs = ["log_format.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":append_truncated", + ":config", + ":globals", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/strings", + "//absl/strings:str_format", + "//absl/time", + "//absl/types:span", + ], +) + +cc_library( + name = "globals", + srcs = ["globals.cc"], + hdrs = ["globals.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/log:__pkg__", + ], + deps = [ + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/base:raw_logging_internal", + "//absl/strings", + "//absl/time", + ], +) + +cc_library( + name = "log_impl", + hdrs = ["log_impl.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":conditions", + ":log_message", + ":strip", + ], +) + +cc_library( + name = "log_message", + srcs = ["log_message.cc"], + hdrs = ["log_message.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/log:__pkg__", + ], + deps = [ + ":append_truncated", + ":format", + ":globals", + ":log_sink_set", + ":nullguard", + ":proto", + "//absl/base", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:errno_saver", + "//absl/base:log_severity", + "//absl/base:raw_logging_internal", + "//absl/base:strerror", + "//absl/container:inlined_vector", + "//absl/debugging:examine_stack", + "//absl/log:globals", + "//absl/log:log_entry", + "//absl/log:log_sink", + "//absl/log:log_sink_registry", + "//absl/memory", + "//absl/strings", + "//absl/time", + "//absl/types:span", + ], +) + +cc_library( + name = "append_truncated", + hdrs = ["append_truncated.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base:config", + "//absl/strings", + "//absl/types:span", + ], +) + +cc_library( + name = "log_sink_set", + srcs = ["log_sink_set.cc"], + hdrs = ["log_sink_set.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS + select({ + "//conditions:default": [], + "@platforms//os:android": ["-llog"], + }), + deps = [ + ":config", + ":globals", + "//absl/base", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/base:raw_logging_internal", + "//absl/cleanup", + "//absl/log:globals", + "//absl/log:log_entry", + "//absl/log:log_sink", + "//absl/strings", + "//absl/synchronization", + "//absl/types:span", + ], +) + +cc_library( + name = "nullguard", + srcs = ["nullguard.cc"], + hdrs = ["nullguard.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base:config", + "//absl/base:core_headers", + ], +) + +cc_library( + name = "nullstream", + hdrs = ["nullstream.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/strings", + ], +) + +cc_library( + name = "strip", + hdrs = ["strip.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log_message", + ":nullstream", + "//absl/base:log_severity", + ], +) + +cc_library( + name = "structured", + hdrs = ["structured.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":log_message", + "//absl/base:config", + "//absl/strings", + ], +) + +cc_library( + name = "test_actions", + testonly = True, + srcs = ["test_actions.cc"], + hdrs = ["test_actions.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/log:log_entry", + "//absl/strings", + "//absl/time", + ] + select({ + "//absl:msvc_compiler": [], + "//conditions:default": [ + ], + }), +) + +cc_library( + name = "test_helpers", + testonly = True, + srcs = ["test_helpers.cc"], + hdrs = ["test_helpers.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":globals", + "//absl/base:config", + "//absl/base:log_severity", + "//absl/log:globals", + "//absl/log:initialize", + "@com_google_googletest//:gtest", + ], +) + +cc_library( + name = "test_matchers", + testonly = True, + srcs = ["test_matchers.cc"], + hdrs = ["test_matchers.h"], + copts = ABSL_DEFAULT_COPTS, + 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", + ] + select({ + "//absl:msvc_compiler": [], + "//conditions:default": [ + ], + }), +) + +cc_library( + name = "voidify", + hdrs = ["voidify.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = ["//absl/base:config"], +) + +cc_library( + name = "proto", + srcs = ["proto.cc"], + hdrs = ["proto.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/strings", + "//absl/types:span", + ], +) + +# Test targets +cc_test( + name = "stderr_log_sink_test", + size = "small", + srcs = ["stderr_log_sink_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = [ + "no_test:os:android", + "no_test:os:ios", + "no_test_android", + "no_test_darwin_x86_64", + "no_test_ios", + "no_test_wasm", + ], + deps = [ + ":test_helpers", + "//absl/base:core_headers", + "//absl/base:log_severity", + "//absl/log", + "//absl/log:globals", + "@com_google_googletest//:gtest_main", + ], +) diff --git a/absl/log/internal/append_truncated.h b/absl/log/internal/append_truncated.h new file mode 100644 index 00000000..f0e7912c --- /dev/null +++ b/absl/log/internal/append_truncated.h @@ -0,0 +1,47 @@ +// 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_LOG_INTERNAL_APPEND_TRUNCATED_H_ +#define ABSL_LOG_INTERNAL_APPEND_TRUNCATED_H_ + +#include <cstddef> +#include <cstring> + +#include "absl/base/config.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { +// Copies into `dst` as many bytes of `src` as will fit, then truncates the +// copied bytes from the front of `dst` and returns the number of bytes written. +inline size_t AppendTruncated(absl::string_view src, absl::Span<char> &dst) { + if (src.size() > dst.size()) src = src.substr(0, dst.size()); + memcpy(dst.data(), src.data(), src.size()); + dst.remove_prefix(src.size()); + return src.size(); +} +// Likewise, but `n` copies of `c`. +inline size_t AppendTruncated(char c, size_t n, absl::Span<char> &dst) { + if (n > dst.size()) n = dst.size(); + memset(dst.data(), c, n); + dst.remove_prefix(n); + return n; +} +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_APPEND_TRUNCATED_H_ diff --git a/absl/log/internal/check_impl.h b/absl/log/internal/check_impl.h new file mode 100644 index 00000000..c9c28e3e --- /dev/null +++ b/absl/log/internal/check_impl.h @@ -0,0 +1,150 @@ +// 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_LOG_INTERNAL_CHECK_IMPL_H_ +#define ABSL_LOG_INTERNAL_CHECK_IMPL_H_ + +#include "absl/base/optimization.h" +#include "absl/log/internal/check_op.h" +#include "absl/log/internal/conditions.h" +#include "absl/log/internal/log_message.h" +#include "absl/log/internal/strip.h" + +// CHECK +#define ABSL_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) \ + 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() + +#ifndef NDEBUG +#define ABSL_DCHECK_IMPL(condition, condition_text) \ + ABSL_CHECK_IMPL(condition, condition_text) +#else +#define ABSL_DCHECK_IMPL(condition, condition_text) \ + ABSL_CHECK_IMPL(true || (condition), "true") +#endif + +// CHECK_EQ +#define ABSL_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) \ + ABSL_LOG_INTERNAL_CHECK_OP(Check_NE, !=, val1, val1_text, val2, val2_text) +#define ABSL_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) \ + ABSL_LOG_INTERNAL_CHECK_OP(Check_LT, <, val1, val1_text, val2, val2_text) +#define ABSL_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) \ + ABSL_LOG_INTERNAL_CHECK_OP(Check_GT, >, val1, val1_text, val2, val2_text) +#define ABSL_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) \ + ABSL_LOG_INTERNAL_QCHECK_OP(Check_NE, !=, val1, val1_text, val2, val2_text) +#define ABSL_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) \ + ABSL_LOG_INTERNAL_QCHECK_OP(Check_LT, <, val1, val1_text, val2, val2_text) +#define ABSL_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) \ + 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) +#else // ndef NDEBUG +#define ABSL_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) \ + ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) +#define ABSL_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) \ + ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) +#define ABSL_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) \ + ABSL_LOG_INTERNAL_DCHECK_NOP(val1, val2) +#endif // def NDEBUG + +// CHECK_OK +#define ABSL_CHECK_OK_IMPL(status, status_text) \ + ABSL_LOG_INTERNAL_CHECK_OK(status, status_text) +#define ABSL_QCHECK_OK_IMPL(status, status_text) \ + ABSL_LOG_INTERNAL_QCHECK_OK(status, status_text) +#ifndef NDEBUG +#define ABSL_DCHECK_OK_IMPL(status, status_text) \ + ABSL_LOG_INTERNAL_CHECK_OK(status, status_text) +#else +#define ABSL_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) \ + ABSL_LOG_INTERNAL_CHECK_STROP(strcmp, ==, true, s1, s1_text, s2, s2_text) +#define ABSL_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) \ + ABSL_LOG_INTERNAL_CHECK_STROP(strcasecmp, ==, true, s1, s1_text, s2, s2_text) +#define ABSL_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) \ + ABSL_LOG_INTERNAL_QCHECK_STROP(strcmp, ==, true, s1, s1_text, s2, s2_text) +#define ABSL_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) \ + 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, \ + 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) +#else // ndef NDEBUG +#define ABSL_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) \ + ABSL_LOG_INTERNAL_DCHECK_NOP(s1, s2) +#define ABSL_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) \ + ABSL_LOG_INTERNAL_DCHECK_NOP(s1, s2) +#endif // def NDEBUG + +#endif // ABSL_LOG_INTERNAL_CHECK_IMPL_H_ diff --git a/absl/log/internal/check_op.cc b/absl/log/internal/check_op.cc new file mode 100644 index 00000000..f4b67647 --- /dev/null +++ b/absl/log/internal/check_op.cc @@ -0,0 +1,118 @@ +// 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/check_op.h" + +#include <string.h> + +#ifdef _MSC_VER +#define strcasecmp _stricmp +#else +#include <strings.h> // for strcasecmp, but msvc does not have this header +#endif + +#include <sstream> +#include <string> + +#include "absl/base/config.h" +#include "absl/strings/str_cat.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +#define ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(x) \ + template std::string* MakeCheckOpString(x, x, const char*) +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(bool); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(int64_t); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(uint64_t); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(float); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(double); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(char); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(unsigned char); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(const std::string&); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(const absl::string_view&); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(const char*); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(const signed char*); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(const unsigned char*); +ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING(const void*); +#undef ABSL_LOGGING_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING + +CheckOpMessageBuilder::CheckOpMessageBuilder(const char* exprtext) { + stream_ << exprtext << " ("; +} + +std::ostream& CheckOpMessageBuilder::ForVar2() { + stream_ << " vs. "; + return stream_; +} + +std::string* CheckOpMessageBuilder::NewString() { + stream_ << ")"; + return new std::string(stream_.str()); +} + +void MakeCheckOpValueString(std::ostream& os, const char v) { + if (v >= 32 && v <= 126) { + os << "'" << v << "'"; + } else { + os << "char value " << int{v}; + } +} + +void MakeCheckOpValueString(std::ostream& os, const signed char v) { + if (v >= 32 && v <= 126) { + os << "'" << v << "'"; + } else { + os << "signed char value " << int{v}; + } +} + +void MakeCheckOpValueString(std::ostream& os, const unsigned char v) { + if (v >= 32 && v <= 126) { + os << "'" << v << "'"; + } else { + os << "unsigned char value " << int{v}; + } +} + +void MakeCheckOpValueString(std::ostream& os, const void* p) { + if (p == nullptr) { + os << "(null)"; + } else { + os << p; + } +} + +// Helper functions for string comparisons. +#define DEFINE_CHECK_STROP_IMPL(name, func, expected) \ + std::string* Check##func##expected##Impl(const char* s1, const char* s2, \ + const char* exprtext) { \ + bool equal = s1 == s2 || (s1 && s2 && !func(s1, s2)); \ + if (equal == expected) { \ + return nullptr; \ + } else { \ + return new std::string( \ + absl::StrCat(exprtext, " (", s1, " vs. ", s2, ")")); \ + } \ + } +DEFINE_CHECK_STROP_IMPL(CHECK_STREQ, strcmp, true) +DEFINE_CHECK_STROP_IMPL(CHECK_STRNE, strcmp, false) +DEFINE_CHECK_STROP_IMPL(CHECK_STRCASEEQ, strcasecmp, true) +DEFINE_CHECK_STROP_IMPL(CHECK_STRCASENE, strcasecmp, false) +#undef DEFINE_CHECK_STROP_IMPL + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/internal/check_op.h b/absl/log/internal/check_op.h new file mode 100644 index 00000000..4907b89b --- /dev/null +++ b/absl/log/internal/check_op.h @@ -0,0 +1,392 @@ +// 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/internal/check_op.h +// ----------------------------------------------------------------------------- +// +// This file declares helpers routines and macros used to implement `CHECK` +// macros. + +#ifndef ABSL_LOG_INTERNAL_CHECK_OP_H_ +#define ABSL_LOG_INTERNAL_CHECK_OP_H_ + +#include <stdint.h> + +#include <ostream> +#include <sstream> +#include <string> +#include <utility> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/optimization.h" +#include "absl/log/internal/nullguard.h" +#include "absl/log/internal/nullstream.h" +#include "absl/log/internal/strip.h" + +// `ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL` wraps string literals that +// should be stripped when `ABSL_MIN_LOG_LEVEL` exceeds `kFatal`. +#ifdef ABSL_MIN_LOG_LEVEL +#define ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(literal) \ + (::absl::LogSeverity::kFatal >= \ + static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) \ + ? (literal) \ + : "") +#else +#define ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(literal) (literal) +#endif + +#ifdef NDEBUG +// `NDEBUG` is defined, so `DCHECK_EQ(x, y)` and so on do nothing. However, we +// still want the compiler to parse `x` and `y`, because we don't want to lose +// potentially useful errors and warnings. +#define ABSL_LOG_INTERNAL_DCHECK_NOP(x, y) \ + while (false && ((void)(x), (void)(y), 0)) \ + ::absl::log_internal::NullStream().InternalStream() +#endif + +#define ABSL_LOG_INTERNAL_CHECK_OP(name, op, val1, val1_text, val2, val2_text) \ + while ( \ + ::std::string* absl_log_internal_check_op_result ABSL_ATTRIBUTE_UNUSED = \ + ::absl::log_internal::name##Impl( \ + ::absl::log_internal::GetReferenceableValue(val1), \ + ::absl::log_internal::GetReferenceableValue(val2), \ + ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(val1_text \ + " " #op " " val2_text))) \ + 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) \ + while (::std::string* absl_log_internal_qcheck_op_result = \ + ::absl::log_internal::name##Impl( \ + ::absl::log_internal::GetReferenceableValue(val1), \ + ::absl::log_internal::GetReferenceableValue(val2), \ + ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL( \ + val1_text " " #op " " val2_text))) \ + 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) \ + while (::std::string* absl_log_internal_check_strop_result = \ + ::absl::log_internal::Check##func##expected##Impl( \ + (s1), (s2), \ + ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(s1_text " " #op \ + " " s2_text))) \ + ABSL_LOG_INTERNAL_CHECK(*absl_log_internal_check_strop_result) \ + .InternalStream() +#define ABSL_LOG_INTERNAL_QCHECK_STROP(func, op, expected, s1, s1_text, s2, \ + s2_text) \ + while (::std::string* absl_log_internal_qcheck_strop_result = \ + ::absl::log_internal::Check##func##expected##Impl( \ + (s1), (s2), \ + ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(s1_text " " #op \ + " " s2_text))) \ + ABSL_LOG_INTERNAL_QCHECK(*absl_log_internal_qcheck_strop_result) \ + .InternalStream() +// This one is tricky: +// * We must evaluate `val` exactly once, yet we need to do two things with it: +// evaluate `.ok()` and (sometimes) `.ToString()`. +// * `val` might be an `absl::Status` or some `absl::StatusOr<T>`. +// * `val` might be e.g. `ATemporary().GetStatus()`, which may return a +// reference to a member of `ATemporary` that is only valid until the end of +// the full expression. +// * We don't want this file to depend on `absl::Status` `#include`s or linkage, +// nor do we want to move the definition to status and introduce a dependency +// in the other direction. We can be assured that callers must already have a +// `Status` and the necessary `#include`s and linkage. +// * Callsites should be small and fast (at least when `val.ok()`): one branch, +// minimal stack footprint. +// * In particular, the string concat stuff should be out-of-line and emitted +// in only one TU to save linker input size +// * We want the `val.ok()` check inline so static analyzers and optimizers can +// see it. +// * 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. +#define ABSL_LOG_INTERNAL_CHECK_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_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) \ + .InternalStream() + +namespace absl { +ABSL_NAMESPACE_BEGIN + +class Status; +template <typename T> +class StatusOr; + +namespace status_internal { +std::string* MakeCheckFailString(const absl::Status* status, + const char* prefix); +} // namespace status_internal + +namespace log_internal { + +// Convert a Status or a StatusOr to its underlying status value. +// +// (This implementation does not require a dep on absl::Status to work.) +inline const absl::Status* AsStatus(const absl::Status& s) { return &s; } +template <typename T> +const absl::Status* AsStatus(const absl::StatusOr<T>& s) { + return &s.status(); +} + +// A helper class for formatting `expr (V1 vs. V2)` in a `CHECK_XX` statement. +// See `MakeCheckOpString` for sample usage. +class CheckOpMessageBuilder final { + public: + // Inserts `exprtext` and ` (` to the stream. + explicit CheckOpMessageBuilder(const char* exprtext); + ~CheckOpMessageBuilder() = default; + // For inserting the first variable. + std::ostream& ForVar1() { return stream_; } + // For inserting the second variable (adds an intermediate ` vs. `). + std::ostream& ForVar2(); + // Get the result (inserts the closing `)`). + std::string* NewString(); + + private: + std::ostringstream stream_; +}; + +// This formats a value for a failing `CHECK_XX` statement. Ordinarily, it uses +// the definition for `operator<<`, with a few special cases below. +template <typename T> +inline void MakeCheckOpValueString(std::ostream& os, const T& v) { + os << log_internal::NullGuard<T>::Guard(v); +} + +// Overloads for char types provide readable values for unprintable characters. +void MakeCheckOpValueString(std::ostream& os, char v); +void MakeCheckOpValueString(std::ostream& os, signed char v); +void MakeCheckOpValueString(std::ostream& os, unsigned char v); +void MakeCheckOpValueString(std::ostream& os, const void* p); + +namespace detect_specialization { + +// MakeCheckOpString is being specialized for every T and U pair that is being +// passed to the CHECK_op macros. However, there is a lot of redundancy in these +// specializations that creates unnecessary library and binary bloat. +// The number of instantiations tends to be O(n^2) because we have two +// independent inputs. This technique works by reducing `n`. +// +// Most user-defined types being passed to CHECK_op end up being printed as a +// builtin type. For example, enums tend to be implicitly converted to its +// underlying type when calling operator<<, and pointers are printed with the +// `const void*` overload. +// To reduce the number of instantiations we coerce these values before calling +// MakeCheckOpString instead of inside it. +// +// To detect if this coercion is needed, we duplicate all the relevant +// operator<< overloads as specified in the standard, just in a different +// namespace. If the call to `stream << value` becomes ambiguous, it means that +// one of these overloads is the one selected by overload resolution. We then +// do overload resolution again just with our overload set to see which one gets +// selected. That tells us which type to coerce to. +// If the augmented call was not ambiguous, it means that none of these were +// selected and we can't coerce the input. +// +// As a secondary step to reduce code duplication, we promote integral types to +// their 64-bit variant. This does not change the printed value, but reduces the +// number of instantiations even further. Promoting an integer is very cheap at +// the call site. +int64_t operator<<(std::ostream&, short value); // NOLINT +int64_t operator<<(std::ostream&, unsigned short value); // NOLINT +int64_t operator<<(std::ostream&, int value); +int64_t operator<<(std::ostream&, unsigned int value); +int64_t operator<<(std::ostream&, long value); // NOLINT +uint64_t operator<<(std::ostream&, unsigned long value); // NOLINT +int64_t operator<<(std::ostream&, long long value); // NOLINT +uint64_t operator<<(std::ostream&, unsigned long long value); // NOLINT +float operator<<(std::ostream&, float value); +double operator<<(std::ostream&, double value); +long double operator<<(std::ostream&, long double value); +bool operator<<(std::ostream&, bool value); +const void* operator<<(std::ostream&, const void* value); +const void* operator<<(std::ostream&, std::nullptr_t); + +// These `char` overloads are specified like this in the standard, so we have to +// write them exactly the same to ensure the call is ambiguous. +// If we wrote it in a different way (eg taking std::ostream instead of the +// template) then one call might have a higher rank than the other and it would +// not be ambiguous. +template <typename Traits> +char operator<<(std::basic_ostream<char, Traits>&, char); +template <typename Traits> +signed char operator<<(std::basic_ostream<char, Traits>&, signed char); +template <typename Traits> +unsigned char operator<<(std::basic_ostream<char, Traits>&, unsigned char); +template <typename Traits> +const char* operator<<(std::basic_ostream<char, Traits>&, const char*); +template <typename Traits> +const signed char* operator<<(std::basic_ostream<char, Traits>&, + const signed char*); +template <typename Traits> +const unsigned char* operator<<(std::basic_ostream<char, Traits>&, + const unsigned char*); + +// This overload triggers when the call is not ambiguous. +// It means that T is being printed with some overload not on this list. +// We keep the value as `const T&`. +template <typename T, typename = decltype(std::declval<std::ostream&>() + << std::declval<const T&>())> +const T& Detect(int); + +// This overload triggers when the call is ambiguous. +// It means that T is either one from this list or printed as one from this +// list. Eg an enum that decays to `int` for printing. +// We ask the overload set to give us the type we want to convert it to. +template <typename T> +decltype(detect_specialization::operator<<(std::declval<std::ostream&>(), + std::declval<const T&>())) +Detect(char); + +} // namespace detect_specialization + +template <typename T> +using CheckOpStreamType = decltype(detect_specialization::Detect<T>(0)); + +// Build the error message string. Specify no inlining for code size. +template <typename T1, typename T2> +ABSL_ATTRIBUTE_RETURNS_NONNULL std::string* MakeCheckOpString( + T1 v1, T2 v2, const char* exprtext) ABSL_ATTRIBUTE_NOINLINE; + +template <typename T1, typename T2> +std::string* MakeCheckOpString(T1 v1, T2 v2, const char* exprtext) { + CheckOpMessageBuilder comb(exprtext); + MakeCheckOpValueString(comb.ForVar1(), v1); + MakeCheckOpValueString(comb.ForVar2(), v2); + return comb.NewString(); +} + +// Add a few commonly used instantiations as extern to reduce size of objects +// files. +#define ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(x) \ + extern template std::string* MakeCheckOpString(x, x, const char*) +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(bool); +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(int64_t); +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(uint64_t); +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(float); +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(double); +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(char); +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(unsigned char); +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(const std::string&); +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(const absl::string_view&); +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(const char*); +ABSL_LOG_INTERNAL_DEFINE_MAKE_CHECK_OP_STRING_EXTERN(const signed char*); +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 + +// 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 +// type. +#define ABSL_LOG_INTERNAL_CHECK_OP_IMPL(name, op) \ + template <typename T1, typename T2> \ + inline constexpr ::std::string* name##Impl(const T1& v1, const T2& v2, \ + const char* exprtext) { \ + using U1 = CheckOpStreamType<T1>; \ + using U2 = CheckOpStreamType<T2>; \ + return ABSL_PREDICT_TRUE(v1 op v2) \ + ? nullptr \ + : MakeCheckOpString<U1, U2>(v1, v2, exprtext); \ + } \ + inline constexpr ::std::string* name##Impl(int v1, int v2, \ + const char* exprtext) { \ + return name##Impl<int, int>(v1, v2, exprtext); \ + } + +ABSL_LOG_INTERNAL_CHECK_OP_IMPL(Check_EQ, ==) +ABSL_LOG_INTERNAL_CHECK_OP_IMPL(Check_NE, !=) +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 + +std::string* CheckstrcmptrueImpl(const char* s1, const char* s2, + const char* exprtext); +std::string* CheckstrcmpfalseImpl(const char* s1, const char* s2, + const char* exprtext); +std::string* CheckstrcasecmptrueImpl(const char* s1, const char* s2, + const char* exprtext); +std::string* CheckstrcasecmpfalseImpl(const char* s1, const char* s2, + const char* exprtext); + +// `CHECK_EQ` and friends want to pass their arguments by reference, however +// this winds up exposing lots of cases where people have defined and +// initialized static const data members but never declared them (i.e. in a .cc +// file), meaning they are not referenceable. This function avoids that problem +// for integers (the most common cases) by overloading for every primitive +// integer type, even the ones we discourage, and returning them by value. +template <typename T> +inline constexpr const T& GetReferenceableValue(const T& t) { + return t; +} +inline constexpr char GetReferenceableValue(char t) { return t; } +inline constexpr unsigned char GetReferenceableValue(unsigned char t) { + return t; +} +inline constexpr signed char GetReferenceableValue(signed char t) { return t; } +inline constexpr short GetReferenceableValue(short t) { return t; } // NOLINT +inline constexpr unsigned short GetReferenceableValue( // NOLINT + unsigned short t) { // NOLINT + return t; +} +inline constexpr int GetReferenceableValue(int t) { return t; } +inline 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 + return t; +} +inline constexpr long long GetReferenceableValue(long long t) { // NOLINT + return t; +} +inline constexpr unsigned long long GetReferenceableValue( // NOLINT + unsigned long long t) { // NOLINT + return t; +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_CHECK_OP_H_ diff --git a/absl/log/internal/conditions.cc b/absl/log/internal/conditions.cc new file mode 100644 index 00000000..a9f4966f --- /dev/null +++ b/absl/log/internal/conditions.cc @@ -0,0 +1,83 @@ +// 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/conditions.h" + +#include <atomic> +#include <cstdint> + +#include "absl/base/config.h" +#include "absl/base/internal/cycleclock.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { +namespace { + +// The following code behaves like AtomicStatsCounter::LossyAdd() for +// speed since it is fine to lose occasional updates. +// Returns old value of *counter. +uint32_t LossyIncrement(std::atomic<uint32_t>* counter) { + const uint32_t value = counter->load(std::memory_order_relaxed); + counter->store(value + 1, std::memory_order_relaxed); + return value; +} + +} // namespace + +bool LogEveryNState::ShouldLog(int n) { + return n > 0 && (LossyIncrement(&counter_) % static_cast<uint32_t>(n)) == 0; +} + +bool LogFirstNState::ShouldLog(int n) { + const uint32_t counter_value = counter_.load(std::memory_order_relaxed); + if (static_cast<int64_t>(counter_value) < n) { + counter_.store(counter_value + 1, std::memory_order_relaxed); + return true; + } + return false; +} + +bool LogEveryPow2State::ShouldLog() { + const uint32_t new_value = LossyIncrement(&counter_) + 1; + return (new_value & (new_value - 1)) == 0; +} + +bool LogEveryNSecState::ShouldLog(double seconds) { + using absl::base_internal::CycleClock; + LossyIncrement(&counter_); + const int64_t now_cycles = CycleClock::Now(); + int64_t next_cycles = next_log_time_cycles_.load(std::memory_order_relaxed); +#if defined(__myriad2__) + // myriad2 does not have 8-byte compare and exchange. Use a racy version that + // is "good enough" but will over-log in the face of concurrent logging. + if (now_cycles > next_cycles) { + next_log_time_cycles_.store(now_cycles + seconds * CycleClock::Frequency(), + std::memory_order_relaxed); + return true; + } + return false; +#else + do { + if (now_cycles <= next_cycles) return false; + } while (!next_log_time_cycles_.compare_exchange_weak( + next_cycles, now_cycles + seconds * CycleClock::Frequency(), + std::memory_order_relaxed, std::memory_order_relaxed)); + return true; +#endif +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/internal/conditions.h b/absl/log/internal/conditions.h new file mode 100644 index 00000000..b89f1dfd --- /dev/null +++ b/absl/log/internal/conditions.h @@ -0,0 +1,222 @@ +// 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/internal/conditions.h +// ----------------------------------------------------------------------------- +// +// This file contains implementation of conditional log statements, like LOG_IF +// including all the ABSL_LOG_INTERNAL_..._CONDITION_... macros and +// various condition classes like LogEveryNState. + +#ifndef ABSL_LOG_INTERNAL_CONDITIONS_H_ +#define ABSL_LOG_INTERNAL_CONDITIONS_H_ + +#ifdef _WIN32 +#include <cstdlib> +#else +#include <unistd.h> +#endif +#include <stdlib.h> + +#include <atomic> +#include <cstdint> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/log/internal/voidify.h" + +// `ABSL_LOG_INTERNAL_CONDITION` prefixes another macro that expands to a +// temporary `LogMessage` instantiation followed by zero or more streamed +// expressions. This definition is tricky to read correctly. It evaluates to +// either +// +// (void)0; +// +// or +// +// ::absl::log_internal::Voidify() && +// ::absl::log_internal::LogMessage(...) << "the user's message"; +// +// If the condition is evaluable at compile time, as is often the case, it +// compiles away to just one side or the other. +// +// Although this is not used anywhere a statement (e.g. `if`) could not go, +// 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. +#define ABSL_LOG_INTERNAL_STATELESS_CONDITION(condition) \ + switch (0) \ + case 0: \ + !(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 +// declarations, including a local static object which stores the state needed +// to implement the stateful macros like `LOG_EVERY_N`. +// +// `for`-loops are used to declare scoped variables without braces (to permit +// streaming into the macro's expansion) and without the dangling-`else` +// problems/diagnostics that come with `if`. +// +// Two more variables are declared in separate `for`-loops: +// +// * `COUNTER` implements a streamable token whose value when streamed is the +// number of times execution has passed through the macro. +// * A boolean flag is used to prevent any of the `for`-loops from ever actually +// looping. +#define ABSL_LOG_INTERNAL_STATEFUL_CONDITION(condition) \ + for (bool absl_log_internal_stateful_condition_do_log(condition); \ + absl_log_internal_stateful_condition_do_log; \ + absl_log_internal_stateful_condition_do_log = false) \ + ABSL_LOG_INTERNAL_STATEFUL_CONDITION_IMPL +#define ABSL_LOG_INTERNAL_STATEFUL_CONDITION_IMPL(kind, ...) \ + for (static ::absl::log_internal::Log##kind##State \ + absl_log_internal_stateful_condition_state; \ + absl_log_internal_stateful_condition_do_log && \ + absl_log_internal_stateful_condition_state.ShouldLog(__VA_ARGS__); \ + absl_log_internal_stateful_condition_do_log = false) \ + for (const uint32_t COUNTER ABSL_ATTRIBUTE_UNUSED = \ + absl_log_internal_stateful_condition_state.counter(); \ + absl_log_internal_stateful_condition_do_log; \ + absl_log_internal_stateful_condition_do_log = false) + +// `ABSL_LOG_INTERNAL_CONDITION_*` serve to combine any conditions from the +// macro (e.g. `LOG_IF` or `VLOG`) with inherent conditions (e.g. +// `ABSL_MIN_LOG_LEVEL`) into a single boolean expression. We could chain +// ternary operators instead, however some versions of Clang sometimes issue +// spurious diagnostics after such expressions due to a control flow analysis +// bug. +#ifdef ABSL_MIN_LOG_LEVEL +#define ABSL_LOG_INTERNAL_CONDITION_INFO(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION( \ + (condition) && ::absl::LogSeverity::kInfo >= \ + static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL)) +#define ABSL_LOG_INTERNAL_CONDITION_WARNING(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION( \ + (condition) && ::absl::LogSeverity::kWarning >= \ + static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL)) +#define ABSL_LOG_INTERNAL_CONDITION_ERROR(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION( \ + (condition) && ::absl::LogSeverity::kError >= \ + static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL)) +// NOTE: Use ternary operators instead of short-circuiting to mitigate +// https://bugs.llvm.org/show_bug.cgi?id=51928. +#define ABSL_LOG_INTERNAL_CONDITION_FATAL(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION( \ + ((condition) \ + ? (::absl::LogSeverity::kFatal >= \ + static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) \ + ? true \ + : (::absl::log_internal::AbortQuietly(), false)) \ + : false)) +// NOTE: Use ternary operators instead of short-circuiting to mitigate +// https://bugs.llvm.org/show_bug.cgi?id=51928. +#define ABSL_LOG_INTERNAL_CONDITION_QFATAL(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION( \ + ((condition) \ + ? (::absl::LogSeverity::kFatal >= \ + static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) \ + ? 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) \ + 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)))) +#else // ndef ABSL_MIN_LOG_LEVEL +#define ABSL_LOG_INTERNAL_CONDITION_INFO(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION(condition) +#define ABSL_LOG_INTERNAL_CONDITION_WARNING(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION(condition) +#define ABSL_LOG_INTERNAL_CONDITION_ERROR(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION(condition) +#define ABSL_LOG_INTERNAL_CONDITION_FATAL(type, condition) \ + 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) \ + ABSL_LOG_INTERNAL_CONDITION_LEVEL_IMPL +#define ABSL_LOG_INTERNAL_CONDITION_LEVEL_IMPL(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION(condition) +#endif // ndef ABSL_MIN_LOG_LEVEL + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +// Stateful condition class name should be "Log" + name + "State". +class LogEveryNState final { + public: + bool ShouldLog(int n); + uint32_t counter() { return counter_.load(std::memory_order_relaxed); } + + private: + std::atomic<uint32_t> counter_{0}; +}; + +class LogFirstNState final { + public: + bool ShouldLog(int n); + uint32_t counter() { return counter_.load(std::memory_order_relaxed); } + + private: + std::atomic<uint32_t> counter_{0}; +}; + +class LogEveryPow2State final { + public: + bool ShouldLog(); + uint32_t counter() { return counter_.load(std::memory_order_relaxed); } + + private: + std::atomic<uint32_t> counter_{0}; +}; + +class LogEveryNSecState final { + public: + bool ShouldLog(double seconds); + uint32_t counter() { return counter_.load(std::memory_order_relaxed); } + + private: + std::atomic<uint32_t> counter_{0}; + // Cycle count according to CycleClock that we should next log at. + std::atomic<int64_t> next_log_time_cycles_{0}; +}; + +// Helper routines to abort the application quietly + +ABSL_ATTRIBUTE_NORETURN inline void AbortQuietly() { abort(); } +ABSL_ATTRIBUTE_NORETURN inline void ExitQuietly() { _exit(1); } +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_CONDITIONS_H_ diff --git a/absl/log/internal/config.h b/absl/log/internal/config.h new file mode 100644 index 00000000..379e9ab9 --- /dev/null +++ b/absl/log/internal/config.h @@ -0,0 +1,45 @@ +// 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/internal/config.h +// ----------------------------------------------------------------------------- +// + +#ifndef ABSL_LOG_INTERNAL_CONFIG_H_ +#define ABSL_LOG_INTERNAL_CONFIG_H_ + +#include "absl/base/config.h" + +#ifdef _WIN32 +#include <cstdint> +#else +#include <sys/types.h> +#endif + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +#ifdef _WIN32 +using Tid = uint32_t; +#else +using Tid = pid_t; +#endif + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_CONFIG_H_ diff --git a/absl/log/internal/flags.h b/absl/log/internal/flags.h new file mode 100644 index 00000000..0c5e81ed --- /dev/null +++ b/absl/log/internal/flags.h @@ -0,0 +1,53 @@ +// 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/log_flags.h +// ----------------------------------------------------------------------------- +// +// This header declares set of flags which can be used to configure Abseil +// Logging library behaviour at runtime. + +#ifndef ABSL_LOG_INTERNAL_FLAGS_H_ +#define ABSL_LOG_INTERNAL_FLAGS_H_ + +#include <string> + +#include "absl/flags/declare.h" + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// These flags should not be used in C++ code to access logging library +// configuration knobs. Use interfaces defined in absl/log/globals.h +// instead. It is still ok to use these flags on a command line. +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +// 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 +// severity levels. +ABSL_DECLARE_FLAG(int, stderrthreshold); + +// Log messages at this severity or above are logged; others are discarded. +// Defaults to `INFO`, i.e. log all severities. See log_severity.h for numeric +// values of severity levels. +ABSL_DECLARE_FLAG(int, minloglevel); + +// If specified in the form file:linenum, any messages logged from a matching +// location will also include a backtrace. +ABSL_DECLARE_FLAG(std::string, log_backtrace_at); + +// If true, the log prefix (severity, date, time, PID, etc.) is prepended to +// each message logged. Defaults to true. +ABSL_DECLARE_FLAG(bool, log_prefix); + +#endif // ABSL_LOG_INTERNAL_FLAGS_H_ diff --git a/absl/log/internal/globals.cc b/absl/log/internal/globals.cc new file mode 100644 index 00000000..863b047f --- /dev/null +++ b/absl/log/internal/globals.cc @@ -0,0 +1,125 @@ +// 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/globals.h" + +#include <atomic> +#include <cstdio> + +#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/time/time.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +namespace { +// Keeps track of whether Logging initialization is finalized. +// Log messages generated before that will go to stderr. +ABSL_CONST_INIT std::atomic<bool> logging_initialized(false); + +// The TimeZone used for logging. This may only be set once. +ABSL_CONST_INIT std::atomic<absl::TimeZone*> timezone_ptr{nullptr}; + +// If true, the logging library will symbolize stack in fatal messages +ABSL_CONST_INIT std::atomic<bool> symbolize_stack_trace(true); + +// Specifies maximum number of stack frames to report in fatal messages. +ABSL_CONST_INIT std::atomic<int> max_frames_in_stack_trace(64); + +ABSL_CONST_INIT std::atomic<bool> exit_on_dfatal(true); +ABSL_CONST_INIT std::atomic<bool> suppress_sigabort_trace(false); +} // namespace + +bool IsInitialized() { + return logging_initialized.load(std::memory_order_acquire); +} + +void SetInitialized() { + logging_initialized.store(true, std::memory_order_release); +} + +void WriteToStderr(absl::string_view message, absl::LogSeverity severity) { + // 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); + +#if defined(_WIN64) || defined(_WIN32) || defined(_WIN16) + // C99 requires stderr to not be fully-buffered by default (7.19.3.7), but + // MS CRT buffers it anyway, so we must `fflush` to ensure the string hits + // the console/file before the program dies (and takes the libc buffers + // with it). + // https://docs.microsoft.com/en-us/cpp/c-runtime-library/stream-i-o + if (severity >= absl::LogSeverity::kWarning) { + std::fflush(stderr); + } +#else + // Avoid unused parameter warning in this branch. + (void)severity; +#endif +} + +void SetTimeZone(absl::TimeZone tz) { + absl::TimeZone* expected = nullptr; + absl::TimeZone* new_tz = new absl::TimeZone(tz); + // timezone_ptr can only be set once, otherwise new_tz is leaked. + if (!timezone_ptr.compare_exchange_strong(expected, new_tz, + std::memory_order_release, + std::memory_order_relaxed)) { + ABSL_RAW_LOG(FATAL, + "absl::log_internal::SetTimeZone() has already been called"); + } +} + +const absl::TimeZone* TimeZone() { + return timezone_ptr.load(std::memory_order_acquire); +} + +bool ShouldSymbolizeLogStackTrace() { + return symbolize_stack_trace.load(std::memory_order_acquire); +} + +void EnableSymbolizeLogStackTrace(bool on_off) { + symbolize_stack_trace.store(on_off, std::memory_order_release); +} + +int MaxFramesInLogStackTrace() { + return max_frames_in_stack_trace.load(std::memory_order_acquire); +} + +void SetMaxFramesInLogStackTrace(int max_num_frames) { + max_frames_in_stack_trace.store(max_num_frames, std::memory_order_release); +} + +bool ExitOnDFatal() { return exit_on_dfatal.load(std::memory_order_acquire); } + +void SetExitOnDFatal(bool on_off) { + exit_on_dfatal.store(on_off, std::memory_order_release); +} + +bool SuppressSigabortTrace() { + return suppress_sigabort_trace.load(std::memory_order_acquire); +} + +bool SetSuppressSigabortTrace(bool on_off) { + return suppress_sigabort_trace.exchange(on_off); +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/internal/globals.h b/absl/log/internal/globals.h new file mode 100644 index 00000000..27bc0d09 --- /dev/null +++ b/absl/log/internal/globals.h @@ -0,0 +1,101 @@ +// 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/internal/globals.h +// ----------------------------------------------------------------------------- +// +// This header file contains various global objects and static helper routines +// use in logging implementation. + +#ifndef ABSL_LOG_INTERNAL_GLOBALS_H_ +#define ABSL_LOG_INTERNAL_GLOBALS_H_ + +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +// IsInitialized returns true if the logging library is initialized. +// This function is async-signal-safe +bool IsInitialized(); + +// SetLoggingInitialized is called once after logging initialization is done. +void SetInitialized(); + +// Unconditionally write a `message` to stderr. If `severity` exceeds kInfo +// we also flush the stderr stream. +void WriteToStderr(absl::string_view message, absl::LogSeverity severity); + +// Set the TimeZone used for human-friendly times (for example, the log message +// prefix) printed by the logging library. This may only be called once. +void SetTimeZone(absl::TimeZone tz); + +// Returns the TimeZone used for human-friendly times (for example, the log +// message prefix) printed by the logging library Returns nullptr prior to +// initialization. +const absl::TimeZone* TimeZone(); + +// Returns true if stack traces emitted by the logging library should be +// symbolized. This function is async-signal-safe. +bool ShouldSymbolizeLogStackTrace(); + +// Enables or disables symbolization of stack traces emitted by the +// logging library. This function is async-signal-safe. +void EnableSymbolizeLogStackTrace(bool on_off); + +// Returns the maximum number of frames that appear in stack traces +// emitted by the logging library. This function is async-signal-safe. +int MaxFramesInLogStackTrace(); + +// Sets the maximum number of frames that appear in stack traces emitted by +// the logging library. This function is async-signal-safe. +void SetMaxFramesInLogStackTrace(int max_num_frames); + +// Determines whether we exit the program for a LOG(DFATAL) message in +// debug mode. It does this by skipping the call to Fail/FailQuietly. +// This is intended for testing only. +// +// This can have some effects on LOG(FATAL) as well. Failure messages +// are always allocated (rather than sharing a buffer), the crash +// reason is not recorded, the "gwq" status message is not updated, +// and the stack trace is not recorded. The LOG(FATAL) *will* still +// exit the program. Since this function is used only in testing, +// these differences are acceptable. +// +// Additionally, LOG(LEVEL(FATAL)) is indistinguishable from LOG(DFATAL) and +// will not terminate the program if SetExitOnDFatal(false) has been called. +bool ExitOnDFatal(); + +// SetExitOnDFatal() sets the ExitOnDFatal() status +void SetExitOnDFatal(bool on_off); + +// Determines if the logging library should suppress logging of stacktraces in +// the `SIGABRT` handler, typically because we just logged a stacktrace as part +// of `LOG(FATAL)` and are about to send ourselves a `SIGABRT` to end the +// program. +bool SuppressSigabortTrace(); + +// Sets the SuppressSigabortTrace() status and returns the previous state. +bool SetSuppressSigabortTrace(bool on_off); + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_GLOBALS_H_ diff --git a/absl/log/internal/log_format.cc b/absl/log/internal/log_format.cc new file mode 100644 index 00000000..b8cd5ac4 --- /dev/null +++ b/absl/log/internal/log_format.cc @@ -0,0 +1,203 @@ +// +// 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/log_format.h" + +#include <string.h> + +#ifdef _MSC_VER +#include <winsock2.h> // For timeval +#else +#include <sys/time.h> +#endif + +#include <cstddef> +#include <cstdint> +#include <limits> +#include <string> +#include <type_traits> + +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/base/optimization.h" +#include "absl/log/internal/append_truncated.h" +#include "absl/log/internal/config.h" +#include "absl/log/internal/globals.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "absl/time/civil_time.h" +#include "absl/time/time.h" +#include "absl/types/span.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { +namespace { + +// This templated function avoids compiler warnings about tautological +// comparisons when log_internal::Tid is unsigned. It can be replaced with a +// constexpr if once the minimum C++ version Abseil suppports is C++17. +template <typename T> +inline std::enable_if_t<!std::is_signed<T>::value> +PutLeadingWhitespace(T tid, char*& p) { + if (tid < 10) *p++ = ' '; + if (tid < 100) *p++ = ' '; + if (tid < 1000) *p++ = ' '; + if (tid < 10000) *p++ = ' '; + if (tid < 100000) *p++ = ' '; + if (tid < 1000000) *p++ = ' '; +} + +template <typename T> +inline std::enable_if_t<std::is_signed<T>::value> +PutLeadingWhitespace(T tid, char*& p) { + if (tid >= 0 && tid < 10) *p++ = ' '; + if (tid > -10 && tid < 100) *p++ = ' '; + if (tid > -100 && tid < 1000) *p++ = ' '; + if (tid > -1000 && tid < 10000) *p++ = ' '; + if (tid > -10000 && tid < 100000) *p++ = ' '; + if (tid > -100000 && tid < 1000000) *p++ = ' '; +} + +// The fields before the filename are all fixed-width except for the thread ID, +// which is of bounded width. +size_t FormatBoundedFields(absl::LogSeverity severity, absl::Time timestamp, + log_internal::Tid tid, absl::Span<char>& buf) { + constexpr size_t kBoundedFieldsMaxLen = + sizeof("SMMDD HH:MM:SS.NNNNNN ") + + (1 + std::numeric_limits<log_internal::Tid>::digits10 + 1) - sizeof(""); + if (ABSL_PREDICT_FALSE(buf.size() < kBoundedFieldsMaxLen)) { + // We don't bother trying to truncate these fields if the buffer is too + // short (or almost too short) because it would require doing a lot more + // length checking (slow) and it should never happen. A 15kB buffer should + // be enough for anyone. Instead we mark `buf` full without writing + // anything. + buf.remove_suffix(buf.size()); + return 0; + } + + // We can't call absl::LocalTime(), localtime_r(), or anything else here that + // isn't async-signal-safe. We can only use the time zone if it has already + // been loaded. + const absl::TimeZone* tz = absl::log_internal::TimeZone(); + if (ABSL_PREDICT_FALSE(tz == nullptr)) { + // If a time zone hasn't been set yet because we are logging before the + // logging library has been initialized, we fallback to a simpler, slower + // method. Just report the raw Unix time in seconds. We cram this into the + // normal time format for the benefit of parsers. + auto tv = absl::ToTimeval(timestamp); + int snprintf_result = absl::SNPrintF( + buf.data(), buf.size(), "%c0000 00:00:%02d.%06d %7d ", + absl::LogSeverityName(severity)[0], static_cast<int>(tv.tv_sec), + static_cast<int>(tv.tv_usec), static_cast<int>(tid)); + if (snprintf_result >= 0) { + buf.remove_prefix(static_cast<size_t>(snprintf_result)); + return static_cast<size_t>(snprintf_result); + } + return 0; + } + + 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); + p += 2; + absl::numbers_internal::PutTwoDigits(static_cast<size_t>(ci.cs.day()), p); + p += 2; + *p++ = ' '; + absl::numbers_internal::PutTwoDigits(static_cast<size_t>(ci.cs.hour()), p); + p += 2; + *p++ = ':'; + absl::numbers_internal::PutTwoDigits(static_cast<size_t>(ci.cs.minute()), p); + p += 2; + *p++ = ':'; + absl::numbers_internal::PutTwoDigits(static_cast<size_t>(ci.cs.second()), p); + p += 2; + *p++ = '.'; + const int64_t usecs = absl::ToInt64Microseconds(ci.subsecond); + absl::numbers_internal::PutTwoDigits(static_cast<size_t>(usecs / 10000), p); + p += 2; + absl::numbers_internal::PutTwoDigits(static_cast<size_t>(usecs / 100 % 100), + p); + p += 2; + absl::numbers_internal::PutTwoDigits(static_cast<size_t>(usecs % 100), p); + p += 2; + *p++ = ' '; + PutLeadingWhitespace(tid, p); + p = absl::numbers_internal::FastIntToBuffer(tid, p); + *p++ = ' '; + const size_t bytes_formatted = static_cast<size_t>(p - buf.data()); + buf.remove_prefix(bytes_formatted); + return bytes_formatted; +} + +size_t FormatLineNumber(int line, absl::Span<char>& buf) { + constexpr size_t kLineFieldMaxLen = + sizeof(":] ") + (1 + std::numeric_limits<int>::digits10 + 1) - sizeof(""); + if (ABSL_PREDICT_FALSE(buf.size() < kLineFieldMaxLen)) { + // As above, we don't bother trying to truncate this if the buffer is too + // short and it should never happen. + buf.remove_suffix(buf.size()); + return 0; + } + char* p = buf.data(); + *p++ = ':'; + p = absl::numbers_internal::FastIntToBuffer(line, p); + *p++ = ']'; + *p++ = ' '; + const size_t bytes_formatted = static_cast<size_t>(p - buf.data()); + buf.remove_prefix(bytes_formatted); + return bytes_formatted; +} + +} // namespace + +std::string FormatLogMessage(absl::LogSeverity severity, + absl::CivilSecond civil_second, + absl::Duration subsecond, log_internal::Tid tid, + absl::string_view basename, int line, + PrefixFormat format, absl::string_view message) { + return absl::StrFormat( + "%c%02d%02d %02d:%02d:%02d.%06d %7d %s:%d] %s%s", + absl::LogSeverityName(severity)[0], civil_second.month(), + civil_second.day(), civil_second.hour(), civil_second.minute(), + civil_second.second(), absl::ToInt64Microseconds(subsecond), tid, + basename, line, format == PrefixFormat::kRaw ? "RAW: " : "", message); +} + +// This method is fairly hot, and the library always passes a huge `buf`, so we +// save some bounds-checking cycles by not trying to do precise truncation. +// Truncating at a field boundary is probably a better UX anyway. +// +// The prefix is written in three parts, each of which does a single +// bounds-check and truncation: +// 1. severity, timestamp, and thread ID +// 2. filename +// 3. line number and bracket +size_t FormatLogPrefix(absl::LogSeverity severity, absl::Time timestamp, + log_internal::Tid tid, absl::string_view basename, + int line, PrefixFormat format, absl::Span<char>& buf) { + auto prefix_size = FormatBoundedFields(severity, timestamp, tid, buf); + prefix_size += log_internal::AppendTruncated(basename, buf); + prefix_size += FormatLineNumber(line, buf); + if (format == PrefixFormat::kRaw) + prefix_size += log_internal::AppendTruncated("RAW: ", buf); + return prefix_size; +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/internal/log_format.h b/absl/log/internal/log_format.h new file mode 100644 index 00000000..95a45edf --- /dev/null +++ b/absl/log/internal/log_format.h @@ -0,0 +1,78 @@ +// 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/internal/log_format.h +// ----------------------------------------------------------------------------- +// +// This file declares routines implementing formatting of log message and log +// prefix. + +#ifndef ABSL_LOG_INTERNAL_LOG_FORMAT_H_ +#define ABSL_LOG_INTERNAL_LOG_FORMAT_H_ + +#include <stddef.h> + +#include <string> + +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/log/internal/config.h" +#include "absl/strings/string_view.h" +#include "absl/time/civil_time.h" +#include "absl/time/time.h" +#include "absl/types/span.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +enum class PrefixFormat { + kNotRaw, + kRaw, +}; + +// Formats log message based on provided data. +std::string FormatLogMessage(absl::LogSeverity severity, + absl::CivilSecond civil_second, + absl::Duration subsecond, log_internal::Tid tid, + absl::string_view basename, int line, + PrefixFormat format, absl::string_view message); + +// Formats various entry metadata into a text string meant for use as a +// prefix on a log message string. Writes into `buf`, advances `buf` to point +// at the remainder of the buffer (i.e. past any written bytes), and returns the +// number of bytes written. +// +// In addition to calling `buf->remove_prefix()` (or the equivalent), this +// function may also do `buf->remove_suffix(buf->size())` in cases where no more +// bytes (i.e. no message data) should be written into the buffer. For example, +// if the prefix ought to be: +// I0926 09:00:00.000000 1234567 foo.cc:123] +// `buf` is too small, the function might fill the whole buffer: +// I0926 09:00:00.000000 1234 +// (note the apparrently incorrect thread ID), or it might write less: +// I0926 09:00:00.000000 +// In this case, it might also empty `buf` prior to returning to prevent +// message data from being written into the space where a reader would expect to +// see a thread ID. +size_t FormatLogPrefix(absl::LogSeverity severity, absl::Time timestamp, + log_internal::Tid tid, absl::string_view basename, + int line, PrefixFormat format, absl::Span<char>& buf); + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_LOG_FORMAT_H_ diff --git a/absl/log/internal/log_impl.h b/absl/log/internal/log_impl.h new file mode 100644 index 00000000..82b5ed84 --- /dev/null +++ b/absl/log/internal/log_impl.h @@ -0,0 +1,212 @@ +// 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_LOG_INTERNAL_LOG_IMPL_H_ +#define ABSL_LOG_INTERNAL_LOG_IMPL_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) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, true) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +// ABSL_PLOG() +#define ABSL_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) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, true) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() +#else +#define ABSL_DLOG_IMPL(severity) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, false) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() +#endif + +#define ABSL_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) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, condition) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ + .WithPerror() + +#ifndef NDEBUG +#define ABSL_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) \ + 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) \ + 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) \ + 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) \ + 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) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryNSec, n_seconds) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#define ABSL_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) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(FirstN, n) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ + .WithPerror() + +#define ABSL_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) \ + 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) \ + (EveryN, n) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#define ABSL_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) \ + (EveryPow2) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#define ABSL_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) \ + (EveryN, n) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#define ABSL_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) \ + (EveryPow2) ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + +#define ABSL_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) \ + 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) \ + 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) \ + 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) \ + 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) \ + 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) \ + 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) \ + 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) \ + 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) \ + 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) \ + 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) \ + 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) \ + 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) \ + 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) \ + 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) \ + 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) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, false && (condition))( \ + EveryNSec, n_seconds) \ + ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() +#endif // def NDEBUG + +#endif // ABSL_LOG_INTERNAL_LOG_IMPL_H_ diff --git a/absl/log/internal/log_message.cc b/absl/log/internal/log_message.cc new file mode 100644 index 00000000..bdb10f2a --- /dev/null +++ b/absl/log/internal/log_message.cc @@ -0,0 +1,618 @@ +// +// 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/log_message.h" + +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#ifndef _WIN32 +#include <unistd.h> +#endif + +#include <algorithm> +#include <array> +#include <atomic> +#include <memory> +#include <ostream> +#include <string> +#include <tuple> + +#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/internal/sysinfo.h" +#include "absl/base/log_severity.h" +#include "absl/container/inlined_vector.h" +#include "absl/debugging/internal/examine_stack.h" +#include "absl/log/globals.h" +#include "absl/log/internal/append_truncated.h" +#include "absl/log/internal/globals.h" +#include "absl/log/internal/log_format.h" +#include "absl/log/internal/log_sink_set.h" +#include "absl/log/internal/proto.h" +#include "absl/log/log_entry.h" +#include "absl/log/log_sink.h" +#include "absl/log/log_sink_registry.h" +#include "absl/memory/memory.h" +#include "absl/strings/string_view.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "absl/types/span.h" + +extern "C" ABSL_ATTRIBUTE_WEAK void ABSL_INTERNAL_C_SYMBOL( + AbslInternalOnFatalLogMessage)(const absl::LogEntry&) { + // Default - Do nothing +} + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +namespace { +// message `logging.proto.Event` +enum EventTag : uint8_t { + kValue = 7, +}; + +// message `logging.proto.Value` +enum ValueTag : uint8_t { + kString = 1, + kStringLiteral = 6, +}; + +// Decodes a `logging.proto.Value` from `buf` and writes a string representation +// into `dst`. The string representation will be truncated if `dst` is not +// large enough to hold it. Returns false if `dst` has size zero or one (i.e. +// sufficient only for a nul-terminator) and no decoded data could be written. +// This function may or may not write a nul-terminator into `dst`, and it may or +// may not truncate the data it writes in order to do make space for that nul +// terminator. In any case, `dst` will be advanced to point at the byte where +// subsequent writes should begin. +bool PrintValue(absl::Span<char>& dst, absl::Span<const char> buf) { + if (dst.size() <= 1) return false; + ProtoField field; + while (field.DecodeFrom(&buf)) { + switch (field.tag()) { + case ValueTag::kString: + case ValueTag::kStringLiteral: + if (field.type() == WireType::kLengthDelimited) + if (log_internal::AppendTruncated(field.string_value(), dst) < + field.string_value().size()) + return false; + } + } + return true; +} + +absl::string_view Basename(absl::string_view filepath) { +#ifdef _WIN32 + size_t path = filepath.find_last_of("/\\"); +#else + size_t path = filepath.find_last_of('/'); +#endif + if (path != filepath.npos) filepath.remove_prefix(path + 1); + return filepath; +} + +void WriteToString(const char* data, void* str) { + reinterpret_cast<std::string*>(str)->append(data); +} +void WriteToStream(const char* data, void* os) { + auto* cast_os = static_cast<std::ostream*>(os); + *cast_os << data; +} +} // namespace + +struct LogMessage::LogMessageData final { + LogMessageData(const char* file, int line, absl::LogSeverity severity, + absl::Time timestamp); + LogMessageData(const LogMessageData&) = delete; + LogMessageData& operator=(const LogMessageData&) = delete; + + // `LogEntry` sent to `LogSink`s; contains metadata. + absl::LogEntry entry; + + // true => this was first fatal msg + bool first_fatal; + // true => all failures should be quiet + bool fail_quietly; + // true => PLOG was requested + bool is_perror; + + // Extra `LogSink`s to log to, in addition to `global_sinks`. + absl::InlinedVector<absl::LogSink*, 16> extra_sinks; + // If true, log to `extra_sinks` but not to `global_sinks` or hardcoded + // non-sink targets (e.g. stderr, log files). + bool extra_sinks_only; + + std::ostream manipulated; // ostream with IO manipulators applied + + // A `logging.proto.Event` proto message is built into `encoded_buf`. + std::array<char, kLogMessageBufferSize> encoded_buf; + // `encoded_remaining` is the suffix of `encoded_buf` that has not been filled + // yet. If a datum to be encoded does not fit into `encoded_remaining` and + // cannot be truncated to fit, the size of `encoded_remaining` will be zeroed + // to prevent encoding of any further data. Note that in this case its data() + // pointer will not point past the end of `encoded_buf`. + absl::Span<char> encoded_remaining; + + // A formatted string message is built in `string_buf`. + std::array<char, kLogMessageBufferSize> string_buf; + + void FinalizeEncodingAndFormat(); +}; + +LogMessage::LogMessageData::LogMessageData(const char* file, int line, + absl::LogSeverity severity, + absl::Time timestamp) + : extra_sinks_only(false), + manipulated(nullptr), + // This `absl::MakeSpan` silences spurious -Wuninitialized from GCC: + encoded_remaining(absl::MakeSpan(encoded_buf)) { + // Legacy defaults for LOG's ostream: + manipulated.setf(std::ios_base::showbase | std::ios_base::boolalpha); + entry.full_filename_ = file; + entry.base_filename_ = Basename(file); + entry.line_ = line; + entry.prefix_ = absl::ShouldPrependLogPrefix(); + entry.severity_ = absl::NormalizeLogSeverity(severity); + entry.verbose_level_ = absl::LogEntry::kNoVerbosityLevel; + entry.timestamp_ = timestamp; + entry.tid_ = absl::base_internal::GetCachedTID(); +} + +void LogMessage::LogMessageData::FinalizeEncodingAndFormat() { + // Note that `encoded_remaining` may have zero size without pointing past the + // end of `encoded_buf`, so the difference between `data()` pointers is used + // to compute the size of `encoded_data`. + absl::Span<const char> encoded_data( + encoded_buf.data(), + static_cast<size_t>(encoded_remaining.data() - encoded_buf.data())); + // `string_remaining` is the suffix of `string_buf` that has not been filled + // yet. + absl::Span<char> string_remaining(string_buf); + // We may need to write a newline and nul-terminator at the end of the decoded + // string data. Rather than worry about whether those should overwrite the + // end of the string (if the buffer is full) or be appended, we avoid writing + // into the last two bytes so we always have space to append. + string_remaining.remove_suffix(2); + entry.prefix_len_ = + entry.prefix() ? log_internal::FormatLogPrefix( + entry.log_severity(), entry.timestamp(), entry.tid(), + entry.source_basename(), entry.source_line(), + log_internal::ThreadIsLoggingToLogSink() + ? PrefixFormat::kRaw + : PrefixFormat::kNotRaw, + string_remaining) + : 0; + // Decode data from `encoded_buf` until we run out of data or we run out of + // `string_remaining`. + ProtoField field; + while (field.DecodeFrom(&encoded_data)) { + switch (field.tag()) { + case EventTag::kValue: + if (field.type() != WireType::kLengthDelimited) continue; + if (PrintValue(string_remaining, field.bytes_value())) continue; + break; + } + break; + } + auto chars_written = + static_cast<size_t>(string_remaining.data() - string_buf.data()); + string_buf[chars_written++] = '\n'; + string_buf[chars_written++] = '\0'; + entry.text_message_with_prefix_and_newline_and_nul_ = + absl::MakeSpan(string_buf).subspan(0, chars_written); +} + +LogMessage::LogMessage(const char* file, int line, absl::LogSeverity severity) + : data_(absl::make_unique<LogMessageData>(file, line, severity, + absl::Now())) { + data_->first_fatal = false; + data_->is_perror = false; + data_->fail_quietly = false; + + // This logs a backtrace even if the location is subsequently changed using + // AtLocation. This quirk, and the behavior when AtLocation is called twice, + // are fixable but probably not worth fixing. + LogBacktraceIfNeeded(); +} + +LogMessage::~LogMessage() { +#ifdef ABSL_MIN_LOG_LEVEL + if (data_->entry.log_severity() < + static_cast<absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) && + data_->entry.log_severity() < absl::LogSeverity::kFatal) { + return; + } +#endif + Flush(); +} + +LogMessage& LogMessage::AtLocation(absl::string_view file, int line) { + data_->entry.full_filename_ = file; + data_->entry.base_filename_ = Basename(file); + data_->entry.line_ = line; + LogBacktraceIfNeeded(); + return *this; +} + +LogMessage& LogMessage::NoPrefix() { + data_->entry.prefix_ = false; + return *this; +} + +LogMessage& LogMessage::WithVerbosity(int verbose_level) { + if (verbose_level == absl::LogEntry::kNoVerbosityLevel) { + data_->entry.verbose_level_ = absl::LogEntry::kNoVerbosityLevel; + } else { + data_->entry.verbose_level_ = std::max(0, verbose_level); + } + return *this; +} + +LogMessage& LogMessage::WithTimestamp(absl::Time timestamp) { + data_->entry.timestamp_ = timestamp; + return *this; +} + +LogMessage& LogMessage::WithThreadID(absl::LogEntry::tid_t tid) { + data_->entry.tid_ = tid; + return *this; +} + +LogMessage& LogMessage::WithMetadataFrom(const absl::LogEntry& entry) { + data_->entry.full_filename_ = entry.full_filename_; + data_->entry.base_filename_ = entry.base_filename_; + data_->entry.line_ = entry.line_; + data_->entry.prefix_ = entry.prefix_; + data_->entry.severity_ = entry.severity_; + data_->entry.verbose_level_ = entry.verbose_level_; + data_->entry.timestamp_ = entry.timestamp_; + data_->entry.tid_ = entry.tid_; + return *this; +} + +LogMessage& LogMessage::WithPerror() { + data_->is_perror = true; + return *this; +} + +LogMessage& LogMessage::ToSinkAlso(absl::LogSink* sink) { + ABSL_INTERNAL_CHECK(sink, "null LogSink*"); + data_->extra_sinks.push_back(sink); + return *this; +} + +LogMessage& LogMessage::ToSinkOnly(absl::LogSink* sink) { + ABSL_INTERNAL_CHECK(sink, "null LogSink*"); + data_->extra_sinks.clear(); + data_->extra_sinks.push_back(sink); + data_->extra_sinks_only = true; + return *this; +} + +#ifdef __ELF__ +extern "C" void __gcov_dump() ABSL_ATTRIBUTE_WEAK; +extern "C" void __gcov_flush() ABSL_ATTRIBUTE_WEAK; +#endif + +void LogMessage::FailWithoutStackTrace() { + // Now suppress repeated trace logging: + log_internal::SetSuppressSigabortTrace(true); +#if defined _DEBUG && defined COMPILER_MSVC + // When debugging on windows, avoid the obnoxious dialog. + __debugbreak(); +#endif + +#ifdef __ELF__ + // For b/8737634, flush coverage if we are in coverage mode. + if (&__gcov_dump != nullptr) { + __gcov_dump(); + } else if (&__gcov_flush != nullptr) { + __gcov_flush(); + } +#endif + + abort(); +} + +void LogMessage::FailQuietly() { + // _exit. Calling abort() would trigger all sorts of death signal handlers + // and a detailed stack trace. Calling exit() would trigger the onexit + // handlers, including the heap-leak checker, which is guaranteed to fail in + // this case: we probably just new'ed the std::string that we logged. + // Anyway, if you're calling Fail or FailQuietly, you're trying to bail out + // of the program quickly, and it doesn't make much sense for FailQuietly to + // offer different guarantees about exit behavior than Fail does. (And as a + // consequence for QCHECK and CHECK to offer different exit behaviors) + _exit(1); +} + +LogMessage& LogMessage::operator<<(const std::string& v) { + CopyToEncodedBuffer(v, StringType::kNotLiteral); + return *this; +} + +LogMessage& LogMessage::operator<<(absl::string_view v) { + CopyToEncodedBuffer(v, StringType::kNotLiteral); + return *this; +} +LogMessage& LogMessage::operator<<(std::ostream& (*m)(std::ostream& os)) { + OstreamView view(*data_); + data_->manipulated << m; + return *this; +} +LogMessage& LogMessage::operator<<(std::ios_base& (*m)(std::ios_base& os)) { + OstreamView view(*data_); + data_->manipulated << m; + return *this; +} +template LogMessage& LogMessage::operator<<(const char& v); +template LogMessage& LogMessage::operator<<(const signed char& v); +template LogMessage& LogMessage::operator<<(const unsigned char& v); +template LogMessage& LogMessage::operator<<(const short& v); // NOLINT +template LogMessage& LogMessage::operator<<(const unsigned short& v); // NOLINT +template LogMessage& LogMessage::operator<<(const int& v); +template LogMessage& LogMessage::operator<<(const unsigned int& v); +template LogMessage& LogMessage::operator<<(const long& v); // NOLINT +template LogMessage& LogMessage::operator<<(const unsigned long& v); // NOLINT +template LogMessage& LogMessage::operator<<(const long long& v); // NOLINT +template LogMessage& LogMessage::operator<<( + const unsigned long long& v); // NOLINT +template LogMessage& LogMessage::operator<<(void* const& v); +template LogMessage& LogMessage::operator<<(const void* const& v); +template LogMessage& LogMessage::operator<<(const float& v); +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_->is_perror) { + InternalStream() << ": " << absl::base_internal::StrError(errno_saver_()) + << " [" << errno_saver_() << "]"; + } + + // Have we already seen a fatal message? + ABSL_CONST_INIT static std::atomic<bool> seen_fatal(false); + if (data_->entry.log_severity() == absl::LogSeverity::kFatal && + absl::log_internal::ExitOnDFatal()) { + // Exactly one LOG(FATAL) message is responsible for aborting the process, + // even if multiple threads LOG(FATAL) concurrently. + bool expected_seen_fatal = false; + if (seen_fatal.compare_exchange_strong(expected_seen_fatal, true, + std::memory_order_relaxed)) { + data_->first_fatal = true; + } + } + + data_->FinalizeEncodingAndFormat(); + data_->entry.encoding_ = + absl::string_view(data_->encoded_buf.data(), + static_cast<size_t>(data_->encoded_remaining.data() - + data_->encoded_buf.data())); + SendToLog(); +} + +void LogMessage::SetFailQuietly() { data_->fail_quietly = true; } + +LogMessage::OstreamView::OstreamView(LogMessageData& message_data) + : data_(message_data), encoded_remaining_copy_(data_.encoded_remaining) { + // This constructor sets the `streambuf` up so that streaming into an attached + // ostream encodes string data in-place. To do that, we write appropriate + // headers into the buffer using a copy of the buffer view so that we can + // decide not to keep them later if nothing is ever streamed in. We don't + // know how much data we'll get, but we can use the size of the remaining + // buffer as an upper bound and fill in the right size once we know it. + message_start_ = + EncodeMessageStart(EventTag::kValue, encoded_remaining_copy_.size(), + &encoded_remaining_copy_); + string_start_ = + EncodeMessageStart(ValueTag::kString, encoded_remaining_copy_.size(), + &encoded_remaining_copy_); + setp(encoded_remaining_copy_.data(), + encoded_remaining_copy_.data() + encoded_remaining_copy_.size()); + data_.manipulated.rdbuf(this); +} + +LogMessage::OstreamView::~OstreamView() { + data_.manipulated.rdbuf(nullptr); + if (!string_start_.data()) { + // The second field header didn't fit. Whether the first one did or not, we + // shouldn't commit `encoded_remaining_copy_`, and we also need to zero the + // size of `data_->encoded_remaining` so that no more data are encoded. + data_.encoded_remaining.remove_suffix(data_.encoded_remaining.size()); + return; + } + const absl::Span<const char> contents(pbase(), + static_cast<size_t>(pptr() - pbase())); + if (contents.empty()) return; + encoded_remaining_copy_.remove_prefix(contents.size()); + EncodeMessageLength(string_start_, &encoded_remaining_copy_); + EncodeMessageLength(message_start_, &encoded_remaining_copy_); + data_.encoded_remaining = encoded_remaining_copy_; +} + +std::ostream& LogMessage::OstreamView::stream() { return data_.manipulated; } + +bool LogMessage::IsFatal() const { + return data_->entry.log_severity() == absl::LogSeverity::kFatal && + absl::log_internal::ExitOnDFatal(); +} + +void LogMessage::PrepareToDie() { + // If we log a FATAL message, flush all the log destinations, then toss + // a signal for others to catch. We leave the logs in a state that + // someone else can use them (as long as they flush afterwards) + if (data_->first_fatal) { + // Notify observers about the upcoming fatal error. + ABSL_INTERNAL_C_SYMBOL(AbslInternalOnFatalLogMessage)(data_->entry); + } + + if (!data_->fail_quietly) { + // Log the message first before we start collecting stack trace. + log_internal::LogToSinks(data_->entry, absl::MakeSpan(data_->extra_sinks), + data_->extra_sinks_only); + + // `DumpStackTrace` generates an empty string under MSVC. + // Adding the constant prefix here simplifies testing. + data_->entry.stacktrace_ = "*** Check failure stack trace: ***\n"; + debugging_internal::DumpStackTrace( + 0, log_internal::MaxFramesInLogStackTrace(), + log_internal::ShouldSymbolizeLogStackTrace(), WriteToString, + &data_->entry.stacktrace_); + } +} + +void LogMessage::Die() { + absl::FlushLogSinks(); + + if (data_->fail_quietly) { + FailQuietly(); + } else { + FailWithoutStackTrace(); + } +} + +void LogMessage::SendToLog() { + if (IsFatal()) PrepareToDie(); + // Also log to all registered sinks, even if OnlyLogToStderr() is set. + log_internal::LogToSinks(data_->entry, absl::MakeSpan(data_->extra_sinks), + data_->extra_sinks_only); + if (IsFatal()) Die(); +} + +void LogMessage::LogBacktraceIfNeeded() { + if (!absl::log_internal::IsInitialized()) return; + + if (!absl::log_internal::ShouldLogBacktraceAt(data_->entry.source_basename(), + data_->entry.source_line())) + return; + OstreamView view(*data_); + view.stream() << " (stacktrace:\n"; + debugging_internal::DumpStackTrace( + 1, log_internal::MaxFramesInLogStackTrace(), + log_internal::ShouldSymbolizeLogStackTrace(), WriteToStream, + &view.stream()); + view.stream() << ") "; +} + +// Encodes into `data_->encoded_remaining` a partial `logging.proto.Event` +// 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) { + auto encoded_remaining_copy = data_->encoded_remaining; + auto start = EncodeMessageStart( + EventTag::kValue, BufferSizeFor(WireType::kLengthDelimited) + str.size(), + &encoded_remaining_copy); + // If the `logging.proto.Event.value` field header did not fit, + // `EncodeMessageStart` will have zeroed `encoded_remaining_copy`'s size and + // `EncodeStringTruncate` will fail too. + if (EncodeStringTruncate(str_type == StringType::kLiteral + ? ValueTag::kStringLiteral + : ValueTag::kString, + str, &encoded_remaining_copy)) { + // The string may have been truncated, but the field header fit. + EncodeMessageLength(start, &encoded_remaining_copy); + data_->encoded_remaining = encoded_remaining_copy; + } else { + // The field header(s) did not fit; zero `encoded_remaining` so we don't + // write anything else later. + data_->encoded_remaining.remove_suffix(data_->encoded_remaining.size()); + } +} +void LogMessage::CopyToEncodedBuffer(char ch, size_t num, StringType str_type) { + auto encoded_remaining_copy = data_->encoded_remaining; + auto value_start = EncodeMessageStart( + EventTag::kValue, BufferSizeFor(WireType::kLengthDelimited) + num, + &encoded_remaining_copy); + auto str_start = EncodeMessageStart(str_type == StringType::kLiteral + ? ValueTag::kStringLiteral + : ValueTag::kString, + num, &encoded_remaining_copy); + if (str_start.data()) { + // The field headers fit. + log_internal::AppendTruncated(ch, num, encoded_remaining_copy); + EncodeMessageLength(str_start, &encoded_remaining_copy); + EncodeMessageLength(value_start, &encoded_remaining_copy); + data_->encoded_remaining = encoded_remaining_copy; + } else { + // The field header(s) did not fit; zero `encoded_remaining` so we don't + // write anything else later. + data_->encoded_remaining.remove_suffix(data_->encoded_remaining.size()); + } +} + +LogMessageFatal::LogMessageFatal(const char* file, int line) + : LogMessage(file, line, absl::LogSeverity::kFatal) {} + +LogMessageFatal::LogMessageFatal(const char* file, int line, + absl::string_view failure_msg) + : LogMessage(file, line, absl::LogSeverity::kFatal) { + *this << "Check failed: " << failure_msg << " "; +} + +// 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__) +#pragma warning(push) +#pragma warning(disable : 4722) +#endif +LogMessageFatal::~LogMessageFatal() { + Flush(); + FailWithoutStackTrace(); +} +#if defined(_MSC_VER) && !defined(__clang__) +#pragma warning(pop) +#endif + +LogMessageQuietlyFatal::LogMessageQuietlyFatal(const char* file, int line) + : LogMessage(file, line, absl::LogSeverity::kFatal) { + SetFailQuietly(); +} + +LogMessageQuietlyFatal::LogMessageQuietlyFatal(const char* file, int line, + absl::string_view failure_msg) + : LogMessage(file, line, absl::LogSeverity::kFatal) { + SetFailQuietly(); + *this << "Check failed: " << failure_msg << " "; +} + +// 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__) +#pragma warning(push) +#pragma warning(disable : 4722) +#endif +LogMessageQuietlyFatal::~LogMessageQuietlyFatal() { + Flush(); + FailQuietly(); +} +#if defined(_MSC_VER) && !defined(__clang__) +#pragma warning(pop) +#endif + +} // namespace log_internal + +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/internal/log_message.h b/absl/log/internal/log_message.h new file mode 100644 index 00000000..3744276b --- /dev/null +++ b/absl/log/internal/log_message.h @@ -0,0 +1,355 @@ +// 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/internal/log_message.h +// ----------------------------------------------------------------------------- +// +// This file declares `class absl::log_internal::LogMessage`. This class more or +// less represents a particular log message. LOG/CHECK macros create a +// temporary instance of `LogMessage` and then stream values to it. At the end +// of the LOG/CHECK statement, LogMessage instance goes out of scope and +// `~LogMessage` directs the message to the registered log sinks. +// Heap-allocation of `LogMessage` is unsupported. Construction outside of a +// `LOG` macro is unsupported. + +#ifndef ABSL_LOG_INTERNAL_LOG_MESSAGE_H_ +#define ABSL_LOG_INTERNAL_LOG_MESSAGE_H_ + +#include <ios> +#include <memory> +#include <ostream> +#include <streambuf> +#include <string> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/internal/errno_saver.h" +#include "absl/base/log_severity.h" +#include "absl/log/internal/nullguard.h" +#include "absl/log/log_entry.h" +#include "absl/log/log_sink.h" +#include "absl/strings/internal/has_absl_stringify.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { +constexpr int kLogMessageBufferSize = 15000; + +class LogMessage { + public: + // Used for `LOG`. + LogMessage(const char* file, int line, + absl::LogSeverity severity) ABSL_ATTRIBUTE_COLD; + LogMessage(const LogMessage&) = delete; + LogMessage& operator=(const LogMessage&) = delete; + ~LogMessage() ABSL_ATTRIBUTE_COLD; + + // Overrides the location inferred from the callsite. The string pointed to + // by `file` must be valid until the end of the statement. + LogMessage& AtLocation(absl::string_view file, int line); + // Omits the prefix from this line. The prefix includes metadata about the + // logged data such as source code location and timestamp. + LogMessage& NoPrefix(); + // 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 `absl::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. + LogMessage& WithVerbosity(int verbose_level); + // Uses the specified timestamp instead of one collected in the constructor. + LogMessage& WithTimestamp(absl::Time timestamp); + // Uses the specified thread ID instead of one collected in the constructor. + LogMessage& WithThreadID(absl::LogEntry::tid_t tid); + // Copies all metadata (but no data) from the specified `absl::LogEntry`. + LogMessage& WithMetadataFrom(const absl::LogEntry& entry); + // Appends to the logged message a colon, a space, a textual description of + // the current value of `errno` (as by strerror(3)), and the numerical value + // of `errno`. + LogMessage& WithPerror(); + // Sends this message to `*sink` in addition to whatever other sinks it would + // otherwise have been sent to. `sink` must not be null. + LogMessage& ToSinkAlso(absl::LogSink* sink); + // Sends this message to `*sink` and no others. `sink` must not be null. + LogMessage& ToSinkOnly(absl::LogSink* sink); + + // Don't call this method from outside this library. + LogMessage& InternalStream() { return *this; } + + // By-value overloads for small, common types let us overlook common failures + // to define globals and static data members (i.e. in a .cc file). + // clang-format off + // The CUDA toolchain cannot handle these <<<'s: + LogMessage& operator<<(char v) { return operator<< <char>(v); } + LogMessage& operator<<(signed char v) { return operator<< <signed char>(v); } + LogMessage& operator<<(unsigned char v) { + return operator<< <unsigned char>(v); + } + LogMessage& operator<<(signed short v) { // NOLINT + return operator<< <signed short>(v); // NOLINT + } + LogMessage& operator<<(signed int v) { return operator<< <signed int>(v); } + LogMessage& operator<<(signed long v) { // NOLINT + return operator<< <signed long>(v); // NOLINT + } + LogMessage& operator<<(signed long long v) { // NOLINT + return operator<< <signed long long>(v); // NOLINT + } + LogMessage& operator<<(unsigned short v) { // NOLINT + return operator<< <unsigned short>(v); // NOLINT + } + LogMessage& operator<<(unsigned int v) { + return operator<< <unsigned int>(v); + } + LogMessage& operator<<(unsigned long v) { // NOLINT + return operator<< <unsigned long>(v); // NOLINT + } + LogMessage& operator<<(unsigned long long v) { // NOLINT + return operator<< <unsigned long long>(v); // NOLINT + } + LogMessage& operator<<(void* v) { return operator<< <void*>(v); } + LogMessage& operator<<(const void* v) { return operator<< <const void*>(v); } + LogMessage& operator<<(float v) { return operator<< <float>(v); } + LogMessage& operator<<(double v) { return operator<< <double>(v); } + LogMessage& operator<<(bool v) { return operator<< <bool>(v); } + // clang-format on + + // These overloads are more efficient since no `ostream` is involved. + LogMessage& operator<<(const std::string& v); + LogMessage& operator<<(absl::string_view v); + + // Handle stream manipulators e.g. std::endl. + LogMessage& operator<<(std::ostream& (*m)(std::ostream& os)); + LogMessage& operator<<(std::ios_base& (*m)(std::ios_base& os)); + + // Literal strings. This allows us to record C string literals as literals in + // the logging.proto.Value. + // + // Allow this overload to be inlined to prevent generating instantiations of + // this template for every value of `SIZE` encountered in each source code + // file. That significantly increases linker input sizes. Inlining is cheap + // because the argument to this overload is almost always a string literal so + // the call to `strlen` can be replaced at compile time. The overload for + // `char[]` below should not be inlined. The compiler typically does not have + // the string at compile time and cannot replace the call to `strlen` so + // inlining it increases the binary size. See the discussion on + // cl/107527369. + template <int SIZE> + LogMessage& operator<<(const char (&buf)[SIZE]); + + // This prevents non-const `char[]` arrays from looking like literals. + template <int SIZE> + LogMessage& operator<<(char (&buf)[SIZE]) ABSL_ATTRIBUTE_NOINLINE; + + // Types that support `AbslStringify()` are serialized that way. + template <typename T, + typename std::enable_if< + strings_internal::HasAbslStringify<T>::value, int>::type = 0> + LogMessage& operator<<(const T& v) ABSL_ATTRIBUTE_NOINLINE; + + // Types that don't support `AbslStringify()` but do support streaming into a + // `std::ostream&` are serialized that way. + template <typename T, + typename std::enable_if< + !strings_internal::HasAbslStringify<T>::value, int>::type = 0> + LogMessage& operator<<(const T& v) ABSL_ATTRIBUTE_NOINLINE; + + // Note: We explicitly do not support `operator<<` for non-const references + // because it breaks logging of non-integer bitfield types (i.e., enums). + + protected: + // Call `abort()` or similar to perform `LOG(FATAL)` crash. It is assumed + // that the caller has already generated and written the trace as appropriate. + ABSL_ATTRIBUTE_NORETURN static void FailWithoutStackTrace(); + + // Similar to `FailWithoutStackTrace()`, but without `abort()`. Terminates + // the process with an error exit code. + ABSL_ATTRIBUTE_NORETURN static void FailQuietly(); + + // Dispatches the completed `absl::LogEntry` to applicable `absl::LogSink`s. + // This might as well be inlined into `~LogMessage` except that + // `~LogMessageFatal` needs to call it early. + void Flush(); + + // After this is called, failures are done as quiet as possible for this log + // message. + void SetFailQuietly(); + + private: + struct LogMessageData; // Opaque type containing message state + friend class AsLiteralImpl; + friend class StringifySink; + + // This streambuf writes directly into the structured logging buffer so that + // arbitrary types can be encoded as string data (using + // `operator<<(std::ostream &, ...)` without any extra allocation or copying. + // Space is reserved before the data to store the length field, which is + // filled in by `~OstreamView`. + class OstreamView final : public std::streambuf { + public: + explicit OstreamView(LogMessageData& message_data); + ~OstreamView() override; + OstreamView(const OstreamView&) = delete; + OstreamView& operator=(const OstreamView&) = delete; + std::ostream& stream(); + + private: + LogMessageData& data_; + absl::Span<char> encoded_remaining_copy_; + absl::Span<char> message_start_; + absl::Span<char> string_start_; + }; + + enum class StringType { + 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; + + // Returns `true` if the message is fatal or enabled debug-fatal. + bool IsFatal() const; + + // Records some tombstone-type data in anticipation of `Die`. + void PrepareToDie(); + void Die(); + + void SendToLog(); + + // Checks `FLAGS_log_backtrace_at` and appends a backtrace if appropriate. + void LogBacktraceIfNeeded(); + + // This should be the first data member so that its initializer captures errno + // before any other initializers alter it (e.g. with calls to new) and so that + // no other destructors run afterward an alter it (e.g. with calls to delete). + absl::base_internal::ErrnoSaver errno_saver_; + + // We keep the data in a separate struct so that each instance of `LogMessage` + // uses less stack space. + std::unique_ptr<LogMessageData> data_; +}; + +// Helper class so that `AbslStringify()` can modify the LogMessage. +class StringifySink final { + public: + explicit StringifySink(LogMessage& message) : message_(message) {} + + void Append(size_t count, char ch) { + message_.CopyToEncodedBuffer(ch, count, + LogMessage::StringType::kNotLiteral); + } + + void Append(absl::string_view v) { + message_.CopyToEncodedBuffer(v, LogMessage::StringType::kNotLiteral); + } + + // For types that implement `AbslStringify` using `absl::Format()`. + friend void AbslFormatFlush(StringifySink* sink, absl::string_view v) { + sink->Append(v); + } + + private: + LogMessage& message_; +}; + +// Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE` +template <typename T, + typename std::enable_if<strings_internal::HasAbslStringify<T>::value, + int>::type> +LogMessage& LogMessage::operator<<(const T& v) { + StringifySink sink(*this); + // Replace with public API. + AbslStringify(sink, v); + return *this; +} + +// Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE` +template <typename T, + typename std::enable_if<!strings_internal::HasAbslStringify<T>::value, + int>::type> +LogMessage& LogMessage::operator<<(const T& v) { + OstreamView view(*data_); + view.stream() << log_internal::NullGuard<T>().Guard(v); + return *this; +} + +template <int SIZE> +LogMessage& LogMessage::operator<<(const char (&buf)[SIZE]) { + CopyToEncodedBuffer(buf, StringType::kLiteral); + return *this; +} + +// Note: the following is declared `ABSL_ATTRIBUTE_NOINLINE` +template <int SIZE> +LogMessage& LogMessage::operator<<(char (&buf)[SIZE]) { + CopyToEncodedBuffer(buf, StringType::kNotLiteral); + return *this; +} +// We instantiate these specializations in the library's TU to save space in +// other TUs. Since the template is marked `ABSL_ATTRIBUTE_NOINLINE` we will be +// emitting a function call either way. +extern template LogMessage& LogMessage::operator<<(const char& v); +extern template LogMessage& LogMessage::operator<<(const signed char& v); +extern template LogMessage& LogMessage::operator<<(const unsigned char& v); +extern template LogMessage& LogMessage::operator<<(const short& v); // NOLINT +extern template LogMessage& LogMessage::operator<<( + const unsigned short& v); // NOLINT +extern template LogMessage& LogMessage::operator<<(const int& v); +extern template LogMessage& LogMessage::operator<<( + const unsigned int& v); // NOLINT +extern template LogMessage& LogMessage::operator<<(const long& v); // NOLINT +extern template LogMessage& LogMessage::operator<<( + const unsigned long& v); // NOLINT +extern template LogMessage& LogMessage::operator<<( + const long long& v); // NOLINT +extern template LogMessage& LogMessage::operator<<( + const unsigned long long& v); // NOLINT +extern template LogMessage& LogMessage::operator<<(void* const& v); +extern template LogMessage& LogMessage::operator<<(const void* const& v); +extern template LogMessage& LogMessage::operator<<(const float& v); +extern template LogMessage& LogMessage::operator<<(const double& v); +extern template LogMessage& LogMessage::operator<<(const bool& v); + +// `LogMessageFatal` ensures the process will exit in failure after logging this +// message. +class LogMessageFatal final : public LogMessage { + public: + LogMessageFatal(const char* file, int line) ABSL_ATTRIBUTE_COLD; + LogMessageFatal(const char* file, int line, + absl::string_view failure_msg) ABSL_ATTRIBUTE_COLD; + ABSL_ATTRIBUTE_NORETURN ~LogMessageFatal(); +}; + +class LogMessageQuietlyFatal final : public LogMessage { + public: + LogMessageQuietlyFatal(const char* file, int line) ABSL_ATTRIBUTE_COLD; + LogMessageQuietlyFatal(const char* file, int line, + absl::string_view failure_msg) ABSL_ATTRIBUTE_COLD; + ABSL_ATTRIBUTE_NORETURN ~LogMessageQuietlyFatal(); +}; + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +extern "C" ABSL_ATTRIBUTE_WEAK void ABSL_INTERNAL_C_SYMBOL( + AbslInternalOnFatalLogMessage)(const absl::LogEntry&); + +#endif // ABSL_LOG_INTERNAL_LOG_MESSAGE_H_ diff --git a/absl/log/internal/log_sink_set.cc b/absl/log/internal/log_sink_set.cc new file mode 100644 index 00000000..f9d030aa --- /dev/null +++ b/absl/log/internal/log_sink_set.cc @@ -0,0 +1,296 @@ +// +// 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/log_sink_set.h" + +#ifndef ABSL_HAVE_THREAD_LOCAL +#include <pthread.h> +#endif + +#ifdef __ANDROID__ +#include <android/log.h> +#endif + +#ifdef _WIN32 +#include <windows.h> +#endif + +#include <algorithm> +#include <vector> + +#include "absl/base/attributes.h" +#include "absl/base/call_once.h" +#include "absl/base/config.h" +#include "absl/base/internal/raw_logging.h" +#include "absl/base/log_severity.h" +#include "absl/base/thread_annotations.h" +#include "absl/cleanup/cleanup.h" +#include "absl/log/globals.h" +#include "absl/log/internal/config.h" +#include "absl/log/internal/globals.h" +#include "absl/log/log_entry.h" +#include "absl/log/log_sink.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" +#include "absl/types/span.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { +namespace { + +// Returns a mutable reference to a thread-local variable that should be true if +// a globally-registered `LogSink`'s `Send()` is currently being invoked on this +// thread. +bool& ThreadIsLoggingStatus() { +#ifdef ABSL_HAVE_THREAD_LOCAL + ABSL_CONST_INIT thread_local bool thread_is_logging = false; + return thread_is_logging; +#else + ABSL_CONST_INIT static pthread_key_t thread_is_logging_key; + static const bool unused = [] { + if (pthread_key_create(&thread_is_logging_key, [](void* data) { + delete reinterpret_cast<bool*>(data); + })) { + perror("pthread_key_create failed!"); + abort(); + } + return true; + }(); + (void)unused; // Fixes -wunused-variable warning + bool* thread_is_logging_ptr = + reinterpret_cast<bool*>(pthread_getspecific(thread_is_logging_key)); + + if (ABSL_PREDICT_FALSE(!thread_is_logging_ptr)) { + thread_is_logging_ptr = new bool{false}; + if (pthread_setspecific(thread_is_logging_key, thread_is_logging_ptr)) { + perror("pthread_setspecific failed"); + abort(); + } + } + return *thread_is_logging_ptr; +#endif +} + +class StderrLogSink final : public LogSink { + public: + ~StderrLogSink() override = default; + + void Send(const absl::LogEntry& entry) override { + if (entry.log_severity() < absl::StderrThreshold() && + absl::log_internal::IsInitialized()) { + return; + } + + ABSL_CONST_INIT static absl::once_flag warn_if_not_initialized; + absl::call_once(warn_if_not_initialized, []() { + if (absl::log_internal::IsInitialized()) return; + const char w[] = + "WARNING: All log messages before absl::InitializeLog() is called" + " are written to STDERR\n"; + absl::log_internal::WriteToStderr(w, absl::LogSeverity::kWarning); + }); + + if (!entry.stacktrace().empty()) { + absl::log_internal::WriteToStderr(entry.stacktrace(), + entry.log_severity()); + } else { + // TODO(b/226937039): do this outside else condition once we avoid + // ReprintFatalMessage + absl::log_internal::WriteToStderr( + entry.text_message_with_prefix_and_newline(), entry.log_severity()); + } + } +}; + +#if defined(__ANDROID__) +class AndroidLogSink final : public LogSink { + public: + ~AndroidLogSink() override = default; + + 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", + 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"); + } + + private: + static int AndroidLogLevel(const absl::LogEntry& entry) { + switch (entry.log_severity()) { + case absl::LogSeverity::kFatal: + return ANDROID_LOG_FATAL; + case absl::LogSeverity::kError: + return ANDROID_LOG_ERROR; + case absl::LogSeverity::kWarning: + return ANDROID_LOG_WARN; + default: + if (entry.verbosity() >= 2) return ANDROID_LOG_VERBOSE; + if (entry.verbosity() == 1) return ANDROID_LOG_DEBUG; + return ANDROID_LOG_INFO; + } + } +}; +#endif // !defined(__ANDROID__) + +#if defined(_WIN32) +class WindowsDebuggerLogSink final : public LogSink { + public: + ~WindowsDebuggerLogSink() override = default; + + void Send(const absl::LogEntry& entry) override { + if (entry.log_severity() < absl::StderrThreshold() && + absl::log_internal::IsInitialized()) { + return; + } + ::OutputDebugStringA(entry.text_message_with_prefix_and_newline_c_str()); + } +}; +#endif // !defined(_WIN32) + +class GlobalLogSinkSet final { + public: + GlobalLogSinkSet() { +#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); +#endif +#ifdef __ANDROID__ + static AndroidLogSink* android_log_sink = new AndroidLogSink; + AddLogSink(android_log_sink); +#endif +#if defined(_WIN32) + static WindowsDebuggerLogSink* debugger_log_sink = + new WindowsDebuggerLogSink; + AddLogSink(debugger_log_sink); +#endif // !defined(_WIN32) + } + + void LogToSinks(const absl::LogEntry& entry, + absl::Span<absl::LogSink*> extra_sinks, bool extra_sinks_only) + ABSL_LOCKS_EXCLUDED(guard_) { + SendToSinks(entry, extra_sinks); + + if (!extra_sinks_only) { + if (ThreadIsLoggingToLogSink()) { + absl::log_internal::WriteToStderr( + entry.text_message_with_prefix_and_newline(), entry.log_severity()); + } else { + absl::ReaderMutexLock global_sinks_lock(&guard_); + ThreadIsLoggingStatus() = true; + // Ensure the "thread is logging" status is reverted upon leaving the + // scope even in case of exceptions. + auto status_cleanup = + absl::MakeCleanup([] { ThreadIsLoggingStatus() = false; }); + SendToSinks(entry, absl::MakeSpan(sinks_)); + } + } + } + + void AddLogSink(absl::LogSink* sink) ABSL_LOCKS_EXCLUDED(guard_) { + { + absl::WriterMutexLock global_sinks_lock(&guard_); + auto pos = std::find(sinks_.begin(), sinks_.end(), sink); + if (pos == sinks_.end()) { + sinks_.push_back(sink); + return; + } + } + ABSL_INTERNAL_LOG(FATAL, "Duplicate log sinks are not supported"); + } + + void RemoveLogSink(absl::LogSink* sink) ABSL_LOCKS_EXCLUDED(guard_) { + { + absl::WriterMutexLock global_sinks_lock(&guard_); + auto pos = std::find(sinks_.begin(), sinks_.end(), sink); + if (pos != sinks_.end()) { + sinks_.erase(pos); + return; + } + } + ABSL_INTERNAL_LOG(FATAL, "Mismatched log sink being removed"); + } + + void FlushLogSinks() ABSL_LOCKS_EXCLUDED(guard_) { + if (ThreadIsLoggingToLogSink()) { + // The thread_local condition demonstrates that we're already holding the + // lock in order to iterate over `sinks_` for dispatch. The thread-safety + // annotations don't know this, so we use `ABSL_NO_THREAD_SAFETY_ANALYSIS` + guard_.AssertReaderHeld(); + FlushLogSinksLocked(); + } else { + absl::ReaderMutexLock global_sinks_lock(&guard_); + // In case if LogSink::Flush overload decides to log + ThreadIsLoggingStatus() = true; + // Ensure the "thread is logging" status is reverted upon leaving the + // scope even in case of exceptions. + auto status_cleanup = + absl::MakeCleanup([] { ThreadIsLoggingStatus() = false; }); + FlushLogSinksLocked(); + } + } + + private: + void FlushLogSinksLocked() ABSL_SHARED_LOCKS_REQUIRED(guard_) { + for (absl::LogSink* sink : sinks_) { + sink->Flush(); + } + } + + // Helper routine for LogToSinks. + static void SendToSinks(const absl::LogEntry& entry, + absl::Span<absl::LogSink*> sinks) { + for (absl::LogSink* sink : sinks) { + sink->Send(entry); + } + } + + using LogSinksSet = std::vector<absl::LogSink*>; + absl::Mutex guard_; + LogSinksSet sinks_ ABSL_GUARDED_BY(guard_); +}; + +// Returns reference to the global LogSinks set. +GlobalLogSinkSet& GlobalSinks() { + static GlobalLogSinkSet* global_sinks = new GlobalLogSinkSet; + return *global_sinks; +} + +} // namespace + +bool ThreadIsLoggingToLogSink() { return ThreadIsLoggingStatus(); } + +void LogToSinks(const absl::LogEntry& entry, + absl::Span<absl::LogSink*> extra_sinks, bool extra_sinks_only) { + log_internal::GlobalSinks().LogToSinks(entry, extra_sinks, extra_sinks_only); +} + +void AddLogSink(absl::LogSink* sink) { + log_internal::GlobalSinks().AddLogSink(sink); +} + +void RemoveLogSink(absl::LogSink* sink) { + log_internal::GlobalSinks().RemoveLogSink(sink); +} + +void FlushLogSinks() { log_internal::GlobalSinks().FlushLogSinks(); } + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/internal/log_sink_set.h b/absl/log/internal/log_sink_set.h new file mode 100644 index 00000000..88ab073b --- /dev/null +++ b/absl/log/internal/log_sink_set.h @@ -0,0 +1,54 @@ +// 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/internal/log_sink_set.h +// ----------------------------------------------------------------------------- + +#ifndef ABSL_LOG_INTERNAL_LOG_SINK_SET_H_ +#define ABSL_LOG_INTERNAL_LOG_SINK_SET_H_ + +#include "absl/base/config.h" +#include "absl/log/log_entry.h" +#include "absl/log/log_sink.h" +#include "absl/types/span.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +// Returns true if a globally-registered `LogSink`'s `Send()` is currently +// being invoked on this thread. +bool ThreadIsLoggingToLogSink(); + +// This function may log to two sets of sinks: +// +// * If `extra_sinks_only` is true, it will dispatch only to `extra_sinks`. +// `LogMessage::ToSinkAlso` and `LogMessage::ToSinkOnly` are used to attach +// extra sinks to the entry. +// * Otherwise it will also log to the global sinks set. This set is managed +// by `absl::AddLogSink` and `absl::RemoveLogSink`. +void LogToSinks(const absl::LogEntry& entry, + absl::Span<absl::LogSink*> extra_sinks, bool extra_sinks_only); + +// Implementation for operations with log sink set. +void AddLogSink(absl::LogSink* sink); +void RemoveLogSink(absl::LogSink* sink); +void FlushLogSinks(); + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_LOG_SINK_SET_H_ diff --git a/absl/log/internal/nullguard.cc b/absl/log/internal/nullguard.cc new file mode 100644 index 00000000..4f2f9d40 --- /dev/null +++ b/absl/log/internal/nullguard.cc @@ -0,0 +1,35 @@ +// 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/nullguard.h" + +#include <array> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +ABSL_DLL ABSL_CONST_INIT const std::array<char, 7> kCharNull{ + {'(', 'n', 'u', 'l', 'l', ')', '\0'}}; +ABSL_DLL ABSL_CONST_INIT const std::array<signed char, 7> kSignedCharNull{ + {'(', 'n', 'u', 'l', 'l', ')', '\0'}}; +ABSL_DLL ABSL_CONST_INIT const std::array<unsigned char, 7> kUnsignedCharNull{ + {'(', 'n', 'u', 'l', 'l', ')', '\0'}}; + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/internal/nullguard.h b/absl/log/internal/nullguard.h new file mode 100644 index 00000000..926f61bb --- /dev/null +++ b/absl/log/internal/nullguard.h @@ -0,0 +1,88 @@ +// 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/internal/nullguard.h +// ----------------------------------------------------------------------------- +// +// NullGuard exists such that NullGuard<T>::Guard(v) returns v, unless passed a +// nullptr_t, or a null char* or const char*, in which case it returns "(null)". +// This allows streaming NullGuard<T>::Guard(v) to an output stream without +// hitting undefined behavior for null values. + +#ifndef ABSL_LOG_INTERNAL_NULLGUARD_H_ +#define ABSL_LOG_INTERNAL_NULLGUARD_H_ + +#include <array> +#include <cstddef> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" + +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> + kSignedCharNull; +ABSL_DLL ABSL_CONST_INIT extern const std::array<unsigned char, 7> + kUnsignedCharNull; + +template <typename T> +struct NullGuard final { + static const T& Guard(const T& v) { return v; } +}; +template <> +struct NullGuard<char*> final { + static const char* Guard(const char* v) { return v ? v : kCharNull.data(); } +}; +template <> +struct NullGuard<const char*> final { + static const char* Guard(const char* v) { return v ? v : kCharNull.data(); } +}; +template <> +struct NullGuard<signed char*> final { + static const signed char* Guard(const signed char* v) { + return v ? v : kSignedCharNull.data(); + } +}; +template <> +struct NullGuard<const signed char*> final { + static const signed char* Guard(const signed char* v) { + return v ? v : kSignedCharNull.data(); + } +}; +template <> +struct NullGuard<unsigned char*> final { + static const unsigned char* Guard(const unsigned char* v) { + return v ? v : kUnsignedCharNull.data(); + } +}; +template <> +struct NullGuard<const unsigned char*> final { + static const unsigned char* Guard(const unsigned char* v) { + return v ? v : kUnsignedCharNull.data(); + } +}; +template <> +struct NullGuard<std::nullptr_t> final { + static const char* Guard(const std::nullptr_t&) { return kCharNull.data(); } +}; + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_NULLGUARD_H_ diff --git a/absl/log/internal/nullstream.h b/absl/log/internal/nullstream.h new file mode 100644 index 00000000..8ed63d52 --- /dev/null +++ b/absl/log/internal/nullstream.h @@ -0,0 +1,134 @@ +// 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/internal/nullstream.h +// ----------------------------------------------------------------------------- +// +// Classes `NullStream`, `NullStreamMaybeFatal ` and `NullStreamFatal` +// implement a subset of the `LogMessage` API and are used instead when logging +// of messages has been disabled. + +#ifndef ABSL_LOG_INTERNAL_NULLSTREAM_H_ +#define ABSL_LOG_INTERNAL_NULLSTREAM_H_ + +#ifdef _WIN32 +#include <cstdlib> +#else +#include <unistd.h> +#endif +#include <ios> +#include <ostream> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +// A `NullStream` implements the API of `LogMessage` (a few methods and +// `operator<<`) but does nothing. All methods are defined inline so the +// compiler can eliminate the whole instance and discard anything that's +// streamed in. +class NullStream { + public: + NullStream& AtLocation(absl::string_view, int) { return *this; } + template <typename SourceLocationType> + NullStream& AtLocation(SourceLocationType) { + return *this; + } + NullStream& NoPrefix() { return *this; } + NullStream& WithVerbosity(int) { return *this; } + template <typename TimeType> + NullStream& WithTimestamp(TimeType) { + return *this; + } + template <typename Tid> + NullStream& WithThreadID(Tid) { + return *this; + } + template <typename LogEntryType> + NullStream& WithMetadataFrom(const LogEntryType&) { + return *this; + } + NullStream& WithPerror() { return *this; } + template <typename LogSinkType> + NullStream& ToSinkAlso(LogSinkType*) { + return *this; + } + template <typename LogSinkType> + NullStream& ToSinkOnly(LogSinkType*) { + return *this; + } + template <typename LogSinkType> + NullStream& OutputToSink(LogSinkType*, bool) { + return *this; + } + NullStream& InternalStream() { return *this; } +}; +template <typename T> +inline NullStream& operator<<(NullStream& str, const T&) { + return str; +} +inline NullStream& operator<<(NullStream& str, + std::ostream& (*)(std::ostream& os)) { + return str; +} +inline NullStream& operator<<(NullStream& str, + std::ios_base& (*)(std::ios_base& os)) { + return str; +} + +// `NullStreamMaybeFatal` implements the process termination semantics of +// `LogMessage`, which is used for `DFATAL` severity and expression-defined +// severity e.g. `LOG(LEVEL(HowBadIsIt()))`. Like `LogMessage`, it terminates +// the process when destroyed if the passed-in severity equals `FATAL`. +class NullStreamMaybeFatal final : public NullStream { + public: + explicit NullStreamMaybeFatal(absl::LogSeverity severity) + : fatal_(severity == absl::LogSeverity::kFatal) {} + ~NullStreamMaybeFatal() { + if (fatal_) _exit(1); + } + + private: + bool fatal_; +}; + +// `NullStreamFatal` implements the process termination semantics of +// `LogMessageFatal`, which means it always terminates the process. `DFATAL` +// and expression-defined severity use `NullStreamMaybeFatal` above. +class NullStreamFatal final : public NullStream { + public: + NullStreamFatal() {} + // 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__) +#pragma warning(push) +#pragma warning(disable : 4722) +#endif + ABSL_ATTRIBUTE_NORETURN ~NullStreamFatal() { _exit(1); } +#if defined(_MSC_VER) && !defined(__clang__) +#pragma warning(pop) +#endif +}; + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_GLOBALS_H_ diff --git a/absl/log/internal/proto.cc b/absl/log/internal/proto.cc new file mode 100644 index 00000000..eb699ae8 --- /dev/null +++ b/absl/log/internal/proto.cc @@ -0,0 +1,220 @@ +// 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/log/internal/proto.h" + +#include <algorithm> +#include <cassert> +#include <cstddef> +#include <cstdint> +#include <cstring> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/types/span.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { +namespace { +void EncodeRawVarint(uint64_t value, size_t size, absl::Span<char> *buf) { + for (size_t s = 0; s < size; s++) { + (*buf)[s] = static_cast<char>((value & 0x7f) | (s + 1 == size ? 0 : 0x80)); + value >>= 7; + } + buf->remove_prefix(size); +} +constexpr uint64_t MakeTagType(uint64_t tag, WireType type) { + return tag << 3 | static_cast<uint64_t>(type); +} +} // namespace + +bool EncodeVarint(uint64_t tag, uint64_t value, absl::Span<char> *buf) { + const uint64_t tag_type = MakeTagType(tag, WireType::kVarint); + const size_t tag_type_size = VarintSize(tag_type); + const size_t value_size = VarintSize(value); + if (tag_type_size + value_size > buf->size()) { + buf->remove_suffix(buf->size()); + return false; + } + EncodeRawVarint(tag_type, tag_type_size, buf); + EncodeRawVarint(value, value_size, buf); + return true; +} + +bool Encode64Bit(uint64_t tag, uint64_t value, absl::Span<char> *buf) { + const uint64_t tag_type = MakeTagType(tag, WireType::k64Bit); + const size_t tag_type_size = VarintSize(tag_type); + if (tag_type_size + sizeof(value) > buf->size()) { + buf->remove_suffix(buf->size()); + return false; + } + EncodeRawVarint(tag_type, tag_type_size, buf); + for (size_t s = 0; s < sizeof(value); s++) { + (*buf)[s] = static_cast<char>(value & 0xff); + value >>= 8; + } + buf->remove_prefix(sizeof(value)); + return true; +} + +bool Encode32Bit(uint64_t tag, uint32_t value, absl::Span<char> *buf) { + const uint64_t tag_type = MakeTagType(tag, WireType::k32Bit); + const size_t tag_type_size = VarintSize(tag_type); + if (tag_type_size + sizeof(value) > buf->size()) { + buf->remove_suffix(buf->size()); + return false; + } + EncodeRawVarint(tag_type, tag_type_size, buf); + for (size_t s = 0; s < sizeof(value); s++) { + (*buf)[s] = static_cast<char>(value & 0xff); + value >>= 8; + } + buf->remove_prefix(sizeof(value)); + return true; +} + +bool EncodeBytes(uint64_t tag, absl::Span<const char> value, + absl::Span<char> *buf) { + const uint64_t tag_type = MakeTagType(tag, WireType::kLengthDelimited); + const size_t tag_type_size = VarintSize(tag_type); + uint64_t length = value.size(); + const size_t length_size = VarintSize(length); + if (tag_type_size + length_size + value.size() > buf->size()) { + buf->remove_suffix(buf->size()); + return false; + } + EncodeRawVarint(tag_type, tag_type_size, buf); + EncodeRawVarint(length, length_size, buf); + memcpy(buf->data(), value.data(), value.size()); + buf->remove_prefix(value.size()); + return true; +} + +bool EncodeBytesTruncate(uint64_t tag, absl::Span<const char> value, + absl::Span<char> *buf) { + const uint64_t tag_type = MakeTagType(tag, WireType::kLengthDelimited); + const size_t tag_type_size = VarintSize(tag_type); + uint64_t length = value.size(); + const size_t length_size = + VarintSize(std::min<uint64_t>(length, buf->size())); + if (tag_type_size + length_size <= buf->size() && + tag_type_size + length_size + value.size() > buf->size()) { + value.remove_suffix(tag_type_size + length_size + value.size() - + buf->size()); + length = value.size(); + } + if (tag_type_size + length_size + value.size() > buf->size()) { + buf->remove_suffix(buf->size()); + return false; + } + EncodeRawVarint(tag_type, tag_type_size, buf); + EncodeRawVarint(length, length_size, buf); + memcpy(buf->data(), value.data(), value.size()); + buf->remove_prefix(value.size()); + return true; +} + +ABSL_MUST_USE_RESULT absl::Span<char> EncodeMessageStart( + uint64_t tag, uint64_t max_size, absl::Span<char> *buf) { + const uint64_t tag_type = MakeTagType(tag, WireType::kLengthDelimited); + const size_t tag_type_size = VarintSize(tag_type); + max_size = std::min<uint64_t>(max_size, buf->size()); + const size_t length_size = VarintSize(max_size); + if (tag_type_size + length_size > buf->size()) { + buf->remove_suffix(buf->size()); + return absl::Span<char>(); + } + EncodeRawVarint(tag_type, tag_type_size, buf); + const absl::Span<char> ret = buf->subspan(0, length_size); + EncodeRawVarint(0, length_size, buf); + return ret; +} + +void EncodeMessageLength(absl::Span<char> msg, const absl::Span<char> *buf) { + if (!msg.data()) return; + assert(buf->data() >= msg.data()); + if (buf->data() < msg.data()) return; + EncodeRawVarint( + static_cast<uint64_t>(buf->data() - (msg.data() + msg.size())), + msg.size(), &msg); +} + +namespace { +uint64_t DecodeVarint(absl::Span<const char> *buf) { + uint64_t value = 0; + size_t s = 0; + while (s < buf->size()) { + value |= static_cast<uint64_t>(static_cast<unsigned char>((*buf)[s]) & 0x7f) + << 7 * s; + if (!((*buf)[s++] & 0x80)) break; + } + buf->remove_prefix(s); + return value; +} + +uint64_t Decode64Bit(absl::Span<const char> *buf) { + uint64_t value = 0; + size_t s = 0; + while (s < buf->size()) { + value |= static_cast<uint64_t>(static_cast<unsigned char>((*buf)[s])) + << 8 * s; + if (++s == sizeof(value)) break; + } + buf->remove_prefix(s); + return value; +} + +uint32_t Decode32Bit(absl::Span<const char> *buf) { + uint32_t value = 0; + size_t s = 0; + while (s < buf->size()) { + value |= static_cast<uint32_t>(static_cast<unsigned char>((*buf)[s])) + << 8 * s; + if (++s == sizeof(value)) break; + } + buf->remove_prefix(s); + return value; +} +} // namespace + +bool ProtoField::DecodeFrom(absl::Span<const char> *data) { + if (data->empty()) return false; + const uint64_t tag_type = DecodeVarint(data); + tag_ = tag_type >> 3; + type_ = static_cast<WireType>(tag_type & 0x07); + switch (type_) { + case WireType::kVarint: + value_ = DecodeVarint(data); + break; + case WireType::k64Bit: + value_ = Decode64Bit(data); + break; + case WireType::kLengthDelimited: { + value_ = DecodeVarint(data); + data_ = data->subspan( + 0, static_cast<size_t>(std::min<uint64_t>(value_, data->size()))); + data->remove_prefix(data_.size()); + break; + } + case WireType::k32Bit: + value_ = Decode32Bit(data); + break; + } + return true; +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/internal/proto.h b/absl/log/internal/proto.h new file mode 100644 index 00000000..c8d14acc --- /dev/null +++ b/absl/log/internal/proto.h @@ -0,0 +1,288 @@ +// 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. + +// ----------------------------------------------------------------------------- +// File: internal/proto.h +// ----------------------------------------------------------------------------- +// +// Declares functions for serializing and deserializing data to and from memory +// buffers in protocol buffer wire format. This library takes no steps to +// ensure that the encoded data matches with any message specification. + +#ifndef ABSL_LOG_INTERNAL_PROTO_H_ +#define ABSL_LOG_INTERNAL_PROTO_H_ + +#include <cstddef> +#include <cstdint> +#include <limits> + +#include "absl/base/attributes.h" +#include "absl/base/casts.h" +#include "absl/base/config.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +// absl::Span<char> represents a view into the available space in a mutable +// buffer during encoding. Encoding functions shrink the span as they go so +// that the same view can be passed to a series of Encode functions. If the +// data do not fit, nothing is encoded, the view is set to size zero (so that +// all subsequent encode calls fail), and false is returned. Otherwise true is +// returned. + +// In particular, attempting to encode a series of data into an insufficient +// buffer has consistent and efficient behavior without any caller-side error +// checking. Individual values will be encoded in their entirety or not at all +// (unless one of the `Truncate` functions is used). Once a value is omitted +// because it does not fit, no subsequent values will be encoded to preserve +// ordering; the decoded sequence will be a prefix of the original sequence. + +// There are two ways to encode a message-typed field: +// +// * Construct its contents in a separate buffer and use `EncodeBytes` to copy +// it into the primary buffer with type, tag, and length. +// * Use `EncodeMessageStart` to write type and tag fields and reserve space for +// the length field, then encode the contents directly into the buffer, then +// use `EncodeMessageLength` to write the actual length into the reserved +// bytes. This works fine if the actual length takes fewer bytes to encode +// than were reserved, although you don't get your extra bytes back. +// This approach will always produce a valid encoding, but your protocol may +// require that the whole message field by omitted if the buffer is too small +// to contain all desired subfields. In this case, operate on a copy of the +// buffer view and assign back only if everything fit, i.e. if the last +// `Encode` call returned true. + +// Encodes the specified integer as a varint field and returns true if it fits. +// Used for int32_t, int64_t, uint32_t, uint64_t, bool, and enum field types. +// Consumes up to kMaxVarintSize * 2 bytes (20). +bool EncodeVarint(uint64_t tag, uint64_t value, absl::Span<char> *buf); +inline bool EncodeVarint(uint64_t tag, int64_t value, absl::Span<char> *buf) { + return EncodeVarint(tag, static_cast<uint64_t>(value), buf); +} +inline bool EncodeVarint(uint64_t tag, uint32_t value, absl::Span<char> *buf) { + return EncodeVarint(tag, static_cast<uint64_t>(value), buf); +} +inline bool EncodeVarint(uint64_t tag, int32_t value, absl::Span<char> *buf) { + return EncodeVarint(tag, static_cast<uint64_t>(value), buf); +} + +// Encodes the specified integer as a varint field using ZigZag encoding and +// returns true if it fits. +// Used for sint32 and sint64 field types. +// Consumes up to kMaxVarintSize * 2 bytes (20). +inline bool EncodeVarintZigZag(uint64_t tag, int64_t value, + absl::Span<char> *buf) { + if (value < 0) + return EncodeVarint(tag, 2 * static_cast<uint64_t>(-(value + 1)) + 1, buf); + return EncodeVarint(tag, 2 * static_cast<uint64_t>(value), buf); +} + +// Encodes the specified integer as a 64-bit field and returns true if it fits. +// Used for fixed64 and sfixed64 field types. +// Consumes up to kMaxVarintSize + 8 bytes (18). +bool Encode64Bit(uint64_t tag, uint64_t value, absl::Span<char> *buf); +inline bool Encode64Bit(uint64_t tag, int64_t value, absl::Span<char> *buf) { + return Encode64Bit(tag, static_cast<uint64_t>(value), buf); +} +inline bool Encode64Bit(uint64_t tag, uint32_t value, absl::Span<char> *buf) { + return Encode64Bit(tag, static_cast<uint64_t>(value), buf); +} +inline bool Encode64Bit(uint64_t tag, int32_t value, absl::Span<char> *buf) { + return Encode64Bit(tag, static_cast<uint64_t>(value), buf); +} + +// Encodes the specified double as a 64-bit field and returns true if it fits. +// Used for double field type. +// Consumes up to kMaxVarintSize + 8 bytes (18). +inline bool EncodeDouble(uint64_t tag, double value, absl::Span<char> *buf) { + return Encode64Bit(tag, absl::bit_cast<uint64_t>(value), buf); +} + +// Encodes the specified integer as a 32-bit field and returns true if it fits. +// Used for fixed32 and sfixed32 field types. +// Consumes up to kMaxVarintSize + 4 bytes (14). +bool Encode32Bit(uint64_t tag, uint32_t value, absl::Span<char> *buf); +inline bool Encode32Bit(uint64_t tag, int32_t value, absl::Span<char> *buf) { + return Encode32Bit(tag, static_cast<uint32_t>(value), buf); +} + +// Encodes the specified float as a 32-bit field and returns true if it fits. +// Used for float field type. +// Consumes up to kMaxVarintSize + 4 bytes (14). +inline bool EncodeFloat(uint64_t tag, float value, absl::Span<char> *buf) { + return Encode32Bit(tag, absl::bit_cast<uint32_t>(value), buf); +} + +// Encodes the specified bytes as a length-delimited field and returns true if +// they fit. +// Used for string, bytes, message, and packed-repeated field type. +// Consumes up to kMaxVarintSize * 2 + value.size() bytes (20 + value.size()). +bool EncodeBytes(uint64_t tag, absl::Span<const char> value, + absl::Span<char> *buf); + +// Encodes as many of the specified bytes as will fit as a length-delimited +// field and returns true as long as the field header (`tag_type` and `length`) +// fits. +// Used for string, bytes, message, and packed-repeated field type. +// Consumes up to kMaxVarintSize * 2 + value.size() bytes (20 + value.size()). +bool EncodeBytesTruncate(uint64_t tag, absl::Span<const char> value, + absl::Span<char> *buf); + +// Encodes the specified string as a length-delimited field and returns true if +// it fits. +// Used for string, bytes, message, and packed-repeated field type. +// Consumes up to kMaxVarintSize * 2 + value.size() bytes (20 + value.size()). +inline bool EncodeString(uint64_t tag, absl::string_view value, + absl::Span<char> *buf) { + return EncodeBytes(tag, value, buf); +} + +// Encodes as much of the specified string as will fit as a length-delimited +// field and returns true as long as the field header (`tag_type` and `length`) +// fits. +// Used for string, bytes, message, and packed-repeated field type. +// Consumes up to kMaxVarintSize * 2 + value.size() bytes (20 + value.size()). +inline bool EncodeStringTruncate(uint64_t tag, absl::string_view value, + absl::Span<char> *buf) { + return EncodeBytesTruncate(tag, value, buf); +} + +// Encodes the header for a length-delimited field containing up to `max_size` +// bytes or the number remaining in the buffer, whichever is less. If the +// header fits, a non-nullptr `Span` is returned; this must be passed to +// `EncodeMessageLength` after all contents are encoded to finalize the length +// field. If the header does not fit, a nullptr `Span` is returned which is +// safe to pass to `EncodeMessageLength` but need not be. +// Used for string, bytes, message, and packed-repeated field type. +// Consumes up to kMaxVarintSize * 2 bytes (20). +ABSL_MUST_USE_RESULT absl::Span<char> EncodeMessageStart(uint64_t tag, + uint64_t max_size, + absl::Span<char> *buf); + +// Finalizes the length field in `msg` so that it encompasses all data encoded +// since the call to `EncodeMessageStart` which returned `msg`. Does nothing if +// `msg` is a `nullptr` `Span`. +void EncodeMessageLength(absl::Span<char> msg, const absl::Span<char> *buf); + +enum class WireType : uint64_t { + kVarint = 0, + k64Bit = 1, + kLengthDelimited = 2, + k32Bit = 5, +}; + +constexpr size_t VarintSize(uint64_t value) { + return value < 128 ? 1 : 1 + VarintSize(value >> 7); +} +constexpr size_t MinVarintSize() { + return VarintSize((std::numeric_limits<uint64_t>::min)()); +} +constexpr size_t MaxVarintSize() { + return VarintSize((std::numeric_limits<uint64_t>::max)()); +} + +constexpr uint64_t MaxVarintForSize(size_t size) { + return size >= 10 ? (std::numeric_limits<uint64_t>::max)() + : (static_cast<uint64_t>(1) << size * 7) - 1; +} + +// `BufferSizeFor` returns a number of bytes guaranteed to be sufficient to +// store encoded fields of the specified WireTypes regardless of tag numbers and +// data values. This only makes sense for `WireType::kLengthDelimited` if you +// add in the length of the contents yourself, e.g. for string and bytes fields +// by adding the lengths of any encoded strings to the return value or for +// submessage fields by enumerating the fields you may encode into their +// contents. +constexpr size_t BufferSizeFor() { return 0; } +template <typename... T> +constexpr size_t BufferSizeFor(WireType type, T... tail) { + // tag_type + data + ... + return MaxVarintSize() + + (type == WireType::kVarint ? MaxVarintSize() : // + type == WireType::k64Bit ? 8 : // + type == WireType::k32Bit ? 4 : MaxVarintSize()) + // + BufferSizeFor(tail...); +} + +// absl::Span<const char> represents a view into the un-processed space in a +// buffer during decoding. Decoding functions shrink the span as they go so +// that the same view can be decoded iteratively until all data are processed. +// In general, if the buffer is exhausted but additional bytes are expected by +// the decoder, it will return values as if the additional bytes were zeros. +// Length-delimited fields are an exception - if the encoded length field +// indicates more data bytes than are available in the buffer, the `bytes_value` +// and `string_value` accessors will return truncated views. + +class ProtoField final { + public: + // Consumes bytes from `data` and returns true if there were any bytes to + // decode. + bool DecodeFrom(absl::Span<const char> *data); + uint64_t tag() const { return tag_; } + WireType type() const { return type_; } + + // These value accessors will return nonsense if the data were not encoded in + // the corresponding wiretype from the corresponding C++ (or other language) + // type. + + double double_value() const { return absl::bit_cast<double>(value_); } + float float_value() const { + return absl::bit_cast<float>(static_cast<uint32_t>(value_)); + } + int32_t int32_value() const { return static_cast<int32_t>(value_); } + int64_t int64_value() const { return static_cast<int64_t>(value_); } + int32_t sint32_value() const { + if (value_ % 2) return static_cast<int32_t>(0 - ((value_ - 1) / 2) - 1); + return static_cast<int32_t>(value_ / 2); + } + int64_t sint64_value() const { + if (value_ % 2) return 0 - ((value_ - 1) / 2) - 1; + return value_ / 2; + } + uint32_t uint32_value() const { return static_cast<uint32_t>(value_); } + uint64_t uint64_value() const { return value_; } + bool bool_value() const { return value_ != 0; } + // To decode an enum, call int32_value() and cast to the appropriate type. + // Note that the official C++ proto compiler treats enum fields with values + // that do not correspond to a defined enumerator as unknown fields. + + // To decode fields within a submessage field, call + // `DecodeNextField(field.BytesValue())`. + absl::Span<const char> bytes_value() const { return data_; } + absl::string_view string_value() const { + const auto data = bytes_value(); + return absl::string_view(data.data(), data.size()); + } + // Returns the encoded length of a length-delimited field. This equals + // `bytes_value().size()` except when the latter has been truncated due to + // buffer underrun. + uint64_t encoded_length() const { return value_; } + + private: + uint64_t tag_; + WireType type_; + // For `kTypeVarint`, `kType64Bit`, and `kType32Bit`, holds the decoded value. + // For `kTypeLengthDelimited`, holds the decoded length. + uint64_t value_; + absl::Span<const char> data_; +}; + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_PROTO_H_ diff --git a/absl/log/internal/stderr_log_sink_test.cc b/absl/log/internal/stderr_log_sink_test.cc new file mode 100644 index 00000000..763690d1 --- /dev/null +++ b/absl/log/internal/stderr_log_sink_test.cc @@ -0,0 +1,105 @@ +// +// 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 <stdlib.h> + +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/base/log_severity.h" +#include "absl/log/globals.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/log/log.h" + +namespace { +using ::testing::AllOf; +using ::testing::HasSubstr; + +auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( + new absl::log_internal::LogTestEnvironment); + +MATCHER_P2(HasSubstrTimes, substr, expected_count, "") { + int count = 0; + std::string::size_type pos = 0; + std::string needle(substr); + while ((pos = arg.find(needle, pos)) != std::string::npos) { + ++count; + pos += needle.size(); + } + + return count == expected_count; +} + +TEST(StderrLogSinkDeathTest, InfoMessagesInStderr) { + EXPECT_DEATH_IF_SUPPORTED( + { + absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo); + LOG(INFO) << "INFO message"; + exit(1); + }, + "INFO message"); +} + +TEST(StderrLogSinkDeathTest, WarningMessagesInStderr) { + EXPECT_DEATH_IF_SUPPORTED( + { + absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo); + LOG(WARNING) << "WARNING message"; + exit(1); + }, + "WARNING message"); +} + +TEST(StderrLogSinkDeathTest, ErrorMessagesInStderr) { + EXPECT_DEATH_IF_SUPPORTED( + { + absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo); + LOG(ERROR) << "ERROR message"; + exit(1); + }, + "ERROR message"); +} + +TEST(StderrLogSinkDeathTest, FatalMessagesInStderr) { + char message[] = "FATAL message"; + char stacktrace[] = "*** Check failure stack trace: ***"; + + int expected_count = 1; + + EXPECT_DEATH_IF_SUPPORTED( + { + absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo); + LOG(FATAL) << message; + }, + AllOf(HasSubstrTimes(message, expected_count), HasSubstr(stacktrace))); +} + +TEST(StderrLogSinkDeathTest, SecondaryFatalMessagesInStderr) { + auto MessageGen = []() -> std::string { + LOG(FATAL) << "Internal failure"; + return "External failure"; + }; + + EXPECT_DEATH_IF_SUPPORTED( + { + absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfo); + LOG(FATAL) << MessageGen(); + }, + "Internal failure"); +} + +} // namespace diff --git a/absl/log/internal/strip.h b/absl/log/internal/strip.h new file mode 100644 index 00000000..848c3867 --- /dev/null +++ b/absl/log/internal/strip.h @@ -0,0 +1,71 @@ +// 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/internal/strip.h +// ----------------------------------------------------------------------------- +// + +#ifndef ABSL_LOG_INTERNAL_STRIP_H_ +#define ABSL_LOG_INTERNAL_STRIP_H_ + +#include "absl/base/log_severity.h" +#include "absl/log/internal/log_message.h" +#include "absl/log/internal/nullstream.h" + +// `ABSL_LOGGING_INTERNAL_LOG_*` evaluates to a temporary `LogMessage` object or +// to a related object with a compatible API but different behavior. This set +// of defines comes in three flavors: vanilla, plus two variants that strip some +// logging in subtly different ways for subtly different reasons (see below). +#if defined(STRIP_LOG) && STRIP_LOG +#define ABSL_LOGGING_INTERNAL_LOG_INFO ::absl::log_internal::NullStream() +#define ABSL_LOGGING_INTERNAL_LOG_WARNING ::absl::log_internal::NullStream() +#define ABSL_LOGGING_INTERNAL_LOG_ERROR ::absl::log_internal::NullStream() +#define ABSL_LOGGING_INTERNAL_LOG_FATAL ::absl::log_internal::NullStreamFatal() +#define ABSL_LOGGING_INTERNAL_LOG_QFATAL ::absl::log_internal::NullStreamFatal() +#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) +#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_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) +// 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) \ + ::absl::log_internal::LogMessageFatal(__FILE__, __LINE__, failure_message) +#define ABSL_LOG_INTERNAL_QCHECK(failure_message) \ + ::absl::log_internal::LogMessageQuietlyFatal(__FILE__, __LINE__, \ + failure_message) +#endif // !defined(STRIP_LOG) || !STRIP_LOG + +#endif // ABSL_LOG_INTERNAL_STRIP_H_ diff --git a/absl/log/internal/structured.h b/absl/log/internal/structured.h new file mode 100644 index 00000000..08caea66 --- /dev/null +++ b/absl/log/internal/structured.h @@ -0,0 +1,58 @@ +// 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/internal/structured.h +// ----------------------------------------------------------------------------- + +#ifndef ABSL_LOG_INTERNAL_STRUCTURED_H_ +#define ABSL_LOG_INTERNAL_STRUCTURED_H_ + +#include <ostream> + +#include "absl/base/config.h" +#include "absl/log/internal/log_message.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +class ABSL_MUST_USE_RESULT AsLiteralImpl final { + public: + explicit AsLiteralImpl(absl::string_view str) : str_(str) {} + AsLiteralImpl(const AsLiteralImpl&) = default; + AsLiteralImpl& operator=(const AsLiteralImpl&) = default; + + private: + absl::string_view str_; + + friend std::ostream& operator<<(std::ostream& os, AsLiteralImpl as_literal) { + return os << as_literal.str_; + } + void AddToMessage(log_internal::LogMessage& m) { + m.CopyToEncodedBuffer(str_, log_internal::LogMessage::StringType::kLiteral); + } + friend log_internal::LogMessage& operator<<(log_internal::LogMessage& m, + AsLiteralImpl as_literal) { + as_literal.AddToMessage(m); + return m; + } +}; + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_STRUCTURED_H_ diff --git a/absl/log/internal/test_actions.cc b/absl/log/internal/test_actions.cc new file mode 100644 index 00000000..bdfd6377 --- /dev/null +++ b/absl/log/internal/test_actions.cc @@ -0,0 +1,75 @@ +// +// 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/test_actions.h" + +#include <cassert> +#include <iostream> +#include <string> +#include <type_traits> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/strings/escaping.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +void WriteToStderrWithFilename::operator()(const absl::LogEntry& entry) const { + std::cerr << message << " (file: " << entry.source_filename() << ")\n"; +} + +void WriteEntryToStderr::operator()(const absl::LogEntry& entry) const { + if (!message.empty()) std::cerr << message << "\n"; + + const std::string source_filename = absl::CHexEscape(entry.source_filename()); + const std::string source_basename = absl::CHexEscape(entry.source_basename()); + const std::string text_message = absl::CHexEscape(entry.text_message()); + const std::string encoded_message = absl::CHexEscape(entry.encoded_message()); + std::string encoded_message_str; + std::cerr << "LogEntry{\n" // + << " source_filename: \"" << source_filename << "\"\n" // + << " source_basename: \"" << source_basename << "\"\n" // + << " source_line: " << entry.source_line() << "\n" // + << " prefix: " << (entry.prefix() ? "true\n" : "false\n") // + << " log_severity: " << entry.log_severity() << "\n" // + << " timestamp: " << entry.timestamp() << "\n" // + << " text_message: \"" << text_message << "\"\n" // + << " verbosity: " << entry.verbosity() << "\n" // + << " encoded_message (raw): \"" << encoded_message << "\"\n" // + << encoded_message_str // + << "}\n"; +} + +void WriteEntryToStderr::operator()(absl::LogSeverity severity, + absl::string_view filename, + absl::string_view log_message) const { + if (!message.empty()) std::cerr << message << "\n"; + const std::string source_filename = absl::CHexEscape(filename); + const std::string text_message = absl::CHexEscape(log_message); + std::cerr << "LogEntry{\n" // + << " source_filename: \"" << source_filename << "\"\n" // + << " log_severity: " << severity << "\n" // + << " text_message: \"" << text_message << "\"\n" // + << "}\n"; +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/internal/test_actions.h b/absl/log/internal/test_actions.h new file mode 100644 index 00000000..649a0505 --- /dev/null +++ b/absl/log/internal/test_actions.h @@ -0,0 +1,90 @@ +// 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/internal/test_actions.h +// ----------------------------------------------------------------------------- +// +// This file declares Googletest's actions used in the Abseil Logging library +// unit tests. + +#ifndef ABSL_LOG_INTERNAL_TEST_ACTIONS_H_ +#define ABSL_LOG_INTERNAL_TEST_ACTIONS_H_ + +#include <iostream> +#include <ostream> +#include <string> + +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/log/log_entry.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +// These actions are used by the child process in a death test. +// +// Expectations set in the child cannot cause test failure in the parent +// directly. Instead, the child can use these actions with +// `EXPECT_CALL`/`WillOnce` and `ON_CALL`/`WillByDefault` (for unexpected calls) +// to write messages to stderr that the parent can match against. +struct WriteToStderr final { + explicit WriteToStderr(absl::string_view m) : message(m) {} + std::string message; + + template <typename... Args> + void operator()(const Args&...) const { + std::cerr << message << std::endl; + } +}; + +struct WriteToStderrWithFilename final { + explicit WriteToStderrWithFilename(absl::string_view m) : message(m) {} + + std::string message; + + void operator()(const absl::LogEntry& entry) const; +}; + +struct WriteEntryToStderr final { + explicit WriteEntryToStderr(absl::string_view m) : message(m) {} + + std::string message = ""; + + void operator()(const absl::LogEntry& entry) const; + void operator()(absl::LogSeverity, absl::string_view, + absl::string_view) const; +}; + +// See the documentation for `DeathTestValidateExpectations` above. +// `DeathTestExpectedLogging` should be used once in a given death test, and the +// applicable severity level is the one that should be passed to +// `DeathTestValidateExpectations`. +inline WriteEntryToStderr DeathTestExpectedLogging() { + return WriteEntryToStderr{"Mock received expected entry:"}; +} + +// `DeathTestUnexpectedLogging` should be used zero or more times to mark +// messages that should not hit the logs as the process dies. +inline WriteEntryToStderr DeathTestUnexpectedLogging() { + return WriteEntryToStderr{"Mock received unexpected entry:"}; +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_TEST_ACTIONS_H_ diff --git a/absl/log/internal/test_helpers.cc b/absl/log/internal/test_helpers.cc new file mode 100644 index 00000000..0de5b96b --- /dev/null +++ b/absl/log/internal/test_helpers.cc @@ -0,0 +1,82 @@ +// 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/test_helpers.h" + +#ifdef __Fuchsia__ +#include <zircon/syscalls.h> +#endif + +#include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/log/globals.h" +#include "absl/log/initialize.h" +#include "absl/log/internal/globals.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +// Returns false if the specified severity level is disabled by +// `ABSL_MIN_LOG_LEVEL` or `absl::MinLogLevel()`. +bool LoggingEnabledAt(absl::LogSeverity severity) { + return severity >= kAbslMinLogLevel && severity >= absl::MinLogLevel(); +} + +// ----------------------------------------------------------------------------- +// Googletest Death Test Predicates +// ----------------------------------------------------------------------------- + +#if GTEST_HAS_DEATH_TEST + +bool DiedOfFatal(int exit_status) { +#if defined(_WIN32) + // Depending on NDEBUG and (configuration?) MSVC's abort either results + // in error code 3 (SIGABRT) or error code 0x80000003 (breakpoint + // triggered). + return ::testing::ExitedWithCode(3)(exit_status & 0x7fffffff); +#elif defined(__Fuchsia__) + // The Fuchsia death test implementation kill()'s the process when it detects + // an exception, so it should exit with the corresponding code. See + // FuchsiaDeathTest::Wait(). + return ::testing::ExitedWithCode(ZX_TASK_RETCODE_SYSCALL_KILL)(exit_status); +#elif defined(__ANDROID__) && defined(__aarch64__) + // These are all run under a qemu config that eats died-due-to-signal exit + // statuses. + return true; +#else + return ::testing::KilledBySignal(SIGABRT)(exit_status); +#endif +} + +bool DiedOfQFatal(int exit_status) { + return ::testing::ExitedWithCode(1)(exit_status); +} + +#endif + +// ----------------------------------------------------------------------------- +// Helper for Log inititalization in test +// ----------------------------------------------------------------------------- + +void LogTestEnvironment::SetUp() { + if (!absl::log_internal::IsInitialized()) { + absl::InitializeLog(); + } +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/internal/test_helpers.h b/absl/log/internal/test_helpers.h new file mode 100644 index 00000000..fd06e295 --- /dev/null +++ b/absl/log/internal/test_helpers.h @@ -0,0 +1,71 @@ +// 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/internal/test_helpers.h +// ----------------------------------------------------------------------------- +// +// This file declares testing helpers for the logging library. + +#ifndef ABSL_LOG_INTERNAL_TEST_HELPERS_H_ +#define ABSL_LOG_INTERNAL_TEST_HELPERS_H_ + +#include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/log/globals.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +// `ABSL_MIN_LOG_LEVEL` can't be used directly since it is not always defined. +constexpr auto kAbslMinLogLevel = +#ifdef ABSL_MIN_LOG_LEVEL + static_cast<absl::LogSeverityAtLeast>(ABSL_MIN_LOG_LEVEL); +#else + absl::LogSeverityAtLeast::kInfo; +#endif + +// Returns false if the specified severity level is disabled by +// `ABSL_MIN_LOG_LEVEL` or `absl::MinLogLevel()`. +bool LoggingEnabledAt(absl::LogSeverity severity); + +// ----------------------------------------------------------------------------- +// Googletest Death Test Predicates +// ----------------------------------------------------------------------------- + +#if GTEST_HAS_DEATH_TEST + +bool DiedOfFatal(int exit_status); +bool DiedOfQFatal(int exit_status); + +#endif + +// ----------------------------------------------------------------------------- +// Helper for Log inititalization in test +// ----------------------------------------------------------------------------- + +class LogTestEnvironment : public ::testing::Environment { + public: + ~LogTestEnvironment() override = default; + + void SetUp() override; +}; + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_TEST_HELPERS_H_ diff --git a/absl/log/internal/test_matchers.cc b/absl/log/internal/test_matchers.cc new file mode 100644 index 00000000..8c6515c4 --- /dev/null +++ b/absl/log/internal/test_matchers.cc @@ -0,0 +1,217 @@ +// +// 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/test_matchers.h" + +#include <ostream> +#include <sstream> +#include <string> +#include <type_traits> +#include <utility> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/strings/string_view.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { +namespace { +using ::testing::_; +using ::testing::AllOf; +using ::testing::Ge; +using ::testing::HasSubstr; +using ::testing::MakeMatcher; +using ::testing::Matcher; +using ::testing::MatcherInterface; +using ::testing::MatchResultListener; +using ::testing::Not; +using ::testing::Property; +using ::testing::ResultOf; +using ::testing::Truly; + +class AsStringImpl final + : public MatcherInterface<absl::string_view> { + public: + explicit AsStringImpl( + const Matcher<const std::string&>& str_matcher) + : str_matcher_(str_matcher) {} + bool MatchAndExplain( + absl::string_view actual, + MatchResultListener* listener) const override { + return str_matcher_.MatchAndExplain(std::string(actual), listener); + } + void DescribeTo(std::ostream* os) const override { + return str_matcher_.DescribeTo(os); + } + + void DescribeNegationTo(std::ostream* os) const override { + return str_matcher_.DescribeNegationTo(os); + } + + private: + const Matcher<const std::string&> str_matcher_; +}; + +class MatchesOstreamImpl final + : public MatcherInterface<absl::string_view> { + public: + explicit MatchesOstreamImpl(std::string expected) + : expected_(std::move(expected)) {} + bool MatchAndExplain(absl::string_view actual, + MatchResultListener*) const override { + return actual == expected_; + } + void DescribeTo(std::ostream* os) const override { + *os << "matches the contents of the ostringstream, which are \"" + << expected_ << "\""; + } + + void DescribeNegationTo(std::ostream* os) const override { + *os << "does not match the contents of the ostringstream, which are \"" + << expected_ << "\""; + } + + private: + const std::string expected_; +}; +} // namespace + +Matcher<absl::string_view> AsString( + const Matcher<const std::string&>& str_matcher) { + return MakeMatcher(new AsStringImpl(str_matcher)); +} + +Matcher<const absl::LogEntry&> SourceFilename( + const Matcher<absl::string_view>& source_filename) { + return Property("source_filename", &absl::LogEntry::source_filename, + source_filename); +} + +Matcher<const absl::LogEntry&> SourceBasename( + const Matcher<absl::string_view>& source_basename) { + return Property("source_basename", &absl::LogEntry::source_basename, + source_basename); +} + +Matcher<const absl::LogEntry&> SourceLine( + const Matcher<int>& source_line) { + return Property("source_line", &absl::LogEntry::source_line, source_line); +} + +Matcher<const absl::LogEntry&> Prefix( + const Matcher<bool>& prefix) { + return Property("prefix", &absl::LogEntry::prefix, prefix); +} + +Matcher<const absl::LogEntry&> LogSeverity( + const Matcher<absl::LogSeverity>& log_severity) { + return Property("log_severity", &absl::LogEntry::log_severity, log_severity); +} + +Matcher<const absl::LogEntry&> Timestamp( + const Matcher<absl::Time>& timestamp) { + return Property("timestamp", &absl::LogEntry::timestamp, timestamp); +} + +Matcher<const absl::LogEntry&> TimestampInMatchWindow() { + return Property("timestamp", &absl::LogEntry::timestamp, + AllOf(Ge(absl::Now()), Truly([](absl::Time arg) { + return arg <= absl::Now(); + }))); +} + +Matcher<const absl::LogEntry&> ThreadID( + const Matcher<absl::LogEntry::tid_t>& tid) { + return Property("tid", &absl::LogEntry::tid, tid); +} + +Matcher<const absl::LogEntry&> TextMessageWithPrefixAndNewline( + const Matcher<absl::string_view>& + text_message_with_prefix_and_newline) { + return Property("text_message_with_prefix_and_newline", + &absl::LogEntry::text_message_with_prefix_and_newline, + text_message_with_prefix_and_newline); +} + +Matcher<const absl::LogEntry&> TextMessageWithPrefix( + const Matcher<absl::string_view>& text_message_with_prefix) { + return Property("text_message_with_prefix", + &absl::LogEntry::text_message_with_prefix, + text_message_with_prefix); +} + +Matcher<const absl::LogEntry&> TextMessage( + const Matcher<absl::string_view>& text_message) { + return Property("text_message", &absl::LogEntry::text_message, text_message); +} + +Matcher<const absl::LogEntry&> TextPrefix( + const Matcher<absl::string_view>& text_prefix) { + return ResultOf( + [](const absl::LogEntry& entry) { + absl::string_view msg = entry.text_message_with_prefix(); + msg.remove_suffix(entry.text_message().size()); + return msg; + }, + text_prefix); +} +Matcher<const absl::LogEntry&> RawEncodedMessage( + const Matcher<absl::string_view>& raw_encoded_message) { + return Property("encoded_message", &absl::LogEntry::encoded_message, + raw_encoded_message); +} + +Matcher<const absl::LogEntry&> Verbosity( + const Matcher<int>& verbosity) { + return Property("verbosity", &absl::LogEntry::verbosity, verbosity); +} + +Matcher<const absl::LogEntry&> Stacktrace( + const Matcher<absl::string_view>& stacktrace) { + return Property("stacktrace", &absl::LogEntry::stacktrace, stacktrace); +} + +Matcher<absl::string_view> MatchesOstream( + const std::ostringstream& stream) { + return MakeMatcher(new MatchesOstreamImpl(stream.str())); +} + +// We need to validate what is and isn't logged as the process dies due to +// `FATAL`, `QFATAL`, `CHECK`, etc., but assertions inside a death test +// subprocess don't directly affect the pass/fail status of the parent process. +// Instead, we use the mock actions `DeathTestExpectedLogging` and +// `DeathTestUnexpectedLogging` to write specific phrases to `stderr` that we +// can validate in the parent process using this matcher. +Matcher<const std::string&> DeathTestValidateExpectations() { + if (log_internal::LoggingEnabledAt(absl::LogSeverity::kFatal)) { + return Matcher<const std::string&>( + AllOf(HasSubstr("Mock received expected entry"), + Not(HasSubstr("Mock received unexpected entry")))); + } + // If `FATAL` logging is disabled, neither message should have been written. + return Matcher<const std::string&>( + AllOf(Not(HasSubstr("Mock received expected entry")), + Not(HasSubstr("Mock received unexpected entry")))); +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/internal/test_matchers.h b/absl/log/internal/test_matchers.h new file mode 100644 index 00000000..fc653a91 --- /dev/null +++ b/absl/log/internal/test_matchers.h @@ -0,0 +1,94 @@ +// 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/internal/test_matchers.h +// ----------------------------------------------------------------------------- +// +// This file declares Googletest's matchers used in the Abseil Logging library +// unit tests. + +#ifndef ABSL_LOG_INTERNAL_TEST_MATCHERS_H_ +#define ABSL_LOG_INTERNAL_TEST_MATCHERS_H_ + +#include <iosfwd> +#include <sstream> +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/log/log_entry.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { +// In some configurations, Googletest's string matchers (e.g. +// `::testing::EndsWith`) need help to match `absl::string_view`. +::testing::Matcher<absl::string_view> AsString( + const ::testing::Matcher<const std::string&>& str_matcher); + +// These matchers correspond to the components of `absl::LogEntry`. +::testing::Matcher<const absl::LogEntry&> SourceFilename( + const ::testing::Matcher<absl::string_view>& source_filename); +::testing::Matcher<const absl::LogEntry&> SourceBasename( + const ::testing::Matcher<absl::string_view>& source_basename); +// Be careful with this one; multi-line statements using `__LINE__` evaluate +// differently on different platforms. In particular, the MSVC implementation +// of `EXPECT_DEATH` returns the line number of the macro expansion to all lines +// within the code block that's expected to die. +::testing::Matcher<const absl::LogEntry&> SourceLine( + const ::testing::Matcher<int>& source_line); +::testing::Matcher<const absl::LogEntry&> Prefix( + const ::testing::Matcher<bool>& prefix); +::testing::Matcher<const absl::LogEntry&> LogSeverity( + const ::testing::Matcher<absl::LogSeverity>& log_severity); +::testing::Matcher<const absl::LogEntry&> Timestamp( + const ::testing::Matcher<absl::Time>& timestamp); +// Matches if the `LogEntry`'s timestamp falls after the instantiation of this +// matcher and before its execution, as is normal when used with EXPECT_CALL. +::testing::Matcher<const absl::LogEntry&> TimestampInMatchWindow(); +::testing::Matcher<const absl::LogEntry&> ThreadID( + const ::testing::Matcher<absl::LogEntry::tid_t>&); +::testing::Matcher<const absl::LogEntry&> TextMessageWithPrefixAndNewline( + const ::testing::Matcher<absl::string_view>& + text_message_with_prefix_and_newline); +::testing::Matcher<const absl::LogEntry&> TextMessageWithPrefix( + const ::testing::Matcher<absl::string_view>& text_message_with_prefix); +::testing::Matcher<const absl::LogEntry&> TextMessage( + const ::testing::Matcher<absl::string_view>& text_message); +::testing::Matcher<const absl::LogEntry&> TextPrefix( + const ::testing::Matcher<absl::string_view>& text_prefix); +::testing::Matcher<const absl::LogEntry&> Verbosity( + const ::testing::Matcher<int>& verbosity); +::testing::Matcher<const absl::LogEntry&> Stacktrace( + const ::testing::Matcher<absl::string_view>& stacktrace); +// Behaves as `Eq(stream.str())`, but produces better failure messages. +::testing::Matcher<absl::string_view> MatchesOstream( + const std::ostringstream& stream); +::testing::Matcher<const std::string&> DeathTestValidateExpectations(); + +::testing::Matcher<const absl::LogEntry&> RawEncodedMessage( + const ::testing::Matcher<absl::string_view>& raw_encoded_message); +#define ENCODED_MESSAGE(message_matcher) ::testing::_ + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_TEST_MATCHERS_H_ diff --git a/absl/log/internal/voidify.h b/absl/log/internal/voidify.h new file mode 100644 index 00000000..8f62da20 --- /dev/null +++ b/absl/log/internal/voidify.h @@ -0,0 +1,44 @@ +// 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/internal/voidify.h +// ----------------------------------------------------------------------------- +// +// This class is used to explicitly ignore values in the conditional logging +// macros. This avoids compiler warnings like "value computed is not used" and +// "statement has no effect". + +#ifndef ABSL_LOG_INTERNAL_VOIDIFY_H_ +#define ABSL_LOG_INTERNAL_VOIDIFY_H_ + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +class Voidify final { + public: + // This has to be an operator with a precedence lower than << but higher than + // ?: + template <typename T> + void operator&&(const T&) const&& {} +}; + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_VOIDIFY_H_ diff --git a/absl/log/log.h b/absl/log/log.h new file mode 100644 index 00000000..e060a0b6 --- /dev/null +++ b/absl/log/log.h @@ -0,0 +1,308 @@ +// 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/log.h +// ----------------------------------------------------------------------------- +// +// This header declares a family of LOG macros. +// +// Basic invocation looks like this: +// +// LOG(INFO) << "Found " << num_cookies << " cookies"; +// +// Most `LOG` macros take a severity level argument. The severity levels are +// `INFO`, `WARNING`, `ERROR`, and `FATAL`. They are defined +// in absl/base/log_severity.h. +// * The `FATAL` severity level terminates the program with a stack trace after +// logging its message. Error handlers registered with `RunOnFailure` +// (process_state.h) are run, but exit handlers registered with `atexit(3)` +// are not. +// * 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. +// 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 +// literal, use `LEVEL(expr)`. +// Example: +// +// LOG(LEVEL(stale ? absl::LogSeverity::kWarning : absl::LogSeverity::kInfo)) +// << "Cookies are " << days << " days old"; + +// `LOG` macros evaluate to an unterminated statement. The value at the end of +// the statement supports some chainable methods: +// +// * .AtLocation(absl::string_view file, int line) +// .AtLocation(absl::SourceLocation loc) +// Overrides the location inferred from the callsite. The string pointed to +// by `file` must be valid until the end of the statement. +// * .NoPrefix() +// Omits the prefix from this line. The prefix includes metadata about the +// logged data such as source code location and timestamp. +// * .WithTimestamp(absl::Time timestamp) +// Uses the specified timestamp instead of one collected at the time of +// execution. +// * .WithThreadID(absl::LogEntry::tid_t tid) +// Uses the specified thread ID instead of one collected at the time of +// execution. +// * .WithMetadataFrom(const absl::LogEntry &entry) +// Copies all metadata (but no data) from the specified `absl::LogEntry`. +// This can be used to change the severity of a message, but it has some +// limitations: +// * `ABSL_MIN_LOG_LEVEL` is evaluated against the severity passed into +// `LOG` (or the implicit `FATAL` level of `CHECK`). +// * `LOG(FATAL)` and `CHECK` terminate the process unconditionally, even if +// the severity is changed later. +// `.WithMetadataFrom(entry)` should almost always be used in combination +// with `LOG(LEVEL(entry.log_severity()))`. +// * .WithPerror() +// Appends to the logged message a colon, a space, a textual description of +// the current value of `errno` (as by `strerror(3)`), and the numerical +// value of `errno`. +// * .ToSinkAlso(absl::LogSink* sink) +// Sends this message to `*sink` in addition to whatever other sinks it +// would otherwise have been sent to. `sink` must not be null. +// * .ToSinkOnly(absl::LogSink* sink) +// Sends this message to `*sink` and no others. `sink` must not be null. +// +// No interfaces in this header are async-signal-safe; their use in signal +// handlers is unsupported and may deadlock your program or eat your lunch. +// +// Many logging statements are inherently conditional. For example, +// `LOG_IF(INFO, !foo)` does nothing if `foo` is true. Even seemingly +// unconditional statements like `LOG(INFO)` might be disabled at +// compile-time to minimize binary size or for security reasons. +// +// * Except for the condition in a `CHECK` or `QCHECK` statement, programs must +// not rely on evaluation of expressions anywhere in logging statements for +// correctness. For example, this is ok: +// +// CHECK((fp = fopen("config.ini", "r")) != nullptr); +// +// But this is probably not ok: +// +// LOG(INFO) << "Server status: " << StartServerAndReturnStatusString(); +// +// The example below is bad too; the `i++` in the `LOG_IF` condition might +// not be evaluated, resulting in an infinite loop: +// +// for (int i = 0; i < 1000000;) +// LOG_IF(INFO, i++ % 1000 == 0) << "Still working..."; +// +// * Except where otherwise noted, conditions which cause a statement not to log +// also cause expressions not to be evaluated. Programs may rely on this for +// performance reasons, e.g. by streaming the result of an expensive function +// call into a `DLOG` or `LOG_EVERY_N` statement. +// * Care has been taken to ensure that expressions are parsed by the compiler +// even if they are never evaluated. This means that syntax errors will be +// caught and variables will be considered used for the purposes of +// unused-variable diagnostics. For example, this statement won't compile +// even if `INFO`-level logging has been compiled out: +// +// int number_of_cakes = 40; +// LOG(INFO) << "Number of cakes: " << number_of_cake; // Note the typo! +// +// Similarly, this won't produce unused-variable compiler diagnostics even +// if `INFO`-level logging is compiled out: +// +// { +// char fox_line1[] = "Hatee-hatee-hatee-ho!"; +// LOG_IF(ERROR, false) << "The fox says " << fox_line1; +// char fox_line2[] = "A-oo-oo-oo-ooo!"; +// LOG(INFO) << "The fox also says " << fox_line2; +// } +// +// This error-checking is not perfect; for example, symbols that have been +// declared but not defined may not produce link errors if used in logging +// statements that compile away. +// +// Expressions streamed into these macros are formatted using `operator<<` just +// as they would be if streamed into a `std::ostream`, however it should be +// noted that their actual type is unspecified. +// +// To implement a custom formatting operator for a type you own, there are two +// options: `AbslStringify()` or `std::ostream& operator<<(std::ostream&, ...)`. +// It is recommended that users make their types loggable through +// `AbslStringify()` as it is a universal stringification extension that also +// enables `absl::StrFormat` and `absl::StrCat` support. If both +// `AbslStringify()` and `std::ostream& operator<<(std::ostream&, ...)` are +// defined, `AbslStringify()` will be used. +// +// To use the `AbslStringify()` API, define a friend function template in your +// type's namespace with the following signature: +// +// template <typename Sink> +// void AbslStringify(Sink& sink, const UserDefinedType& value); +// +// `Sink` has the same interface as `absl::FormatSink`, but without +// `PutPaddedString()`. +// +// Example: +// +// struct Point { +// template <typename Sink> +// friend void AbslStringify(Sink& sink, const Point& p) { +// absl::Format(&sink, "(%v, %v)", p.x, p.y); +// } +// +// int x; +// int y; +// }; +// +// To use `std::ostream& operator<<(std::ostream&, ...)`, define +// `std::ostream& operator<<(std::ostream&, ...)` in your type's namespace (for +// ADL) just as you would to stream it to `std::cout`. +// +// Currently `AbslStringify()` ignores output manipulators but this is not +// guaranteed behavior and may be subject to change in the future. If you would +// like guaranteed behavior regarding output manipulators, please use +// `std::ostream& operator<<(std::ostream&, ...)` to make custom types loggable +// instead. +// +// Those macros that support streaming honor output manipulators and `fmtflag` +// changes that output data (e.g. `std::ends`) or control formatting of data +// (e.g. `std::hex` and `std::fixed`), however flushing such a stream is +// ignored. The message produced by a log statement is sent to registered +// `absl::LogSink` instances at the end of the statement; those sinks are +// responsible for their own flushing (e.g. to disk) semantics. +// +// Flag settings are not carried over from one `LOG` statement to the next; this +// is a bit different than e.g. `std::cout`: +// +// LOG(INFO) << std::hex << 0xdeadbeef; // logs "0xdeadbeef" +// LOG(INFO) << 0xdeadbeef; // logs "3735928559" + +#ifndef ABSL_LOG_LOG_H_ +#define ABSL_LOG_LOG_H_ + +#include "absl/log/internal/log_impl.h" + +// LOG() +// +// `LOG` takes a single argument which is a severity level. Data streamed in +// comprise the logged message. +// Example: +// +// LOG(INFO) << "Found " << num_cookies << " cookies"; +#define LOG(severity) ABSL_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) + +// 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) + +// `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) + +// LOG_EVERY_N +// +// An instance of `LOG_EVERY_N` increments a hidden zero-initialized counter +// every time execution passes through it and logs the specified message when +// the counter's value is a multiple of `n`, doing nothing otherwise. Each +// instance has its own counter. The counter's value can be logged by streaming +// the symbol `COUNTER`. `LOG_EVERY_N` is thread-safe. +// Example: +// +// 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) + +// 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) + +// 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) + +// LOG_EVERY_N_SEC +// +// An instance of `LOG_EVERY_N_SEC` uses a hidden state variable to log the +// specified message at most once every `n_seconds`. A hidden counter of +// executions (whether a message is logged or not) is also maintained and can be +// logged by streaming the symbol `COUNTER`. `LOG_EVERY_N_SEC` is thread-safe. +// Example: +// +// 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) + +#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_SEC(severity, n_seconds) \ + ABSL_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_SEC(severity, n_seconds) \ + ABSL_DLOG_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 +// `LOG_IF`). +// Example: +// +// 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) +#define LOG_IF_FIRST_N(severity, condition, n) \ + ABSL_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) +#define LOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \ + ABSL_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) +#define PLOG_IF_FIRST_N(severity, condition, n) \ + ABSL_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) +#define PLOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \ + ABSL_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) +#define DLOG_IF_FIRST_N(severity, condition, n) \ + ABSL_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) +#define DLOG_IF_EVERY_N_SEC(severity, condition, n_seconds) \ + ABSL_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 new file mode 100644 index 00000000..b8d87c94 --- /dev/null +++ b/absl/log/log_basic_test.cc @@ -0,0 +1,21 @@ +// +// 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/log.h" + +#define ABSL_TEST_LOG LOG + +#include "gtest/gtest.h" +#include "absl/log/log_basic_test_impl.h" diff --git a/absl/log/log_basic_test_impl.h b/absl/log/log_basic_test_impl.h new file mode 100644 index 00000000..35c0b690 --- /dev/null +++ b/absl/log/log_basic_test_impl.h @@ -0,0 +1,455 @@ +// +// 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. + +// The testcases in this file are expected to pass or be skipped with any value +// of ABSL_MIN_LOG_LEVEL + +#ifndef ABSL_LOG_LOG_BASIC_TEST_IMPL_H_ +#define ABSL_LOG_LOG_BASIC_TEST_IMPL_H_ + +// Verify that both sets of macros behave identically by parameterizing the +// entire test file. +#ifndef ABSL_TEST_LOG +#error ABSL_TEST_LOG must be defined for these tests to work. +#endif + +#include <cerrno> +#include <sstream> +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/internal/sysinfo.h" +#include "absl/base/log_severity.h" +#include "absl/log/globals.h" +#include "absl/log/internal/test_actions.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/log/internal/test_matchers.h" +#include "absl/log/log_entry.h" +#include "absl/log/scoped_mock_log.h" + +namespace absl_log_internal { +#if GTEST_HAS_DEATH_TEST +using ::absl::log_internal::DeathTestExpectedLogging; +using ::absl::log_internal::DeathTestUnexpectedLogging; +using ::absl::log_internal::DeathTestValidateExpectations; +using ::absl::log_internal::DiedOfFatal; +using ::absl::log_internal::DiedOfQFatal; +#endif +using ::absl::log_internal::LoggingEnabledAt; +using ::absl::log_internal::LogSeverity; +using ::absl::log_internal::Prefix; +using ::absl::log_internal::SourceBasename; +using ::absl::log_internal::SourceFilename; +using ::absl::log_internal::SourceLine; +using ::absl::log_internal::Stacktrace; +using ::absl::log_internal::TextMessage; +using ::absl::log_internal::ThreadID; +using ::absl::log_internal::TimestampInMatchWindow; +using ::absl::log_internal::Verbosity; +using ::testing::AnyNumber; +using ::testing::Eq; +using ::testing::IsEmpty; +using ::testing::IsTrue; + +class BasicLogTest : public testing::TestWithParam<absl::LogSeverityAtLeast> {}; + +std::string ThresholdName( + testing::TestParamInfo<absl::LogSeverityAtLeast> severity) { + std::stringstream ostr; + ostr << severity.param; + return ostr.str().substr( + severity.param == absl::LogSeverityAtLeast::kInfinity ? 0 : 2); +} + +INSTANTIATE_TEST_SUITE_P(WithParam, BasicLogTest, + testing::Values(absl::LogSeverityAtLeast::kInfo, + absl::LogSeverityAtLeast::kWarning, + absl::LogSeverityAtLeast::kError, + absl::LogSeverityAtLeast::kFatal, + absl::LogSeverityAtLeast::kInfinity), + ThresholdName); + +TEST_P(BasicLogTest, Info) { + 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(INFO) << "hello world"; }; + + if (LoggingEnabledAt(absl::LogSeverity::kInfo)) { + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq(__FILE__)), + SourceBasename(Eq("log_basic_test_impl.h")), + SourceLine(Eq(log_line)), Prefix(IsTrue()), + LogSeverity(Eq(absl::LogSeverity::kInfo)), + 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(); +} + +TEST_P(BasicLogTest, Warning) { + 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(WARNING) << "hello world"; }; + + if (LoggingEnabledAt(absl::LogSeverity::kWarning)) { + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq(__FILE__)), + SourceBasename(Eq("log_basic_test_impl.h")), + SourceLine(Eq(log_line)), Prefix(IsTrue()), + LogSeverity(Eq(absl::LogSeverity::kWarning)), + 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(); +} + +TEST_P(BasicLogTest, Error) { + 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(ERROR) << "hello world"; }; + + if (LoggingEnabledAt(absl::LogSeverity::kError)) { + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq(__FILE__)), + SourceBasename(Eq("log_basic_test_impl.h")), + 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(); +} + +#if GTEST_HAS_DEATH_TEST +using BasicLogDeathTest = BasicLogTest; + +INSTANTIATE_TEST_SUITE_P(WithParam, BasicLogDeathTest, + testing::Values(absl::LogSeverityAtLeast::kInfo, + absl::LogSeverityAtLeast::kFatal, + absl::LogSeverityAtLeast::kInfinity), + ThresholdName); + +TEST_P(BasicLogDeathTest, Fatal) { + absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); + + const int log_line = __LINE__ + 1; + auto do_log = [] { ABSL_TEST_LOG(FATAL) << "hello world"; }; + + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink( + absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + + ::testing::InSequence s; + + // Note the logic in DeathTestValidateExpectations() caters for the case + // of logging being disabled at FATAL level. + + 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.h")), + 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.h")), + 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()); +} + +TEST_P(BasicLogDeathTest, QFatal) { + absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); + + const int log_line = __LINE__ + 1; + auto do_log = [] { ABSL_TEST_LOG(QFATAL) << "hello world"; }; + + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink( + absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + + if (LoggingEnabledAt(absl::LogSeverity::kFatal)) { + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq(__FILE__)), + SourceBasename(Eq("log_basic_test_impl.h")), + 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()); + } + + test_sink.StartCapturingLogs(); + do_log(); + }, + DiedOfQFatal, DeathTestValidateExpectations()); +} +#endif + +TEST_P(BasicLogTest, Level) { + absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); + + for (auto severity : {absl::LogSeverity::kInfo, absl::LogSeverity::kWarning, + absl::LogSeverity::kError}) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int log_line = __LINE__ + 2; + auto do_log = [severity] { + ABSL_TEST_LOG(LEVEL(severity)) << "hello world"; + }; + + if (LoggingEnabledAt(severity)) { + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq(__FILE__)), + SourceBasename(Eq("log_basic_test_impl.h")), + SourceLine(Eq(log_line)), Prefix(IsTrue()), + LogSeverity(Eq(severity)), 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(); + } +} + +#if GTEST_HAS_DEATH_TEST +TEST_P(BasicLogDeathTest, Level) { + // TODO(b/242568884): re-enable once bug is fixed. + // absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); + + // Ensure that `severity` is not a compile-time constant to prove that + // `LOG(LEVEL(severity))` works regardless: + auto volatile severity = absl::LogSeverity::kFatal; + + const int log_line = __LINE__ + 1; + auto do_log = [severity] { ABSL_TEST_LOG(LEVEL(severity)) << "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)) { + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq(__FILE__)), + SourceBasename(Eq("log_basic_test_impl.h")), + 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()); + + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq(__FILE__)), + SourceBasename(Eq("log_basic_test_impl.h")), + 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, LevelClampsNegativeValues) { + absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); + + if (!LoggingEnabledAt(absl::LogSeverity::kInfo)) { + GTEST_SKIP() << "This test cases required INFO log to be enabled"; + return; + } + + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send(LogSeverity(Eq(absl::LogSeverity::kInfo)))); + + test_sink.StartCapturingLogs(); + ABSL_TEST_LOG(LEVEL(-1)) << "hello world"; +} + +TEST_P(BasicLogTest, LevelClampsLargeValues) { + absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); + + if (!LoggingEnabledAt(absl::LogSeverity::kError)) { + GTEST_SKIP() << "This test cases required ERROR log to be enabled"; + return; + } + + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send(LogSeverity(Eq(absl::LogSeverity::kError)))); + + test_sink.StartCapturingLogs(); + ABSL_TEST_LOG(LEVEL(static_cast<int>(absl::LogSeverity::kFatal) + 1)) + << "hello world"; +} + +TEST(ErrnoPreservationTest, InSeverityExpression) { + errno = 77; + int saved_errno; + ABSL_TEST_LOG(LEVEL((saved_errno = errno, absl::LogSeverity::kInfo))); + EXPECT_THAT(saved_errno, Eq(77)); +} + +TEST(ErrnoPreservationTest, InStreamedExpression) { + if (!LoggingEnabledAt(absl::LogSeverity::kInfo)) { + GTEST_SKIP() << "This test cases required INFO log to be enabled"; + return; + } + + errno = 77; + int saved_errno = 0; + ABSL_TEST_LOG(INFO) << (saved_errno = errno, "hello world"); + EXPECT_THAT(saved_errno, Eq(77)); +} + +TEST(ErrnoPreservationTest, AfterStatement) { + errno = 77; + ABSL_TEST_LOG(INFO); + const int saved_errno = errno; + EXPECT_THAT(saved_errno, Eq(77)); +} + +// Tests that using a variable/parameter in a logging statement suppresses +// unused-variable/parameter warnings. +// ----------------------------------------------------------------------- +class UnusedVariableWarningCompileTest { + // These four don't prove anything unless `ABSL_MIN_LOG_LEVEL` is greater than + // `kInfo`. + static void LoggedVariable() { + const int x = 0; + ABSL_TEST_LOG(INFO) << x; + } + static void LoggedParameter(const int x) { ABSL_TEST_LOG(INFO) << x; } + static void SeverityVariable() { + const int x = 0; + ABSL_TEST_LOG(LEVEL(x)) << "hello world"; + } + static void SeverityParameter(const int x) { + ABSL_TEST_LOG(LEVEL(x)) << "hello world"; + } +}; + +} // namespace absl_log_internal + +#endif // ABSL_LOG_LOG_BASIC_TEST_IMPL_H_ diff --git a/absl/log/log_benchmark.cc b/absl/log/log_benchmark.cc new file mode 100644 index 00000000..45d9a5d6 --- /dev/null +++ b/absl/log/log_benchmark.cc @@ -0,0 +1,97 @@ +// 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/attributes.h" +#include "absl/base/log_severity.h" +#include "absl/flags/flag.h" +#include "absl/log/check.h" +#include "absl/log/globals.h" +#include "absl/log/log.h" +#include "absl/log/log_entry.h" +#include "absl/log/log_sink.h" +#include "absl/log/log_sink_registry.h" +#include "benchmark/benchmark.h" + +namespace { + +class NullLogSink : public absl::LogSink { + public: + NullLogSink() { absl::AddLogSink(this); } + + ~NullLogSink() override { absl::RemoveLogSink(this); } + + void Send(const absl::LogEntry&) override {} +}; + +constexpr int x = -1; + +void BM_SuccessfulBinaryCheck(benchmark::State& state) { + int n = 0; + while (state.KeepRunningBatch(8)) { + CHECK_GE(n, x); + CHECK_GE(n, x); + CHECK_GE(n, x); + CHECK_GE(n, x); + CHECK_GE(n, x); + CHECK_GE(n, x); + CHECK_GE(n, x); + CHECK_GE(n, x); + ++n; + } + benchmark::DoNotOptimize(n); +} +BENCHMARK(BM_SuccessfulBinaryCheck); + +static void BM_SuccessfulUnaryCheck(benchmark::State& state) { + int n = 0; + while (state.KeepRunningBatch(8)) { + CHECK(n >= x); + CHECK(n >= x); + CHECK(n >= x); + CHECK(n >= x); + CHECK(n >= x); + CHECK(n >= x); + CHECK(n >= x); + CHECK(n >= x); + ++n; + } + benchmark::DoNotOptimize(n); +} +BENCHMARK(BM_SuccessfulUnaryCheck); + +static void BM_DisabledLogOverhead(benchmark::State& state) { + absl::ScopedStderrThreshold disable_stderr_logging( + absl::LogSeverityAtLeast::kInfinity); + absl::log_internal::ScopedMinLogLevel scoped_min_log_level( + absl::LogSeverityAtLeast::kInfinity); + for (auto _ : state) { + LOG(INFO); + } +} +BENCHMARK(BM_DisabledLogOverhead); + +static void BM_EnabledLogOverhead(benchmark::State& state) { + absl::ScopedStderrThreshold stderr_logging( + absl::LogSeverityAtLeast::kInfinity); + absl::log_internal::ScopedMinLogLevel scoped_min_log_level( + absl::LogSeverityAtLeast::kInfo); + ABSL_ATTRIBUTE_UNUSED NullLogSink null_sink; + for (auto _ : state) { + LOG(INFO); + } +} +BENCHMARK(BM_EnabledLogOverhead); + +} // namespace + diff --git a/absl/log/log_entry.cc b/absl/log/log_entry.cc new file mode 100644 index 00000000..19c3b3f1 --- /dev/null +++ b/absl/log/log_entry.cc @@ -0,0 +1,29 @@ +// +// 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/log_entry.h" + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +#ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL +constexpr int LogEntry::kNoVerbosityLevel; +constexpr int LogEntry::kNoVerboseLevel; +#endif + +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/log_entry.h b/absl/log/log_entry.h new file mode 100644 index 00000000..9e4ae8eb --- /dev/null +++ b/absl/log/log_entry.h @@ -0,0 +1,220 @@ +// 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/log_entry.h +// ----------------------------------------------------------------------------- +// +// This header declares `class absl::LogEntry`, which represents a log record as +// passed to `LogSink::Send`. Data returned by pointer or by reference or by +// `absl::string_view` must be copied if they are needed after the lifetime of +// the `absl::LogEntry`. + +#ifndef ABSL_LOG_LOG_ENTRY_H_ +#define ABSL_LOG_LOG_ENTRY_H_ + +#include <cstddef> +#include <string> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/log/internal/config.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" +#include "absl/types/span.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +namespace log_internal { +// Test only friend. +class LogEntryTestPeer; +class LogMessage; +} // namespace log_internal + +// LogEntry +// +// Represents a single entry in a log, i.e., one `LOG` statement or failed +// `CHECK`. +// +// `LogEntry` is thread-compatible. +class LogEntry final { + public: + using tid_t = log_internal::Tid; + + // For non-verbose log entries, `verbosity()` returns `kNoVerbosityLevel`. + static constexpr int kNoVerbosityLevel = -1; + static constexpr int kNoVerboseLevel = -1; // TO BE removed + + // Pass `LogEntry` by reference, and do not store it as its state does not + // outlive the call to `LogSink::Send()`. + LogEntry(const LogEntry&) = delete; + LogEntry& operator=(const LogEntry&) = delete; + + // Source file and line where the log message occurred. Taken from `__FILE__` + // and `__LINE__` unless overridden by `LOG(...).AtLocation(...)`. + // + // Take special care not to use the values returned by `source_filename()` and + // `source_basename()` after the lifetime of the entry. This is always + // incorrect, but it will often work in practice because they usually point + // into a statically allocated character array obtained from `__FILE__`. + // Statements like `LOG(INFO).AtLocation(std::string(...), ...)` will expose + // the bug. If you need the data later, you must copy them. + absl::string_view source_filename() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return full_filename_; + } + absl::string_view source_basename() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return base_filename_; + } + int source_line() const { return line_; } + + // LogEntry::prefix() + // + // True unless the metadata prefix was suppressed once by + // `LOG(...).NoPrefix()` or globally by `absl::EnableLogPrefix(false)`. + // Implies `text_message_with_prefix() == text_message()`. + bool prefix() const { return prefix_; } + + // LogEntry::log_severity() + // + // Returns this entry's severity. For `LOG`, taken from the first argument; + // for `CHECK`, always `absl::LogSeverity::kFatal`. + absl::LogSeverity log_severity() const { return severity_; } + + // LogEntry::verbosity() + // + // Returns this entry's verbosity, or `kNoVerbosityLevel` for a non-verbose + // entry. Verbosity control is not available outside of Google yet. + int verbosity() const { return verbose_level_; } + + // LogEntry::timestamp() + // + // Returns the time at which this entry was written. Captured during + // evaluation of `LOG`, but can be overridden by + // `LOG(...).WithTimestamp(...)`. + // + // Take care not to rely on timestamps increasing monotonically, or even to + // rely on timestamps having any particular relationship with reality (since + // they can be overridden). + absl::Time timestamp() const { return timestamp_; } + + // LogEntry::tid() + // + // Returns the ID of the thread that wrote this entry. Captured during + // evaluation of `LOG`, but can be overridden by `LOG(...).WithThreadID(...)`. + // + // Take care not to *rely* on reported thread IDs as they can be overridden as + // specified above. + tid_t tid() const { return tid_; } + + // Text-formatted version of the log message. An underlying buffer holds + // these contiguous data: + // + // * A prefix formed by formatting metadata (timestamp, filename, line number, + // etc.) + // The prefix may be empty - see `LogEntry::prefix()` - and may rarely be + // truncated if the metadata are very long. + // * The streamed data + // The data may be empty if nothing was streamed, or may be truncated to fit + // the buffer. + // * A newline + // * A nul terminator + // + // The newline and nul terminator will be present even if the prefix and/or + // data are truncated. + // + // These methods give access to the most commonly useful substrings of the + // buffer's contents. Other combinations can be obtained with substring + // arithmetic. + // + // The buffer does not outlive the entry; if you need the data later, you must + // copy them. + absl::string_view text_message_with_prefix_and_newline() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return absl::string_view( + text_message_with_prefix_and_newline_and_nul_.data(), + text_message_with_prefix_and_newline_and_nul_.size() - 1); + } + absl::string_view text_message_with_prefix() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return absl::string_view( + text_message_with_prefix_and_newline_and_nul_.data(), + text_message_with_prefix_and_newline_and_nul_.size() - 2); + } + absl::string_view text_message_with_newline() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return absl::string_view( + text_message_with_prefix_and_newline_and_nul_.data() + prefix_len_, + text_message_with_prefix_and_newline_and_nul_.size() - prefix_len_ - 1); + } + absl::string_view text_message() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return absl::string_view( + text_message_with_prefix_and_newline_and_nul_.data() + prefix_len_, + text_message_with_prefix_and_newline_and_nul_.size() - prefix_len_ - 2); + } + const char* text_message_with_prefix_and_newline_c_str() const + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return text_message_with_prefix_and_newline_and_nul_.data(); + } + + // Returns a serialized protobuf holding the operands streamed into this + // log message. The message definition is not yet published. + // + // The buffer does not outlive the entry; if you need the data later, you must + // copy them. + absl::string_view encoded_message() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return encoding_; + } + + // LogEntry::stacktrace() + // + // Optional stacktrace, e.g. for `FATAL` logs and failed `CHECK`s. + // + // Fatal entries are dispatched to each sink twice: first with all data and + // metadata but no stacktrace, and then with the stacktrace. This is done + // because stacktrace collection is sometimes slow and fallible, and it's + // critical to log enough information to diagnose the failure even if the + // stacktrace collection hangs. + // + // The buffer does not outlive the entry; if you need the data later, you must + // copy them. + absl::string_view stacktrace() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return stacktrace_; + } + + private: + LogEntry() = default; + + absl::string_view full_filename_; + absl::string_view base_filename_; + int line_; + bool prefix_; + absl::LogSeverity severity_; + int verbose_level_; // >=0 for `VLOG`, etc.; otherwise `kNoVerbosityLevel`. + absl::Time timestamp_; + tid_t tid_; + absl::Span<const char> text_message_with_prefix_and_newline_and_nul_; + size_t prefix_len_; + absl::string_view encoding_; + std::string stacktrace_; + + friend class log_internal::LogEntryTestPeer; + friend class log_internal::LogMessage; +}; + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_LOG_ENTRY_H_ diff --git a/absl/log/log_entry_test.cc b/absl/log/log_entry_test.cc new file mode 100644 index 00000000..d9bfa1f4 --- /dev/null +++ b/absl/log/log_entry_test.cc @@ -0,0 +1,468 @@ +// +// 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/log_entry.h" + +#include <stddef.h> +#include <stdint.h> + +#include <cstring> +#include <limits> +#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/log_severity.h" +#include "absl/log/internal/append_truncated.h" +#include "absl/log/internal/log_format.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_split.h" +#include "absl/strings/string_view.h" +#include "absl/time/civil_time.h" +#include "absl/time/time.h" +#include "absl/types/span.h" + +namespace { +using ::absl::log_internal::LogEntryTestPeer; +using ::testing::Eq; +using ::testing::IsTrue; +using ::testing::StartsWith; +using ::testing::StrEq; + +auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( + new absl::log_internal::LogTestEnvironment); +} // namespace + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +class LogEntryTestPeer { + public: + LogEntryTestPeer(absl::string_view base_filename, int line, bool prefix, + absl::LogSeverity severity, absl::string_view timestamp, + absl::LogEntry::tid_t tid, PrefixFormat format, + absl::string_view text_message) + : format_{format}, buf_(15000, '\0') { + entry_.base_filename_ = base_filename; + entry_.line_ = line; + entry_.prefix_ = prefix; + entry_.severity_ = severity; + std::string time_err; + EXPECT_THAT( + absl::ParseTime("%Y-%m-%d%ET%H:%M:%E*S", timestamp, + absl::LocalTimeZone(), &entry_.timestamp_, &time_err), + IsTrue()) + << "Failed to parse time " << timestamp << ": " << time_err; + entry_.tid_ = tid; + std::pair<absl::string_view, std::string> timestamp_bits = + absl::StrSplit(timestamp, absl::ByChar('.')); + EXPECT_THAT(absl::ParseCivilTime(timestamp_bits.first, &ci_.cs), IsTrue()) + << "Failed to parse time " << timestamp_bits.first; + timestamp_bits.second.resize(9, '0'); + int64_t nanos = 0; + EXPECT_THAT(absl::SimpleAtoi(timestamp_bits.second, &nanos), IsTrue()) + << "Failed to parse time " << timestamp_bits.first; + ci_.subsecond = absl::Nanoseconds(nanos); + + absl::Span<char> view = absl::MakeSpan(buf_); + view.remove_suffix(2); + entry_.prefix_len_ = + entry_.prefix_ + ? log_internal::FormatLogPrefix( + entry_.log_severity(), entry_.timestamp(), entry_.tid(), + entry_.source_basename(), entry_.source_line(), format_, view) + : 0; + + EXPECT_THAT(entry_.prefix_len_, + Eq(static_cast<size_t>(view.data() - buf_.data()))); + log_internal::AppendTruncated(text_message, view); + view = absl::Span<char>(view.data(), view.size() + 2); + view[0] = '\n'; + view[1] = '\0'; + view.remove_prefix(2); + buf_.resize(static_cast<size_t>(view.data() - buf_.data())); + entry_.text_message_with_prefix_and_newline_and_nul_ = absl::MakeSpan(buf_); + } + LogEntryTestPeer(const LogEntryTestPeer&) = delete; + LogEntryTestPeer& operator=(const LogEntryTestPeer&) = delete; + + std::string FormatLogMessage() const { + return log_internal::FormatLogMessage( + entry_.log_severity(), ci_.cs, ci_.subsecond, entry_.tid(), + entry_.source_basename(), entry_.source_line(), format_, + entry_.text_message()); + } + std::string FormatPrefixIntoSizedBuffer(size_t sz) { + std::string str(sz, '\0'); + absl::Span<char> buf(&str[0], str.size()); + const size_t prefix_size = log_internal::FormatLogPrefix( + entry_.log_severity(), entry_.timestamp(), entry_.tid(), + entry_.source_basename(), entry_.source_line(), format_, buf); + EXPECT_THAT(prefix_size, Eq(static_cast<size_t>(buf.data() - str.data()))); + str.resize(prefix_size); + return str; + } + const absl::LogEntry& entry() const { return entry_; } + + private: + absl::LogEntry entry_; + PrefixFormat format_; + absl::TimeZone::CivilInfo ci_; + std::vector<char> buf_; +}; + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +namespace { +constexpr bool kUsePrefix = true, kNoPrefix = false; + +TEST(LogEntryTest, Baseline) { + LogEntryTestPeer entry("foo.cc", 1234, kUsePrefix, absl::LogSeverity::kInfo, + "2020-01-02T03:04:05.6789", 451, + absl::log_internal::PrefixFormat::kNotRaw, + "hello world"); + EXPECT_THAT(entry.FormatLogMessage(), + Eq("I0102 03:04:05.678900 451 foo.cc:1234] hello world")); + EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), + Eq("I0102 03:04:05.678900 451 foo.cc:1234] ")); + for (size_t sz = strlen("I0102 03:04:05.678900 451 foo.cc:1234] ") + 20; + sz != std::numeric_limits<size_t>::max(); sz--) + EXPECT_THAT("I0102 03:04:05.678900 451 foo.cc:1234] ", + StartsWith(entry.FormatPrefixIntoSizedBuffer(sz))); + + EXPECT_THAT(entry.entry().text_message_with_prefix_and_newline(), + Eq("I0102 03:04:05.678900 451 foo.cc:1234] hello world\n")); + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline_c_str(), + StrEq("I0102 03:04:05.678900 451 foo.cc:1234] hello world\n")); + EXPECT_THAT(entry.entry().text_message_with_prefix(), + Eq("I0102 03:04:05.678900 451 foo.cc:1234] hello world")); + EXPECT_THAT(entry.entry().text_message(), Eq("hello world")); +} + +TEST(LogEntryTest, NoPrefix) { + LogEntryTestPeer entry("foo.cc", 1234, kNoPrefix, absl::LogSeverity::kInfo, + "2020-01-02T03:04:05.6789", 451, + absl::log_internal::PrefixFormat::kNotRaw, + "hello world"); + EXPECT_THAT(entry.FormatLogMessage(), + Eq("I0102 03:04:05.678900 451 foo.cc:1234] hello world")); + // These methods are not responsible for honoring `prefix()`. + EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), + Eq("I0102 03:04:05.678900 451 foo.cc:1234] ")); + for (size_t sz = strlen("I0102 03:04:05.678900 451 foo.cc:1234] ") + 20; + sz != std::numeric_limits<size_t>::max(); sz--) + EXPECT_THAT("I0102 03:04:05.678900 451 foo.cc:1234] ", + StartsWith(entry.FormatPrefixIntoSizedBuffer(sz))); + + EXPECT_THAT(entry.entry().text_message_with_prefix_and_newline(), + Eq("hello world\n")); + EXPECT_THAT(entry.entry().text_message_with_prefix_and_newline_c_str(), + StrEq("hello world\n")); + EXPECT_THAT(entry.entry().text_message_with_prefix(), Eq("hello world")); + EXPECT_THAT(entry.entry().text_message(), Eq("hello world")); +} + +TEST(LogEntryTest, EmptyFields) { + LogEntryTestPeer entry("", 0, kUsePrefix, absl::LogSeverity::kInfo, + "2020-01-02T03:04:05", 0, + absl::log_internal::PrefixFormat::kNotRaw, ""); + const std::string format_message = entry.FormatLogMessage(); + EXPECT_THAT(format_message, Eq("I0102 03:04:05.000000 0 :0] ")); + EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), Eq(format_message)); + for (size_t sz = format_message.size() + 20; + sz != std::numeric_limits<size_t>::max(); sz--) + EXPECT_THAT(format_message, + StartsWith(entry.FormatPrefixIntoSizedBuffer(sz))); + + EXPECT_THAT(entry.entry().text_message_with_prefix_and_newline(), + Eq("I0102 03:04:05.000000 0 :0] \n")); + EXPECT_THAT(entry.entry().text_message_with_prefix_and_newline_c_str(), + StrEq("I0102 03:04:05.000000 0 :0] \n")); + EXPECT_THAT(entry.entry().text_message_with_prefix(), + Eq("I0102 03:04:05.000000 0 :0] ")); + EXPECT_THAT(entry.entry().text_message(), Eq("")); +} + +TEST(LogEntryTest, NegativeFields) { + // When Abseil's minimum C++ version is C++17, this conditional can be + // converted to a constexpr if and the static_cast below removed. + if (std::is_signed<absl::LogEntry::tid_t>::value) { + LogEntryTestPeer entry( + "foo.cc", -1234, kUsePrefix, absl::LogSeverity::kInfo, + "2020-01-02T03:04:05.6789", static_cast<absl::LogEntry::tid_t>(-451), + absl::log_internal::PrefixFormat::kNotRaw, "hello world"); + EXPECT_THAT(entry.FormatLogMessage(), + Eq("I0102 03:04:05.678900 -451 foo.cc:-1234] hello world")); + EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), + Eq("I0102 03:04:05.678900 -451 foo.cc:-1234] ")); + for (size_t sz = + strlen("I0102 03:04:05.678900 -451 foo.cc:-1234] ") + 20; + sz != std::numeric_limits<size_t>::max(); sz--) + EXPECT_THAT("I0102 03:04:05.678900 -451 foo.cc:-1234] ", + StartsWith(entry.FormatPrefixIntoSizedBuffer(sz))); + + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline(), + Eq("I0102 03:04:05.678900 -451 foo.cc:-1234] hello world\n")); + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline_c_str(), + StrEq("I0102 03:04:05.678900 -451 foo.cc:-1234] hello world\n")); + EXPECT_THAT(entry.entry().text_message_with_prefix(), + Eq("I0102 03:04:05.678900 -451 foo.cc:-1234] hello world")); + EXPECT_THAT(entry.entry().text_message(), Eq("hello world")); + } else { + LogEntryTestPeer entry("foo.cc", -1234, kUsePrefix, + absl::LogSeverity::kInfo, "2020-01-02T03:04:05.6789", + 451, absl::log_internal::PrefixFormat::kNotRaw, + "hello world"); + EXPECT_THAT(entry.FormatLogMessage(), + Eq("I0102 03:04:05.678900 451 foo.cc:-1234] hello world")); + EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), + Eq("I0102 03:04:05.678900 451 foo.cc:-1234] ")); + for (size_t sz = + strlen("I0102 03:04:05.678900 451 foo.cc:-1234] ") + 20; + sz != std::numeric_limits<size_t>::max(); sz--) + EXPECT_THAT("I0102 03:04:05.678900 451 foo.cc:-1234] ", + StartsWith(entry.FormatPrefixIntoSizedBuffer(sz))); + + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline(), + Eq("I0102 03:04:05.678900 451 foo.cc:-1234] hello world\n")); + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline_c_str(), + StrEq("I0102 03:04:05.678900 451 foo.cc:-1234] hello world\n")); + EXPECT_THAT(entry.entry().text_message_with_prefix(), + Eq("I0102 03:04:05.678900 451 foo.cc:-1234] hello world")); + EXPECT_THAT(entry.entry().text_message(), Eq("hello world")); + } +} + +TEST(LogEntryTest, LongFields) { + LogEntryTestPeer entry( + "I am the very model of a modern Major-General / " + "I've information vegetable, animal, and mineral.", + 2147483647, kUsePrefix, absl::LogSeverity::kInfo, + "2020-01-02T03:04:05.678967896789", 2147483647, + absl::log_internal::PrefixFormat::kNotRaw, + "I know the kings of England, and I quote the fights historical / " + "From Marathon to Waterloo, in order categorical."); + EXPECT_THAT(entry.FormatLogMessage(), + Eq("I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.")); + EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), + Eq("I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:2147483647] ")); + for (size_t sz = + strlen("I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:2147483647] ") + + 20; + sz != std::numeric_limits<size_t>::max(); sz--) + EXPECT_THAT( + "I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:2147483647] ", + StartsWith(entry.FormatPrefixIntoSizedBuffer(sz))); + + EXPECT_THAT(entry.entry().text_message_with_prefix_and_newline(), + Eq("I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.\n")); + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline_c_str(), + StrEq("I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.\n")); + EXPECT_THAT(entry.entry().text_message_with_prefix(), + Eq("I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.")); + EXPECT_THAT( + entry.entry().text_message(), + Eq("I know the kings of England, and I quote the fights historical / " + "From Marathon to Waterloo, in order categorical.")); +} + +TEST(LogEntryTest, LongNegativeFields) { + // When Abseil's minimum C++ version is C++17, this conditional can be + // converted to a constexpr if and the static_cast below removed. + if (std::is_signed<absl::LogEntry::tid_t>::value) { + LogEntryTestPeer entry( + "I am the very model of a modern Major-General / " + "I've information vegetable, animal, and mineral.", + -2147483647, kUsePrefix, absl::LogSeverity::kInfo, + "2020-01-02T03:04:05.678967896789", + static_cast<absl::LogEntry::tid_t>(-2147483647), + absl::log_internal::PrefixFormat::kNotRaw, + "I know the kings of England, and I quote the fights historical / " + "From Marathon to Waterloo, in order categorical."); + EXPECT_THAT( + entry.FormatLogMessage(), + Eq("I0102 03:04:05.678967 -2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.")); + EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), + Eq("I0102 03:04:05.678967 -2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] ")); + for (size_t sz = + strlen( + "I0102 03:04:05.678967 -2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] ") + + 20; + sz != std::numeric_limits<size_t>::max(); sz--) + EXPECT_THAT( + "I0102 03:04:05.678967 -2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] ", + StartsWith(entry.FormatPrefixIntoSizedBuffer(sz))); + + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline(), + Eq("I0102 03:04:05.678967 -2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.\n")); + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline_c_str(), + StrEq("I0102 03:04:05.678967 -2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.\n")); + EXPECT_THAT( + entry.entry().text_message_with_prefix(), + Eq("I0102 03:04:05.678967 -2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.")); + EXPECT_THAT( + entry.entry().text_message(), + Eq("I know the kings of England, and I quote the fights historical / " + "From Marathon to Waterloo, in order categorical.")); + } else { + LogEntryTestPeer entry( + "I am the very model of a modern Major-General / " + "I've information vegetable, animal, and mineral.", + -2147483647, kUsePrefix, absl::LogSeverity::kInfo, + "2020-01-02T03:04:05.678967896789", 2147483647, + absl::log_internal::PrefixFormat::kNotRaw, + "I know the kings of England, and I quote the fights historical / " + "From Marathon to Waterloo, in order categorical."); + EXPECT_THAT( + entry.FormatLogMessage(), + Eq("I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.")); + EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), + Eq("I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] ")); + for (size_t sz = + strlen( + "I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] ") + + 20; + sz != std::numeric_limits<size_t>::max(); sz--) + EXPECT_THAT( + "I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] ", + StartsWith(entry.FormatPrefixIntoSizedBuffer(sz))); + + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline(), + Eq("I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.\n")); + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline_c_str(), + StrEq("I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.\n")); + EXPECT_THAT( + entry.entry().text_message_with_prefix(), + Eq("I0102 03:04:05.678967 2147483647 I am the very model of a " + "modern Major-General / I've information vegetable, animal, " + "and mineral.:-2147483647] I know the kings of England, and I " + "quote the fights historical / From Marathon to Waterloo, in " + "order categorical.")); + EXPECT_THAT( + entry.entry().text_message(), + Eq("I know the kings of England, and I quote the fights historical / " + "From Marathon to Waterloo, in order categorical.")); + } +} + +TEST(LogEntryTest, Raw) { + LogEntryTestPeer entry("foo.cc", 1234, kUsePrefix, absl::LogSeverity::kInfo, + "2020-01-02T03:04:05.6789", 451, + absl::log_internal::PrefixFormat::kRaw, "hello world"); + EXPECT_THAT( + entry.FormatLogMessage(), + Eq("I0102 03:04:05.678900 451 foo.cc:1234] RAW: hello world")); + EXPECT_THAT(entry.FormatPrefixIntoSizedBuffer(1000), + Eq("I0102 03:04:05.678900 451 foo.cc:1234] RAW: ")); + for (size_t sz = + strlen("I0102 03:04:05.678900 451 foo.cc:1234] RAW: ") + 20; + sz != std::numeric_limits<size_t>::max(); sz--) + EXPECT_THAT("I0102 03:04:05.678900 451 foo.cc:1234] RAW: ", + StartsWith(entry.FormatPrefixIntoSizedBuffer(sz))); + + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline(), + Eq("I0102 03:04:05.678900 451 foo.cc:1234] RAW: hello world\n")); + EXPECT_THAT( + entry.entry().text_message_with_prefix_and_newline_c_str(), + StrEq("I0102 03:04:05.678900 451 foo.cc:1234] RAW: hello world\n")); + EXPECT_THAT( + entry.entry().text_message_with_prefix(), + Eq("I0102 03:04:05.678900 451 foo.cc:1234] RAW: hello world")); + EXPECT_THAT(entry.entry().text_message(), Eq("hello world")); +} + +} // namespace diff --git a/absl/log/log_format_test.cc b/absl/log/log_format_test.cc new file mode 100644 index 00000000..dbad5d97 --- /dev/null +++ b/absl/log/log_format_test.cc @@ -0,0 +1,1872 @@ +// +// 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 <math.h> + +#include <iomanip> +#include <ios> +#include <limits> +#include <ostream> +#include <sstream> +#include <string> +#include <type_traits> + +#ifdef __ANDROID__ +#include <android/api-level.h> +#endif +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/log/check.h" +#include "absl/log/internal/test_matchers.h" +#include "absl/log/log.h" +#include "absl/log/scoped_mock_log.h" +#include "absl/strings/match.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" + +namespace { +using ::absl::log_internal::AsString; +using ::absl::log_internal::MatchesOstream; +using ::absl::log_internal::RawEncodedMessage; +using ::absl::log_internal::TextMessage; +using ::absl::log_internal::TextPrefix; +using ::testing::AllOf; +using ::testing::AnyOf; +using ::testing::Each; +using ::testing::EndsWith; +using ::testing::Eq; +using ::testing::Ge; +using ::testing::IsEmpty; +using ::testing::Le; +using ::testing::SizeIs; +using ::testing::Types; + +// Some aspects of formatting streamed data (e.g. pointer handling) are +// implementation-defined. Others are buggy in supported implementations. +// These tests validate that the formatting matches that performed by a +// `std::ostream` and also that the result is one of a list of expected formats. + +std::ostringstream ComparisonStream() { + std::ostringstream str; + str.setf(std::ios_base::showbase | std::ios_base::boolalpha | + std::ios_base::internal); + return str; +} + +TEST(LogFormatTest, NoMessage) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int log_line = __LINE__ + 1; + auto do_log = [] { LOG(INFO); }; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(ComparisonStream())), + TextPrefix(AsString(EndsWith(absl::StrCat( + " log_format_test.cc:", log_line, "] ")))), + TextMessage(IsEmpty()), + ENCODED_MESSAGE(EqualsProto(R"pb()pb"))))); + + test_sink.StartCapturingLogs(); + do_log(); +} + +template <typename T> +class CharLogFormatTest : public testing::Test {}; +using CharTypes = Types<char, signed char, unsigned char>; +TYPED_TEST_SUITE(CharLogFormatTest, CharTypes); + +TYPED_TEST(CharLogFormatTest, Printable) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = 'x'; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("x")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "x" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(CharLogFormatTest, Unprintable) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + constexpr auto value = static_cast<TypeParam>(0xeeu); + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("\xee")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "\xee" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +template <typename T> +class UnsignedIntLogFormatTest : public testing::Test {}; +using UnsignedIntTypes = Types<unsigned short, unsigned int, // NOLINT + unsigned long, unsigned long long>; // NOLINT +TYPED_TEST_SUITE(UnsignedIntLogFormatTest, UnsignedIntTypes); + +TYPED_TEST(UnsignedIntLogFormatTest, Positive) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = 224; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("224")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "224" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(UnsignedIntLogFormatTest, BitfieldPositive) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const struct { + TypeParam bits : 6; + } value{42}; + auto comparison_stream = ComparisonStream(); + comparison_stream << value.bits; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("42")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "42" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value.bits; +} + +template <typename T> +class SignedIntLogFormatTest : public testing::Test {}; +using SignedIntTypes = + Types<signed short, signed int, signed long, signed long long>; // NOLINT +TYPED_TEST_SUITE(SignedIntLogFormatTest, SignedIntTypes); + +TYPED_TEST(SignedIntLogFormatTest, Positive) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = 224; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("224")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "224" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(SignedIntLogFormatTest, Negative) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = -112; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("-112")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "-112" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(SignedIntLogFormatTest, BitfieldPositive) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const struct { + TypeParam bits : 6; + } value{21}; + auto comparison_stream = ComparisonStream(); + comparison_stream << value.bits; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("21")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "21" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value.bits; +} + +TYPED_TEST(SignedIntLogFormatTest, BitfieldNegative) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const struct { + TypeParam bits : 6; + } value{-21}; + auto comparison_stream = ComparisonStream(); + comparison_stream << value.bits; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("-21")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "-21" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value.bits; +} + +// Ignore these test cases on GCC due to "is too small to hold all values ..." +// warning. +#if !defined(__GNUC__) || defined(__clang__) +// The implementation may choose a signed or unsigned integer type to represent +// this enum, so it may be tested by either `UnsignedEnumLogFormatTest` or +// `SignedEnumLogFormatTest`. +enum MyUnsignedEnum { + MyUnsignedEnum_ZERO = 0, + MyUnsignedEnum_FORTY_TWO = 42, + MyUnsignedEnum_TWO_HUNDRED_TWENTY_FOUR = 224, +}; +enum MyUnsignedIntEnum : unsigned int { + MyUnsignedIntEnum_ZERO = 0, + MyUnsignedIntEnum_FORTY_TWO = 42, + MyUnsignedIntEnum_TWO_HUNDRED_TWENTY_FOUR = 224, +}; + +template <typename T> +class UnsignedEnumLogFormatTest : public testing::Test {}; +using UnsignedEnumTypes = std::conditional< + std::is_signed<std::underlying_type<MyUnsignedEnum>::type>::value, + Types<MyUnsignedIntEnum>, Types<MyUnsignedEnum, MyUnsignedIntEnum>>::type; +TYPED_TEST_SUITE(UnsignedEnumLogFormatTest, UnsignedEnumTypes); + +TYPED_TEST(UnsignedEnumLogFormatTest, Positive) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = static_cast<TypeParam>(224); + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("224")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "224" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(UnsignedEnumLogFormatTest, BitfieldPositive) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const struct { + TypeParam bits : 6; + } value{static_cast<TypeParam>(42)}; + auto comparison_stream = ComparisonStream(); + comparison_stream << value.bits; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("42")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "42" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value.bits; +} + +enum MySignedEnum { + MySignedEnum_NEGATIVE_ONE_HUNDRED_TWELVE = -112, + MySignedEnum_NEGATIVE_TWENTY_ONE = -21, + MySignedEnum_ZERO = 0, + MySignedEnum_TWENTY_ONE = 21, + MySignedEnum_TWO_HUNDRED_TWENTY_FOUR = 224, +}; +enum MySignedIntEnum : signed int { + MySignedIntEnum_NEGATIVE_ONE_HUNDRED_TWELVE = -112, + MySignedIntEnum_NEGATIVE_TWENTY_ONE = -21, + MySignedIntEnum_ZERO = 0, + MySignedIntEnum_TWENTY_ONE = 21, + MySignedIntEnum_TWO_HUNDRED_TWENTY_FOUR = 224, +}; + +template <typename T> +class SignedEnumLogFormatTest : public testing::Test {}; +using SignedEnumTypes = std::conditional< + std::is_signed<std::underlying_type<MyUnsignedEnum>::type>::value, + Types<MyUnsignedEnum, MySignedEnum, MySignedIntEnum>, + Types<MySignedEnum, MySignedIntEnum>>::type; +TYPED_TEST_SUITE(SignedEnumLogFormatTest, SignedEnumTypes); + +TYPED_TEST(SignedEnumLogFormatTest, Positive) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = static_cast<TypeParam>(224); + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("224")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "224" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(SignedEnumLogFormatTest, Negative) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = static_cast<TypeParam>(-112); + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("-112")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "-112" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(SignedEnumLogFormatTest, BitfieldPositive) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const struct { + TypeParam bits : 6; + } value{static_cast<TypeParam>(21)}; + auto comparison_stream = ComparisonStream(); + comparison_stream << value.bits; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("21")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "21" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value.bits; +} + +TYPED_TEST(SignedEnumLogFormatTest, BitfieldNegative) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const struct { + TypeParam bits : 6; + } value{static_cast<TypeParam>(-21)}; + auto comparison_stream = ComparisonStream(); + comparison_stream << value.bits; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("-21")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "-21" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value.bits; +} +#endif + +TEST(FloatLogFormatTest, Positive) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const float value = 6.02e23f; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("6.02e+23")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "6.02e+23" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TEST(FloatLogFormatTest, Negative) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const float value = -6.02e23f; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("-6.02e+23")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "-6.02e+23" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TEST(FloatLogFormatTest, NegativeExponent) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const float value = 6.02e-23f; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("6.02e-23")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "6.02e-23" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TEST(DoubleLogFormatTest, Positive) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = 6.02e23; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("6.02e+23")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "6.02e+23" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TEST(DoubleLogFormatTest, Negative) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = -6.02e23; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("-6.02e+23")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "-6.02e+23" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TEST(DoubleLogFormatTest, NegativeExponent) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = 6.02e-23; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("6.02e-23")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "6.02e-23" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +template <typename T> +class FloatingPointLogFormatTest : public testing::Test {}; +using FloatingPointTypes = Types<float, double>; +TYPED_TEST_SUITE(FloatingPointLogFormatTest, FloatingPointTypes); + +TYPED_TEST(FloatingPointLogFormatTest, Zero) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = 0.0; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("0")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "0" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(FloatingPointLogFormatTest, Integer) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = 1.0; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("1")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "1" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(FloatingPointLogFormatTest, Infinity) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = std::numeric_limits<TypeParam>::infinity(); + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(AnyOf(Eq("inf"), Eq("Inf"))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "inf" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(FloatingPointLogFormatTest, NegativeInfinity) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = -std::numeric_limits<TypeParam>::infinity(); + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(AnyOf(Eq("-inf"), Eq("-Inf"))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "-inf" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(FloatingPointLogFormatTest, NaN) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = std::numeric_limits<TypeParam>::quiet_NaN(); + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(AnyOf(Eq("nan"), Eq("NaN"))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "nan" })pb"))))); + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(FloatingPointLogFormatTest, NegativeNaN) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = + std::copysign(std::numeric_limits<TypeParam>::quiet_NaN(), -1.0); + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(MatchesOstream(comparison_stream)), + TextMessage(AnyOf(Eq("-nan"), Eq("nan"), Eq("NaN"), Eq("-nan(ind)"))), + ENCODED_MESSAGE( + AnyOf(EqualsProto(R"pb(value { str: "-nan" })pb"), + EqualsProto(R"pb(value { str: "nan" })pb"), + EqualsProto(R"pb(value { str: "-nan(ind)" })pb")))))); + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +template <typename T> +class VoidPtrLogFormatTest : public testing::Test {}; +using VoidPtrTypes = Types<void *, const void *>; +TYPED_TEST_SUITE(VoidPtrLogFormatTest, VoidPtrTypes); + +TYPED_TEST(VoidPtrLogFormatTest, Null) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = nullptr; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(AnyOf(Eq("(nil)"), Eq("0"), Eq("0x0"), + Eq("00000000"), Eq("0000000000000000")))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(VoidPtrLogFormatTest, NonNull) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = reinterpret_cast<TypeParam>(0xdeadbeefULL); + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(MatchesOstream(comparison_stream)), + TextMessage( + AnyOf(Eq("0xdeadbeef"), Eq("DEADBEEF"), Eq("00000000DEADBEEF"))), + ENCODED_MESSAGE(AnyOf( + EqualsProto(R"pb(value { str: "0xdeadbeef" })pb"), + EqualsProto(R"pb(value { str: "00000000DEADBEEF" })pb")))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +template <typename T> +class VolatilePtrLogFormatTest : public testing::Test {}; +using VolatilePtrTypes = + Types<volatile void*, const volatile void*, volatile char*, + const volatile char*, volatile signed char*, + const volatile signed char*, volatile unsigned char*, + const volatile unsigned char*>; +TYPED_TEST_SUITE(VolatilePtrLogFormatTest, VolatilePtrTypes); + +TYPED_TEST(VolatilePtrLogFormatTest, Null) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = nullptr; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("false")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "false" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(VolatilePtrLogFormatTest, NonNull) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const TypeParam value = reinterpret_cast<TypeParam>(0xdeadbeefLL); + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("true")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "true" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +template <typename T> +class CharPtrLogFormatTest : public testing::Test {}; +using CharPtrTypes = Types<char, const char, signed char, const signed char, + unsigned char, const unsigned char>; +TYPED_TEST_SUITE(CharPtrLogFormatTest, CharPtrTypes); + +TYPED_TEST(CharPtrLogFormatTest, Null) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + // Streaming `([cv] char *)nullptr` into a `std::ostream` is UB, and some C++ + // standard library implementations choose to crash. We take measures to log + // something useful instead of crashing, even when that differs from the + // standard library in use (and thus the behavior of `std::ostream`). + TypeParam* const value = nullptr; + + EXPECT_CALL( + test_sink, + Send(AllOf( + // `MatchesOstream` deliberately omitted since we deliberately differ. + TextMessage(Eq("(null)")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "(null)" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TYPED_TEST(CharPtrLogFormatTest, NonNull) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + TypeParam data[] = {'v', 'a', 'l', 'u', 'e', '\0'}; + TypeParam* const value = data; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("value")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "value" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TEST(BoolLogFormatTest, True) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const bool value = true; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("true")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "true" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TEST(BoolLogFormatTest, False) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const bool value = false; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("false")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "false" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +TEST(LogFormatTest, StringLiteral) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + auto comparison_stream = ComparisonStream(); + comparison_stream << "value"; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("value")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + literal: "value" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << "value"; +} + +TEST(LogFormatTest, CharArray) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + char value[] = "value"; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("value")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "value" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +class CustomClass {}; +std::ostream& operator<<(std::ostream& os, const CustomClass&) { + return os << "CustomClass{}"; +} + +TEST(LogFormatTest, Custom) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + CustomClass value; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("CustomClass{}")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "CustomClass{}" + })pb"))))); + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +class CustomClassNonCopyable { + public: + CustomClassNonCopyable() = default; + CustomClassNonCopyable(const CustomClassNonCopyable&) = delete; + CustomClassNonCopyable& operator=(const CustomClassNonCopyable&) = delete; +}; +std::ostream& operator<<(std::ostream& os, const CustomClassNonCopyable&) { + return os << "CustomClassNonCopyable{}"; +} + +TEST(LogFormatTest, CustomNonCopyable) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + CustomClassNonCopyable value; + auto comparison_stream = ComparisonStream(); + comparison_stream << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("CustomClassNonCopyable{}")), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { str: "CustomClassNonCopyable{}" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value; +} + +struct Point { + template <typename Sink> + friend void AbslStringify(Sink& sink, const Point& p) { + absl::Format(&sink, "(%d, %d)", p.x, p.y); + } + + int x = 10; + int y = 20; +}; + +TEST(LogFormatTest, AbslStringifyExample) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + Point p; + + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(Eq("(10, 20)")), TextMessage(Eq(absl::StrCat(p))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "(10, 20)" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << p; +} + +struct PointWithAbslStringifiyAndOstream { + template <typename Sink> + friend void AbslStringify(Sink& sink, + const PointWithAbslStringifiyAndOstream& p) { + absl::Format(&sink, "(%d, %d)", p.x, p.y); + } + + int x = 10; + int y = 20; +}; + +ABSL_ATTRIBUTE_UNUSED std::ostream& operator<<( + std::ostream& os, const PointWithAbslStringifiyAndOstream&) { + return os << "Default to AbslStringify()"; +} + +TEST(LogFormatTest, CustomWithAbslStringifyAndOstream) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + PointWithAbslStringifiyAndOstream p; + + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(Eq("(10, 20)")), TextMessage(Eq(absl::StrCat(p))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "(10, 20)" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << p; +} + +struct PointStreamsNothing { + template <typename Sink> + friend void AbslStringify(Sink&, const PointStreamsNothing&) {} + + int x = 10; + int y = 20; +}; + +TEST(LogFormatTest, AbslStringifyStreamsNothing) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + PointStreamsNothing p; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(Eq("77")), TextMessage(Eq(absl::StrCat(p, 77))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "77" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << p << 77; +} + +struct PointMultipleAppend { + template <typename Sink> + friend void AbslStringify(Sink& sink, const PointMultipleAppend& p) { + sink.Append("("); + sink.Append(absl::StrCat(p.x, ", ", p.y, ")")); + } + + int x = 10; + int y = 20; +}; + +TEST(LogFormatTest, AbslStringifyMultipleAppend) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + PointMultipleAppend p; + + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(Eq("(10, 20)")), TextMessage(Eq(absl::StrCat(p))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "(" } + value { str: "10, 20)" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << p; +} + +TEST(ManipulatorLogFormatTest, BoolAlphaTrue) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const bool value = true; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::noboolalpha << value << " " // + << std::boolalpha << value << " " // + << std::noboolalpha << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("1 true 1")), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { str: "1" } + value { literal: " " } + value { str: "true" } + value { literal: " " } + value { str: "1" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::noboolalpha << value << " " // + << std::boolalpha << value << " " // + << std::noboolalpha << value; +} + +TEST(ManipulatorLogFormatTest, BoolAlphaFalse) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const bool value = false; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::noboolalpha << value << " " // + << std::boolalpha << value << " " // + << std::noboolalpha << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("0 false 0")), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { str: "0" } + value { literal: " " } + value { str: "false" } + value { literal: " " } + value { str: "0" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::noboolalpha << value << " " // + << std::boolalpha << value << " " // + << std::noboolalpha << value; +} + +TEST(ManipulatorLogFormatTest, ShowPoint) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = 77.0; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::noshowpoint << value << " " // + << std::showpoint << value << " " // + << std::noshowpoint << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("77 77.0000 77")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "77" } + value { literal: " " } + value { str: "77.0000" } + value { literal: " " } + value { str: "77" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::noshowpoint << value << " " // + << std::showpoint << value << " " // + << std::noshowpoint << value; +} + +TEST(ManipulatorLogFormatTest, ShowPos) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = 77; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::noshowpos << value << " " // + << std::showpos << value << " " // + << std::noshowpos << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("77 +77 77")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "77" } + value { literal: " " } + value { str: "+77" } + value { literal: " " } + value { str: "77" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::noshowpos << value << " " // + << std::showpos << value << " " // + << std::noshowpos << value; +} + +TEST(ManipulatorLogFormatTest, UppercaseFloat) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = 7.7e7; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::nouppercase << value << " " // + << std::uppercase << value << " " // + << std::nouppercase << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("7.7e+07 7.7E+07 7.7e+07")), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { str: "7.7e+07" } + value { literal: " " } + value { str: "7.7E+07" } + value { literal: " " } + value { str: "7.7e+07" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::nouppercase << value << " " // + << std::uppercase << value << " " // + << std::nouppercase << value; +} + +TEST(ManipulatorLogFormatTest, Hex) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = 0x77; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::hex << value; + + EXPECT_CALL( + test_sink, Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("0x77")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "0x77" + })pb"))))); + test_sink.StartCapturingLogs(); + LOG(INFO) << std::hex << value; +} + +TEST(ManipulatorLogFormatTest, Oct) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = 077; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::oct << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("077")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "077" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::oct << value; +} + +TEST(ManipulatorLogFormatTest, Dec) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = 77; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::hex << std::dec << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("77")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "77" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::hex << std::dec << value; +} + +TEST(ManipulatorLogFormatTest, ShowbaseHex) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = 0x77; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::hex // + << std::noshowbase << value << " " // + << std::showbase << value << " " // + << std::noshowbase << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("77 0x77 77")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "77" } + value { literal: " " } + value { str: "0x77" } + value { literal: " " } + value { str: "77" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::hex // + << std::noshowbase << value << " " // + << std::showbase << value << " " // + << std::noshowbase << value; +} + +TEST(ManipulatorLogFormatTest, ShowbaseOct) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = 077; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::oct // + << std::noshowbase << value << " " // + << std::showbase << value << " " // + << std::noshowbase << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("77 077 77")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "77" } + value { literal: " " } + value { str: "077" } + value { literal: " " } + value { str: "77" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::oct // + << std::noshowbase << value << " " // + << std::showbase << value << " " // + << std::noshowbase << value; +} + +TEST(ManipulatorLogFormatTest, UppercaseHex) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = 0xbeef; + auto comparison_stream = ComparisonStream(); + comparison_stream // + << std::hex // + << std::nouppercase << value << " " // + << std::uppercase << value << " " // + << std::nouppercase << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("0xbeef 0XBEEF 0xbeef")), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { str: "0xbeef" } + value { literal: " " } + value { str: "0XBEEF" } + value { literal: " " } + value { str: "0xbeef" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::hex // + << std::nouppercase << value << " " // + << std::uppercase << value << " " // + << std::nouppercase << value; +} + +TEST(ManipulatorLogFormatTest, FixedFloat) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = 7.7e7; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::fixed << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("77000000.000000")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "77000000.000000" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::fixed << value; +} + +TEST(ManipulatorLogFormatTest, ScientificFloat) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = 7.7e7; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::scientific << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("7.700000e+07")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "7.700000e+07" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::scientific << value; +} + +#if defined(__BIONIC__) && (!defined(__ANDROID_API__) || __ANDROID_API__ < 22) +// Bionic doesn't support `%a` until API 22, so this prints 'a' even if the +// C++ standard library implements it correctly (by forwarding to printf). +#elif defined(__GLIBCXX__) && __cplusplus < 201402L +// libstdc++ shipped C++11 support without `std::hexfloat`. +#else +TEST(ManipulatorLogFormatTest, FixedAndScientificFloat) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = 7.7e7; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::setiosflags(std::ios_base::scientific | + std::ios_base::fixed) + << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(AnyOf(Eq("0x1.25bb50p+26"), Eq("0x1.25bb5p+26"), + Eq("0x1.25bb500000000p+26"))), + ENCODED_MESSAGE( + AnyOf(EqualsProto(R"pb(value { str: "0x1.25bb5p+26" })pb"), + EqualsProto(R"pb(value { + str: "0x1.25bb500000000p+26" + })pb")))))); + + test_sink.StartCapturingLogs(); + + // This combination should mean the same thing as `std::hexfloat`. + LOG(INFO) << std::setiosflags(std::ios_base::scientific | + std::ios_base::fixed) + << value; +} +#endif + +#if defined(__BIONIC__) && (!defined(__ANDROID_API__) || __ANDROID_API__ < 22) +// Bionic doesn't support `%a` until API 22, so this prints 'a' even if the C++ +// standard library supports `std::hexfloat` (by forwarding to printf). +#elif defined(__GLIBCXX__) && __cplusplus < 201402L +// libstdc++ shipped C++11 support without `std::hexfloat`. +#else +TEST(ManipulatorLogFormatTest, HexfloatFloat) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = 7.7e7; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::hexfloat << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(AnyOf(Eq("0x1.25bb50p+26"), Eq("0x1.25bb5p+26"), + Eq("0x1.25bb500000000p+26"))), + ENCODED_MESSAGE( + AnyOf(EqualsProto(R"pb(value { str: "0x1.25bb5p+26" })pb"), + EqualsProto(R"pb(value { + str: "0x1.25bb500000000p+26" + })pb")))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::hexfloat << value; +} +#endif + +TEST(ManipulatorLogFormatTest, DefaultFloatFloat) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = 7.7e7; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::hexfloat << std::defaultfloat << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("7.7e+07")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "7.7e+07" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::hexfloat << std::defaultfloat << value; +} + +TEST(ManipulatorLogFormatTest, Ends) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + auto comparison_stream = ComparisonStream(); + comparison_stream << std::ends; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq(absl::string_view("\0", 1))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "\0" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::ends; +} + +TEST(ManipulatorLogFormatTest, Endl) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + auto comparison_stream = ComparisonStream(); + comparison_stream << std::endl; + + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("\n")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "\n" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::endl; +} + +TEST(ManipulatorLogFormatTest, SetIosFlags) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = 0x77; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::resetiosflags(std::ios_base::basefield) + << std::setiosflags(std::ios_base::hex) << value << " " // + << std::resetiosflags(std::ios_base::basefield) + << std::setiosflags(std::ios_base::dec) << value; + + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("0x77 119")), + // `std::setiosflags` and `std::resetiosflags` aren't manipulators. + // We're unable to distinguish their return type(s) from arbitrary + // user-defined types and thus don't suppress the empty str value. + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "0x77" } + value { literal: " " } + value { str: "119" } + )pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::resetiosflags(std::ios_base::basefield) + << std::setiosflags(std::ios_base::hex) << value << " " // + << std::resetiosflags(std::ios_base::basefield) + << std::setiosflags(std::ios_base::dec) << value; +} + +TEST(ManipulatorLogFormatTest, SetBase) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = 0x77; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::setbase(16) << value << " " // + << std::setbase(0) << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("0x77 119")), + // `std::setbase` isn't a manipulator. We're unable to + // distinguish its return type from arbitrary user-defined + // types and thus don't suppress the empty str value. + ENCODED_MESSAGE(EqualsProto( + R"pb(value { str: "0x77" } + value { literal: " " } + value { str: "119" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::setbase(16) << value << " " // + << std::setbase(0) << value; +} + +TEST(ManipulatorLogFormatTest, SetPrecision) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = 6.022140857e23; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::setprecision(4) << value; + + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("6.022e+23")), + // `std::setprecision` isn't a manipulator. We're unable to + // distinguish its return type from arbitrary user-defined + // types and thus don't suppress the empty str value. + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "6.022e+23" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::setprecision(4) << value; +} + +TEST(ManipulatorLogFormatTest, SetPrecisionOverflow) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const double value = 6.022140857e23; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::setprecision(200) << value; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("602214085700000015187968")), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { str: "602214085700000015187968" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::setprecision(200) << value; +} + +TEST(ManipulatorLogFormatTest, SetW) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = 77; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::setw(8) << value; + + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq(" 77")), + // `std::setw` isn't a manipulator. We're unable to + // distinguish its return type from arbitrary user-defined + // types and thus don't suppress the empty str value. + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: " 77" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::setw(8) << value; +} + +TEST(ManipulatorLogFormatTest, Left) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = -77; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::left << std::setw(8) << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("-77 ")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "-77 " + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::left << std::setw(8) << value; +} + +TEST(ManipulatorLogFormatTest, Right) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = -77; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::right << std::setw(8) << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq(" -77")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: " -77" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::right << std::setw(8) << value; +} + +TEST(ManipulatorLogFormatTest, Internal) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = -77; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::internal << std::setw(8) << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("- 77")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "- 77" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::internal << std::setw(8) << value; +} + +TEST(ManipulatorLogFormatTest, SetFill) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + const int value = 77; + auto comparison_stream = ComparisonStream(); + comparison_stream << std::setfill('0') << std::setw(8) << value; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("00000077")), + // `std::setfill` isn't a manipulator. We're + // unable to distinguish its return + // type from arbitrary user-defined types and + // thus don't suppress the empty str value. + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "00000077" + })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::setfill('0') << std::setw(8) << value; +} + +class FromCustomClass {}; +std::ostream& operator<<(std::ostream& os, const FromCustomClass&) { + return os << "FromCustomClass{}" << std::hex; +} + +TEST(ManipulatorLogFormatTest, FromCustom) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + FromCustomClass value; + auto comparison_stream = ComparisonStream(); + comparison_stream << value << " " << 0x77; + + EXPECT_CALL(test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("FromCustomClass{} 0x77")), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { str: "FromCustomClass{}" } + value { literal: " " } + value { str: "0x77" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value << " " << 0x77; +} + +class StreamsNothing {}; +std::ostream& operator<<(std::ostream& os, const StreamsNothing&) { return os; } + +TEST(ManipulatorLogFormatTest, CustomClassStreamsNothing) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + StreamsNothing value; + auto comparison_stream = ComparisonStream(); + comparison_stream << value << 77; + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(MatchesOstream(comparison_stream)), + TextMessage(Eq("77")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "77" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << value << 77; +} + +struct PointPercentV { + template <typename Sink> + friend void AbslStringify(Sink& sink, const PointPercentV& p) { + absl::Format(&sink, "(%v, %v)", p.x, p.y); + } + + int x = 10; + int y = 20; +}; + +TEST(ManipulatorLogFormatTest, IOManipsDoNotAffectAbslStringify) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + PointPercentV p; + + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(Eq("(10, 20)")), TextMessage(Eq(absl::StrCat(p))), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "(10, 20)" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::hex << p; +} + +TEST(StructuredLoggingOverflowTest, TruncatesStrings) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + // This message is too long and should be truncated to some unspecified size + // no greater than the buffer size but not too much less either. It should be + // truncated rather than discarded. + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(AllOf( + SizeIs(AllOf(Ge(absl::log_internal::kLogMessageBufferSize - 256), + Le(absl::log_internal::kLogMessageBufferSize))), + Each(Eq('x')))), + ENCODED_MESSAGE(HasOneStrThat(AllOf( + SizeIs(AllOf(Ge(absl::log_internal::kLogMessageBufferSize - 256), + Le(absl::log_internal::kLogMessageBufferSize))), + Each(Eq('x')))))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << std::string(2 * absl::log_internal::kLogMessageBufferSize, 'x'); +} + +struct StringLike { + absl::string_view data; +}; +std::ostream& operator<<(std::ostream& os, StringLike str) { + return os << str.data; +} + +TEST(StructuredLoggingOverflowTest, TruncatesInsertionOperators) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + // This message is too long and should be truncated to some unspecified size + // no greater than the buffer size but not too much less either. It should be + // truncated rather than discarded. + EXPECT_CALL( + test_sink, + Send(AllOf( + TextMessage(AllOf( + SizeIs(AllOf(Ge(absl::log_internal::kLogMessageBufferSize - 256), + Le(absl::log_internal::kLogMessageBufferSize))), + Each(Eq('x')))), + ENCODED_MESSAGE(HasOneStrThat(AllOf( + SizeIs(AllOf(Ge(absl::log_internal::kLogMessageBufferSize - 256), + Le(absl::log_internal::kLogMessageBufferSize))), + Each(Eq('x')))))))); + + test_sink.StartCapturingLogs(); + LOG(INFO) << StringLike{ + std::string(2 * absl::log_internal::kLogMessageBufferSize, 'x')}; +} + +// Returns the size of the largest string that will fit in a `LOG` message +// buffer with no prefix. +size_t MaxLogFieldLengthNoPrefix() { + class StringLengthExtractorSink : public absl::LogSink { + public: + void Send(const absl::LogEntry& entry) override { + CHECK(!size_.has_value()); + CHECK_EQ(entry.text_message().find_first_not_of('x'), + absl::string_view::npos); + size_.emplace(entry.text_message().size()); + } + size_t size() const { + CHECK(size_.has_value()); + return *size_; + } + + private: + absl::optional<size_t> size_; + } extractor_sink; + LOG(INFO).NoPrefix().ToSinkOnly(&extractor_sink) + << std::string(2 * absl::log_internal::kLogMessageBufferSize, 'x'); + return extractor_sink.size(); +} + +TEST(StructuredLoggingOverflowTest, TruncatesStringsCleanly) { + const size_t longest_fit = MaxLogFieldLengthNoPrefix(); + // To log a second value field, we need four bytes: two tag/type bytes and two + // sizes. To put any data in the field we need a fifth byte. + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, + Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( + AllOf(SizeIs(longest_fit), Each(Eq('x'))))), + RawEncodedMessage(AsString(EndsWith("x")))))); + test_sink.StartCapturingLogs(); + // x fits exactly, no part of y fits. + LOG(INFO).NoPrefix() << std::string(longest_fit, 'x') << "y"; + } + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, + Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( + AllOf(SizeIs(longest_fit - 1), Each(Eq('x'))))), + RawEncodedMessage(AsString(EndsWith("x")))))); + test_sink.StartCapturingLogs(); + // x fits, one byte from y's header fits but shouldn't be visible. + LOG(INFO).NoPrefix() << std::string(longest_fit - 1, 'x') << "y"; + } + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, + Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( + AllOf(SizeIs(longest_fit - 2), Each(Eq('x'))))), + RawEncodedMessage(AsString(EndsWith("x")))))); + test_sink.StartCapturingLogs(); + // x fits, two bytes from y's header fit but shouldn't be visible. + LOG(INFO).NoPrefix() << std::string(longest_fit - 2, 'x') << "y"; + } + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, + Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( + AllOf(SizeIs(longest_fit - 3), Each(Eq('x'))))), + RawEncodedMessage(AsString(EndsWith("x")))))); + test_sink.StartCapturingLogs(); + // x fits, three bytes from y's header fit but shouldn't be visible. + LOG(INFO).NoPrefix() << std::string(longest_fit - 3, 'x') << "y"; + } + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, + Send(AllOf(ENCODED_MESSAGE(HasOneStrAndOneLiteralThat( + AllOf(SizeIs(longest_fit - 4), Each(Eq('x'))), + IsEmpty())), + RawEncodedMessage(Not(AsString(EndsWith("x"))))))); + test_sink.StartCapturingLogs(); + // x fits, all four bytes from y's header fit but no data bytes do, so we + // encode an empty string. + LOG(INFO).NoPrefix() << std::string(longest_fit - 4, 'x') << "y"; + } + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL( + test_sink, + Send(AllOf(ENCODED_MESSAGE(HasOneStrAndOneLiteralThat( + AllOf(SizeIs(longest_fit - 5), Each(Eq('x'))), Eq("y"))), + RawEncodedMessage(AsString(EndsWith("y")))))); + test_sink.StartCapturingLogs(); + // x fits, y fits exactly. + LOG(INFO).NoPrefix() << std::string(longest_fit - 5, 'x') << "y"; + } +} + +TEST(StructuredLoggingOverflowTest, TruncatesInsertionOperatorsCleanly) { + const size_t longest_fit = MaxLogFieldLengthNoPrefix(); + // To log a second value field, we need four bytes: two tag/type bytes and two + // sizes. To put any data in the field we need a fifth byte. + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, + Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( + AllOf(SizeIs(longest_fit), Each(Eq('x'))))), + RawEncodedMessage(AsString(EndsWith("x")))))); + test_sink.StartCapturingLogs(); + // x fits exactly, no part of y fits. + LOG(INFO).NoPrefix() << std::string(longest_fit, 'x') << StringLike{"y"}; + } + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, + Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( + AllOf(SizeIs(longest_fit - 1), Each(Eq('x'))))), + RawEncodedMessage(AsString(EndsWith("x")))))); + test_sink.StartCapturingLogs(); + // x fits, one byte from y's header fits but shouldn't be visible. + LOG(INFO).NoPrefix() << std::string(longest_fit - 1, 'x') + << StringLike{"y"}; + } + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, + Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( + AllOf(SizeIs(longest_fit - 2), Each(Eq('x'))))), + RawEncodedMessage(AsString(EndsWith("x")))))); + test_sink.StartCapturingLogs(); + // x fits, two bytes from y's header fit but shouldn't be visible. + LOG(INFO).NoPrefix() << std::string(longest_fit - 2, 'x') + << StringLike{"y"}; + } + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, + Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( + AllOf(SizeIs(longest_fit - 3), Each(Eq('x'))))), + RawEncodedMessage(AsString(EndsWith("x")))))); + test_sink.StartCapturingLogs(); + // x fits, three bytes from y's header fit but shouldn't be visible. + LOG(INFO).NoPrefix() << std::string(longest_fit - 3, 'x') + << StringLike{"y"}; + } + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, + Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( + AllOf(SizeIs(longest_fit - 4), Each(Eq('x'))))), + RawEncodedMessage(AsString(EndsWith("x")))))); + test_sink.StartCapturingLogs(); + // x fits, all four bytes from y's header fit but no data bytes do. We + // don't encode an empty string here because every I/O manipulator hits this + // codepath and those shouldn't leave empty strings behind. + LOG(INFO).NoPrefix() << std::string(longest_fit - 4, 'x') + << StringLike{"y"}; + } + { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL( + test_sink, + Send(AllOf(ENCODED_MESSAGE(HasTwoStrsThat( + AllOf(SizeIs(longest_fit - 5), Each(Eq('x'))), Eq("y"))), + RawEncodedMessage(AsString(EndsWith("y")))))); + test_sink.StartCapturingLogs(); + // x fits, y fits exactly. + LOG(INFO).NoPrefix() << std::string(longest_fit - 5, 'x') + << StringLike{"y"}; + } +} + +} // namespace diff --git a/absl/log/log_macro_hygiene_test.cc b/absl/log/log_macro_hygiene_test.cc new file mode 100644 index 00000000..dad9389e --- /dev/null +++ b/absl/log/log_macro_hygiene_test.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 "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/base/log_severity.h" +#include "absl/log/log.h" +#include "absl/log/scoped_mock_log.h" + +namespace { +using ::testing::_; +using ::testing::Eq; + +namespace not_absl { + +class Dummy { + public: + Dummy() {} + + private: + Dummy(const Dummy&) = delete; + Dummy& operator=(const Dummy&) = delete; +}; + +// This line tests that local definitions of INFO, WARNING, ERROR, and +// etc don't shadow the global ones used by the logging macros. If +// they do, the LOG() calls in the tests won't compile, catching the +// bug. +const Dummy INFO, WARNING, ERROR, FATAL, NUM_SEVERITIES; + +// These makes sure that the uses of same-named types in the +// implementation of the logging macros are fully qualified. +class string {}; +class vector {}; +class LogMessage {}; +class LogMessageFatal {}; +class LogMessageQuietlyFatal {}; +class LogMessageVoidify {}; +class LogSink {}; +class NullStream {}; +class NullStreamFatal {}; + +} // namespace not_absl + +using namespace not_absl; // NOLINT + +// Tests for LOG(LEVEL(()). + +TEST(LogHygieneTest, WorksForQualifiedSeverity) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + ::testing::InSequence seq; + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, _, "To INFO")); + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kWarning, _, "To WARNING")); + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kError, _, "To ERROR")); + + test_sink.StartCapturingLogs(); + // Note that LOG(LEVEL()) expects the severity as a run-time + // expression (as opposed to a compile-time constant). Hence we + // test that :: is allowed before INFO, etc. + LOG(LEVEL(absl::LogSeverity::kInfo)) << "To INFO"; + LOG(LEVEL(absl::LogSeverity::kWarning)) << "To WARNING"; + LOG(LEVEL(absl::LogSeverity::kError)) << "To ERROR"; +} + +TEST(LogHygieneTest, WorksWithAlternativeINFOSymbol) { + const double INFO ABSL_ATTRIBUTE_UNUSED = 7.77; + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, _, "Hello world")); + + test_sink.StartCapturingLogs(); + LOG(INFO) << "Hello world"; +} + +TEST(LogHygieneTest, WorksWithAlternativeWARNINGSymbol) { + const double WARNING ABSL_ATTRIBUTE_UNUSED = 7.77; + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kWarning, _, "Hello world")); + + test_sink.StartCapturingLogs(); + LOG(WARNING) << "Hello world"; +} + +TEST(LogHygieneTest, WorksWithAlternativeERRORSymbol) { + const double ERROR ABSL_ATTRIBUTE_UNUSED = 7.77; + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kError, _, "Hello world")); + + test_sink.StartCapturingLogs(); + LOG(ERROR) << "Hello world"; +} + +TEST(LogHygieneTest, WorksWithAlternativeLEVELSymbol) { + const double LEVEL ABSL_ATTRIBUTE_UNUSED = 7.77; + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kError, _, "Hello world")); + + test_sink.StartCapturingLogs(); + LOG(LEVEL(absl::LogSeverity::kError)) << "Hello world"; +} + +#define INFO Bogus +#ifdef NDEBUG +constexpr bool IsOptimized = false; +#else +constexpr bool IsOptimized = true; +#endif + +TEST(LogHygieneTest, WorksWithINFODefined) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, _, "Hello world")) + .Times(2 + (IsOptimized ? 2 : 0)); + + test_sink.StartCapturingLogs(); + LOG(INFO) << "Hello world"; + LOG_IF(INFO, true) << "Hello world"; + + DLOG(INFO) << "Hello world"; + DLOG_IF(INFO, true) << "Hello world"; +} + +#undef INFO + +#define _INFO Bogus +TEST(LogHygieneTest, WorksWithUnderscoreINFODefined) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, _, "Hello world")) + .Times(2 + (IsOptimized ? 2 : 0)); + + test_sink.StartCapturingLogs(); + LOG(INFO) << "Hello world"; + LOG_IF(INFO, true) << "Hello world"; + + DLOG(INFO) << "Hello world"; + DLOG_IF(INFO, true) << "Hello world"; +} +#undef _INFO + +TEST(LogHygieneTest, ExpressionEvaluationInLEVELSeverity) { + auto i = static_cast<int>(absl::LogSeverity::kInfo); + LOG(LEVEL(++i)) << "hello world"; // NOLINT + EXPECT_THAT(i, Eq(static_cast<int>(absl::LogSeverity::kInfo) + 1)); +} + +TEST(LogHygieneTest, ExpressionEvaluationInStreamedMessage) { + int i = 0; + LOG(INFO) << ++i; + EXPECT_THAT(i, 1); + LOG_IF(INFO, false) << ++i; + EXPECT_THAT(i, 1); +} + +// Tests that macros are usable in unbraced switch statements. +// ----------------------------------------------------------- + +class UnbracedSwitchCompileTest { + static void Log() { + switch (0) { + case 0: + LOG(INFO); + break; + default: + break; + } + } +}; + +} // namespace diff --git a/absl/log/log_modifier_methods_test.cc b/absl/log/log_modifier_methods_test.cc new file mode 100644 index 00000000..42e13b1b --- /dev/null +++ b/absl/log/log_modifier_methods_test.cc @@ -0,0 +1,233 @@ +// +// 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 <errno.h> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/log/internal/test_actions.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/log/internal/test_matchers.h" +#include "absl/log/log.h" +#include "absl/log/log_sink.h" +#include "absl/log/scoped_mock_log.h" +#include "absl/strings/match.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" + +namespace { +#if GTEST_HAS_DEATH_TEST +using ::absl::log_internal::DeathTestExpectedLogging; +using ::absl::log_internal::DeathTestUnexpectedLogging; +using ::absl::log_internal::DeathTestValidateExpectations; +using ::absl::log_internal::DiedOfQFatal; +#endif +using ::absl::log_internal::LogSeverity; +using ::absl::log_internal::Prefix; +using ::absl::log_internal::SourceBasename; +using ::absl::log_internal::SourceFilename; +using ::absl::log_internal::SourceLine; +using ::absl::log_internal::Stacktrace; +using ::absl::log_internal::TextMessage; +using ::absl::log_internal::TextMessageWithPrefix; +using ::absl::log_internal::TextMessageWithPrefixAndNewline; +using ::absl::log_internal::TextPrefix; +using ::absl::log_internal::ThreadID; +using ::absl::log_internal::Timestamp; +using ::absl::log_internal::Verbosity; + +using ::testing::AllOf; +using ::testing::AnyNumber; +using ::testing::AnyOf; +using ::testing::Eq; +using ::testing::IsEmpty; +using ::testing::IsFalse; +using ::testing::Truly; + +TEST(TailCallsModifiesTest, AtLocationFileLine) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL( + test_sink, + Send(AllOf( + // The metadata should change: + SourceFilename(Eq("/my/very/very/very_long_source_file.cc")), + SourceBasename(Eq("very_long_source_file.cc")), SourceLine(Eq(777)), + // The logged line should change too, even though the prefix must + // grow to fit the new metadata. + TextMessageWithPrefix(Truly([](absl::string_view msg) { + return absl::EndsWith(msg, + " very_long_source_file.cc:777] hello world"); + }))))); + + test_sink.StartCapturingLogs(); + LOG(INFO).AtLocation("/my/very/very/very_long_source_file.cc", 777) + << "hello world"; +} + +TEST(TailCallsModifiesTest, NoPrefix) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send(AllOf(Prefix(IsFalse()), TextPrefix(IsEmpty()), + TextMessageWithPrefix(Eq("hello world"))))); + + test_sink.StartCapturingLogs(); + LOG(INFO).NoPrefix() << "hello world"; +} + +TEST(TailCallsModifiesTest, NoPrefixNoMessageNoShirtNoShoesNoService) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, + Send(AllOf(Prefix(IsFalse()), TextPrefix(IsEmpty()), + TextMessageWithPrefix(IsEmpty()), + TextMessageWithPrefixAndNewline(Eq("\n"))))); + test_sink.StartCapturingLogs(); + LOG(INFO).NoPrefix(); +} + +TEST(TailCallsModifiesTest, WithVerbosity) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send(Verbosity(Eq(2)))); + + test_sink.StartCapturingLogs(); + LOG(INFO).WithVerbosity(2) << "hello world"; +} + +TEST(TailCallsModifiesTest, WithVerbosityNoVerbosity) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, + Send(Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)))); + + test_sink.StartCapturingLogs(); + LOG(INFO).WithVerbosity(2).WithVerbosity(absl::LogEntry::kNoVerbosityLevel) + << "hello world"; +} + +TEST(TailCallsModifiesTest, WithTimestamp) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send(Timestamp(Eq(absl::UnixEpoch())))); + + test_sink.StartCapturingLogs(); + LOG(INFO).WithTimestamp(absl::UnixEpoch()) << "hello world"; +} + +TEST(TailCallsModifiesTest, WithThreadID) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, + Send(AllOf(ThreadID(Eq(absl::LogEntry::tid_t{1234}))))); + + test_sink.StartCapturingLogs(); + LOG(INFO).WithThreadID(1234) << "hello world"; +} + +TEST(TailCallsModifiesTest, WithMetadataFrom) { + class ForwardingLogSink : public absl::LogSink { + public: + void Send(const absl::LogEntry &entry) override { + LOG(LEVEL(entry.log_severity())).WithMetadataFrom(entry) + << "forwarded: " << entry.text_message(); + } + } forwarding_sink; + + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq("fake/file")), SourceBasename(Eq("file")), + SourceLine(Eq(123)), Prefix(IsFalse()), + LogSeverity(Eq(absl::LogSeverity::kWarning)), + Timestamp(Eq(absl::UnixEpoch())), + ThreadID(Eq(absl::LogEntry::tid_t{456})), + TextMessage(Eq("forwarded: hello world")), Verbosity(Eq(7)), + ENCODED_MESSAGE( + EqualsProto(R"pb(value { literal: "forwarded: " } + value { str: "hello world" })pb"))))); + + test_sink.StartCapturingLogs(); + LOG(WARNING) + .AtLocation("fake/file", 123) + .NoPrefix() + .WithTimestamp(absl::UnixEpoch()) + .WithThreadID(456) + .WithVerbosity(7) + .ToSinkOnly(&forwarding_sink) + << "hello world"; +} + +TEST(TailCallsModifiesTest, WithPerror) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL( + test_sink, + Send(AllOf(TextMessage(AnyOf(Eq("hello world: Bad file number [9]"), + Eq("hello world: Bad file descriptor [9]"), + Eq("hello world: Bad file descriptor [8]"))), + ENCODED_MESSAGE( + AnyOf(EqualsProto(R"pb(value { literal: "hello world" } + value { literal: ": " } + value { str: "Bad file number" } + value { literal: " [" } + value { str: "9" } + value { literal: "]" })pb"), + EqualsProto(R"pb(value { literal: "hello world" } + value { literal: ": " } + value { str: "Bad file descriptor" } + value { literal: " [" } + value { str: "9" } + value { literal: "]" })pb"), + EqualsProto(R"pb(value { literal: "hello world" } + value { literal: ": " } + value { str: "Bad file descriptor" } + value { literal: " [" } + value { str: "8" } + value { literal: "]" })pb")))))); + + test_sink.StartCapturingLogs(); + errno = EBADF; + LOG(INFO).WithPerror() << "hello world"; +} + +#if GTEST_HAS_DEATH_TEST +TEST(ModifierMethodDeathTest, ToSinkOnlyQFatal) { + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink( + absl::MockLogDefault::kDisallowUnexpected); + + auto do_log = [&test_sink] { + LOG(QFATAL).ToSinkOnly(&test_sink.UseAsLocalSink()) << "hello world"; + }; + + EXPECT_CALL(test_sink, Send) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + + EXPECT_CALL(test_sink, Send(AllOf(TextMessage(Eq("hello world")), + Stacktrace(IsEmpty())))) + .WillOnce(DeathTestExpectedLogging()); + + test_sink.StartCapturingLogs(); + do_log(); + }, + DiedOfQFatal, DeathTestValidateExpectations()); +} +#endif + +} // namespace diff --git a/absl/log/log_sink.cc b/absl/log/log_sink.cc new file mode 100644 index 00000000..01d7ca82 --- /dev/null +++ b/absl/log/log_sink.cc @@ -0,0 +1,23 @@ +// 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/log_sink.h" + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +void LogSink::KeyFunction() const {} +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/log_sink.h b/absl/log/log_sink.h new file mode 100644 index 00000000..9bfa6f86 --- /dev/null +++ b/absl/log/log_sink.h @@ -0,0 +1,64 @@ +// 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/log_sink.h +// ----------------------------------------------------------------------------- +// +// This header declares the interface class `absl::LogSink`. + +#ifndef ABSL_LOG_LOG_SINK_H_ +#define ABSL_LOG_LOG_SINK_H_ + +#include "absl/base/config.h" +#include "absl/log/log_entry.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +// absl::LogSink +// +// `absl::LogSink` is an interface which can be extended to intercept and +// process particular messages (with `LOG.ToSinkOnly()` or +// `LOG.ToSinkAlso()`) or all messages (if registered with +// `absl::AddLogSink`). Implementations must be thread-safe, and should take +// care not to take any locks that might be held by the `LOG` caller. +class LogSink { + public: + virtual ~LogSink() = default; + + // LogSink::Send() + // + // `Send` is called synchronously during the log statement. + // + // It is safe to use `LOG` within an implementation of `Send`. `ToSinkOnly` + // and `ToSinkAlso` are safe in general but can be used to create an infinite + // loop if you try. + virtual void Send(const absl::LogEntry& entry) = 0; + + // LogSink::Flush() + // + // Sinks that buffer messages should override this method to flush the buffer + // and return. + virtual void Flush() {} + + private: + // https://lld.llvm.org/missingkeyfunction.html#missing-key-function + virtual void KeyFunction() const final; // NOLINT(readability/inheritance) +}; + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_LOG_SINK_H_ diff --git a/absl/log/log_sink_registry.h b/absl/log/log_sink_registry.h new file mode 100644 index 00000000..bf76ccee --- /dev/null +++ b/absl/log/log_sink_registry.h @@ -0,0 +1,61 @@ +// 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/log_sink_registry.h +// ----------------------------------------------------------------------------- +// +// This header declares APIs to operate on global set of registered log sinks. + +#ifndef ABSL_LOG_LOG_SINK_REGISTRY_H_ +#define ABSL_LOG_LOG_SINK_REGISTRY_H_ + +#include "absl/base/config.h" +#include "absl/log/internal/log_sink_set.h" +#include "absl/log/log_sink.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +// AddLogSink(), RemoveLogSink() +// +// Adds or removes a `absl::LogSink` as a consumer of logging data. +// +// These functions are thread-safe. +// +// It is an error to attempt to add a sink that's already registered or to +// attempt to remove one that isn't. +// +// To avoid unbounded recursion, dispatch to registered `absl::LogSink`s is +// disabled per-thread while running the `Send()` method of registered +// `absl::LogSink`s. Affected messages are dispatched to a special internal +// sink instead which writes them to `stderr`. +// +// Do not call these inside `absl::LogSink::Send`. +inline void AddLogSink(absl::LogSink* sink) { log_internal::AddLogSink(sink); } +inline void RemoveLogSink(absl::LogSink* sink) { + log_internal::RemoveLogSink(sink); +} + +// FlushLogSinks() +// +// Calls `absl::LogSink::Flush` on all registered sinks. +// +// Do not call this inside `absl::LogSink::Send`. +inline void FlushLogSinks() { log_internal::FlushLogSinks(); } + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_LOG_SINK_REGISTRY_H_ diff --git a/absl/log/log_sink_test.cc b/absl/log/log_sink_test.cc new file mode 100644 index 00000000..8903da72 --- /dev/null +++ b/absl/log/log_sink_test.cc @@ -0,0 +1,419 @@ +// +// 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/log_sink.h" + +#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" +#include "absl/log/log.h" +#include "absl/log/log_sink_registry.h" +#include "absl/log/scoped_mock_log.h" +#include "absl/strings/string_view.h" + +namespace { + +using ::absl::log_internal::DeathTestExpectedLogging; +using ::absl::log_internal::DeathTestUnexpectedLogging; +using ::absl::log_internal::DeathTestValidateExpectations; +using ::absl::log_internal::DiedOfFatal; +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::HasSubstr; +using ::testing::InSequence; + +auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( + new absl::log_internal::LogTestEnvironment); + +// Tests for global log sink registration. +// --------------------------------------- + +TEST(LogSinkRegistryTest, AddLogSink) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + InSequence s; + EXPECT_CALL(test_sink, Log(_, _, "hello world")).Times(0); + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, __FILE__, "Test : 42")); + EXPECT_CALL(test_sink, + Log(absl::LogSeverity::kWarning, __FILE__, "Danger ahead")); + EXPECT_CALL(test_sink, + Log(absl::LogSeverity::kError, __FILE__, "This is an error")); + + LOG(INFO) << "hello world"; + test_sink.StartCapturingLogs(); + + LOG(INFO) << "Test : " << 42; + LOG(WARNING) << "Danger" << ' ' << "ahead"; + LOG(ERROR) << "This is an error"; + + test_sink.StopCapturingLogs(); + LOG(INFO) << "Goodby world"; +} + +TEST(LogSinkRegistryTest, MultipleLogSinks) { + absl::ScopedMockLog test_sink1(absl::MockLogDefault::kDisallowUnexpected); + absl::ScopedMockLog test_sink2(absl::MockLogDefault::kDisallowUnexpected); + + ::testing::InSequence seq; + EXPECT_CALL(test_sink1, Log(absl::LogSeverity::kInfo, _, "First")).Times(1); + EXPECT_CALL(test_sink2, Log(absl::LogSeverity::kInfo, _, "First")).Times(0); + + EXPECT_CALL(test_sink1, Log(absl::LogSeverity::kInfo, _, "Second")).Times(1); + EXPECT_CALL(test_sink2, Log(absl::LogSeverity::kInfo, _, "Second")).Times(1); + + EXPECT_CALL(test_sink1, Log(absl::LogSeverity::kInfo, _, "Third")).Times(0); + EXPECT_CALL(test_sink2, Log(absl::LogSeverity::kInfo, _, "Third")).Times(1); + + LOG(INFO) << "Before first"; + + test_sink1.StartCapturingLogs(); + LOG(INFO) << "First"; + + test_sink2.StartCapturingLogs(); + LOG(INFO) << "Second"; + + test_sink1.StopCapturingLogs(); + LOG(INFO) << "Third"; + + test_sink2.StopCapturingLogs(); + LOG(INFO) << "Fourth"; +} + +TEST(LogSinkRegistrationDeathTest, DuplicateSinkRegistration) { + ASSERT_DEATH_IF_SUPPORTED( + { + absl::ScopedMockLog sink; + sink.StartCapturingLogs(); + absl::AddLogSink(&sink.UseAsLocalSink()); + }, + HasSubstr("Duplicate log sinks")); +} + +TEST(LogSinkRegistrationDeathTest, MismatchSinkRemoval) { + ASSERT_DEATH_IF_SUPPORTED( + { + absl::ScopedMockLog sink; + absl::RemoveLogSink(&sink.UseAsLocalSink()); + }, + HasSubstr("Mismatched log sink")); +} + +// Tests for log sink semantic. +// --------------------------------------- + +TEST(LogSinkTest, FlushSinks) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Flush()).Times(2); + + test_sink.StartCapturingLogs(); + + absl::FlushLogSinks(); + absl::FlushLogSinks(); +} + +TEST(LogSinkDeathTest, DeathInSend) { + class FatalSendSink : public absl::LogSink { + public: + void Send(const absl::LogEntry&) override { LOG(FATAL) << "goodbye world"; } + }; + + FatalSendSink sink; + EXPECT_EXIT({ LOG(INFO).ToSinkAlso(&sink) << "hello world"; }, DiedOfFatal, + _); +} + +// Tests for explicit log sink redirection. +// --------------------------------------- + +TEST(LogSinkTest, ToSinkAlso) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + absl::ScopedMockLog another_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Log(_, _, "hello world")); + EXPECT_CALL(another_sink, Log(_, _, "hello world")); + + test_sink.StartCapturingLogs(); + LOG(INFO).ToSinkAlso(&another_sink.UseAsLocalSink()) << "hello world"; +} + +TEST(LogSinkTest, ToSinkOnly) { + absl::ScopedMockLog another_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(another_sink, Log(_, _, "hello world")); + LOG(INFO).ToSinkOnly(&another_sink.UseAsLocalSink()) << "hello world"; +} + +TEST(LogSinkTest, ToManySinks) { + absl::ScopedMockLog sink1(absl::MockLogDefault::kDisallowUnexpected); + absl::ScopedMockLog sink2(absl::MockLogDefault::kDisallowUnexpected); + absl::ScopedMockLog sink3(absl::MockLogDefault::kDisallowUnexpected); + absl::ScopedMockLog sink4(absl::MockLogDefault::kDisallowUnexpected); + absl::ScopedMockLog sink5(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(sink3, Log(_, _, "hello world")); + EXPECT_CALL(sink4, Log(_, _, "hello world")); + EXPECT_CALL(sink5, Log(_, _, "hello world")); + + LOG(INFO) + .ToSinkAlso(&sink1.UseAsLocalSink()) + .ToSinkAlso(&sink2.UseAsLocalSink()) + .ToSinkOnly(&sink3.UseAsLocalSink()) + .ToSinkAlso(&sink4.UseAsLocalSink()) + .ToSinkAlso(&sink5.UseAsLocalSink()) + << "hello world"; +} + +class ReentrancyTest : public ::testing::Test { + protected: + ReentrancyTest() = default; + enum class LogMode : int { kNormal, kToSinkAlso, kToSinkOnly }; + + class ReentrantSendLogSink : public absl::LogSink { + public: + explicit ReentrantSendLogSink(absl::LogSeverity severity, + absl::LogSink* sink, LogMode mode) + : severity_(severity), sink_(sink), mode_(mode) {} + explicit ReentrantSendLogSink(absl::LogSeverity severity) + : ReentrantSendLogSink(severity, nullptr, LogMode::kNormal) {} + + void Send(const absl::LogEntry&) override { + switch (mode_) { + case LogMode::kNormal: + LOG(LEVEL(severity_)) << "The log is coming from *inside the sink*."; + break; + case LogMode::kToSinkAlso: + LOG(LEVEL(severity_)).ToSinkAlso(sink_) + << "The log is coming from *inside the sink*."; + break; + case LogMode::kToSinkOnly: + LOG(LEVEL(severity_)).ToSinkOnly(sink_) + << "The log is coming from *inside the sink*."; + break; + default: + ABSL_RAW_LOG(FATAL, "Invalid mode %d.\n", static_cast<int>(mode_)); + } + } + + private: + absl::LogSeverity severity_; + absl::LogSink* sink_; + LogMode mode_; + }; + + static absl::string_view LogAndReturn(absl::LogSeverity severity, + absl::string_view to_log, + absl::string_view to_return) { + LOG(LEVEL(severity)) << to_log; + return to_return; + } +}; + +TEST_F(ReentrancyTest, LogFunctionThatLogs) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + InSequence seq; + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, _, "hello")); + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, _, "world")); + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kWarning, _, "danger")); + EXPECT_CALL(test_sink, Log(absl::LogSeverity::kInfo, _, "here")); + + test_sink.StartCapturingLogs(); + LOG(INFO) << LogAndReturn(absl::LogSeverity::kInfo, "hello", "world"); + LOG(INFO) << LogAndReturn(absl::LogSeverity::kWarning, "danger", "here"); +} + +TEST_F(ReentrancyTest, RegisteredLogSinkThatLogsInSend) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + ReentrantSendLogSink renentrant_sink(absl::LogSeverity::kInfo); + EXPECT_CALL(test_sink, Log(_, _, "hello world")); + + test_sink.StartCapturingLogs(); + absl::AddLogSink(&renentrant_sink); + LOG(INFO) << "hello world"; + absl::RemoveLogSink(&renentrant_sink); +} + +TEST_F(ReentrancyTest, AlsoLogSinkThatLogsInSend) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kInfo); + EXPECT_CALL(test_sink, Log(_, _, "hello world")); + EXPECT_CALL(test_sink, + Log(_, _, "The log is coming from *inside the sink*.")); + + test_sink.StartCapturingLogs(); + LOG(INFO).ToSinkAlso(&reentrant_sink) << "hello world"; +} + +TEST_F(ReentrancyTest, RegisteredAlsoLogSinkThatLogsInSend) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kInfo); + EXPECT_CALL(test_sink, Log(_, _, "hello world")); + // We only call into the test_log sink once with this message, since the + // second time log statement is run we are in "ThreadIsLogging" mode and all + // the log statements are redirected into stderr. + EXPECT_CALL(test_sink, + Log(_, _, "The log is coming from *inside the sink*.")); + + test_sink.StartCapturingLogs(); + absl::AddLogSink(&reentrant_sink); + LOG(INFO).ToSinkAlso(&reentrant_sink) << "hello world"; + absl::RemoveLogSink(&reentrant_sink); +} + +TEST_F(ReentrancyTest, OnlyLogSinkThatLogsInSend) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kInfo); + EXPECT_CALL(test_sink, + Log(_, _, "The log is coming from *inside the sink*.")); + + test_sink.StartCapturingLogs(); + LOG(INFO).ToSinkOnly(&reentrant_sink) << "hello world"; +} + +TEST_F(ReentrancyTest, RegisteredOnlyLogSinkThatLogsInSend) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kInfo); + EXPECT_CALL(test_sink, + Log(_, _, "The log is coming from *inside the sink*.")); + + test_sink.StartCapturingLogs(); + absl::AddLogSink(&reentrant_sink); + LOG(INFO).ToSinkOnly(&reentrant_sink) << "hello world"; + absl::RemoveLogSink(&reentrant_sink); +} + +using ReentrancyDeathTest = ReentrancyTest; + +TEST_F(ReentrancyDeathTest, LogFunctionThatLogsFatal) { + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink; + + EXPECT_CALL(test_sink, Log) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + EXPECT_CALL(test_sink, Log(_, _, "hello")) + .WillOnce(DeathTestExpectedLogging()); + + test_sink.StartCapturingLogs(); + LOG(INFO) << LogAndReturn(absl::LogSeverity::kFatal, "hello", "world"); + }, + DiedOfFatal, DeathTestValidateExpectations()); +} + +TEST_F(ReentrancyDeathTest, RegisteredLogSinkThatLogsFatalInSend) { + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink; + ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kFatal); + EXPECT_CALL(test_sink, Log) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + EXPECT_CALL(test_sink, Log(_, _, "hello world")) + .WillOnce(DeathTestExpectedLogging()); + + test_sink.StartCapturingLogs(); + absl::AddLogSink(&reentrant_sink); + LOG(INFO) << "hello world"; + // No need to call RemoveLogSink - process is dead at this point. + }, + DiedOfFatal, DeathTestValidateExpectations()); +} + +TEST_F(ReentrancyDeathTest, AlsoLogSinkThatLogsFatalInSend) { + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink; + ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kFatal); + + EXPECT_CALL(test_sink, Log) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + EXPECT_CALL(test_sink, Log(_, _, "hello world")) + .WillOnce(DeathTestExpectedLogging()); + EXPECT_CALL(test_sink, + Log(_, _, "The log is coming from *inside the sink*.")) + .WillOnce(DeathTestExpectedLogging()); + + test_sink.StartCapturingLogs(); + LOG(INFO).ToSinkAlso(&reentrant_sink) << "hello world"; + }, + DiedOfFatal, DeathTestValidateExpectations()); +} + +TEST_F(ReentrancyDeathTest, RegisteredAlsoLogSinkThatLogsFatalInSend) { + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink; + ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kFatal); + EXPECT_CALL(test_sink, Log) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + EXPECT_CALL(test_sink, Log(_, _, "hello world")) + .WillOnce(DeathTestExpectedLogging()); + EXPECT_CALL(test_sink, + Log(_, _, "The log is coming from *inside the sink*.")) + .WillOnce(DeathTestExpectedLogging()); + + test_sink.StartCapturingLogs(); + absl::AddLogSink(&reentrant_sink); + LOG(INFO).ToSinkAlso(&reentrant_sink) << "hello world"; + // No need to call RemoveLogSink - process is dead at this point. + }, + DiedOfFatal, DeathTestValidateExpectations()); +} + +TEST_F(ReentrancyDeathTest, OnlyLogSinkThatLogsFatalInSend) { + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink; + ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kFatal); + EXPECT_CALL(test_sink, Log) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + EXPECT_CALL(test_sink, + Log(_, _, "The log is coming from *inside the sink*.")) + .WillOnce(DeathTestExpectedLogging()); + + test_sink.StartCapturingLogs(); + LOG(INFO).ToSinkOnly(&reentrant_sink) << "hello world"; + }, + DiedOfFatal, DeathTestValidateExpectations()); +} + +TEST_F(ReentrancyDeathTest, RegisteredOnlyLogSinkThatLogsFatalInSend) { + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink; + ReentrantSendLogSink reentrant_sink(absl::LogSeverity::kFatal); + EXPECT_CALL(test_sink, Log) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + EXPECT_CALL(test_sink, + Log(_, _, "The log is coming from *inside the sink*.")) + .WillOnce(DeathTestExpectedLogging()); + + test_sink.StartCapturingLogs(); + absl::AddLogSink(&reentrant_sink); + LOG(INFO).ToSinkOnly(&reentrant_sink) << "hello world"; + // No need to call RemoveLogSink - process is dead at this point. + }, + DiedOfFatal, DeathTestValidateExpectations()); +} + +} // namespace diff --git a/absl/log/log_streamer.h b/absl/log/log_streamer.h new file mode 100644 index 00000000..2d41a07f --- /dev/null +++ b/absl/log/log_streamer.h @@ -0,0 +1,171 @@ +// 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/log_streamer.h +// ----------------------------------------------------------------------------- +// +// This header declares the class `LogStreamer` and convenience functions to +// construct LogStreamer objects with different associated log severity levels. + +#ifndef ABSL_LOG_LOG_STREAMER_H_ +#define ABSL_LOG_LOG_STREAMER_H_ + +#include <ios> +#include <memory> +#include <ostream> +#include <string> +#include <utility> + +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/log/absl_log.h" +#include "absl/strings/internal/ostringstream.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" +#include "absl/utility/utility.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +// LogStreamer +// +// Although you can stream into `LOG(INFO)`, you can't pass it into a function +// that takes a `std::ostream` parameter. `LogStreamer::stream()` provides a +// `std::ostream` that buffers everything that's streamed in. The buffer's +// contents are logged as if by `LOG` when the `LogStreamer` is destroyed. +// If nothing is streamed in, an empty message is logged. If the specified +// severity is `absl::LogSeverity::kFatal`, the program will be terminated when +// the `LogStreamer` is destroyed regardless of whether any data were streamed +// in. +// +// Factory functions corresponding to the `absl::LogSeverity` enumerators +// are provided for convenience; if the desired severity is variable, invoke the +// constructor directly. +// +// LogStreamer is movable, but not copyable. +// +// Examples: +// +// ShaveYakAndWriteToStream( +// yak, absl::LogInfoStreamer(__FILE__, __LINE__).stream()); +// +// { +// // This logs a single line containing data streamed by all three function +// // calls. +// absl::LogStreamer streamer(absl::LogSeverity::kInfo, __FILE__, __LINE__); +// ShaveYakAndWriteToStream(yak1, streamer.stream()); +// streamer.stream() << " "; +// ShaveYakAndWriteToStream(yak2, streamer.stream()); +// streamer.stream() << " "; +// ShaveYakAndWriteToStreamPointer(yak3, &streamer.stream()); +// } +class LogStreamer final { + public: + // LogStreamer::LogStreamer() + // + // Creates a LogStreamer with a given `severity` that will log a message + // attributed to the given `file` and `line`. + explicit LogStreamer(absl::LogSeverity severity, absl::string_view file, + int line) + : severity_(severity), + line_(line), + file_(file), + stream_(absl::in_place, &buf_) { + // To match `LOG`'s defaults: + stream_->setf(std::ios_base::showbase | std::ios_base::boolalpha); + } + + // A moved-from `absl::LogStreamer` does not `LOG` when destroyed, + // and a program that streams into one has undefined behavior. + LogStreamer(LogStreamer&& that) noexcept + : severity_(that.severity_), + line_(that.line_), + file_(std::move(that.file_)), + buf_(std::move(that.buf_)), + stream_(std::move(that.stream_)) { + if (stream_.has_value()) stream_->str(&buf_); + that.stream_.reset(); + } + LogStreamer& operator=(LogStreamer&& that) { + ABSL_LOG_IF(LEVEL(severity_), stream_).AtLocation(file_, line_) << buf_; + severity_ = that.severity_; + file_ = std::move(that.file_); + line_ = that.line_; + buf_ = std::move(that.buf_); + stream_ = std::move(that.stream_); + if (stream_.has_value()) stream_->str(&buf_); + that.stream_.reset(); + return *this; + } + + // LogStreamer::~LogStreamer() + // + // Logs this LogStreamer's buffered content as if by LOG. + ~LogStreamer() { + ABSL_LOG_IF(LEVEL(severity_), stream_.has_value()).AtLocation(file_, line_) + << buf_; + } + + // LogStreamer::stream() + // + // Returns the `std::ostream` to use to write into this LogStreamer' internal + // buffer. + std::ostream& stream() { return *stream_; } + + private: + absl::LogSeverity severity_; + int line_; + std::string file_; + std::string buf_; + // A disengaged `stream_` indicates a moved-from `LogStreamer` that should not + // `LOG` upon destruction. + absl::optional<absl::strings_internal::OStringStream> stream_; +}; + +// LogInfoStreamer() +// +// Returns a LogStreamer that writes at level LogSeverity::kInfo. +inline LogStreamer LogInfoStreamer(absl::string_view file, int line) { + return absl::LogStreamer(absl::LogSeverity::kInfo, file, line); +} + +// LogWarningStreamer() +// +// Returns a LogStreamer that writes at level LogSeverity::kWarning. +inline LogStreamer LogWarningStreamer(absl::string_view file, int line) { + return absl::LogStreamer(absl::LogSeverity::kWarning, file, line); +} + +// LogErrorStreamer() +// +// Returns a LogStreamer that writes at level LogSeverity::kError. +inline LogStreamer LogErrorStreamer(absl::string_view file, int line) { + return absl::LogStreamer(absl::LogSeverity::kError, file, line); +} + +// LogFatalStreamer() +// +// Returns a LogStreamer that writes at level LogSeverity::kFatal. +// +// The program will be terminated when this `LogStreamer` is destroyed, +// regardless of whether any data were streamed in. +inline LogStreamer LogFatalStreamer(absl::string_view file, int line) { + return absl::LogStreamer(absl::LogSeverity::kFatal, file, line); +} + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_LOG_STREAMER_H_ diff --git a/absl/log/log_streamer_test.cc b/absl/log/log_streamer_test.cc new file mode 100644 index 00000000..328d70d0 --- /dev/null +++ b/absl/log/log_streamer_test.cc @@ -0,0 +1,365 @@ +// +// 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/log_streamer.h" + +#include <ios> +#include <iostream> +#include <utility> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/base/internal/sysinfo.h" +#include "absl/base/log_severity.h" +#include "absl/log/internal/test_actions.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/log/internal/test_matchers.h" +#include "absl/log/log.h" +#include "absl/log/scoped_mock_log.h" +#include "absl/strings/string_view.h" + +namespace { +using ::absl::log_internal::DeathTestExpectedLogging; +using ::absl::log_internal::DeathTestUnexpectedLogging; +using ::absl::log_internal::DeathTestValidateExpectations; +#if GTEST_HAS_DEATH_TEST +using ::absl::log_internal::DiedOfFatal; +#endif +using ::absl::log_internal::LogSeverity; +using ::absl::log_internal::Prefix; +using ::absl::log_internal::SourceFilename; +using ::absl::log_internal::SourceLine; +using ::absl::log_internal::Stacktrace; +using ::absl::log_internal::TextMessage; +using ::absl::log_internal::ThreadID; +using ::absl::log_internal::TimestampInMatchWindow; +using ::testing::AnyNumber; +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::IsEmpty; +using ::testing::IsTrue; + +auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( + new absl::log_internal::LogTestEnvironment); + +void WriteToStream(absl::string_view data, std::ostream* os) { + *os << "WriteToStream: " << data; +} +void WriteToStreamRef(absl::string_view data, std::ostream& os) { + os << "WriteToStreamRef: " << data; +} + +TEST(LogStreamerTest, LogInfoStreamer) { + 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::kInfo)), + 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::LogInfoStreamer("path/file.cc", 1234).stream()); +} + +TEST(LogStreamerTest, LogWarningStreamer) { + 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::kWarning)), + 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::LogWarningStreamer("path/file.cc", 1234).stream()); +} + +TEST(LogStreamerTest, LogErrorStreamer) { + 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::LogErrorStreamer("path/file.cc", 1234).stream()); +} + +#if GTEST_HAS_DEATH_TEST +TEST(LogStreamerDeathTest, LogFatalStreamer) { + 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::LogFatalStreamer("path/file.cc", 1234).stream()); + }, + DiedOfFatal, DeathTestValidateExpectations()); +} +#endif + +TEST(LogStreamerTest, LogStreamer) { + 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::LogStreamer(absl::LogSeverity::kError, "path/file.cc", 1234) + .stream()); +} + +#if GTEST_HAS_DEATH_TEST +TEST(LogStreamerDeathTest, LogStreamer) { + 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::LogStreamer(absl::LogSeverity::kFatal, + "path/file.cc", 1234) + .stream()); + }, + DiedOfFatal, DeathTestValidateExpectations()); +} +#endif + +TEST(LogStreamerTest, PassedByReference) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)), + TextMessage(Eq("WriteToStreamRef: foo")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "WriteToStreamRef: foo" + })pb")), + Stacktrace(IsEmpty())))); + + test_sink.StartCapturingLogs(); + WriteToStreamRef("foo", absl::LogInfoStreamer("path/file.cc", 1234).stream()); +} + +TEST(LogStreamerTest, StoredAsLocal) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + auto streamer = absl::LogInfoStreamer("path/file.cc", 1234); + WriteToStream("foo", &streamer.stream()); + streamer.stream() << " "; + WriteToStreamRef("bar", streamer.stream()); + + // The call should happen when `streamer` goes out of scope; if it + // happened before this `EXPECT_CALL` the call would be unexpected and the + // test would fail. + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)), + TextMessage(Eq("WriteToStream: foo WriteToStreamRef: bar")), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { + str: "WriteToStream: foo WriteToStreamRef: bar" + })pb")), + Stacktrace(IsEmpty())))); + + test_sink.StartCapturingLogs(); +} + +#if GTEST_HAS_DEATH_TEST +TEST(LogStreamerDeathTest, StoredAsLocal) { + EXPECT_EXIT( + { + // This is fatal when it goes out of scope, but not until then: + auto streamer = absl::LogFatalStreamer("path/file.cc", 1234); + std::cerr << "I'm still alive" << std::endl; + WriteToStream("foo", &streamer.stream()); + }, + DiedOfFatal, HasSubstr("I'm still alive")); +} +#endif + +TEST(LogStreamerTest, LogsEmptyLine) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(test_sink, Send(AllOf(SourceFilename(Eq("path/file.cc")), + SourceLine(Eq(1234)), TextMessage(Eq("")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "" + })pb")), + Stacktrace(IsEmpty())))); + + test_sink.StartCapturingLogs(); + absl::LogInfoStreamer("path/file.cc", 1234); +} + +#if GTEST_HAS_DEATH_TEST +TEST(LogStreamerDeathTest, LogsEmptyLine) { + EXPECT_EXIT( + { + absl::ScopedMockLog test_sink; + + EXPECT_CALL(test_sink, Log) + .Times(AnyNumber()) + .WillRepeatedly(DeathTestUnexpectedLogging()); + + EXPECT_CALL( + test_sink, + Send(AllOf( + SourceFilename(Eq("path/file.cc")), TextMessage(Eq("")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { str: "" })pb"))))) + .WillOnce(DeathTestExpectedLogging()); + + test_sink.StartCapturingLogs(); + // This is fatal even though it's never used: + auto streamer = absl::LogFatalStreamer("path/file.cc", 1234); + }, + DiedOfFatal, DeathTestValidateExpectations()); +} +#endif + +TEST(LogStreamerTest, MoveConstruction) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)), + LogSeverity(Eq(absl::LogSeverity::kInfo)), + TextMessage(Eq("hello 0x10 world 0x10")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "hello 0x10 world 0x10" + })pb")), + Stacktrace(IsEmpty())))); + + test_sink.StartCapturingLogs(); + auto streamer1 = absl::LogInfoStreamer("path/file.cc", 1234); + streamer1.stream() << "hello " << std::hex << 16; + absl::LogStreamer streamer2(std::move(streamer1)); + streamer2.stream() << " world " << 16; +} + +TEST(LogStreamerTest, MoveAssignment) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + testing::InSequence seq; + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq("path/file2.cc")), SourceLine(Eq(5678)), + LogSeverity(Eq(absl::LogSeverity::kWarning)), + TextMessage(Eq("something else")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "something else" + })pb")), + Stacktrace(IsEmpty())))); + EXPECT_CALL( + test_sink, + Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)), + LogSeverity(Eq(absl::LogSeverity::kInfo)), + TextMessage(Eq("hello 0x10 world 0x10")), + ENCODED_MESSAGE(EqualsProto(R"pb(value { + str: "hello 0x10 world 0x10" + })pb")), + Stacktrace(IsEmpty())))); + + test_sink.StartCapturingLogs(); + auto streamer1 = absl::LogInfoStreamer("path/file.cc", 1234); + streamer1.stream() << "hello " << std::hex << 16; + auto streamer2 = absl::LogWarningStreamer("path/file2.cc", 5678); + streamer2.stream() << "something else"; + streamer2 = std::move(streamer1); + streamer2.stream() << " world " << 16; +} + +TEST(LogStreamerTest, CorrectDefaultFlags) { + absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + + // The `boolalpha` and `showbase` flags should be set by default, to match + // `LOG`. + EXPECT_CALL(test_sink, Send(AllOf(TextMessage(Eq("false0xdeadbeef"))))) + .Times(2); + + test_sink.StartCapturingLogs(); + absl::LogInfoStreamer("path/file.cc", 1234).stream() + << false << std::hex << 0xdeadbeef; + LOG(INFO) << false << std::hex << 0xdeadbeef; +} + +} // namespace diff --git a/absl/log/scoped_mock_log.cc b/absl/log/scoped_mock_log.cc new file mode 100644 index 00000000..4ebc0a9f --- /dev/null +++ b/absl/log/scoped_mock_log.cc @@ -0,0 +1,86 @@ +// +// 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/scoped_mock_log.h" + +#include <atomic> +#include <string> + +#include "gmock/gmock.h" +#include "absl/base/config.h" +#include "absl/base/internal/raw_logging.h" +#include "absl/log/log_entry.h" +#include "absl/log/log_sink.h" +#include "absl/log/log_sink_registry.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +ScopedMockLog::ScopedMockLog(MockLogDefault default_exp) + : sink_(this), is_capturing_logs_(false) { + if (default_exp == MockLogDefault::kIgnoreUnexpected) { + // Ignore all calls to Log we did not set expectations for. + EXPECT_CALL(*this, Log).Times(::testing::AnyNumber()); + } else { + // Disallow all calls to Log we did not set expectations for. + EXPECT_CALL(*this, Log).Times(0); + } + // By default Send mock forwards to Log mock. + EXPECT_CALL(*this, Send) + .Times(::testing::AnyNumber()) + .WillRepeatedly([this](const absl::LogEntry& entry) { + is_triggered_.store(true, std::memory_order_relaxed); + Log(entry.log_severity(), std::string(entry.source_filename()), + std::string(entry.text_message())); + }); + + // By default We ignore all Flush calls. + EXPECT_CALL(*this, Flush).Times(::testing::AnyNumber()); +} + +ScopedMockLog::~ScopedMockLog() { + ABSL_RAW_CHECK(is_triggered_.load(std::memory_order_relaxed), + "Did you forget to call StartCapturingLogs()?"); + + if (is_capturing_logs_) StopCapturingLogs(); +} + +void ScopedMockLog::StartCapturingLogs() { + ABSL_RAW_CHECK(!is_capturing_logs_, + "StartCapturingLogs() can be called only when the " + "absl::ScopedMockLog object is not capturing logs."); + + is_capturing_logs_ = true; + is_triggered_.store(true, std::memory_order_relaxed); + absl::AddLogSink(&sink_); +} + +void ScopedMockLog::StopCapturingLogs() { + ABSL_RAW_CHECK(is_capturing_logs_, + "StopCapturingLogs() can be called only when the " + "absl::ScopedMockLog object is capturing logs."); + + is_capturing_logs_ = false; + absl::RemoveLogSink(&sink_); +} + +absl::LogSink& ScopedMockLog::UseAsLocalSink() { + is_triggered_.store(true, std::memory_order_relaxed); + return sink_; +} + +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/log/scoped_mock_log.h b/absl/log/scoped_mock_log.h new file mode 100644 index 00000000..44470c16 --- /dev/null +++ b/absl/log/scoped_mock_log.h @@ -0,0 +1,194 @@ +// 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/scoped_mock_log.h +// ----------------------------------------------------------------------------- +// +// This header declares `class absl::ScopedMockLog`, for use in testing. + +#ifndef ABSL_LOG_SCOPED_MOCK_LOG_H_ +#define ABSL_LOG_SCOPED_MOCK_LOG_H_ + +#include <atomic> +#include <string> + +#include "gmock/gmock.h" +#include "absl/base/config.h" +#include "absl/base/log_severity.h" +#include "absl/log/log_entry.h" +#include "absl/log/log_sink.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +// MockLogDefault +// +// Controls how ScopedMockLog responds to unexpected calls by default. +enum class MockLogDefault { kIgnoreUnexpected, kDisallowUnexpected }; + +// ScopedMockLog +// +// ScopedMockLog is a LogSink that intercepts LOG() messages issued during its +// lifespan. +// +// Using this together with GoogleTest, it's easy to test how a piece of code +// calls LOG(). The typical usage, noting the distinction between +// "uninteresting" and "unexpected", looks like this: +// +// using ::testing::_; +// using ::testing::AnyNumber; +// using ::testing::EndsWith; +// using ::testing::kDoNotCaptureLogsYet; +// using ::testing::Lt; +// +// TEST(FooTest, LogsCorrectly) { +// // Simple robust setup, ignores unexpected logs. +// absl::ScopedMockLog log; +// +// // We expect the WARNING "Something bad!" exactly twice. +// EXPECT_CALL(log, Log(absl::LogSeverity::kWarning, _, "Something bad!")) +// .Times(2); +// +// // But we want no messages from foo.cc. +// EXPECT_CALL(log, Log(_, EndsWith("/foo.cc"), _)).Times(0); +// +// log.StartCapturingLogs(); // Call this after done setting expectations. +// Foo(); // Exercises the code under test. +// } +// +// TEST(BarTest, LogsExactlyCorrectly) { +// // Strict checking, fails for unexpected logs. +// absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected); +// +// // ... but ignore low severity messages +// EXPECT_CALL(log, Log(Lt(absl::LogSeverity::kWarning), _, _)) +// .Times(AnyNumber()); +// +// // We expect the ERROR "Something bad!" exactly once. +// EXPECT_CALL(log, Log(absl::LogSeverity::kError, EndsWith("/foo.cc"), +// "Something bad!")) +// .Times(1); +// +// log.StartCapturingLogs(); // Call this after done setting expectations. +// Bar(); // Exercises the code under test. +// } +// +// Note that in a multi-threaded environment, all LOG() messages from a single +// thread will be handled in sequence, but that cannot be guaranteed for +// messages from different threads. In fact, if the same or multiple +// expectations are matched on two threads concurrently, their actions will be +// executed concurrently as well and may interleave. +class ScopedMockLog final { + public: + // ScopedMockLog::ScopedMockLog() + // + // Sets up the log and adds default expectations. + explicit ScopedMockLog( + MockLogDefault default_exp = MockLogDefault::kIgnoreUnexpected); + ScopedMockLog(const ScopedMockLog&) = delete; + ScopedMockLog& operator=(const ScopedMockLog&) = delete; + + // ScopedMockLog::~ScopedMockLog() + // + // Stops intercepting logs and destroys this ScopedMockLog. + ~ScopedMockLog(); + + // ScopedMockLog::StartCapturingLogs() + // + // Starts log capturing if the object isn't already doing so. Otherwise + // crashes. + // + // Usually this method is called in the same thread that created this + // ScopedMockLog. It is the user's responsibility to not call this method if + // another thread may be calling it or StopCapturingLogs() at the same time. + // It is undefined behavior to add expectations while capturing logs is + // enabled. + void StartCapturingLogs(); + + // ScopedMockLog::StopCapturingLogs() + // + // Stops log capturing if the object is capturing logs. Otherwise crashes. + // + // Usually this method is called in the same thread that created this object. + // It is the user's responsibility to not call this method if another thread + // may be calling it or StartCapturingLogs() at the same time. + // + // It is UB to add expectations, while capturing logs is enabled. + void StopCapturingLogs(); + + // ScopedMockLog::UseAsLocalSink() + // + // Each `ScopedMockLog` is implemented with an `absl::LogSink`; this method + // returns a reference to that sink (e.g. for use with + // `LOG(...).ToSinkOnly()`) and marks the `ScopedMockLog` as having been used + // even if `StartCapturingLogs` is never called. + absl::LogSink& UseAsLocalSink(); + + // Implements the mock method: + // + // void Log(LogSeverity severity, absl::string_view file_path, + // absl::string_view message); + // + // The second argument to Log() is the full path of the source file in + // which the LOG() was issued. + // + // This is a shorthand form, which should be used by most users. Use the + // `Send` mock only if you want to add expectations for other log message + // attributes. + MOCK_METHOD(void, Log, + (absl::LogSeverity severity, const std::string& file_path, + const std::string& message)); + + // Implements the mock method: + // + // void Send(const absl::LogEntry& entry); + // + // This is the most generic form of mock that can be specified. Use this mock + // only if you want to add expectations for log message attributes different + // from the log message text, log message path and log message severity. + // + // If no expectations are specified for this mock, the default action is to + // forward the call to the `Log` mock. + MOCK_METHOD(void, Send, (const absl::LogEntry&)); + + // Implements the mock method: + // + // void Flush(); + // + // Use this mock only if you want to add expectations for log flush calls. + MOCK_METHOD(void, Flush, ()); + + private: + class ForwardingSink final : public absl::LogSink { + public: + explicit ForwardingSink(ScopedMockLog* sml) : sml_(sml) {} + ForwardingSink(const ForwardingSink&) = delete; + ForwardingSink& operator=(const ForwardingSink&) = delete; + void Send(const absl::LogEntry& entry) override { sml_->Send(entry); } + void Flush() override { sml_->Flush(); } + + private: + ScopedMockLog* sml_; + }; + + ForwardingSink sink_; + bool is_capturing_logs_; + std::atomic<bool> is_triggered_; +}; + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_SCOPED_MOCK_LOG_H_ diff --git a/absl/log/scoped_mock_log_test.cc b/absl/log/scoped_mock_log_test.cc new file mode 100644 index 00000000..44b8d737 --- /dev/null +++ b/absl/log/scoped_mock_log_test.cc @@ -0,0 +1,290 @@ +// +// 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/scoped_mock_log.h" + +#include <memory> +#include <thread> // NOLINT(build/c++11) + +#include "gmock/gmock.h" +#include "gtest/gtest-spi.h" +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/base/log_severity.h" +#include "absl/log/globals.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/log/internal/test_matchers.h" +#include "absl/log/log.h" +#include "absl/memory/memory.h" +#include "absl/strings/match.h" +#include "absl/strings/string_view.h" +#include "absl/synchronization/barrier.h" +#include "absl/synchronization/notification.h" + +namespace { + +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::InSequence; +using ::testing::Lt; +using ::testing::Truly; +using absl::log_internal::SourceBasename; +using absl::log_internal::SourceFilename; +using absl::log_internal::SourceLine; +using absl::log_internal::TextMessageWithPrefix; +using absl::log_internal::ThreadID; + +auto* test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( + new absl::log_internal::LogTestEnvironment); + +#if GTEST_HAS_DEATH_TEST +TEST(ScopedMockLogDeathTest, + StartCapturingLogsCannotBeCalledWhenAlreadyCapturing) { + EXPECT_DEATH( + { + absl::ScopedMockLog log; + log.StartCapturingLogs(); + log.StartCapturingLogs(); + }, + "StartCapturingLogs"); +} + +TEST(ScopedMockLogDeathTest, StopCapturingLogsCannotBeCalledWhenNotCapturing) { + EXPECT_DEATH( + { + absl::ScopedMockLog log; + log.StopCapturingLogs(); + }, + "StopCapturingLogs"); +} +#endif + +// Tests that ScopedMockLog intercepts LOG()s when it's alive. +TEST(ScopedMockLogTest, LogMockCatchAndMatchStrictExpectations) { + absl::ScopedMockLog log; + + // The following expectations must match in the order they appear. + InSequence s; + EXPECT_CALL(log, + Log(absl::LogSeverity::kWarning, HasSubstr(__FILE__), "Danger.")); + EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "Working...")).Times(2); + EXPECT_CALL(log, Log(absl::LogSeverity::kError, _, "Bad!!")); + + log.StartCapturingLogs(); + LOG(WARNING) << "Danger."; + LOG(INFO) << "Working..."; + LOG(INFO) << "Working..."; + LOG(ERROR) << "Bad!!"; +} + +TEST(ScopedMockLogTest, LogMockCatchAndMatchSendExpectations) { + absl::ScopedMockLog log; + + EXPECT_CALL( + log, + Send(AllOf(SourceFilename(Eq("/my/very/very/very_long_source_file.cc")), + SourceBasename(Eq("very_long_source_file.cc")), + SourceLine(Eq(777)), ThreadID(Eq(absl::LogEntry::tid_t{1234})), + TextMessageWithPrefix(Truly([](absl::string_view msg) { + return absl::EndsWith( + msg, " very_long_source_file.cc:777] Info message"); + }))))); + + log.StartCapturingLogs(); + LOG(INFO) + .AtLocation("/my/very/very/very_long_source_file.cc", 777) + .WithThreadID(1234) + << "Info message"; +} + +TEST(ScopedMockLogTest, ScopedMockLogCanBeNice) { + absl::ScopedMockLog log; + + InSequence s; + EXPECT_CALL(log, + Log(absl::LogSeverity::kWarning, HasSubstr(__FILE__), "Danger.")); + EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "Working...")).Times(2); + EXPECT_CALL(log, Log(absl::LogSeverity::kError, _, "Bad!!")); + + log.StartCapturingLogs(); + + // Any number of these are OK. + LOG(INFO) << "Info message."; + // Any number of these are OK. + LOG(WARNING).AtLocation("SomeOtherFile.cc", 100) << "Danger "; + + LOG(WARNING) << "Danger."; + + // Any number of these are OK. + LOG(INFO) << "Info message."; + // Any number of these are OK. + LOG(WARNING).AtLocation("SomeOtherFile.cc", 100) << "Danger "; + + LOG(INFO) << "Working..."; + + // Any number of these are OK. + LOG(INFO) << "Info message."; + // Any number of these are OK. + LOG(WARNING).AtLocation("SomeOtherFile.cc", 100) << "Danger "; + + LOG(INFO) << "Working..."; + + // Any number of these are OK. + LOG(INFO) << "Info message."; + // Any number of these are OK. + LOG(WARNING).AtLocation("SomeOtherFile.cc", 100) << "Danger "; + + LOG(ERROR) << "Bad!!"; + + // Any number of these are OK. + LOG(INFO) << "Info message."; + // Any number of these are OK. + LOG(WARNING).AtLocation("SomeOtherFile.cc", 100) << "Danger "; +} + +// Tests that ScopedMockLog generates a test failure if a message is logged +// that is not expected (here, that means ERROR or FATAL). +TEST(ScopedMockLogTest, RejectsUnexpectedLogs) { + EXPECT_NONFATAL_FAILURE( + { + absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected); + // Any INFO and WARNING messages are permitted. + EXPECT_CALL(log, Log(Lt(absl::LogSeverity::kError), _, _)) + .Times(AnyNumber()); + log.StartCapturingLogs(); + LOG(INFO) << "Ignored"; + LOG(WARNING) << "Ignored"; + LOG(ERROR) << "Should not be ignored"; + }, + "Should not be ignored"); +} + +TEST(ScopedMockLogTest, CapturesLogsAfterStartCapturingLogs) { + absl::SetStderrThreshold(absl::LogSeverityAtLeast::kInfinity); + absl::ScopedMockLog log; + + // The ScopedMockLog object shouldn't see these LOGs, as it hasn't + // started capturing LOGs yet. + LOG(INFO) << "Ignored info"; + LOG(WARNING) << "Ignored warning"; + LOG(ERROR) << "Ignored error"; + + EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "Expected info")); + log.StartCapturingLogs(); + + // Only this LOG will be seen by the ScopedMockLog. + LOG(INFO) << "Expected info"; +} + +TEST(ScopedMockLogTest, DoesNotCaptureLogsAfterStopCapturingLogs) { + absl::ScopedMockLog log; + EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, "Expected info")); + + log.StartCapturingLogs(); + + // This LOG should be seen by the ScopedMockLog. + LOG(INFO) << "Expected info"; + + log.StopCapturingLogs(); + + // The ScopedMockLog object shouldn't see these LOGs, as it has + // stopped capturing LOGs. + LOG(INFO) << "Ignored info"; + LOG(WARNING) << "Ignored warning"; + LOG(ERROR) << "Ignored error"; +} + +// Tests that all messages are intercepted regardless of issuing thread. The +// purpose of this test is NOT to exercise thread-safety. +TEST(ScopedMockLogTest, LogFromMultipleThreads) { + absl::ScopedMockLog log; + + // We don't establish an order to expectations here, since the threads may + // execute their log statements in different order. + EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, __FILE__, "Thread 1")); + EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, __FILE__, "Thread 2")); + + log.StartCapturingLogs(); + + absl::Barrier barrier(2); + std::thread thread1([&barrier]() { + barrier.Block(); + LOG(INFO) << "Thread 1"; + }); + std::thread thread2([&barrier]() { + barrier.Block(); + LOG(INFO) << "Thread 2"; + }); + + thread1.join(); + thread2.join(); +} + +// Tests that no sequence will be imposed on two LOG message expectations from +// different threads. This test would actually deadlock if replaced to two LOG +// statements from the same thread. +TEST(ScopedMockLogTest, NoSequenceWithMultipleThreads) { + absl::ScopedMockLog log; + + absl::Barrier barrier(2); + EXPECT_CALL(log, Log(absl::LogSeverity::kInfo, _, _)) + .Times(2) + .WillRepeatedly([&barrier]() { barrier.Block(); }); + + log.StartCapturingLogs(); + + std::thread thread1([]() { LOG(INFO) << "Thread 1"; }); + std::thread thread2([]() { LOG(INFO) << "Thread 2"; }); + + thread1.join(); + thread2.join(); +} + +TEST(ScopedMockLogTsanTest, + ScopedMockLogCanBeDeletedWhenAnotherThreadIsLogging) { + auto log = absl::make_unique<absl::ScopedMockLog>(); + EXPECT_CALL(*log, Log(absl::LogSeverity::kInfo, __FILE__, "Thread log")) + .Times(AnyNumber()); + + log->StartCapturingLogs(); + + absl::Notification logging_started; + + std::thread thread([&logging_started]() { + for (int i = 0; i < 100; ++i) { + if (i == 50) logging_started.Notify(); + LOG(INFO) << "Thread log"; + } + }); + + logging_started.WaitForNotification(); + log.reset(); + thread.join(); +} + +TEST(ScopedMockLogTest, AsLocalSink) { + absl::ScopedMockLog log(absl::MockLogDefault::kDisallowUnexpected); + + EXPECT_CALL(log, Log(_, _, "two")); + EXPECT_CALL(log, Log(_, _, "three")); + + LOG(INFO) << "one"; + LOG(INFO).ToSinkOnly(&log.UseAsLocalSink()) << "two"; + LOG(INFO).ToSinkAlso(&log.UseAsLocalSink()) << "three"; +} + +} // namespace diff --git a/absl/log/stripping_test.cc b/absl/log/stripping_test.cc new file mode 100644 index 00000000..d6a6606e --- /dev/null +++ b/absl/log/stripping_test.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. +// +// Tests for stripping of literal strings. +// --------------------------------------- +// +// When a `LOG` statement can be trivially proved at compile time to never fire, +// e.g. due to `ABSL_MIN_LOG_LEVEL`, `NDEBUG`, or some explicit condition, data +// streamed in can be dropped from the compiled program completely if they are +// not used elsewhere. This most commonly affects string literals, which users +// often want to strip to reduce binary size and/or redact information about +// their program's internals (e.g. in a release build). +// +// These tests log strings and then validate whether they appear in the compiled +// binary. This is done by opening the file corresponding to the running test +// and running a simple string search on its contents. The strings to be logged +// and searched for must be unique, and we must take care not to emit them into +// the binary in any other place, e.g. when searching for them. The latter is +// accomplished by computing them using base64; the source string appears in the +// binary but the target string is computed at runtime. + +#include <stdio.h> + +#if defined(__MACH__) +#include <mach-o/dyld.h> +#elif defined(_WIN32) +#include <Windows.h> +#include <tchar.h> +#endif + +#include <algorithm> +#include <functional> +#include <memory> +#include <ostream> +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/internal/strerror.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/strings/escaping.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" + +namespace { +using ::testing::_; +using ::testing::Eq; +using ::testing::NotNull; + +using absl::log_internal::kAbslMinLogLevel; + +std::string Base64UnescapeOrDie(absl::string_view data) { + std::string decoded; + CHECK(absl::Base64Unescape(data, &decoded)); + return decoded; +} + +// ----------------------------------------------------------------------------- +// A Googletest matcher which searches the running binary for a given string +// ----------------------------------------------------------------------------- + +// This matcher is used to validate that literal strings streamed into +// `LOG` statements that ought to be compiled out (e.g. `LOG_IF(INFO, false)`) +// do not appear in the binary. +// +// Note that passing the string to be sought directly to `FileHasSubstr()` all +// but forces its inclusion in the binary regardless of the logging library's +// behavior. For example: +// +// LOG_IF(INFO, false) << "you're the man now dog"; +// // This will always pass: +// // EXPECT_THAT(fp, FileHasSubstr("you're the man now dog")); +// // So use this instead: +// EXPECT_THAT(fp, FileHasSubstr( +// Base64UnescapeOrDie("eW91J3JlIHRoZSBtYW4gbm93IGRvZw=="))); + +class FileHasSubstrMatcher final : public ::testing::MatcherInterface<FILE*> { + public: + explicit FileHasSubstrMatcher(absl::string_view needle) : needle_(needle) {} + + bool MatchAndExplain( + FILE* fp, ::testing::MatchResultListener* listener) const override { + std::string buf( + std::max<std::string::size_type>(needle_.size() * 2, 163840000), '\0'); + size_t buf_start_offset = 0; // The file offset of the byte at `buf[0]`. + size_t buf_data_size = 0; // The number of bytes of `buf` which contain + // data. + + ::fseek(fp, 0, SEEK_SET); + while (true) { + // Fill the buffer to capacity or EOF: + while (buf_data_size < buf.size()) { + const size_t ret = fread(&buf[buf_data_size], sizeof(char), + buf.size() - buf_data_size, fp); + if (ret == 0) break; + buf_data_size += ret; + } + if (ferror(fp)) { + *listener << "error reading file"; + return false; + } + const absl::string_view haystack(&buf[0], buf_data_size); + const auto off = haystack.find(needle_); + if (off != haystack.npos) { + *listener << "string found at offset " << buf_start_offset + off; + return true; + } + if (feof(fp)) { + *listener << "string not found"; + return false; + } + // Copy the end of `buf` to the beginning so we catch matches that span + // buffer boundaries. `buf` and `buf_data_size` are always large enough + // that these ranges don't overlap. + memcpy(&buf[0], &buf[buf_data_size - needle_.size()], needle_.size()); + buf_start_offset += buf_data_size - needle_.size(); + buf_data_size = needle_.size(); + } + } + void DescribeTo(std::ostream* os) const override { + *os << "contains the string \"" << needle_ << "\" (base64(\"" + << Base64UnescapeOrDie(needle_) << "\"))"; + } + + void DescribeNegationTo(std::ostream* os) const override { + *os << "does not "; + DescribeTo(os); + } + + private: + std::string needle_; +}; + +class StrippingTest : public ::testing::Test { + protected: + void SetUp() override { +#ifndef NDEBUG + // Non-optimized builds don't necessarily eliminate dead code at all, so we + // don't attempt to validate stripping against such builds. + GTEST_SKIP() << "StrippingTests skipped since this build is not optimized"; +#elif defined(__EMSCRIPTEN__) + // These tests require a way to examine the running binary and look for + // strings; there's no portable way to do that. + GTEST_SKIP() + << "StrippingTests skipped since this platform is not optimized"; +#endif + } + + // Opens this program's executable file. Returns `nullptr` and writes to + // `stderr` on failure. + std::unique_ptr<FILE, std::function<void(FILE*)>> OpenTestExecutable() { +#if defined(__linux__) + std::unique_ptr<FILE, std::function<void(FILE*)>> fp( + fopen("/proc/self/exe", "rb"), [](FILE* fp) { fclose(fp); }); + if (!fp) { + const std::string err = absl::base_internal::StrError(errno); + absl::FPrintF(stderr, "Failed to open /proc/self/exe: %s\n", err); + } + return fp; +#elif defined(__Fuchsia__) + // TODO(b/242579714): We need to restore the test coverage on this platform. + std::unique_ptr<FILE, std::function<void(FILE*)>> fp( + fopen(absl::StrCat("/pkg/bin/", + absl::flags_internal::ShortProgramInvocationName()) + .c_str(), + "rb"), + [](FILE* fp) { fclose(fp); }); + if (!fp) { + const std::string err = absl::base_internal::StrError(errno); + absl::FPrintF(stderr, "Failed to open /pkg/bin/<binary name>: %s\n", err); + } + return fp; +#elif defined(__MACH__) + uint32_t size = 0; + int ret = _NSGetExecutablePath(nullptr, &size); + if (ret != -1) { + absl::FPrintF(stderr, + "Failed to get executable path: " + "_NSGetExecutablePath(nullptr) returned %d\n", + ret); + return nullptr; + } + std::string path(size, '\0'); + ret = _NSGetExecutablePath(&path[0], &size); + if (ret != 0) { + absl::FPrintF( + stderr, + "Failed to get executable path: _NSGetExecutablePath(buffer) " + "returned %d\n", + ret); + return nullptr; + } + std::unique_ptr<FILE, std::function<void(FILE*)>> fp( + fopen(path.c_str(), "rb"), [](FILE* fp) { fclose(fp); }); + if (!fp) { + const std::string err = absl::base_internal::StrError(errno); + absl::FPrintF(stderr, "Failed to open executable at %s: %s\n", path, err); + } + return fp; +#elif defined(_WIN32) + std::basic_string<TCHAR> path(4096, _T('\0')); + while (true) { + const uint32_t ret = ::GetModuleFileName(nullptr, &path[0], + static_cast<DWORD>(path.size())); + if (ret == 0) { + absl::FPrintF( + stderr, + "Failed to get executable path: GetModuleFileName(buffer) " + "returned 0\n"); + return nullptr; + } + if (ret < path.size()) break; + path.resize(path.size() * 2, _T('\0')); + } + std::unique_ptr<FILE, std::function<void(FILE*)>> fp( + _tfopen(path.c_str(), _T("rb")), [](FILE* fp) { fclose(fp); }); + if (!fp) absl::FPrintF(stderr, "Failed to open executable\n"); + return fp; +#else + absl::FPrintF(stderr, + "OpenTestExecutable() unimplemented on this platform\n"); + return nullptr; +#endif + } + + ::testing::Matcher<FILE*> FileHasSubstr(absl::string_view needle) { + return MakeMatcher(new FileHasSubstrMatcher(needle)); + } +}; + +// This tests whether out methodology for testing stripping works on this +// platform by looking for one string that definitely ought to be there and one +// that definitely ought not to. If this fails, none of the `StrippingTest`s +// are going to produce meaningful results. +TEST_F(StrippingTest, Control) { + constexpr char kEncodedPositiveControl[] = + "U3RyaXBwaW5nVGVzdC5Qb3NpdGl2ZUNvbnRyb2w="; + const std::string encoded_negative_control = + absl::Base64Escape("StrippingTest.NegativeControl"); + + // Verify this mainly so we can encode other strings and know definitely they + // won't encode to `kEncodedPositiveControl`. + EXPECT_THAT(Base64UnescapeOrDie("U3RyaXBwaW5nVGVzdC5Qb3NpdGl2ZUNvbnRyb2w="), + Eq("StrippingTest.PositiveControl")); + + auto exe = OpenTestExecutable(); + ASSERT_THAT(exe, NotNull()); + EXPECT_THAT(exe.get(), FileHasSubstr(kEncodedPositiveControl)); + EXPECT_THAT(exe.get(), Not(FileHasSubstr(encoded_negative_control))); +} + +TEST_F(StrippingTest, Literal) { + // 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.Literal"); + LOG(INFO) << "U3RyaXBwaW5nVGVzdC5MaXRlcmFs"; + auto exe = OpenTestExecutable(); + ASSERT_THAT(exe, NotNull()); + if (absl::LogSeverity::kInfo >= kAbslMinLogLevel) { + EXPECT_THAT(exe.get(), FileHasSubstr(needle)); + } else { + EXPECT_THAT(exe.get(), Not(FileHasSubstr(needle))); + } +} + +TEST_F(StrippingTest, LiteralInExpression) { + // 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.LiteralInExpression"); + LOG(INFO) << absl::StrCat("secret: ", + "U3RyaXBwaW5nVGVzdC5MaXRlcmFsSW5FeHByZXNzaW9u"); + std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable(); + ASSERT_THAT(exe, NotNull()); + if (absl::LogSeverity::kInfo >= kAbslMinLogLevel) { + EXPECT_THAT(exe.get(), FileHasSubstr(needle)); + } else { + EXPECT_THAT(exe.get(), Not(FileHasSubstr(needle))); + } +} + +TEST_F(StrippingTest, Fatal) { + // 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.Fatal"); + EXPECT_DEATH_IF_SUPPORTED(LOG(FATAL) << "U3RyaXBwaW5nVGVzdC5GYXRhbA==", ""); + std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable(); + ASSERT_THAT(exe, NotNull()); + if (absl::LogSeverity::kFatal >= kAbslMinLogLevel) { + EXPECT_THAT(exe.get(), FileHasSubstr(needle)); + } else { + EXPECT_THAT(exe.get(), Not(FileHasSubstr(needle))); + } +} + +TEST_F(StrippingTest, Level) { + const std::string needle = absl::Base64Escape("StrippingTest.Level"); + volatile auto severity = absl::LogSeverity::kWarning; + // Ensure that `severity` is not a compile-time constant to prove that + // stripping works regardless: + LOG(LEVEL(severity)) << "U3RyaXBwaW5nVGVzdC5MZXZlbA=="; + std::unique_ptr<FILE, std::function<void(FILE*)>> exe = OpenTestExecutable(); + ASSERT_THAT(exe, NotNull()); + if (absl::LogSeverity::kFatal >= kAbslMinLogLevel) { + // This can't be stripped at compile-time because it might evaluate to a + // level that shouldn't be stripped. + EXPECT_THAT(exe.get(), FileHasSubstr(needle)); + } else { +#if (defined(_MSC_VER) && !defined(__clang__)) || defined(__APPLE__) + // Dead code elimination misses this case. +#else + // All levels should be stripped, so it doesn't matter what the severity + // winds up being. + EXPECT_THAT(exe.get(), Not(FileHasSubstr(needle))); +#endif + } +} + +} // namespace diff --git a/absl/log/structured.h b/absl/log/structured.h new file mode 100644 index 00000000..9ad69fbd --- /dev/null +++ b/absl/log/structured.h @@ -0,0 +1,70 @@ +// 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/structured.h +// ----------------------------------------------------------------------------- +// +// This header declares APIs supporting structured logging, allowing log +// statements to be more easily parsed, especially by automated processes. +// +// When structured logging is in use, data streamed into a `LOG` statement are +// encoded as `Value` fields in a `logging.proto.Event` protocol buffer message. +// The individual data are exposed programmatically to `LogSink`s and to the +// user via some log reading tools which are able to query the structured data +// more usefully than would be possible if each message was a single opaque +// string. These helpers allow user code to add additional structure to the +// data they stream. + +#ifndef ABSL_LOG_STRUCTURED_H_ +#define ABSL_LOG_STRUCTURED_H_ + +#include <ostream> + +#include "absl/base/config.h" +#include "absl/log/internal/structured.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +// LogAsLiteral() +// +// Annotates its argument as a string literal so that structured logging +// captures it as a `literal` field instead of a `str` field (the default). +// This does not affect the text representation, only the structure. +// +// Streaming `LogAsLiteral(s)` into a `std::ostream` behaves just like streaming +// `s` directly. +// +// Using `LogAsLiteral()` is occasionally appropriate and useful when proxying +// data logged from another system or another language. For example: +// +// void Logger::LogString(absl::string_view str, absl::LogSeverity severity, +// const char *file, int line) { +// LOG(LEVEL(severity)).AtLocation(file, line) << str; +// } +// void Logger::LogStringLiteral(absl::string_view str, +// absl::LogSeverity severity, const char *file, +// int line) { +// LOG(LEVEL(severity)).AtLocation(file, line) << absl::LogAsLiteral(str); +// } +inline log_internal::AsLiteralImpl LogAsLiteral(absl::string_view s) { + return log_internal::AsLiteralImpl(s); +} + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_STRUCTURED_H_ diff --git a/absl/log/structured_test.cc b/absl/log/structured_test.cc new file mode 100644 index 00000000..490a35d8 --- /dev/null +++ b/absl/log/structured_test.cc @@ -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. + +#include "absl/log/structured.h" + +#include <ios> +#include <sstream> +#include <string> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/log/internal/test_helpers.h" +#include "absl/log/internal/test_matchers.h" +#include "absl/log/log.h" +#include "absl/log/scoped_mock_log.h" + +namespace { +using ::absl::log_internal::MatchesOstream; +using ::absl::log_internal::TextMessage; +using ::testing::Eq; + +auto *test_env ABSL_ATTRIBUTE_UNUSED = ::testing::AddGlobalTestEnvironment( + new absl::log_internal::LogTestEnvironment); + +// Abseil Logging library uses these by default, so we set them on the +// `std::ostream` we compare against too. +std::ios &LoggingDefaults(std::ios &str) { + str.setf(std::ios_base::showbase | std::ios_base::boolalpha | + std::ios_base::internal); + return str; +} + +TEST(StreamingFormatTest, LogAsLiteral) { + std::ostringstream stream; + const std::string not_a_literal("hello world"); + stream << LoggingDefaults << absl::LogAsLiteral(not_a_literal); + + absl::ScopedMockLog sink; + + EXPECT_CALL(sink, + Send(AllOf(TextMessage(MatchesOstream(stream)), + TextMessage(Eq("hello world")), + ENCODED_MESSAGE(EqualsProto( + R"pb(value { literal: "hello world" })pb"))))); + + sink.StartCapturingLogs(); + LOG(INFO) << absl::LogAsLiteral(not_a_literal); +} + +} // namespace diff --git a/absl/memory/BUILD.bazel b/absl/memory/BUILD.bazel index 389aedf3..a93f54a6 100644 --- a/absl/memory/BUILD.bazel +++ b/absl/memory/BUILD.bazel @@ -50,18 +50,3 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) - -cc_test( - name = "memory_exception_safety_test", - srcs = [ - "memory_exception_safety_test.cc", - ], - copts = ABSL_TEST_COPTS, - linkopts = ABSL_DEFAULT_LINKOPTS, - deps = [ - ":memory", - "//absl/base:config", - "//absl/base:exception_safety_testing", - "@com_google_googletest//:gtest_main", - ], -) diff --git a/absl/memory/CMakeLists.txt b/absl/memory/CMakeLists.txt index 9d50e1dc..c5ed4b42 100644 --- a/absl/memory/CMakeLists.txt +++ b/absl/memory/CMakeLists.txt @@ -39,17 +39,3 @@ absl_cc_test( absl::core_headers GTest::gmock_main ) - -absl_cc_test( - NAME - memory_exception_safety_test - SRCS - "memory_exception_safety_test.cc" - COPTS - ${ABSL_TEST_COPTS} - DEPS - absl::memory - absl::config - absl::exception_safety_testing - GTest::gmock_main -) diff --git a/absl/memory/memory.h b/absl/memory/memory.h index d6332606..3508135c 100644 --- a/absl/memory/memory.h +++ b/absl/memory/memory.h @@ -75,32 +75,6 @@ std::unique_ptr<T> WrapUnique(T* ptr) { return std::unique_ptr<T>(ptr); } -namespace memory_internal { - -// Traits to select proper overload and return type for `absl::make_unique<>`. -template <typename T> -struct MakeUniqueResult { - using scalar = std::unique_ptr<T>; -}; -template <typename T> -struct MakeUniqueResult<T[]> { - using array = std::unique_ptr<T[]>; -}; -template <typename T, size_t N> -struct MakeUniqueResult<T[N]> { - using invalid = void; -}; - -} // namespace memory_internal - -// gcc 4.8 has __cplusplus at 201301 but the libstdc++ shipped with it doesn't -// define make_unique. Other supported compilers either just define __cplusplus -// as 201103 but have make_unique (msvc), or have make_unique whenever -// __cplusplus > 201103 (clang). -#if (__cplusplus > 201103L || defined(_MSC_VER)) && \ - !(defined(__GLIBCXX__) && !defined(__cpp_lib_make_unique)) -using std::make_unique; -#else // ----------------------------------------------------------------------------- // Function Template: make_unique<T>() // ----------------------------------------------------------------------------- @@ -109,82 +83,18 @@ using std::make_unique; // during the construction process. `absl::make_unique<>` also avoids redundant // type declarations, by avoiding the need to explicitly use the `new` operator. // -// This implementation of `absl::make_unique<>` is designed for C++11 code and -// will be replaced in C++14 by the equivalent `std::make_unique<>` abstraction. -// `absl::make_unique<>` is designed to be 100% compatible with -// `std::make_unique<>` so that the eventual migration will involve a simple -// rename operation. +// https://en.cppreference.com/w/cpp/memory/unique_ptr/make_unique // // For more background on why `std::unique_ptr<T>(new T(a,b))` is problematic, // see Herb Sutter's explanation on // (Exception-Safe Function Calls)[https://herbsutter.com/gotw/_102/]. // (In general, reviewers should treat `new T(a,b)` with scrutiny.) // -// Example usage: -// -// auto p = make_unique<X>(args...); // 'p' is a std::unique_ptr<X> -// auto pa = make_unique<X[]>(5); // 'pa' is a std::unique_ptr<X[]> -// -// Three overloads of `absl::make_unique` are required: -// -// - For non-array T: -// -// Allocates a T with `new T(std::forward<Args> args...)`, -// forwarding all `args` to T's constructor. -// Returns a `std::unique_ptr<T>` owning that object. -// -// - For an array of unknown bounds T[]: -// -// `absl::make_unique<>` will allocate an array T of type U[] with -// `new U[n]()` and return a `std::unique_ptr<U[]>` owning that array. -// -// Note that 'U[n]()' is different from 'U[n]', and elements will be -// value-initialized. Note as well that `std::unique_ptr` will perform its -// own destruction of the array elements upon leaving scope, even though -// the array [] does not have a default destructor. -// -// NOTE: an array of unknown bounds T[] may still be (and often will be) -// initialized to have a size, and will still use this overload. E.g: -// -// auto my_array = absl::make_unique<int[]>(10); -// -// - For an array of known bounds T[N]: -// -// `absl::make_unique<>` is deleted (like with `std::make_unique<>`) as -// this overload is not useful. -// -// NOTE: an array of known bounds T[N] is not considered a useful -// construction, and may cause undefined behavior in templates. E.g: -// -// auto my_array = absl::make_unique<int[10]>(); -// -// In those cases, of course, you can still use the overload above and -// simply initialize it to its desired size: -// -// auto my_array = absl::make_unique<int[]>(10); - -// `absl::make_unique` overload for non-array types. -template <typename T, typename... Args> -typename memory_internal::MakeUniqueResult<T>::scalar make_unique( - Args&&... args) { - return std::unique_ptr<T>(new T(std::forward<Args>(args)...)); -} - -// `absl::make_unique` overload for an array T[] of unknown bounds. -// The array allocation needs to use the `new T[size]` form and cannot take -// element constructor arguments. The `std::unique_ptr` will manage destructing -// these array elements. -template <typename T> -typename memory_internal::MakeUniqueResult<T>::array make_unique(size_t n) { - return std::unique_ptr<T>(new typename absl::remove_extent_t<T>[n]()); -} - -// `absl::make_unique` overload for an array T[N] of known bounds. -// This construction will be rejected. -template <typename T, typename... Args> -typename memory_internal::MakeUniqueResult<T>::invalid make_unique( - Args&&... /* args */) = delete; -#endif +// Historical note: Abseil once provided a C++11 compatible implementation of +// the C++14's `std::make_unique`. Now that C++11 support has been sunsetted, +// `absl::make_unique` simply uses the STL-provided implementation. New code +// should use `std::make_unique`. +using std::make_unique; // ----------------------------------------------------------------------------- // Function Template: RawPtr() @@ -248,6 +158,26 @@ std::weak_ptr<T> WeakenPtr(const std::shared_ptr<T>& ptr) { return std::weak_ptr<T>(ptr); } +// ----------------------------------------------------------------------------- +// Class Template: pointer_traits +// ----------------------------------------------------------------------------- +// +// Historical note: Abseil once provided an implementation of +// `std::pointer_traits` for platforms that had not yet provided it. Those +// platforms are no longer supported. New code should simply use +// `std::pointer_traits`. +using std::pointer_traits; + +// ----------------------------------------------------------------------------- +// Class Template: allocator_traits +// ----------------------------------------------------------------------------- +// +// Historical note: Abseil once provided an implementation of +// `std::allocator_traits` for platforms that had not yet provided it. Those +// platforms are no longer supported. New code should simply use +// `std::allocator_traits`. +using std::allocator_traits; + namespace memory_internal { // ExtractOr<E, O, D>::type evaluates to E<O> if possible. Otherwise, D. @@ -265,357 +195,6 @@ struct ExtractOr<Extract, Obj, Default, void_t<Extract<Obj>>> { template <template <typename> class Extract, typename Obj, typename Default> using ExtractOrT = typename ExtractOr<Extract, Obj, Default, void>::type; -// Extractors for the features of allocators. -template <typename T> -using GetPointer = typename T::pointer; - -template <typename T> -using GetConstPointer = typename T::const_pointer; - -template <typename T> -using GetVoidPointer = typename T::void_pointer; - -template <typename T> -using GetConstVoidPointer = typename T::const_void_pointer; - -template <typename T> -using GetDifferenceType = typename T::difference_type; - -template <typename T> -using GetSizeType = typename T::size_type; - -template <typename T> -using GetPropagateOnContainerCopyAssignment = - typename T::propagate_on_container_copy_assignment; - -template <typename T> -using GetPropagateOnContainerMoveAssignment = - typename T::propagate_on_container_move_assignment; - -template <typename T> -using GetPropagateOnContainerSwap = typename T::propagate_on_container_swap; - -template <typename T> -using GetIsAlwaysEqual = typename T::is_always_equal; - -template <typename T> -struct GetFirstArg; - -template <template <typename...> class Class, typename T, typename... Args> -struct GetFirstArg<Class<T, Args...>> { - using type = T; -}; - -template <typename Ptr, typename = void> -struct ElementType { - using type = typename GetFirstArg<Ptr>::type; -}; - -template <typename T> -struct ElementType<T, void_t<typename T::element_type>> { - using type = typename T::element_type; -}; - -template <typename T, typename U> -struct RebindFirstArg; - -template <template <typename...> class Class, typename T, typename... Args, - typename U> -struct RebindFirstArg<Class<T, Args...>, U> { - using type = Class<U, Args...>; -}; - -template <typename T, typename U, typename = void> -struct RebindPtr { - using type = typename RebindFirstArg<T, U>::type; -}; - -template <typename T, typename U> -struct RebindPtr<T, U, void_t<typename T::template rebind<U>>> { - using type = typename T::template rebind<U>; -}; - -template <typename T, typename U> -constexpr bool HasRebindAlloc(...) { - return false; -} - -template <typename T, typename U> -constexpr bool HasRebindAlloc(typename T::template rebind<U>::other*) { - return true; -} - -template <typename T, typename U, bool = HasRebindAlloc<T, U>(nullptr)> -struct RebindAlloc { - using type = typename RebindFirstArg<T, U>::type; -}; - -template <typename T, typename U> -struct RebindAlloc<T, U, true> { - using type = typename T::template rebind<U>::other; -}; - -} // namespace memory_internal - -// ----------------------------------------------------------------------------- -// Class Template: pointer_traits -// ----------------------------------------------------------------------------- -// -// An implementation of C++11's std::pointer_traits. -// -// Provided for portability on toolchains that have a working C++11 compiler, -// but the standard library is lacking in C++11 support. For example, some -// version of the Android NDK. -// - -template <typename Ptr> -struct pointer_traits { - using pointer = Ptr; - - // element_type: - // Ptr::element_type if present. Otherwise T if Ptr is a template - // instantiation Template<T, Args...> - using element_type = typename memory_internal::ElementType<Ptr>::type; - - // difference_type: - // Ptr::difference_type if present, otherwise std::ptrdiff_t - using difference_type = - memory_internal::ExtractOrT<memory_internal::GetDifferenceType, Ptr, - std::ptrdiff_t>; - - // rebind: - // Ptr::rebind<U> if exists, otherwise Template<U, Args...> if Ptr is a - // template instantiation Template<T, Args...> - template <typename U> - using rebind = typename memory_internal::RebindPtr<Ptr, U>::type; - - // pointer_to: - // Calls Ptr::pointer_to(r) - static pointer pointer_to(element_type& r) { // NOLINT(runtime/references) - return Ptr::pointer_to(r); - } -}; - -// Specialization for T*. -template <typename T> -struct pointer_traits<T*> { - using pointer = T*; - using element_type = T; - using difference_type = std::ptrdiff_t; - - template <typename U> - using rebind = U*; - - // pointer_to: - // Calls std::addressof(r) - static pointer pointer_to( - element_type& r) noexcept { // NOLINT(runtime/references) - return std::addressof(r); - } -}; - -// ----------------------------------------------------------------------------- -// Class Template: allocator_traits -// ----------------------------------------------------------------------------- -// -// A C++11 compatible implementation of C++17's std::allocator_traits. -// -#if __cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L) -using std::allocator_traits; -#else // __cplusplus >= 201703L -template <typename Alloc> -struct allocator_traits { - using allocator_type = Alloc; - - // value_type: - // Alloc::value_type - using value_type = typename Alloc::value_type; - - // pointer: - // Alloc::pointer if present, otherwise value_type* - using pointer = memory_internal::ExtractOrT<memory_internal::GetPointer, - Alloc, value_type*>; - - // const_pointer: - // Alloc::const_pointer if present, otherwise - // absl::pointer_traits<pointer>::rebind<const value_type> - using const_pointer = - memory_internal::ExtractOrT<memory_internal::GetConstPointer, Alloc, - typename absl::pointer_traits<pointer>:: - template rebind<const value_type>>; - - // void_pointer: - // Alloc::void_pointer if present, otherwise - // absl::pointer_traits<pointer>::rebind<void> - using void_pointer = memory_internal::ExtractOrT< - memory_internal::GetVoidPointer, Alloc, - typename absl::pointer_traits<pointer>::template rebind<void>>; - - // const_void_pointer: - // Alloc::const_void_pointer if present, otherwise - // absl::pointer_traits<pointer>::rebind<const void> - using const_void_pointer = memory_internal::ExtractOrT< - memory_internal::GetConstVoidPointer, Alloc, - typename absl::pointer_traits<pointer>::template rebind<const void>>; - - // difference_type: - // Alloc::difference_type if present, otherwise - // absl::pointer_traits<pointer>::difference_type - using difference_type = memory_internal::ExtractOrT< - memory_internal::GetDifferenceType, Alloc, - typename absl::pointer_traits<pointer>::difference_type>; - - // size_type: - // Alloc::size_type if present, otherwise - // std::make_unsigned<difference_type>::type - using size_type = memory_internal::ExtractOrT< - memory_internal::GetSizeType, Alloc, - typename std::make_unsigned<difference_type>::type>; - - // propagate_on_container_copy_assignment: - // Alloc::propagate_on_container_copy_assignment if present, otherwise - // std::false_type - using propagate_on_container_copy_assignment = memory_internal::ExtractOrT< - memory_internal::GetPropagateOnContainerCopyAssignment, Alloc, - std::false_type>; - - // propagate_on_container_move_assignment: - // Alloc::propagate_on_container_move_assignment if present, otherwise - // std::false_type - using propagate_on_container_move_assignment = memory_internal::ExtractOrT< - memory_internal::GetPropagateOnContainerMoveAssignment, Alloc, - std::false_type>; - - // propagate_on_container_swap: - // Alloc::propagate_on_container_swap if present, otherwise std::false_type - using propagate_on_container_swap = - memory_internal::ExtractOrT<memory_internal::GetPropagateOnContainerSwap, - Alloc, std::false_type>; - - // is_always_equal: - // Alloc::is_always_equal if present, otherwise std::is_empty<Alloc>::type - using is_always_equal = - memory_internal::ExtractOrT<memory_internal::GetIsAlwaysEqual, Alloc, - typename std::is_empty<Alloc>::type>; - - // rebind_alloc: - // Alloc::rebind<T>::other if present, otherwise Alloc<T, Args> if this Alloc - // is Alloc<U, Args> - template <typename T> - using rebind_alloc = typename memory_internal::RebindAlloc<Alloc, T>::type; - - // rebind_traits: - // absl::allocator_traits<rebind_alloc<T>> - template <typename T> - using rebind_traits = absl::allocator_traits<rebind_alloc<T>>; - - // allocate(Alloc& a, size_type n): - // Calls a.allocate(n) - static pointer allocate(Alloc& a, // NOLINT(runtime/references) - size_type n) { - return a.allocate(n); - } - - // allocate(Alloc& a, size_type n, const_void_pointer hint): - // Calls a.allocate(n, hint) if possible. - // If not possible, calls a.allocate(n) - static pointer allocate(Alloc& a, size_type n, // NOLINT(runtime/references) - const_void_pointer hint) { - return allocate_impl(0, a, n, hint); - } - - // deallocate(Alloc& a, pointer p, size_type n): - // Calls a.deallocate(p, n) - static void deallocate(Alloc& a, pointer p, // NOLINT(runtime/references) - size_type n) { - a.deallocate(p, n); - } - - // construct(Alloc& a, T* p, Args&&... args): - // Calls a.construct(p, std::forward<Args>(args)...) if possible. - // If not possible, calls - // ::new (static_cast<void*>(p)) T(std::forward<Args>(args)...) - template <typename T, typename... Args> - static void construct(Alloc& a, T* p, // NOLINT(runtime/references) - Args&&... args) { - construct_impl(0, a, p, std::forward<Args>(args)...); - } - - // destroy(Alloc& a, T* p): - // Calls a.destroy(p) if possible. If not possible, calls p->~T(). - template <typename T> - static void destroy(Alloc& a, T* p) { // NOLINT(runtime/references) - destroy_impl(0, a, p); - } - - // max_size(const Alloc& a): - // Returns a.max_size() if possible. If not possible, returns - // std::numeric_limits<size_type>::max() / sizeof(value_type) - static size_type max_size(const Alloc& a) { return max_size_impl(0, a); } - - // select_on_container_copy_construction(const Alloc& a): - // Returns a.select_on_container_copy_construction() if possible. - // If not possible, returns a. - static Alloc select_on_container_copy_construction(const Alloc& a) { - return select_on_container_copy_construction_impl(0, a); - } - - private: - template <typename A> - static auto allocate_impl(int, A& a, // NOLINT(runtime/references) - size_type n, const_void_pointer hint) - -> decltype(a.allocate(n, hint)) { - return a.allocate(n, hint); - } - static pointer allocate_impl(char, Alloc& a, // NOLINT(runtime/references) - size_type n, const_void_pointer) { - return a.allocate(n); - } - - template <typename A, typename... Args> - static auto construct_impl(int, A& a, // NOLINT(runtime/references) - Args&&... args) - -> decltype(a.construct(std::forward<Args>(args)...)) { - a.construct(std::forward<Args>(args)...); - } - - template <typename T, typename... Args> - static void construct_impl(char, Alloc&, T* p, Args&&... args) { - ::new (static_cast<void*>(p)) T(std::forward<Args>(args)...); - } - - template <typename A, typename T> - static auto destroy_impl(int, A& a, // NOLINT(runtime/references) - T* p) -> decltype(a.destroy(p)) { - a.destroy(p); - } - template <typename T> - static void destroy_impl(char, Alloc&, T* p) { - p->~T(); - } - - template <typename A> - static auto max_size_impl(int, const A& a) -> decltype(a.max_size()) { - return a.max_size(); - } - static size_type max_size_impl(char, const Alloc&) { - return (std::numeric_limits<size_type>::max)() / sizeof(value_type); - } - - template <typename A> - static auto select_on_container_copy_construction_impl(int, const A& a) - -> decltype(a.select_on_container_copy_construction()) { - return a.select_on_container_copy_construction(); - } - static Alloc select_on_container_copy_construction_impl(char, - const Alloc& a) { - return a; - } -}; -#endif // __cplusplus >= 201703L - -namespace memory_internal { - // This template alias transforms Alloc::is_nothrow into a metafunction with // Alloc as a parameter so it can be used with ExtractOrT<>. template <typename Alloc> diff --git a/absl/memory/memory_exception_safety_test.cc b/absl/memory/memory_exception_safety_test.cc deleted file mode 100644 index 1df72614..00000000 --- a/absl/memory/memory_exception_safety_test.cc +++ /dev/null @@ -1,57 +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. - -#include "absl/memory/memory.h" - -#include "absl/base/config.h" - -#ifdef ABSL_HAVE_EXCEPTIONS - -#include "gtest/gtest.h" -#include "absl/base/internal/exception_safety_testing.h" - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace { - -constexpr int kLength = 50; -using Thrower = testing::ThrowingValue<testing::TypeSpec::kEverythingThrows>; - -TEST(MakeUnique, CheckForLeaks) { - constexpr int kValue = 321; - auto tester = testing::MakeExceptionSafetyTester() - .WithInitialValue(Thrower(kValue)) - // Ensures make_unique does not modify the input. The real - // test, though, is ConstructorTracker checking for leaks. - .WithContracts(testing::strong_guarantee); - - EXPECT_TRUE(tester.Test([](Thrower* thrower) { - static_cast<void>(absl::make_unique<Thrower>(*thrower)); - })); - - EXPECT_TRUE(tester.Test([](Thrower* thrower) { - static_cast<void>(absl::make_unique<Thrower>(std::move(*thrower))); - })); - - // Test T[n] overload - EXPECT_TRUE(tester.Test([&](Thrower*) { - static_cast<void>(absl::make_unique<Thrower[]>(kLength)); - })); -} - -} // namespace -ABSL_NAMESPACE_END -} // namespace absl - -#endif // ABSL_HAVE_EXCEPTIONS diff --git a/absl/memory/memory_test.cc b/absl/memory/memory_test.cc index 5d5719b3..fafd3a41 100644 --- a/absl/memory/memory_test.cc +++ b/absl/memory/memory_test.cc @@ -63,12 +63,6 @@ TEST(WrapUniqueTest, WrapUnique) { } EXPECT_EQ(0, DestructorVerifier::instance_count()); } -TEST(MakeUniqueTest, Basic) { - std::unique_ptr<std::string> p = absl::make_unique<std::string>(); - EXPECT_EQ("", *p); - p = absl::make_unique<std::string>("hi"); - EXPECT_EQ("hi", *p); -} // InitializationVerifier fills in a pattern when allocated so we can // distinguish between its default and value initialized states (without @@ -93,65 +87,6 @@ struct InitializationVerifier { int b; }; -TEST(Initialization, MakeUnique) { - auto p = absl::make_unique<InitializationVerifier>(); - - EXPECT_EQ(0, p->a); - EXPECT_EQ(0, p->b); -} - -TEST(Initialization, MakeUniqueArray) { - auto p = absl::make_unique<InitializationVerifier[]>(2); - - EXPECT_EQ(0, p[0].a); - EXPECT_EQ(0, p[0].b); - EXPECT_EQ(0, p[1].a); - EXPECT_EQ(0, p[1].b); -} - -struct MoveOnly { - MoveOnly() = default; - explicit MoveOnly(int i1) : ip1{new int{i1}} {} - MoveOnly(int i1, int i2) : ip1{new int{i1}}, ip2{new int{i2}} {} - std::unique_ptr<int> ip1; - std::unique_ptr<int> ip2; -}; - -struct AcceptMoveOnly { - explicit AcceptMoveOnly(MoveOnly m) : m_(std::move(m)) {} - MoveOnly m_; -}; - -TEST(MakeUniqueTest, MoveOnlyTypeAndValue) { - using ExpectedType = std::unique_ptr<MoveOnly>; - { - auto p = absl::make_unique<MoveOnly>(); - static_assert(std::is_same<decltype(p), ExpectedType>::value, - "unexpected return type"); - EXPECT_TRUE(!p->ip1); - EXPECT_TRUE(!p->ip2); - } - { - auto p = absl::make_unique<MoveOnly>(1); - static_assert(std::is_same<decltype(p), ExpectedType>::value, - "unexpected return type"); - EXPECT_TRUE(p->ip1 && *p->ip1 == 1); - EXPECT_TRUE(!p->ip2); - } - { - auto p = absl::make_unique<MoveOnly>(1, 2); - static_assert(std::is_same<decltype(p), ExpectedType>::value, - "unexpected return type"); - EXPECT_TRUE(p->ip1 && *p->ip1 == 1); - EXPECT_TRUE(p->ip2 && *p->ip2 == 2); - } -} - -TEST(MakeUniqueTest, AcceptMoveOnly) { - auto p = absl::make_unique<AcceptMoveOnly>(MoveOnly()); - p = std::unique_ptr<AcceptMoveOnly>(new AcceptMoveOnly(MoveOnly())); -} - struct ArrayWatch { void* operator new[](size_t n) { allocs().push_back(n); @@ -164,38 +99,6 @@ struct ArrayWatch { } }; -TEST(Make_UniqueTest, Array) { - // Ensure state is clean before we start so that these tests - // are order-agnostic. - ArrayWatch::allocs().clear(); - - auto p = absl::make_unique<ArrayWatch[]>(5); - static_assert(std::is_same<decltype(p), std::unique_ptr<ArrayWatch[]>>::value, - "unexpected return type"); - EXPECT_THAT(ArrayWatch::allocs(), ElementsAre(5 * sizeof(ArrayWatch))); -} - -TEST(Make_UniqueTest, NotAmbiguousWithStdMakeUnique) { - // Ensure that absl::make_unique is not ambiguous with std::make_unique. - // In C++14 mode, the below call to make_unique has both types as candidates. - struct TakesStdType { - explicit TakesStdType(const std::vector<int>& vec) {} - }; - using absl::make_unique; - (void)make_unique<TakesStdType>(std::vector<int>()); -} - -#if 0 -// These tests shouldn't compile. -TEST(MakeUniqueTestNC, AcceptMoveOnlyLvalue) { - auto m = MoveOnly(); - auto p = absl::make_unique<AcceptMoveOnly>(m); -} -TEST(MakeUniqueTestNC, KnownBoundArray) { - auto p = absl::make_unique<ArrayWatch[5]>(); -} -#endif - TEST(RawPtrTest, RawPointer) { int i = 5; EXPECT_EQ(&i, absl::RawPtr(&i)); @@ -287,338 +190,6 @@ TEST(RawPtrTest, NotAPointer) { } */ -template <typename T> -struct SmartPointer { - using difference_type = char; -}; - -struct PointerWith { - using element_type = int32_t; - using difference_type = int16_t; - template <typename U> - using rebind = SmartPointer<U>; - - static PointerWith pointer_to( - element_type& r) { // NOLINT(runtime/references) - return PointerWith{&r}; - } - - element_type* ptr; -}; - -template <typename... Args> -struct PointerWithout {}; - -TEST(PointerTraits, Types) { - using TraitsWith = absl::pointer_traits<PointerWith>; - EXPECT_TRUE((std::is_same<TraitsWith::pointer, PointerWith>::value)); - EXPECT_TRUE((std::is_same<TraitsWith::element_type, int32_t>::value)); - EXPECT_TRUE((std::is_same<TraitsWith::difference_type, int16_t>::value)); - EXPECT_TRUE(( - std::is_same<TraitsWith::rebind<int64_t>, SmartPointer<int64_t>>::value)); - - using TraitsWithout = absl::pointer_traits<PointerWithout<double, int>>; - EXPECT_TRUE((std::is_same<TraitsWithout::pointer, - PointerWithout<double, int>>::value)); - EXPECT_TRUE((std::is_same<TraitsWithout::element_type, double>::value)); - EXPECT_TRUE( - (std::is_same<TraitsWithout ::difference_type, std::ptrdiff_t>::value)); - EXPECT_TRUE((std::is_same<TraitsWithout::rebind<int64_t>, - PointerWithout<int64_t, int>>::value)); - - using TraitsRawPtr = absl::pointer_traits<char*>; - EXPECT_TRUE((std::is_same<TraitsRawPtr::pointer, char*>::value)); - EXPECT_TRUE((std::is_same<TraitsRawPtr::element_type, char>::value)); - EXPECT_TRUE( - (std::is_same<TraitsRawPtr::difference_type, std::ptrdiff_t>::value)); - EXPECT_TRUE((std::is_same<TraitsRawPtr::rebind<int64_t>, int64_t*>::value)); -} - -TEST(PointerTraits, Functions) { - int i; - EXPECT_EQ(&i, absl::pointer_traits<PointerWith>::pointer_to(i).ptr); - EXPECT_EQ(&i, absl::pointer_traits<int*>::pointer_to(i)); -} - -TEST(AllocatorTraits, Typedefs) { - struct A { - struct value_type {}; - }; - EXPECT_TRUE(( - std::is_same<A, - typename absl::allocator_traits<A>::allocator_type>::value)); - EXPECT_TRUE( - (std::is_same<A::value_type, - typename absl::allocator_traits<A>::value_type>::value)); - - struct X {}; - struct HasPointer { - using value_type = X; - using pointer = SmartPointer<X>; - }; - EXPECT_TRUE((std::is_same<SmartPointer<X>, typename absl::allocator_traits< - HasPointer>::pointer>::value)); - EXPECT_TRUE( - (std::is_same<A::value_type*, - typename absl::allocator_traits<A>::pointer>::value)); - - EXPECT_TRUE( - (std::is_same< - SmartPointer<const X>, - typename absl::allocator_traits<HasPointer>::const_pointer>::value)); - EXPECT_TRUE( - (std::is_same<const A::value_type*, - typename absl::allocator_traits<A>::const_pointer>::value)); - - struct HasVoidPointer { - using value_type = X; - struct void_pointer {}; - }; - - EXPECT_TRUE((std::is_same<HasVoidPointer::void_pointer, - typename absl::allocator_traits< - HasVoidPointer>::void_pointer>::value)); - EXPECT_TRUE( - (std::is_same<SmartPointer<void>, typename absl::allocator_traits< - HasPointer>::void_pointer>::value)); - - struct HasConstVoidPointer { - using value_type = X; - struct const_void_pointer {}; - }; - - EXPECT_TRUE( - (std::is_same<HasConstVoidPointer::const_void_pointer, - typename absl::allocator_traits< - HasConstVoidPointer>::const_void_pointer>::value)); - EXPECT_TRUE((std::is_same<SmartPointer<const void>, - typename absl::allocator_traits< - HasPointer>::const_void_pointer>::value)); - - struct HasDifferenceType { - using value_type = X; - using difference_type = int; - }; - EXPECT_TRUE( - (std::is_same<int, typename absl::allocator_traits< - HasDifferenceType>::difference_type>::value)); - EXPECT_TRUE((std::is_same<char, typename absl::allocator_traits< - HasPointer>::difference_type>::value)); - - struct HasSizeType { - using value_type = X; - using size_type = unsigned int; - }; - EXPECT_TRUE((std::is_same<unsigned int, typename absl::allocator_traits< - HasSizeType>::size_type>::value)); - EXPECT_TRUE((std::is_same<unsigned char, typename absl::allocator_traits< - HasPointer>::size_type>::value)); - - struct HasPropagateOnCopy { - using value_type = X; - struct propagate_on_container_copy_assignment {}; - }; - - EXPECT_TRUE( - (std::is_same<HasPropagateOnCopy::propagate_on_container_copy_assignment, - typename absl::allocator_traits<HasPropagateOnCopy>:: - propagate_on_container_copy_assignment>::value)); - EXPECT_TRUE( - (std::is_same<std::false_type, - typename absl::allocator_traits< - A>::propagate_on_container_copy_assignment>::value)); - - struct HasPropagateOnMove { - using value_type = X; - struct propagate_on_container_move_assignment {}; - }; - - EXPECT_TRUE( - (std::is_same<HasPropagateOnMove::propagate_on_container_move_assignment, - typename absl::allocator_traits<HasPropagateOnMove>:: - propagate_on_container_move_assignment>::value)); - EXPECT_TRUE( - (std::is_same<std::false_type, - typename absl::allocator_traits< - A>::propagate_on_container_move_assignment>::value)); - - struct HasPropagateOnSwap { - using value_type = X; - struct propagate_on_container_swap {}; - }; - - EXPECT_TRUE( - (std::is_same<HasPropagateOnSwap::propagate_on_container_swap, - typename absl::allocator_traits<HasPropagateOnSwap>:: - propagate_on_container_swap>::value)); - EXPECT_TRUE( - (std::is_same<std::false_type, typename absl::allocator_traits<A>:: - propagate_on_container_swap>::value)); - - struct HasIsAlwaysEqual { - using value_type = X; - struct is_always_equal {}; - }; - - EXPECT_TRUE((std::is_same<HasIsAlwaysEqual::is_always_equal, - typename absl::allocator_traits< - HasIsAlwaysEqual>::is_always_equal>::value)); - EXPECT_TRUE((std::is_same<std::true_type, typename absl::allocator_traits< - A>::is_always_equal>::value)); - struct NonEmpty { - using value_type = X; - int i; - }; - EXPECT_TRUE( - (std::is_same<std::false_type, - absl::allocator_traits<NonEmpty>::is_always_equal>::value)); -} - -template <typename T> -struct AllocWithPrivateInheritance : private std::allocator<T> { - using value_type = T; -}; - -TEST(AllocatorTraits, RebindWithPrivateInheritance) { - // Regression test for some versions of gcc that do not like the sfinae we - // used in combination with private inheritance. - EXPECT_TRUE( - (std::is_same<AllocWithPrivateInheritance<int>, - absl::allocator_traits<AllocWithPrivateInheritance<char>>:: - rebind_alloc<int>>::value)); -} - -template <typename T> -struct Rebound {}; - -struct AllocWithRebind { - using value_type = int; - template <typename T> - struct rebind { - using other = Rebound<T>; - }; -}; - -template <typename T, typename U> -struct AllocWithoutRebind { - using value_type = int; -}; - -TEST(AllocatorTraits, Rebind) { - EXPECT_TRUE( - (std::is_same<Rebound<int>, - typename absl::allocator_traits< - AllocWithRebind>::template rebind_alloc<int>>::value)); - EXPECT_TRUE( - (std::is_same<absl::allocator_traits<Rebound<int>>, - typename absl::allocator_traits< - AllocWithRebind>::template rebind_traits<int>>::value)); - - EXPECT_TRUE( - (std::is_same<AllocWithoutRebind<double, char>, - typename absl::allocator_traits<AllocWithoutRebind< - int, char>>::template rebind_alloc<double>>::value)); - EXPECT_TRUE( - (std::is_same<absl::allocator_traits<AllocWithoutRebind<double, char>>, - typename absl::allocator_traits<AllocWithoutRebind< - int, char>>::template rebind_traits<double>>::value)); -} - -struct TestValue { - TestValue() {} - explicit TestValue(int* trace) : trace(trace) { ++*trace; } - ~TestValue() { - if (trace) --*trace; - } - int* trace = nullptr; -}; - -struct MinimalMockAllocator { - MinimalMockAllocator() : value(0) {} - explicit MinimalMockAllocator(int value) : value(value) {} - MinimalMockAllocator(const MinimalMockAllocator& other) - : value(other.value) {} - using value_type = TestValue; - MOCK_METHOD(value_type*, allocate, (size_t)); - MOCK_METHOD(void, deallocate, (value_type*, size_t)); - - int value; -}; - -TEST(AllocatorTraits, FunctionsMinimal) { - int trace = 0; - int hint; - alignas(TestValue) char buffer[sizeof(TestValue)]; - auto* x = reinterpret_cast<TestValue*>(buffer); - MinimalMockAllocator mock; - using Traits = absl::allocator_traits<MinimalMockAllocator>; - EXPECT_CALL(mock, allocate(7)).WillRepeatedly(Return(x)); - EXPECT_CALL(mock, deallocate(x, 7)); - - EXPECT_EQ(x, Traits::allocate(mock, 7)); - static_cast<void>(Traits::allocate(mock, 7, static_cast<const void*>(&hint))); - EXPECT_EQ(x, Traits::allocate(mock, 7, static_cast<const void*>(&hint))); - Traits::deallocate(mock, x, 7); - - EXPECT_EQ(0, trace); - Traits::construct(mock, x, &trace); - EXPECT_EQ(1, trace); - Traits::destroy(mock, x); - EXPECT_EQ(0, trace); - - EXPECT_EQ(std::numeric_limits<size_t>::max() / sizeof(TestValue), - Traits::max_size(mock)); - - EXPECT_EQ(0, mock.value); - EXPECT_EQ(0, Traits::select_on_container_copy_construction(mock).value); -} - -struct FullMockAllocator { - FullMockAllocator() : value(0) {} - explicit FullMockAllocator(int value) : value(value) {} - FullMockAllocator(const FullMockAllocator& other) : value(other.value) {} - using value_type = TestValue; - MOCK_METHOD(value_type*, allocate, (size_t)); - MOCK_METHOD(value_type*, allocate, (size_t, const void*)); - MOCK_METHOD(void, construct, (value_type*, int*)); - MOCK_METHOD(void, destroy, (value_type*)); - MOCK_METHOD(size_t, max_size, (), - (const)); - MOCK_METHOD(FullMockAllocator, select_on_container_copy_construction, (), - (const)); - - int value; -}; - -TEST(AllocatorTraits, FunctionsFull) { - int trace = 0; - int hint; - TestValue x(&trace), y; - FullMockAllocator mock; - using Traits = absl::allocator_traits<FullMockAllocator>; - EXPECT_CALL(mock, allocate(7)).WillRepeatedly(Return(&x)); - EXPECT_CALL(mock, allocate(13, &hint)).WillRepeatedly(Return(&y)); - EXPECT_CALL(mock, construct(&x, &trace)); - EXPECT_CALL(mock, destroy(&x)); - EXPECT_CALL(mock, max_size()).WillRepeatedly(Return(17)); - EXPECT_CALL(mock, select_on_container_copy_construction()) - .WillRepeatedly(Return(FullMockAllocator(23))); - - EXPECT_EQ(&x, Traits::allocate(mock, 7)); - EXPECT_EQ(&y, Traits::allocate(mock, 13, static_cast<const void*>(&hint))); - - EXPECT_EQ(1, trace); - Traits::construct(mock, &x, &trace); - EXPECT_EQ(1, trace); - Traits::destroy(mock, &x); - EXPECT_EQ(1, trace); - - EXPECT_EQ(17, Traits::max_size(mock)); - - EXPECT_EQ(0, mock.value); - EXPECT_EQ(23, Traits::select_on_container_copy_construction(mock).value); -} - TEST(AllocatorNoThrowTest, DefaultAllocator) { #if defined(ABSL_ALLOCATOR_NOTHROW) && ABSL_ALLOCATOR_NOTHROW EXPECT_TRUE(absl::default_allocator_is_nothrow::value); diff --git a/absl/meta/BUILD.bazel b/absl/meta/BUILD.bazel index fb379251..125446f9 100644 --- a/absl/meta/BUILD.bazel +++ b/absl/meta/BUILD.bazel @@ -42,6 +42,9 @@ cc_test( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":type_traits", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/time", "@com_google_googletest//:gtest_main", ], ) diff --git a/absl/meta/CMakeLists.txt b/absl/meta/CMakeLists.txt index 9de4bd37..bb767d12 100644 --- a/absl/meta/CMakeLists.txt +++ b/absl/meta/CMakeLists.txt @@ -34,6 +34,9 @@ absl_cc_test( COPTS ${ABSL_TEST_COPTS} DEPS + absl::config + absl::time + absl::core_headers absl::type_traits GTest::gmock_main ) diff --git a/absl/meta/type_traits.h b/absl/meta/type_traits.h index d886cb30..b1656c39 100644 --- a/absl/meta/type_traits.h +++ b/absl/meta/type_traits.h @@ -114,18 +114,6 @@ struct VoidTImpl { using type = void; }; -// This trick to retrieve a default alignment is necessary for our -// implementation of aligned_storage_t to be consistent with any implementation -// of std::aligned_storage. -template <size_t Len, typename T = std::aligned_storage<Len>> -struct default_alignment_of_aligned_storage; - -template <size_t Len, size_t Align> -struct default_alignment_of_aligned_storage<Len, - std::aligned_storage<Len, Align>> { - static constexpr size_t value = Align; -}; - //////////////////////////////// // Library Fundamentals V2 TS // //////////////////////////////// @@ -298,8 +286,12 @@ struct is_function // 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 == @@ -347,9 +339,13 @@ struct is_trivially_destructible // 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) @@ -381,10 +377,14 @@ struct is_trivially_default_constructible // 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) @@ -490,9 +490,13 @@ struct is_trivially_move_assignable // `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 = @@ -544,6 +548,11 @@ namespace type_traits_internal { // 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; @@ -569,6 +578,7 @@ 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 // ----------------------------------------------------------------------------- @@ -620,6 +630,21 @@ 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; +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 +// implementation of std::aligned_storage. +template <size_t Len, typename T = std::aligned_storage<Len>> +struct default_alignment_of_aligned_storage; + +template <size_t Len, size_t Align> +struct default_alignment_of_aligned_storage< + Len, std::aligned_storage<Len, Align>> { + static constexpr size_t value = Align; +}; +} // namespace type_traits_internal + +// TODO(b/260219225): std::aligned_storage(_t) is deprecated in C++23. 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; @@ -791,6 +816,73 @@ using swap_internal::Swap; 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. +// +// Example: +// +// if constexpr (absl::is_trivially_relocatable<T>::value) { +// memcpy(new_location, old_location, sizeof(T)); +// } else { +// new(new_location) T(std::move(*old_location)); +// old_location->~T(); +// } +// +// Upstream documentation: +// +// https://clang.llvm.org/docs/LanguageExtensions.html#:~:text=__is_trivially_relocatable +// +#if ABSL_HAVE_BUILTIN(__is_trivially_relocatable) +template <class T> +struct is_trivially_relocatable + : std::integral_constant<bool, __is_trivially_relocatable(T)> {}; +#else +template <class T> +struct is_trivially_relocatable : std::integral_constant<bool, false> {}; +#endif + +// absl::is_constant_evaluated() +// +// Detects whether the function call occurs within a constant-evaluated context. +// Returns true if the evaluation of the call occurs within the evaluation of an +// expression or conversion that is manifestly constant-evaluated; otherwise +// returns false. +// +// This function is implemented in terms of `std::is_constant_evaluated` for +// c++20 and up. For older c++ versions, the function is implemented in terms +// of `__builtin_is_constant_evaluated` if available, otherwise the function +// will fail to compile. +// +// Applications can inspect `ABSL_HAVE_CONSTANT_EVALUATED` at compile time +// to check if this function is supported. +// +// Example: +// +// constexpr MyClass::MyClass(int param) { +// #ifdef ABSL_HAVE_CONSTANT_EVALUATED +// if (!absl::is_constant_evaluated()) { +// ABSL_LOG(INFO) << "MyClass(" << param << ")"; +// } +// #endif // ABSL_HAVE_CONSTANT_EVALUATED +// } +// +// Upstream documentation: +// +// http://en.cppreference.com/w/cpp/types/is_constant_evaluated +// http://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#:~:text=__builtin_is_constant_evaluated +// +#if defined(ABSL_HAVE_CONSTANT_EVALUATED) +constexpr bool is_constant_evaluated() noexcept { +#ifdef __cpp_lib_is_constant_evaluated + return std::is_constant_evaluated(); +#elif ABSL_HAVE_BUILTIN(__builtin_is_constant_evaluated) + return __builtin_is_constant_evaluated(); +#endif +} +#endif // ABSL_HAVE_CONSTANT_EVALUATED ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/meta/type_traits_test.cc b/absl/meta/type_traits_test.cc index 0ef5b665..b2a7a67b 100644 --- a/absl/meta/type_traits_test.cc +++ b/absl/meta/type_traits_test.cc @@ -21,6 +21,10 @@ #include <vector> #include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" namespace { @@ -336,6 +340,7 @@ struct MovableNonCopyable { struct NonCopyableOrMovable { NonCopyableOrMovable() = default; + virtual ~NonCopyableOrMovable() = default; NonCopyableOrMovable(const NonCopyableOrMovable&) = delete; NonCopyableOrMovable(NonCopyableOrMovable&&) = delete; NonCopyableOrMovable& operator=(const NonCopyableOrMovable&) = delete; @@ -1393,4 +1398,51 @@ 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 + + struct Trivial {}; + struct NonTrivial { + NonTrivial(const NonTrivial&) {} // NOLINT + }; + struct ABSL_ATTRIBUTE_TRIVIAL_ABI TrivialAbi { + TrivialAbi(const TrivialAbi&) {} // NOLINT + }; + EXPECT_TRUE(absl::is_trivially_relocatable<Trivial>::value); + EXPECT_FALSE(absl::is_trivially_relocatable<NonTrivial>::value); + EXPECT_TRUE(absl::is_trivially_relocatable<TrivialAbi>::value); +} + +#ifdef ABSL_HAVE_CONSTANT_EVALUATED + +constexpr int64_t NegateIfConstantEvaluated(int64_t i) { + if (absl::is_constant_evaluated()) { + return -i; + } else { + return i; + } +} + +#endif // ABSL_HAVE_CONSTANT_EVALUATED + +TEST(TrivallyRelocatable, is_constant_evaluated) { +#ifdef ABSL_HAVE_CONSTANT_EVALUATED + constexpr int64_t constant = NegateIfConstantEvaluated(42); + EXPECT_EQ(constant, -42); + + int64_t now = absl::ToUnixSeconds(absl::Now()); + int64_t not_constant = NegateIfConstantEvaluated(now); + EXPECT_EQ(not_constant, now); + + static int64_t const_init = NegateIfConstantEvaluated(42); + EXPECT_EQ(const_init, -42); +#else + GTEST_SKIP() << "absl::is_constant_evaluated is not defined"; +#endif // ABSL_HAVE_CONSTANT_EVALUATED +} + + } // namespace diff --git a/absl/numeric/BUILD.bazel b/absl/numeric/BUILD.bazel index eaa27dfd..ec0b8701 100644 --- a/absl/numeric/BUILD.bazel +++ b/absl/numeric/BUILD.bazel @@ -95,7 +95,6 @@ cc_test( deps = [ ":int128", "//absl/base", - "//absl/base:core_headers", "//absl/hash:hash_testing", "//absl/meta:type_traits", "@com_google_googletest//:gtest_main", diff --git a/absl/numeric/CMakeLists.txt b/absl/numeric/CMakeLists.txt index 26df5cf7..384c0fc0 100644 --- a/absl/numeric/CMakeLists.txt +++ b/absl/numeric/CMakeLists.txt @@ -70,7 +70,6 @@ absl_cc_test( DEPS absl::int128 absl::base - absl::core_headers absl::hash_testing absl::type_traits GTest::gmock_main diff --git a/absl/numeric/bits.h b/absl/numeric/bits.h index 628cdf50..df81b9a9 100644 --- a/absl/numeric/bits.h +++ b/absl/numeric/bits.h @@ -131,10 +131,9 @@ has_single_bit(T x) noexcept { // fractional part discarded. template <class T> ABSL_INTERNAL_CONSTEXPR_CLZ inline - typename std::enable_if<std::is_unsigned<T>::value, T>::type + typename std::enable_if<std::is_unsigned<T>::value, int>::type bit_width(T x) noexcept { - return std::numeric_limits<T>::digits - - static_cast<unsigned int>(countl_zero(x)); + return std::numeric_limits<T>::digits - countl_zero(x); } // Returns: If x == 0, 0; otherwise the maximal value y such that diff --git a/absl/numeric/bits_benchmark.cc b/absl/numeric/bits_benchmark.cc index b9759583..2c89afdb 100644 --- a/absl/numeric/bits_benchmark.cc +++ b/absl/numeric/bits_benchmark.cc @@ -24,50 +24,50 @@ namespace absl { namespace { template <typename T> -static void BM_bitwidth(benchmark::State& state) { - const int count = state.range(0); +static void BM_bit_width(benchmark::State& state) { + const auto count = static_cast<size_t>(state.range(0)); absl::BitGen rng; std::vector<T> values; values.reserve(count); - for (int i = 0; i < count; ++i) { + for (size_t i = 0; i < count; ++i) { values.push_back(absl::Uniform<T>(rng, 0, std::numeric_limits<T>::max())); } - while (state.KeepRunningBatch(count)) { - for (int i = 0; i < count; ++i) { - benchmark::DoNotOptimize(values[i]); + while (state.KeepRunningBatch(static_cast<int64_t>(count))) { + for (size_t i = 0; i < count; ++i) { + benchmark::DoNotOptimize(absl::bit_width(values[i])); } } } -BENCHMARK_TEMPLATE(BM_bitwidth, uint8_t)->Range(1, 1 << 20); -BENCHMARK_TEMPLATE(BM_bitwidth, uint16_t)->Range(1, 1 << 20); -BENCHMARK_TEMPLATE(BM_bitwidth, uint32_t)->Range(1, 1 << 20); -BENCHMARK_TEMPLATE(BM_bitwidth, uint64_t)->Range(1, 1 << 20); +BENCHMARK_TEMPLATE(BM_bit_width, uint8_t)->Range(1, 1 << 20); +BENCHMARK_TEMPLATE(BM_bit_width, uint16_t)->Range(1, 1 << 20); +BENCHMARK_TEMPLATE(BM_bit_width, uint32_t)->Range(1, 1 << 20); +BENCHMARK_TEMPLATE(BM_bit_width, uint64_t)->Range(1, 1 << 20); template <typename T> -static void BM_bitwidth_nonzero(benchmark::State& state) { - const int count = state.range(0); +static void BM_bit_width_nonzero(benchmark::State& state) { + const auto count = static_cast<size_t>(state.range(0)); absl::BitGen rng; std::vector<T> values; values.reserve(count); - for (int i = 0; i < count; ++i) { + for (size_t i = 0; i < count; ++i) { values.push_back(absl::Uniform<T>(rng, 1, std::numeric_limits<T>::max())); } - while (state.KeepRunningBatch(count)) { - for (int i = 0; i < count; ++i) { + while (state.KeepRunningBatch(static_cast<int64_t>(count))) { + for (size_t i = 0; i < count; ++i) { const T value = values[i]; ABSL_ASSUME(value > 0); - benchmark::DoNotOptimize(value); + benchmark::DoNotOptimize(absl::bit_width(value)); } } } -BENCHMARK_TEMPLATE(BM_bitwidth_nonzero, uint8_t)->Range(1, 1 << 20); -BENCHMARK_TEMPLATE(BM_bitwidth_nonzero, uint16_t)->Range(1, 1 << 20); -BENCHMARK_TEMPLATE(BM_bitwidth_nonzero, uint32_t)->Range(1, 1 << 20); -BENCHMARK_TEMPLATE(BM_bitwidth_nonzero, uint64_t)->Range(1, 1 << 20); +BENCHMARK_TEMPLATE(BM_bit_width_nonzero, uint8_t)->Range(1, 1 << 20); +BENCHMARK_TEMPLATE(BM_bit_width_nonzero, uint16_t)->Range(1, 1 << 20); +BENCHMARK_TEMPLATE(BM_bit_width_nonzero, uint32_t)->Range(1, 1 << 20); +BENCHMARK_TEMPLATE(BM_bit_width_nonzero, uint64_t)->Range(1, 1 << 20); } // namespace } // namespace absl diff --git a/absl/numeric/int128.cc b/absl/numeric/int128.cc index 8cdcbf05..e5526c6f 100644 --- a/absl/numeric/int128.cc +++ b/absl/numeric/int128.cc @@ -209,15 +209,16 @@ std::ostream& operator<<(std::ostream& os, uint128 v) { // Add the requisite padding. std::streamsize width = os.width(0); if (static_cast<size_t>(width) > rep.size()) { + const size_t count = static_cast<size_t>(width) - rep.size(); std::ios::fmtflags adjustfield = flags & std::ios::adjustfield; if (adjustfield == std::ios::left) { - rep.append(width - rep.size(), os.fill()); + rep.append(count, os.fill()); } else if (adjustfield == std::ios::internal && (flags & std::ios::showbase) && (flags & std::ios::basefield) == std::ios::hex && v != 0) { - rep.insert(2, width - rep.size(), os.fill()); + rep.insert(2, count, os.fill()); } else { - rep.insert(0, width - rep.size(), os.fill()); + rep.insert(0, count, os.fill()); } } @@ -306,22 +307,23 @@ std::ostream& operator<<(std::ostream& os, int128 v) { // Add the requisite padding. std::streamsize width = os.width(0); if (static_cast<size_t>(width) > rep.size()) { + const size_t count = static_cast<size_t>(width) - rep.size(); switch (flags & std::ios::adjustfield) { case std::ios::left: - rep.append(width - rep.size(), os.fill()); + rep.append(count, os.fill()); break; case std::ios::internal: if (print_as_decimal && (rep[0] == '+' || rep[0] == '-')) { - rep.insert(1, width - rep.size(), os.fill()); + rep.insert(1, count, os.fill()); } else if ((flags & std::ios::basefield) == std::ios::hex && (flags & std::ios::showbase) && v != 0) { - rep.insert(2, width - rep.size(), os.fill()); + rep.insert(2, count, os.fill()); } else { - rep.insert(0, width - rep.size(), os.fill()); + rep.insert(0, count, os.fill()); } break; default: // std::ios::right - rep.insert(0, width - rep.size(), os.fill()); + rep.insert(0, count, os.fill()); break; } } diff --git a/absl/numeric/int128_stream_test.cc b/absl/numeric/int128_stream_test.cc index 479ad66c..81d2adee 100644 --- a/absl/numeric/int128_stream_test.cc +++ b/absl/numeric/int128_stream_test.cc @@ -76,16 +76,6 @@ std::string StreamFormatToString(std::ios_base::fmtflags flags, return msg.str(); } -void CheckUint128Case(const Uint128TestCase& test_case) { - std::ostringstream os; - os.flags(test_case.flags); - os.width(test_case.width); - os.fill(kFill); - os << test_case.value; - SCOPED_TRACE(StreamFormatToString(test_case.flags, test_case.width)); - EXPECT_EQ(test_case.expected, os.str()); -} - constexpr std::ios::fmtflags kDec = std::ios::dec; constexpr std::ios::fmtflags kOct = std::ios::oct; constexpr std::ios::fmtflags kHex = std::ios::hex; @@ -96,6 +86,16 @@ constexpr std::ios::fmtflags kUpper = std::ios::uppercase; constexpr std::ios::fmtflags kBase = std::ios::showbase; constexpr std::ios::fmtflags kPos = std::ios::showpos; +void CheckUint128Case(const Uint128TestCase& test_case) { + std::ostringstream os; + os.flags(test_case.flags); + os.width(test_case.width); + os.fill(kFill); + os << test_case.value; + SCOPED_TRACE(StreamFormatToString(test_case.flags, test_case.width)); + EXPECT_EQ(os.str(), test_case.expected); +} + TEST(Uint128, OStreamValueTest) { CheckUint128Case({1, kDec, /*width = */ 0, "1"}); CheckUint128Case({1, kOct, /*width = */ 0, "1"}); @@ -161,7 +161,7 @@ void CheckInt128Case(const Int128TestCase& test_case) { os.fill(kFill); os << test_case.value; SCOPED_TRACE(StreamFormatToString(test_case.flags, test_case.width)); - EXPECT_EQ(test_case.expected, os.str()); + EXPECT_EQ(os.str(), test_case.expected); } TEST(Int128, OStreamValueTest) { @@ -194,35 +194,33 @@ TEST(Int128, OStreamValueTest) { {absl::MakeInt128(1, 0), kHex, /*width = */ 0, "10000000000000000"}); CheckInt128Case({absl::MakeInt128(std::numeric_limits<int64_t>::max(), std::numeric_limits<uint64_t>::max()), - std::ios::dec, /*width = */ 0, + kDec, /*width = */ 0, "170141183460469231731687303715884105727"}); CheckInt128Case({absl::MakeInt128(std::numeric_limits<int64_t>::max(), std::numeric_limits<uint64_t>::max()), - std::ios::oct, /*width = */ 0, + kOct, /*width = */ 0, "1777777777777777777777777777777777777777777"}); CheckInt128Case({absl::MakeInt128(std::numeric_limits<int64_t>::max(), std::numeric_limits<uint64_t>::max()), - std::ios::hex, /*width = */ 0, - "7fffffffffffffffffffffffffffffff"}); + kHex, /*width = */ 0, "7fffffffffffffffffffffffffffffff"}); CheckInt128Case({absl::MakeInt128(std::numeric_limits<int64_t>::min(), 0), - std::ios::dec, /*width = */ 0, + kDec, /*width = */ 0, "-170141183460469231731687303715884105728"}); CheckInt128Case({absl::MakeInt128(std::numeric_limits<int64_t>::min(), 0), - std::ios::oct, /*width = */ 0, + kOct, /*width = */ 0, "2000000000000000000000000000000000000000000"}); CheckInt128Case({absl::MakeInt128(std::numeric_limits<int64_t>::min(), 0), - std::ios::hex, /*width = */ 0, - "80000000000000000000000000000000"}); - CheckInt128Case({-1, std::ios::dec, /*width = */ 0, "-1"}); - CheckInt128Case({-1, std::ios::oct, /*width = */ 0, + kHex, /*width = */ 0, "80000000000000000000000000000000"}); + CheckInt128Case({-1, kDec, /*width = */ 0, "-1"}); + CheckInt128Case({-1, kOct, /*width = */ 0, "3777777777777777777777777777777777777777777"}); CheckInt128Case( - {-1, std::ios::hex, /*width = */ 0, "ffffffffffffffffffffffffffffffff"}); - CheckInt128Case({-12345, std::ios::dec, /*width = */ 0, "-12345"}); - CheckInt128Case({-12345, std::ios::oct, /*width = */ 0, + {-1, kHex, /*width = */ 0, "ffffffffffffffffffffffffffffffff"}); + CheckInt128Case({-12345, kDec, /*width = */ 0, "-12345"}); + CheckInt128Case({-12345, kOct, /*width = */ 0, "3777777777777777777777777777777777777747707"}); - CheckInt128Case({-12345, std::ios::hex, /*width = */ 0, - "ffffffffffffffffffffffffffffcfc7"}); + CheckInt128Case( + {-12345, kHex, /*width = */ 0, "ffffffffffffffffffffffffffffcfc7"}); } std::vector<Int128TestCase> GetInt128FormatCases(); diff --git a/absl/profiling/internal/exponential_biased_test.cc b/absl/profiling/internal/exponential_biased_test.cc index 6a6c317e..ebfbcad4 100644 --- a/absl/profiling/internal/exponential_biased_test.cc +++ b/absl/profiling/internal/exponential_biased_test.cc @@ -94,13 +94,14 @@ double AndersonDarlingPValue(int n, double z) { } double AndersonDarlingStatistic(const std::vector<double>& random_sample) { - int n = random_sample.size(); + size_t n = random_sample.size(); double ad_sum = 0; - for (int i = 0; i < n; i++) { + for (size_t i = 0; i < n; i++) { ad_sum += (2 * i + 1) * std::log(random_sample[i] * (1 - random_sample[n - 1 - i])); } - double ad_statistic = -n - 1 / static_cast<double>(n) * ad_sum; + const auto n_as_double = static_cast<double>(n); + double ad_statistic = -n_as_double - 1 / n_as_double * ad_sum; return ad_statistic; } @@ -111,14 +112,15 @@ double AndersonDarlingStatistic(const std::vector<double>& random_sample) { // Marsaglia and Marsaglia for details. double AndersonDarlingTest(const std::vector<double>& random_sample) { double ad_statistic = AndersonDarlingStatistic(random_sample); - double p = AndersonDarlingPValue(random_sample.size(), ad_statistic); + double p = AndersonDarlingPValue(static_cast<int>(random_sample.size()), + ad_statistic); return p; } TEST(ExponentialBiasedTest, CoinTossDemoWithGetSkipCount) { ExponentialBiased eb; for (int runs = 0; runs < 10; ++runs) { - for (int flips = eb.GetSkipCount(1); flips > 0; --flips) { + for (int64_t flips = eb.GetSkipCount(1); flips > 0; --flips) { printf("head..."); } printf("tail\n"); @@ -132,7 +134,7 @@ TEST(ExponentialBiasedTest, CoinTossDemoWithGetSkipCount) { TEST(ExponentialBiasedTest, SampleDemoWithStride) { ExponentialBiased eb; - int stride = eb.GetStride(10); + int64_t stride = eb.GetStride(10); int samples = 0; for (int i = 0; i < 10000000; ++i) { if (--stride == 0) { @@ -147,7 +149,7 @@ TEST(ExponentialBiasedTest, SampleDemoWithStride) { // Testing that NextRandom generates uniform random numbers. Applies the // Anderson-Darling test for uniformity TEST(ExponentialBiasedTest, TestNextRandom) { - for (auto n : std::vector<int>({ + for (auto n : std::vector<size_t>({ 10, // Check short-range correlation 100, 1000, 10000 // Make sure there's no systemic error @@ -161,7 +163,7 @@ TEST(ExponentialBiasedTest, TestNextRandom) { } std::vector<uint64_t> int_random_sample(n); // Collect samples - for (int i = 0; i < n; i++) { + for (size_t i = 0; i < n; i++) { int_random_sample[i] = x; x = ExponentialBiased::NextRandom(x); } @@ -169,7 +171,7 @@ TEST(ExponentialBiasedTest, TestNextRandom) { std::sort(int_random_sample.begin(), int_random_sample.end()); std::vector<double> random_sample(n); // Convert them to uniform randoms (in the range [0,1]) - for (int i = 0; i < n; i++) { + for (size_t i = 0; i < n; i++) { random_sample[i] = static_cast<double>(int_random_sample[i]) / max_prng_value; } diff --git a/absl/profiling/internal/sample_recorder.h b/absl/profiling/internal/sample_recorder.h index 5f65983b..371f6c47 100644 --- a/absl/profiling/internal/sample_recorder.h +++ b/absl/profiling/internal/sample_recorder.h @@ -77,8 +77,8 @@ class SampleRecorder { // samples that have been dropped. int64_t Iterate(const std::function<void(const T& stack)>& f); - int32_t GetMaxSamples() const; - void SetMaxSamples(int32_t max); + size_t GetMaxSamples() const; + void SetMaxSamples(size_t max); private: void PushNew(T* sample); @@ -88,7 +88,7 @@ class SampleRecorder { std::atomic<size_t> dropped_samples_; std::atomic<size_t> size_estimate_; - std::atomic<int32_t> max_samples_{1 << 20}; + std::atomic<size_t> max_samples_{1 << 20}; // Intrusive lock free linked lists for tracking samples. // @@ -186,7 +186,7 @@ T* SampleRecorder<T>::PopDead(Targs... args) { template <typename T> template <typename... Targs> T* SampleRecorder<T>::Register(Targs&&... args) { - int64_t size = size_estimate_.fetch_add(1, std::memory_order_relaxed); + size_t size = size_estimate_.fetch_add(1, std::memory_order_relaxed); if (size > max_samples_.load(std::memory_order_relaxed)) { size_estimate_.fetch_sub(1, std::memory_order_relaxed); dropped_samples_.fetch_add(1, std::memory_order_relaxed); @@ -199,6 +199,14 @@ T* SampleRecorder<T>::Register(Targs&&... args) { sample = new T(); { absl::MutexLock sample_lock(&sample->init_mu); + // If flag initialization happens to occur (perhaps in another thread) + // while in this block, it will lock `graveyard_` which is usually always + // locked before any sample. This will appear as a lock inversion. + // However, this code is run exactly once per sample, and this sample + // cannot be accessed until after it is returned from this method. This + // means that this lock state can never be recreated, so we can safely + // inform the deadlock detector to ignore it. + sample->init_mu.ForgetDeadlockInfo(); sample->PrepareForSampling(std::forward<Targs>(args)...); } PushNew(sample); @@ -229,12 +237,12 @@ int64_t SampleRecorder<T>::Iterate( } template <typename T> -void SampleRecorder<T>::SetMaxSamples(int32_t max) { +void SampleRecorder<T>::SetMaxSamples(size_t max) { max_samples_.store(max, std::memory_order_release); } template <typename T> -int32_t SampleRecorder<T>::GetMaxSamples() const { +size_t SampleRecorder<T>::GetMaxSamples() const { return max_samples_.load(std::memory_order_acquire); } diff --git a/absl/random/BUILD.bazel b/absl/random/BUILD.bazel index 08ecd197..133c0659 100644 --- a/absl/random/BUILD.bazel +++ b/absl/random/BUILD.bazel @@ -306,6 +306,7 @@ cc_test( cc_test( name = "exponential_distribution_test", size = "small", + timeout = "moderate", srcs = ["exponential_distribution_test.cc"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, @@ -493,18 +494,6 @@ cc_test( ], ) -BENCHMARK_TAGS = [ - "benchmark", - "no_test_android_arm", - "no_test_android_arm64", - "no_test_android_x86", - "no_test_darwin_x86_64", - "no_test_ios_x86_64", - "no_test_loonix", - "no_test_msvc_x64", - "no_test_wasm", -] - # Benchmarks for various methods / test utilities cc_binary( name = "benchmarks", @@ -514,7 +503,7 @@ cc_binary( ], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, - tags = BENCHMARK_TAGS, + tags = ["benchmark"], deps = [ ":distributions", ":random", diff --git a/absl/random/CMakeLists.txt b/absl/random/CMakeLists.txt index d04c7081..c74fd300 100644 --- a/absl/random/CMakeLists.txt +++ b/absl/random/CMakeLists.txt @@ -569,7 +569,7 @@ absl_cc_library( ${ABSL_DEFAULT_COPTS} LINKOPTS ${ABSL_DEFAULT_LINKOPTS} - $<$<BOOL:${MINGW}>:"bcrypt"> + $<$<BOOL:${MINGW}>:-lbcrypt> DEPS absl::core_headers absl::optional diff --git a/absl/random/internal/BUILD.bazel b/absl/random/internal/BUILD.bazel index fd5b6195..81ca669b 100644 --- a/absl/random/internal/BUILD.bazel +++ b/absl/random/internal/BUILD.bazel @@ -24,9 +24,11 @@ load( "absl_random_randen_copts_init", ) -package(default_visibility = [ +default_package_visibility = [ "//absl/random:__pkg__", -]) +] + +package(default_visibility = default_package_visibility) licenses(["notice"]) @@ -248,6 +250,8 @@ cc_library( hdrs = ["randen_engine.h"], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = default_package_visibility + [ + ], deps = [ ":iostream_state_saver", ":randen", @@ -389,7 +393,7 @@ ABSL_RANDOM_NONPORTABLE_TAGS = [ "no_test_darwin_x86_64", "no_test_ios_x86_64", "no_test_loonix", - "no_test_msvc_x64", + "no_test_lexan", "no_test_wasm", ] diff --git a/absl/random/internal/fast_uniform_bits.h b/absl/random/internal/fast_uniform_bits.h index f3a5c00f..8d8ed045 100644 --- a/absl/random/internal/fast_uniform_bits.h +++ b/absl/random/internal/fast_uniform_bits.h @@ -151,7 +151,8 @@ FastUniformBits<UIntType>::Generate(URBG& g, // NOLINT(runtime/references) result_type r = static_cast<result_type>(g() - kMin); for (size_t n = 1; n < kIters; ++n) { - r = (r << kShift) + static_cast<result_type>(g() - kMin); + r = static_cast<result_type>(r << kShift) + + static_cast<result_type>(g() - kMin); } return r; } diff --git a/absl/random/internal/nonsecure_base.h b/absl/random/internal/nonsecure_base.h index c7d7fa4b..c3b80335 100644 --- a/absl/random/internal/nonsecure_base.h +++ b/absl/random/internal/nonsecure_base.h @@ -44,7 +44,7 @@ class RandenPoolSeedSeq { // Generate random unsigned values directly into the buffer. template <typename Contiguous> void generate_impl(ContiguousTag, Contiguous begin, Contiguous end) { - const size_t n = std::distance(begin, end); + const size_t n = static_cast<size_t>(std::distance(begin, end)); auto* a = &(*begin); RandenPool<uint8_t>::Fill( absl::MakeSpan(reinterpret_cast<uint8_t*>(a), sizeof(*a) * n)); diff --git a/absl/random/internal/pcg_engine.h b/absl/random/internal/pcg_engine.h index 4ab44c94..e1f4ef33 100644 --- a/absl/random/internal/pcg_engine.h +++ b/absl/random/internal/pcg_engine.h @@ -221,47 +221,26 @@ class pcg_engine { template <uint64_t kMultA, uint64_t kMultB, uint64_t kIncA, uint64_t kIncB> class pcg128_params { public: -#if ABSL_HAVE_INTRINSIC_INT128 - using state_type = __uint128_t; - static inline constexpr state_type make_u128(uint64_t a, uint64_t b) { - return (static_cast<__uint128_t>(a) << 64) | b; - } -#else using state_type = absl::uint128; - static inline constexpr state_type make_u128(uint64_t a, uint64_t b) { - return absl::MakeUint128(a, b); - } -#endif - static inline constexpr state_type multiplier() { - return make_u128(kMultA, kMultB); + return absl::MakeUint128(kMultA, kMultB); } static inline constexpr state_type increment() { - return make_u128(kIncA, kIncB); + return absl::MakeUint128(kIncA, kIncB); } }; // Implementation of the PCG xsl_rr_128_64 128-bit mixing function, which // accepts an input of state_type and mixes it into an output of result_type. struct pcg_xsl_rr_128_64 { -#if ABSL_HAVE_INTRINSIC_INT128 - using state_type = __uint128_t; -#else using state_type = absl::uint128; -#endif using result_type = uint64_t; inline uint64_t operator()(state_type state) { // This is equivalent to the xsl_rr_128_64 mixing function. -#if ABSL_HAVE_INTRINSIC_INT128 uint64_t rotate = static_cast<uint64_t>(state >> 122u); state ^= state >> 64; uint64_t s = static_cast<uint64_t>(state); -#else - uint64_t h = Uint128High64(state); - uint64_t rotate = h >> 58u; - uint64_t s = Uint128Low64(state) ^ h; -#endif return rotr(s, static_cast<int>(rotate)); } }; diff --git a/absl/random/internal/pool_urbg.cc b/absl/random/internal/pool_urbg.cc index 725100a4..5aefa7d9 100644 --- a/absl/random/internal/pool_urbg.cc +++ b/absl/random/internal/pool_urbg.cc @@ -131,7 +131,7 @@ void RandenPoolEntry::Fill(uint8_t* out, size_t bytes) { } // Number of pooled urbg entries. -static constexpr int kPoolSize = 8; +static constexpr size_t kPoolSize = 8; // Shared pool entries. static absl::once_flag pool_once; @@ -147,15 +147,15 @@ ABSL_CACHELINE_ALIGNED static RandenPoolEntry* shared_pools[kPoolSize]; // on subsequent runs the order within the same program may be significantly // different. However, as other thread IDs are not assigned sequentially, // this is not expected to matter. -int GetPoolID() { +size_t GetPoolID() { static_assert(kPoolSize >= 1, "At least one urbg instance is required for PoolURBG"); - ABSL_CONST_INIT static std::atomic<int64_t> sequence{0}; + ABSL_CONST_INIT static std::atomic<uint64_t> sequence{0}; #ifdef ABSL_HAVE_THREAD_LOCAL - static thread_local int my_pool_id = -1; - if (ABSL_PREDICT_FALSE(my_pool_id < 0)) { + static thread_local size_t my_pool_id = kPoolSize; + if (ABSL_PREDICT_FALSE(my_pool_id == kPoolSize)) { my_pool_id = (sequence++ % kPoolSize); } return my_pool_id; @@ -171,8 +171,8 @@ int GetPoolID() { // Store the value in the pthread_{get/set}specific. However an uninitialized // value is 0, so add +1 to distinguish from the null value. - intptr_t my_pool_id = - reinterpret_cast<intptr_t>(pthread_getspecific(tid_key)); + uintptr_t my_pool_id = + reinterpret_cast<uintptr_t>(pthread_getspecific(tid_key)); if (ABSL_PREDICT_FALSE(my_pool_id == 0)) { // No allocated ID, allocate the next value, cache it, and return. my_pool_id = (sequence++ % kPoolSize) + 1; @@ -194,7 +194,7 @@ RandenPoolEntry* PoolAlignedAlloc() { // Not all the platforms that we build for have std::aligned_alloc, however // since we never free these objects, we can over allocate and munge the // pointers to the correct alignment. - intptr_t x = reinterpret_cast<intptr_t>( + uintptr_t x = reinterpret_cast<uintptr_t>( new char[sizeof(RandenPoolEntry) + kAlignment]); auto y = x % kAlignment; void* aligned = reinterpret_cast<void*>(y == 0 ? x : (x + kAlignment - y)); @@ -215,7 +215,7 @@ void InitPoolURBG() { absl::MakeSpan(seed_material))) { random_internal::ThrowSeedGenException(); } - for (int i = 0; i < kPoolSize; i++) { + for (size_t i = 0; i < kPoolSize; i++) { shared_pools[i] = PoolAlignedAlloc(); shared_pools[i]->Init( absl::MakeSpan(&seed_material[i * kSeedSize], kSeedSize)); diff --git a/absl/random/internal/seed_material.cc b/absl/random/internal/seed_material.cc index c03cad85..1041302b 100644 --- a/absl/random/internal/seed_material.cc +++ b/absl/random/internal/seed_material.cc @@ -173,12 +173,12 @@ bool ReadSeedMaterialFromDevURandom(absl::Span<uint32_t> values) { } while (success && buffer_size > 0) { - int bytes_read = read(dev_urandom, buffer, buffer_size); + ssize_t bytes_read = read(dev_urandom, buffer, buffer_size); int read_error = errno; success = (bytes_read > 0); if (success) { buffer += bytes_read; - buffer_size -= bytes_read; + buffer_size -= static_cast<size_t>(bytes_read); } else if (bytes_read == -1 && read_error == EINTR) { success = true; // Need to try again. } diff --git a/absl/random/random.h b/absl/random/random.h index 71b63092..76720867 100644 --- a/absl/random/random.h +++ b/absl/random/random.h @@ -68,7 +68,7 @@ ABSL_NAMESPACE_BEGIN // // `absl::BitGen` may be constructed with an optional seed sequence type, // conforming to [rand.req.seed_seq], which will be mixed with additional -// non-deterministic data. +// non-deterministic data as detailed below. // // Example: // @@ -79,16 +79,16 @@ ABSL_NAMESPACE_BEGIN // // Generate an integer value in the closed interval [1,6] // int die_roll2 = absl::uniform_int_distribution<int>(1, 6)(gen_with_seed); // +// Constructing two `absl::BitGen`s with the same seed sequence in the same +// process will produce the same sequence of variates, but need not do so across +// multiple processes even if they're executing the same binary. +// // `absl::BitGen` meets the requirements of the Uniform Random Bit Generator // (URBG) concept as per the C++17 standard [rand.req.urng] though differs // slightly with [rand.req.eng]. Like its standard library equivalents (e.g. // `std::mersenne_twister_engine`) `absl::BitGen` is not cryptographically // secure. // -// Constructing two `absl::BitGen`s with the same seed sequence in the same -// binary will produce the same sequence of variates within the same binary, but -// need not do so across multiple binary invocations. -// // This type has been optimized to perform better than Mersenne Twister // (https://en.wikipedia.org/wiki/Mersenne_Twister) and many other complex URBG // types on modern x86, ARM, and PPC architectures. @@ -147,7 +147,7 @@ using BitGen = random_internal::NonsecureURBGBase< // // `absl::InsecureBitGen` may be constructed with an optional seed sequence // type, conforming to [rand.req.seed_seq], which will be mixed with additional -// non-deterministic data. (See std_seed_seq.h for more information.) +// non-deterministic data, as detailed in the `absl::BitGen` comment. // // `absl::InsecureBitGen` meets the requirements of the Uniform Random Bit // Generator (URBG) concept as per the C++17 standard [rand.req.urng] though diff --git a/absl/random/uniform_int_distribution_test.cc b/absl/random/uniform_int_distribution_test.cc index 276d72ad..a830117a 100644 --- a/absl/random/uniform_int_distribution_test.cc +++ b/absl/random/uniform_int_distribution_test.cc @@ -19,6 +19,7 @@ #include <iterator> #include <random> #include <sstream> +#include <string> #include <vector> #include "gmock/gmock.h" @@ -136,7 +137,7 @@ TYPED_TEST(UniformIntDistributionTest, TestMoments) { typename absl::uniform_int_distribution<TypeParam>::param_type; // We use a fixed bit generator for distribution accuracy tests. This allows - // these tests to be deterministic, while still testing the qualify of the + // these tests to be deterministic, while still testing the quality of the // implementation. absl::random_internal::pcg64_2018_engine rng{0x2B7E151628AED2A6}; @@ -172,7 +173,7 @@ TYPED_TEST(UniformIntDistributionTest, ChiSquaredTest50) { using absl::random_internal::kChiSquared; constexpr size_t kTrials = 1000; - constexpr int kBuckets = 50; // inclusive, so actally +1 + constexpr int kBuckets = 50; // inclusive, so actually +1 constexpr double kExpected = static_cast<double>(kTrials) / static_cast<double>(kBuckets); @@ -184,7 +185,7 @@ TYPED_TEST(UniformIntDistributionTest, ChiSquaredTest50) { const TypeParam max = min + kBuckets; // We use a fixed bit generator for distribution accuracy tests. This allows - // these tests to be deterministic, while still testing the qualify of the + // these tests to be deterministic, while still testing the quality of the // implementation. absl::random_internal::pcg64_2018_engine rng{0x2B7E151628AED2A6}; diff --git a/absl/status/BUILD.bazel b/absl/status/BUILD.bazel index ce0ea70c..1f58b307 100644 --- a/absl/status/BUILD.bazel +++ b/absl/status/BUILD.bazel @@ -61,6 +61,7 @@ cc_test( name = "status_test", srcs = ["status_test.cc"], copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":status", "//absl/strings", diff --git a/absl/status/internal/status_internal.h b/absl/status/internal/status_internal.h index 19a4a7aa..873eb5c2 100644 --- a/absl/status/internal/status_internal.h +++ b/absl/status/internal/status_internal.h @@ -14,6 +14,7 @@ #ifndef ABSL_STATUS_INTERNAL_STATUS_INTERNAL_H_ #define ABSL_STATUS_INTERNAL_STATUS_INTERNAL_H_ +#include <memory> #include <string> #include <utility> diff --git a/absl/status/status.cc b/absl/status/status.cc index 88e8eda9..160eb417 100644 --- a/absl/status/status.cc +++ b/absl/status/status.cc @@ -16,9 +16,11 @@ #include <errno.h> #include <cassert> +#include <utility> #include "absl/base/internal/raw_logging.h" #include "absl/base/internal/strerror.h" +#include "absl/base/macros.h" #include "absl/debugging/stacktrace.h" #include "absl/debugging/symbolize.h" #include "absl/status/status_payload_printer.h" @@ -77,15 +79,17 @@ std::ostream& operator<<(std::ostream& os, StatusCode code) { namespace status_internal { -static int FindPayloadIndexByUrl(const Payloads* payloads, - absl::string_view type_url) { - if (payloads == nullptr) return -1; +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 -1; + return absl::nullopt; } // Convert canonical code to a value known to this binary. @@ -119,8 +123,10 @@ absl::StatusCode MapToLocalCode(int value) { absl::optional<absl::Cord> Status::GetPayload( absl::string_view type_url) const { const auto* payloads = GetPayloads(); - int index = status_internal::FindPayloadIndexByUrl(payloads, type_url); - if (index != -1) return (*payloads)[index].payload; + absl::optional<size_t> index = + status_internal::FindPayloadIndexByUrl(payloads, type_url); + if (index.has_value()) + return (*payloads)[index.value()].payload; return absl::nullopt; } @@ -135,10 +141,10 @@ void Status::SetPayload(absl::string_view type_url, absl::Cord payload) { rep->payloads = absl::make_unique<status_internal::Payloads>(); } - int index = + absl::optional<size_t> index = status_internal::FindPayloadIndexByUrl(rep->payloads.get(), type_url); - if (index != -1) { - (*rep->payloads)[index].payload = std::move(payload); + if (index.has_value()) { + (*rep->payloads)[index.value()].payload = std::move(payload); return; } @@ -146,10 +152,11 @@ void Status::SetPayload(absl::string_view type_url, absl::Cord payload) { } bool Status::ErasePayload(absl::string_view type_url) { - int index = status_internal::FindPayloadIndexByUrl(GetPayloads(), type_url); - if (index != -1) { + absl::optional<size_t> index = + status_internal::FindPayloadIndexByUrl(GetPayloads(), type_url); + if (index.has_value()) { PrepareToModify(); - GetPayloads()->erase(GetPayloads()->begin() + index); + 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). diff --git a/absl/status/status.h b/absl/status/status.h index 9292b83a..4e8292fc 100644 --- a/absl/status/status.h +++ b/absl/status/status.h @@ -51,10 +51,10 @@ #ifndef ABSL_STATUS_STATUS_H_ #define ABSL_STATUS_STATUS_H_ -#include <iostream> +#include <ostream> #include <string> +#include <utility> -#include "absl/container/inlined_vector.h" #include "absl/functional/function_ref.h" #include "absl/status/internal/status_internal.h" #include "absl/strings/cord.h" diff --git a/absl/status/status_test.cc b/absl/status/status_test.cc index 89cce7df..74a64ace 100644 --- a/absl/status/status_test.cc +++ b/absl/status/status_test.cc @@ -505,4 +505,5 @@ TEST(StatusErrno, ErrnoToStatus) { EXPECT_EQ(status.code(), absl::StatusCode::kNotFound); EXPECT_EQ(status.message(), "Cannot open 'path': No such file or directory"); } + } // namespace diff --git a/absl/status/statusor_test.cc b/absl/status/statusor_test.cc index 7cae90e1..29021543 100644 --- a/absl/status/statusor_test.cc +++ b/absl/status/statusor_test.cc @@ -1521,7 +1521,7 @@ static absl::StatusOr<int> MakeStatus() { return 100; } TEST(StatusOr, TestIgnoreError) { MakeStatus().IgnoreError(); } TEST(StatusOr, EqualityOperator) { - constexpr int kNumCases = 4; + constexpr size_t kNumCases = 4; std::array<absl::StatusOr<int>, kNumCases> group1 = { absl::StatusOr<int>(1), absl::StatusOr<int>(2), absl::StatusOr<int>(absl::InvalidArgumentError("msg")), @@ -1530,8 +1530,8 @@ TEST(StatusOr, EqualityOperator) { absl::StatusOr<int>(1), absl::StatusOr<int>(2), absl::StatusOr<int>(absl::InvalidArgumentError("msg")), absl::StatusOr<int>(absl::InternalError("msg"))}; - for (int i = 0; i < kNumCases; ++i) { - for (int j = 0; j < kNumCases; ++j) { + for (size_t i = 0; i < kNumCases; ++i) { + for (size_t j = 0; j < kNumCases; ++j) { if (i == j) { EXPECT_TRUE(group1[i] == group2[j]); EXPECT_FALSE(group1[i] != group2[j]); diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index 3f51252f..53c57718 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel @@ -37,11 +37,14 @@ cc_library( "internal/charconv_bigint.h", "internal/charconv_parse.cc", "internal/charconv_parse.h", + "internal/damerau_levenshtein_distance.cc", "internal/memutil.cc", "internal/memutil.h", "internal/stl_type_traits.h", "internal/str_join_internal.h", "internal/str_split_internal.h", + "internal/stringify_sink.cc", + "internal/stringify_sink.h", "match.cc", "numbers.cc", "str_cat.cc", @@ -54,6 +57,8 @@ cc_library( "ascii.h", "charconv.h", "escaping.h", + "internal/damerau_levenshtein_distance.h", + "internal/has_absl_stringify.h", "internal/string_constant.h", "match.h", "numbers.h", @@ -179,6 +184,19 @@ cc_test( ) cc_test( + name = "damerau_levenshtein_distance_test", + size = "small", + srcs = [ + "internal/damerau_levenshtein_distance_test.cc", + ], + copts = ABSL_TEST_COPTS, + deps = [ + "//absl/strings", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( name = "memutil_benchmark", srcs = [ "internal/memutil.h", @@ -304,8 +322,10 @@ cc_library( "//absl/base:raw_logging_internal", "//absl/base:throw_delegate", "//absl/container:compressed_tuple", + "//absl/container:container_memory", "//absl/container:inlined_vector", "//absl/container:layout", + "//absl/crc:crc_cord_state", "//absl/functional:function_ref", "//absl/meta:type_traits", "//absl/types:span", @@ -387,6 +407,7 @@ cc_test( ":cord_internal", ":cord_rep_test_util", "//absl/base:config", + "//absl/crc:crc_cord_state", "@com_google_googletest//:gtest_main", ], ) @@ -445,6 +466,7 @@ cc_library( "//absl/base:raw_logging_internal", "//absl/container:fixed_array", "//absl/container:inlined_vector", + "//absl/crc:crc_cord_state", "//absl/functional:function_ref", "//absl/meta:type_traits", "//absl/numeric:bits", @@ -641,6 +663,7 @@ cc_test( ":cordz_update_scope", ":cordz_update_tracker", "//absl/base:config", + "//absl/crc:crc_cord_state", "//absl/synchronization", "//absl/synchronization:thread_pool", "@com_google_googletest//:gtest_main", @@ -747,13 +770,13 @@ cc_test( ":cordz_test_helpers", ":str_format", ":strings", - "//absl/base", "//absl/base:config", "//absl/base:core_headers", "//absl/base:endian", "//absl/base:raw_logging_internal", "//absl/container:fixed_array", "//absl/hash", + "//absl/log", "//absl/random", "@com_google_googletest//:gtest_main", ], @@ -770,8 +793,8 @@ cc_test( "no_test_android_arm64", "no_test_android_x86", "no_test_ios_x86_64", + "no_test_lexan", "no_test_loonix", - "no_test_msvc_x64", ], visibility = ["//visibility:private"], deps = [ @@ -963,8 +986,8 @@ cc_test( copts = ABSL_TEST_COPTS, visibility = ["//visibility:private"], deps = [ + ":str_format", ":strings", - "//absl/base:core_headers", "@com_google_googletest//:gtest_main", ], ) @@ -1132,6 +1155,7 @@ cc_library( "internal/str_format/arg.h", "internal/str_format/bind.h", "internal/str_format/checker.h", + "internal/str_format/constexpr_parser.h", "internal/str_format/extension.h", "internal/str_format/float_conversion.h", "internal/str_format/output.h", @@ -1164,7 +1188,6 @@ cc_test( ":cord", ":str_format", ":strings", - "//absl/base:core_headers", "@com_google_googletest//:gtest_main", ], ) @@ -1279,3 +1302,18 @@ cc_test( "@com_google_googletest//:gtest_main", ], ) + +cc_binary( + name = "atod_manual_test", + testonly = 1, + srcs = ["atod_manual_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:private"], + deps = [ + ":str_format", + ":strings", + "//absl/base", + "//absl/types:optional", + ], +) diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index bb073301..a0f7cc54 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt @@ -21,7 +21,9 @@ absl_cc_library( "ascii.h" "charconv.h" "escaping.h" + "internal/damerau_levenshtein_distance.h" "internal/string_constant.h" + "internal/has_absl_stringify.h" "match.h" "numbers.h" "str_cat.h" @@ -39,8 +41,11 @@ absl_cc_library( "internal/charconv_bigint.h" "internal/charconv_parse.cc" "internal/charconv_parse.h" + "internal/damerau_levenshtein_distance.cc" "internal/memutil.cc" "internal/memutil.h" + "internal/stringify_sink.h" + "internal/stringify_sink.cc" "internal/stl_type_traits.h" "internal/str_join_internal.h" "internal/str_split_internal.h" @@ -134,6 +139,19 @@ absl_cc_test( absl_cc_test( NAME + damerau_levenshtein_distance_test + SRCS + "internal/damerau_levenshtein_distance_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::strings + absl::base + GTest::gmock_main +) + +absl_cc_test( + NAME memutil_test SRCS "internal/memutil.h" @@ -281,6 +299,7 @@ absl_cc_test( ${ABSL_TEST_COPTS} DEPS absl::strings + absl::str_format absl::core_headers GTest::gmock_main ) @@ -394,6 +413,7 @@ absl_cc_library( "internal/str_format/arg.h" "internal/str_format/bind.h" "internal/str_format/checker.h" + "internal/str_format/constexpr_parser.h" "internal/str_format/extension.h" "internal/str_format/float_conversion.h" "internal/str_format/output.h" @@ -584,7 +604,9 @@ absl_cc_library( absl::base_internal absl::compressed_tuple absl::config + absl::container_memory absl::core_headers + absl::crc_cord_state absl::endian absl::inlined_vector absl::layout @@ -763,6 +785,7 @@ absl_cc_test( absl::cordz_statistics absl::cordz_update_scope absl::cordz_update_tracker + absl::crc_cord_state absl::thread_pool GTest::gmock_main ) @@ -862,6 +885,7 @@ absl_cc_library( absl::cordz_update_scope absl::cordz_update_tracker absl::core_headers + absl::crc_cord_state absl::endian absl::fixed_array absl::function_ref @@ -903,6 +927,7 @@ absl_cc_library( absl::cord_internal absl::strings TESTONLY + PUBLIC ) # Internal-only target, do not depend on directly. @@ -1033,6 +1058,7 @@ absl_cc_test( absl::config absl::cord_internal absl::cord_rep_test_util + absl::crc_cord_state GTest::gmock_main ) diff --git a/absl/strings/ascii.cc b/absl/strings/ascii.cc index 93bb03e9..868df2d1 100644 --- a/absl/strings/ascii.cc +++ b/absl/strings/ascii.cc @@ -157,13 +157,13 @@ ABSL_DLL const char kToUpper[256] = { void AsciiStrToLower(std::string* s) { for (auto& ch : *s) { - ch = absl::ascii_tolower(ch); + ch = absl::ascii_tolower(static_cast<unsigned char>(ch)); } } void AsciiStrToUpper(std::string* s) { for (auto& ch : *s) { - ch = absl::ascii_toupper(ch); + ch = absl::ascii_toupper(static_cast<unsigned char>(ch)); } } @@ -183,17 +183,17 @@ void RemoveExtraAsciiWhitespace(std::string* str) { for (; input_it < input_end; ++input_it) { if (is_ws) { // Consecutive whitespace? Keep only the last. - is_ws = absl::ascii_isspace(*input_it); + is_ws = absl::ascii_isspace(static_cast<unsigned char>(*input_it)); if (is_ws) --output_it; } else { - is_ws = absl::ascii_isspace(*input_it); + is_ws = absl::ascii_isspace(static_cast<unsigned char>(*input_it)); } *output_it = *input_it; ++output_it; } - str->erase(output_it - &(*str)[0]); + str->erase(static_cast<size_t>(output_it - &(*str)[0])); } ABSL_NAMESPACE_END diff --git a/absl/strings/ascii_test.cc b/absl/strings/ascii_test.cc index 83af7825..dfed114c 100644 --- a/absl/strings/ascii_test.cc +++ b/absl/strings/ascii_test.cc @@ -27,103 +27,99 @@ namespace { TEST(AsciiIsFoo, All) { for (int i = 0; i < 256; i++) { - if ((i >= 'a' && i <= 'z') || (i >= 'A' && i <= 'Z')) - EXPECT_TRUE(absl::ascii_isalpha(i)) << ": failed on " << i; + const auto c = static_cast<unsigned char>(i); + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) + EXPECT_TRUE(absl::ascii_isalpha(c)) << ": failed on " << c; else - EXPECT_TRUE(!absl::ascii_isalpha(i)) << ": failed on " << i; + EXPECT_TRUE(!absl::ascii_isalpha(c)) << ": failed on " << c; } for (int i = 0; i < 256; i++) { - if ((i >= '0' && i <= '9')) - EXPECT_TRUE(absl::ascii_isdigit(i)) << ": failed on " << i; + const auto c = static_cast<unsigned char>(i); + if ((c >= '0' && c <= '9')) + EXPECT_TRUE(absl::ascii_isdigit(c)) << ": failed on " << c; else - EXPECT_TRUE(!absl::ascii_isdigit(i)) << ": failed on " << i; + EXPECT_TRUE(!absl::ascii_isdigit(c)) << ": failed on " << c; } for (int i = 0; i < 256; i++) { - if (absl::ascii_isalpha(i) || absl::ascii_isdigit(i)) - EXPECT_TRUE(absl::ascii_isalnum(i)) << ": failed on " << i; + const auto c = static_cast<unsigned char>(i); + if (absl::ascii_isalpha(c) || absl::ascii_isdigit(c)) + EXPECT_TRUE(absl::ascii_isalnum(c)) << ": failed on " << c; else - EXPECT_TRUE(!absl::ascii_isalnum(i)) << ": failed on " << i; + EXPECT_TRUE(!absl::ascii_isalnum(c)) << ": failed on " << c; } for (int i = 0; i < 256; i++) { + const auto c = static_cast<unsigned char>(i); if (i != '\0' && strchr(" \r\n\t\v\f", i)) - EXPECT_TRUE(absl::ascii_isspace(i)) << ": failed on " << i; + EXPECT_TRUE(absl::ascii_isspace(c)) << ": failed on " << c; else - EXPECT_TRUE(!absl::ascii_isspace(i)) << ": failed on " << i; + EXPECT_TRUE(!absl::ascii_isspace(c)) << ": failed on " << c; } for (int i = 0; i < 256; i++) { + const auto c = static_cast<unsigned char>(i); if (i >= 32 && i < 127) - EXPECT_TRUE(absl::ascii_isprint(i)) << ": failed on " << i; + EXPECT_TRUE(absl::ascii_isprint(c)) << ": failed on " << c; else - EXPECT_TRUE(!absl::ascii_isprint(i)) << ": failed on " << i; + EXPECT_TRUE(!absl::ascii_isprint(c)) << ": failed on " << c; } for (int i = 0; i < 256; i++) { - if (absl::ascii_isprint(i) && !absl::ascii_isspace(i) && - !absl::ascii_isalnum(i)) - EXPECT_TRUE(absl::ascii_ispunct(i)) << ": failed on " << i; - else - EXPECT_TRUE(!absl::ascii_ispunct(i)) << ": failed on " << i; + const auto c = static_cast<unsigned char>(i); + if (absl::ascii_isprint(c) && !absl::ascii_isspace(c) && + !absl::ascii_isalnum(c)) { + EXPECT_TRUE(absl::ascii_ispunct(c)) << ": failed on " << c; + } else { + EXPECT_TRUE(!absl::ascii_ispunct(c)) << ": failed on " << c; + } } for (int i = 0; i < 256; i++) { + const auto c = static_cast<unsigned char>(i); if (i == ' ' || i == '\t') - EXPECT_TRUE(absl::ascii_isblank(i)) << ": failed on " << i; + EXPECT_TRUE(absl::ascii_isblank(c)) << ": failed on " << c; else - EXPECT_TRUE(!absl::ascii_isblank(i)) << ": failed on " << i; + EXPECT_TRUE(!absl::ascii_isblank(c)) << ": failed on " << c; } for (int i = 0; i < 256; i++) { + const auto c = static_cast<unsigned char>(i); if (i < 32 || i == 127) - EXPECT_TRUE(absl::ascii_iscntrl(i)) << ": failed on " << i; + EXPECT_TRUE(absl::ascii_iscntrl(c)) << ": failed on " << c; else - EXPECT_TRUE(!absl::ascii_iscntrl(i)) << ": failed on " << i; + EXPECT_TRUE(!absl::ascii_iscntrl(c)) << ": failed on " << c; } for (int i = 0; i < 256; i++) { - if (absl::ascii_isdigit(i) || (i >= 'A' && i <= 'F') || - (i >= 'a' && i <= 'f')) - EXPECT_TRUE(absl::ascii_isxdigit(i)) << ": failed on " << i; - else - EXPECT_TRUE(!absl::ascii_isxdigit(i)) << ": failed on " << i; + const auto c = static_cast<unsigned char>(i); + if (absl::ascii_isdigit(c) || (i >= 'A' && i <= 'F') || + (i >= 'a' && i <= 'f')) { + EXPECT_TRUE(absl::ascii_isxdigit(c)) << ": failed on " << c; + } else { + EXPECT_TRUE(!absl::ascii_isxdigit(c)) << ": failed on " << c; + } } for (int i = 0; i < 256; i++) { + const auto c = static_cast<unsigned char>(i); if (i > 32 && i < 127) - EXPECT_TRUE(absl::ascii_isgraph(i)) << ": failed on " << i; + EXPECT_TRUE(absl::ascii_isgraph(c)) << ": failed on " << c; else - EXPECT_TRUE(!absl::ascii_isgraph(i)) << ": failed on " << i; + EXPECT_TRUE(!absl::ascii_isgraph(c)) << ": failed on " << c; } for (int i = 0; i < 256; i++) { + const auto c = static_cast<unsigned char>(i); if (i >= 'A' && i <= 'Z') - EXPECT_TRUE(absl::ascii_isupper(i)) << ": failed on " << i; + EXPECT_TRUE(absl::ascii_isupper(c)) << ": failed on " << c; else - EXPECT_TRUE(!absl::ascii_isupper(i)) << ": failed on " << i; + EXPECT_TRUE(!absl::ascii_isupper(c)) << ": failed on " << c; } for (int i = 0; i < 256; i++) { + const auto c = static_cast<unsigned char>(i); if (i >= 'a' && i <= 'z') - EXPECT_TRUE(absl::ascii_islower(i)) << ": failed on " << i; + EXPECT_TRUE(absl::ascii_islower(c)) << ": failed on " << c; else - EXPECT_TRUE(!absl::ascii_islower(i)) << ": failed on " << i; + EXPECT_TRUE(!absl::ascii_islower(c)) << ": failed on " << c; } - for (int i = 0; i < 128; i++) { - EXPECT_TRUE(absl::ascii_isascii(i)) << ": failed on " << i; + for (unsigned char c = 0; c < 128; c++) { + EXPECT_TRUE(absl::ascii_isascii(c)) << ": failed on " << c; } for (int i = 128; i < 256; i++) { - EXPECT_TRUE(!absl::ascii_isascii(i)) << ": failed on " << i; - } - - // The official is* functions don't accept negative signed chars, but - // our absl::ascii_is* functions do. - for (int i = 0; i < 256; i++) { - signed char sc = static_cast<signed char>(static_cast<unsigned char>(i)); - EXPECT_EQ(absl::ascii_isalpha(i), absl::ascii_isalpha(sc)) << i; - EXPECT_EQ(absl::ascii_isdigit(i), absl::ascii_isdigit(sc)) << i; - EXPECT_EQ(absl::ascii_isalnum(i), absl::ascii_isalnum(sc)) << i; - EXPECT_EQ(absl::ascii_isspace(i), absl::ascii_isspace(sc)) << i; - EXPECT_EQ(absl::ascii_ispunct(i), absl::ascii_ispunct(sc)) << i; - EXPECT_EQ(absl::ascii_isblank(i), absl::ascii_isblank(sc)) << i; - EXPECT_EQ(absl::ascii_iscntrl(i), absl::ascii_iscntrl(sc)) << i; - EXPECT_EQ(absl::ascii_isxdigit(i), absl::ascii_isxdigit(sc)) << i; - EXPECT_EQ(absl::ascii_isprint(i), absl::ascii_isprint(sc)) << i; - EXPECT_EQ(absl::ascii_isgraph(i), absl::ascii_isgraph(sc)) << i; - EXPECT_EQ(absl::ascii_isupper(i), absl::ascii_isupper(sc)) << i; - EXPECT_EQ(absl::ascii_islower(i), absl::ascii_islower(sc)) << i; - EXPECT_EQ(absl::ascii_isascii(i), absl::ascii_isascii(sc)) << i; + const auto c = static_cast<unsigned char>(i); + EXPECT_TRUE(!absl::ascii_isascii(c)) << ": failed on " << c; } } @@ -137,19 +133,20 @@ TEST(AsciiIsFoo, SameAsIsFoo) { #endif for (int i = 0; i < 256; i++) { - EXPECT_EQ(isalpha(i) != 0, absl::ascii_isalpha(i)) << i; - EXPECT_EQ(isdigit(i) != 0, absl::ascii_isdigit(i)) << i; - EXPECT_EQ(isalnum(i) != 0, absl::ascii_isalnum(i)) << i; - EXPECT_EQ(isspace(i) != 0, absl::ascii_isspace(i)) << i; - EXPECT_EQ(ispunct(i) != 0, absl::ascii_ispunct(i)) << i; - EXPECT_EQ(isblank(i) != 0, absl::ascii_isblank(i)) << i; - EXPECT_EQ(iscntrl(i) != 0, absl::ascii_iscntrl(i)) << i; - EXPECT_EQ(isxdigit(i) != 0, absl::ascii_isxdigit(i)) << i; - EXPECT_EQ(isprint(i) != 0, absl::ascii_isprint(i)) << i; - EXPECT_EQ(isgraph(i) != 0, absl::ascii_isgraph(i)) << i; - EXPECT_EQ(isupper(i) != 0, absl::ascii_isupper(i)) << i; - EXPECT_EQ(islower(i) != 0, absl::ascii_islower(i)) << i; - EXPECT_EQ(isascii(i) != 0, absl::ascii_isascii(i)) << i; + const auto c = static_cast<unsigned char>(i); + EXPECT_EQ(isalpha(c) != 0, absl::ascii_isalpha(c)) << c; + EXPECT_EQ(isdigit(c) != 0, absl::ascii_isdigit(c)) << c; + EXPECT_EQ(isalnum(c) != 0, absl::ascii_isalnum(c)) << c; + EXPECT_EQ(isspace(c) != 0, absl::ascii_isspace(c)) << c; + EXPECT_EQ(ispunct(c) != 0, absl::ascii_ispunct(c)) << c; + EXPECT_EQ(isblank(c) != 0, absl::ascii_isblank(c)) << c; + EXPECT_EQ(iscntrl(c) != 0, absl::ascii_iscntrl(c)) << c; + EXPECT_EQ(isxdigit(c) != 0, absl::ascii_isxdigit(c)) << c; + EXPECT_EQ(isprint(c) != 0, absl::ascii_isprint(c)) << c; + EXPECT_EQ(isgraph(c) != 0, absl::ascii_isgraph(c)) << c; + EXPECT_EQ(isupper(c) != 0, absl::ascii_isupper(c)) << c; + EXPECT_EQ(islower(c) != 0, absl::ascii_islower(c)) << c; + EXPECT_EQ(isascii(c) != 0, absl::ascii_isascii(c)) << c; } #ifndef __ANDROID__ @@ -166,25 +163,20 @@ TEST(AsciiToFoo, All) { #endif for (int i = 0; i < 256; i++) { - if (absl::ascii_islower(i)) - EXPECT_EQ(absl::ascii_toupper(i), 'A' + (i - 'a')) << i; + const auto c = static_cast<unsigned char>(i); + if (absl::ascii_islower(c)) + EXPECT_EQ(absl::ascii_toupper(c), 'A' + (i - 'a')) << c; else - EXPECT_EQ(absl::ascii_toupper(i), static_cast<char>(i)) << i; + EXPECT_EQ(absl::ascii_toupper(c), static_cast<char>(i)) << c; - if (absl::ascii_isupper(i)) - EXPECT_EQ(absl::ascii_tolower(i), 'a' + (i - 'A')) << i; + if (absl::ascii_isupper(c)) + EXPECT_EQ(absl::ascii_tolower(c), 'a' + (i - 'A')) << c; else - EXPECT_EQ(absl::ascii_tolower(i), static_cast<char>(i)) << i; + EXPECT_EQ(absl::ascii_tolower(c), static_cast<char>(i)) << c; // These CHECKs only hold in a C locale. - EXPECT_EQ(static_cast<char>(tolower(i)), absl::ascii_tolower(i)) << i; - EXPECT_EQ(static_cast<char>(toupper(i)), absl::ascii_toupper(i)) << i; - - // The official to* functions don't accept negative signed chars, but - // our absl::ascii_to* functions do. - signed char sc = static_cast<signed char>(static_cast<unsigned char>(i)); - EXPECT_EQ(absl::ascii_tolower(i), absl::ascii_tolower(sc)) << i; - EXPECT_EQ(absl::ascii_toupper(i), absl::ascii_toupper(sc)) << i; + EXPECT_EQ(static_cast<char>(tolower(i)), absl::ascii_tolower(c)) << c; + EXPECT_EQ(static_cast<char>(toupper(i)), absl::ascii_toupper(c)) << c; } #ifndef __ANDROID__ // restore the old locale. diff --git a/absl/strings/atod_manual_test.cc b/absl/strings/atod_manual_test.cc new file mode 100644 index 00000000..6cf28b0d --- /dev/null +++ b/absl/strings/atod_manual_test.cc @@ -0,0 +1,193 @@ +// 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. + +// This program tests the absl::SimpleAtod and absl::SimpleAtof functions. Run +// it as "atod_manual_test pnftd/data/*.txt" where the pnftd directory is a +// local checkout of the https://github.com/nigeltao/parse-number-fxx-test-data +// repository. The test suite lives in a separate repository because its more +// than 5 million test cases weigh over several hundred megabytes and because +// the test cases are also useful to other software projects, not just Abseil. +// Its data/*.txt files contain one test case per line, like: +// +// 3C00 3F800000 3FF0000000000000 1 +// 3D00 3FA00000 3FF4000000000000 1.25 +// 3D9A 3FB33333 3FF6666666666666 1.4 +// 57B7 42F6E979 405EDD2F1A9FBE77 123.456 +// 622A 44454000 4088A80000000000 789 +// 7C00 7F800000 7FF0000000000000 123.456e789 +// +// For each line (and using 0-based column indexes), columns [5..13] and +// [14..30] contain the 32-bit float and 64-bit double result of parsing +// columns [31..]. +// +// For example, parsing "1.4" as a float gives the bits 0x3FB33333. +// +// In this 6-line example, the final line's float and double values are all +// infinity. The largest finite float and double values are approximately +// 3.40e+38 and 1.80e+308. + +#include <cstdint> +#include <cstdio> +#include <string> + +#include "absl/base/casts.h" +#include "absl/strings/numbers.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "absl/types/optional.h" + +static constexpr uint8_t kUnhex[256] = { + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, // '0' ..= '7' + 0x8, 0x9, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // '8' ..= '9' + + 0x0, 0xA, 0xB, 0xC, 0xD, 0xE, 0xF, 0x0, // 'A' ..= 'F' + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, // +}; + +static absl::optional<std::string> ReadFileToString(const char* filename) { + FILE* f = fopen(filename, "rb"); + if (!f) { + return absl::nullopt; + } + fseek(f, 0, SEEK_END); + size_t size = ftell(f); + fseek(f, 0, SEEK_SET); + std::string s(size, '\x00'); + size_t n = fread(&s[0], 1, size, f); + fclose(f); + if (n != size) { + return absl::nullopt; + } + return s; +} + +static bool ProcessOneTestFile(const char* filename) { + absl::optional<std::string> contents = ReadFileToString(filename); + if (!contents) { + absl::FPrintF(stderr, "Invalid file: %s\n", filename); + return false; + } + + int num_cases = 0; + for (absl::string_view v(*contents); !v.empty();) { + size_t new_line = v.find('\n'); + if ((new_line == absl::string_view::npos) || (new_line < 32)) { + break; + } + absl::string_view input = v.substr(31, new_line - 31); + + // Test absl::SimpleAtof. + { + float f; + if (!absl::SimpleAtof(input, &f)) { + absl::FPrintF(stderr, "Could not parse \"%s\" in %s\n", input, + filename); + return false; + } + uint32_t have32 = absl::bit_cast<uint32_t>(f); + + uint32_t want32 = 0; + for (int i = 0; i < 8; i++) { + want32 = (want32 << 4) | kUnhex[static_cast<unsigned char>(v[5 + i])]; + } + + if (have32 != want32) { + absl::FPrintF(stderr, + "absl::SimpleAtof failed parsing \"%s\" in %s\n have " + "%08X\n want %08X\n", + input, filename, have32, want32); + return false; + } + } + + // Test absl::SimpleAtod. + { + double d; + if (!absl::SimpleAtod(input, &d)) { + absl::FPrintF(stderr, "Could not parse \"%s\" in %s\n", input, + filename); + return false; + } + uint64_t have64 = absl::bit_cast<uint64_t>(d); + + uint64_t want64 = 0; + for (int i = 0; i < 16; i++) { + want64 = (want64 << 4) | kUnhex[static_cast<unsigned char>(v[14 + i])]; + } + + if (have64 != want64) { + absl::FPrintF(stderr, + "absl::SimpleAtod failed parsing \"%s\" in %s\n have " + "%016X\n want %016X\n", + input, filename, have64, want64); + return false; + } + } + + num_cases++; + v = v.substr(new_line + 1); + } + printf("%8d OK in %s\n", num_cases, filename); + return true; +} + +int main(int argc, char** argv) { + if (argc < 2) { + absl::FPrintF( + stderr, + "Usage: %s pnftd/data/*.txt\nwhere the pnftd directory is a local " + "checkout of " + "the\nhttps://github.com/nigeltao/parse-number-fxx-test-data " + "repository.\n", + argv[0]); + return 1; + } + + for (int i = 1; i < argc; i++) { + if (!ProcessOneTestFile(argv[i])) { + return 1; + } + } + return 0; +} diff --git a/absl/strings/charconv.cc b/absl/strings/charconv.cc index fefcfc90..69d420bc 100644 --- a/absl/strings/charconv.cc +++ b/absl/strings/charconv.cc @@ -18,6 +18,7 @@ #include <cassert> #include <cmath> #include <cstring> +#include <limits> #include "absl/base/casts.h" #include "absl/numeric/bits.h" @@ -65,6 +66,14 @@ struct FloatTraits; template <> struct FloatTraits<double> { + using mantissa_t = uint64_t; + + // The number of bits in the given float type. + static constexpr int kTargetBits = 64; + + // The number of exponent bits in the given float type. + static constexpr int kTargetExponentBits = 11; + // The number of mantissa bits in the given float type. This includes the // implied high bit. static constexpr int kTargetMantissaBits = 53; @@ -83,6 +92,31 @@ struct FloatTraits<double> { // m * 2**kMinNormalExponent is exactly equal to DBL_MIN. static constexpr int kMinNormalExponent = -1074; + // The IEEE exponent bias. It equals ((1 << (kTargetExponentBits - 1)) - 1). + static constexpr int kExponentBias = 1023; + + // The Eisel-Lemire "Shifting to 54/25 Bits" adjustment. It equals (63 - 1 - + // kTargetMantissaBits). + static constexpr int kEiselLemireShift = 9; + + // The Eisel-Lemire high64_mask. It equals ((1 << kEiselLemireShift) - 1). + static constexpr uint64_t kEiselLemireMask = uint64_t{0x1FF}; + + // The smallest negative integer N (smallest negative means furthest from + // zero) such that parsing 9999999999999999999eN, with 19 nines, is still + // positive. Parsing a smaller (more negative) N will produce zero. + // + // Adjusting the decimal point and exponent, without adjusting the value, + // 9999999999999999999eN equals 9.999999999999999999eM where M = N + 18. + // + // 9999999999999999999, with 19 nines but no decimal point, is the largest + // "repeated nines" integer that fits in a uint64_t. + static constexpr int kEiselLemireMinInclusiveExp10 = -324 - 18; + + // The smallest positive integer N such that parsing 1eN produces infinity. + // Parsing a smaller N will produce something finite. + static constexpr int kEiselLemireMaxExclusiveExp10 = 309; + static double MakeNan(const char* tagp) { // Support nan no matter which namespace it's in. Some platforms // incorrectly don't put it in namespace std. @@ -103,7 +137,7 @@ struct FloatTraits<double> { // a normal value is made, or it must be less narrow than that, in which case // `exponent` must be exactly kMinNormalExponent, and a subnormal value is // made. - static double Make(uint64_t mantissa, int exponent, bool sign) { + static double Make(mantissa_t mantissa, int exponent, bool sign) { #ifndef ABSL_BIT_PACK_FLOATS // Support ldexp no matter which namespace it's in. Some platforms // incorrectly don't put it in namespace std. @@ -116,8 +150,10 @@ struct FloatTraits<double> { if (mantissa > kMantissaMask) { // Normal value. // Adjust by 1023 for the exponent representation bias, and an additional - // 52 due to the implied decimal point in the IEEE mantissa represenation. - dbl += uint64_t{exponent + 1023u + kTargetMantissaBits - 1} << 52; + // 52 due to the implied decimal point in the IEEE mantissa + // representation. + dbl += static_cast<uint64_t>(exponent + 1023 + kTargetMantissaBits - 1) + << 52; mantissa &= kMantissaMask; } else { // subnormal value @@ -134,16 +170,27 @@ struct FloatTraits<double> { // members and methods. template <> struct FloatTraits<float> { + using mantissa_t = uint32_t; + + static constexpr int kTargetBits = 32; + static constexpr int kTargetExponentBits = 8; static constexpr int kTargetMantissaBits = 24; static constexpr int kMaxExponent = 104; static constexpr int kMinNormalExponent = -149; + static constexpr int kExponentBias = 127; + static constexpr int kEiselLemireShift = 38; + static constexpr uint64_t kEiselLemireMask = uint64_t{0x3FFFFFFFFF}; + static constexpr int kEiselLemireMinInclusiveExp10 = -46 - 18; + static constexpr int kEiselLemireMaxExclusiveExp10 = 39; + static float MakeNan(const char* tagp) { // 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); } - static float Make(uint32_t mantissa, int exponent, bool sign) { + + static float Make(mantissa_t mantissa, int exponent, bool sign) { #ifndef ABSL_BIT_PACK_FLOATS // Support ldexpf no matter which namespace it's in. Some platforms // incorrectly don't put it in namespace std. @@ -157,7 +204,8 @@ struct FloatTraits<float> { // 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. - flt += uint32_t{exponent + 127u + kTargetMantissaBits - 1} << 23; + flt += static_cast<uint32_t>(exponent + 127 + kTargetMantissaBits - 1) + << 23; mantissa &= kMantissaMask; } else { // subnormal value @@ -181,39 +229,45 @@ struct FloatTraits<float> { // // 2**63 <= Power10Mantissa(n) < 2**64. // +// See the "Table of powers of 10" comment below for a "1e60" example. +// // Lookups into the power-of-10 table must first check the Power10Overflow() and // Power10Underflow() functions, to avoid out-of-bounds table access. // -// Indexes into these tables are biased by -kPower10TableMin, and the table has -// values in the range [kPower10TableMin, kPower10TableMax]. -extern const uint64_t kPower10MantissaTable[]; -extern const int16_t kPower10ExponentTable[]; +// Indexes into these tables are biased by -kPower10TableMinInclusive. Valid +// indexes range from kPower10TableMinInclusive to kPower10TableMaxExclusive. +extern const uint64_t kPower10MantissaHighTable[]; // High 64 of 128 bits. +extern const uint64_t kPower10MantissaLowTable[]; // Low 64 of 128 bits. -// The smallest allowed value for use with the Power10Mantissa() and -// Power10Exponent() functions below. (If a smaller exponent is needed in +// The smallest (inclusive) allowed value for use with the Power10Mantissa() +// and Power10Exponent() functions below. (If a smaller exponent is needed in // calculations, the end result is guaranteed to underflow.) -constexpr int kPower10TableMin = -342; +constexpr int kPower10TableMinInclusive = -342; -// The largest allowed value for use with the Power10Mantissa() and -// Power10Exponent() functions below. (If a smaller exponent is needed in -// calculations, the end result is guaranteed to overflow.) -constexpr int kPower10TableMax = 308; +// The largest (exclusive) allowed value for use with the Power10Mantissa() and +// Power10Exponent() functions below. (If a larger-or-equal exponent is needed +// in calculations, the end result is guaranteed to overflow.) +constexpr int kPower10TableMaxExclusive = 309; uint64_t Power10Mantissa(int n) { - return kPower10MantissaTable[n - kPower10TableMin]; + return kPower10MantissaHighTable[n - kPower10TableMinInclusive]; } int Power10Exponent(int n) { - return kPower10ExponentTable[n - kPower10TableMin]; + // The 217706 etc magic numbers encode the results as a formula instead of a + // table. Their equivalence (over the kPower10TableMinInclusive .. + // kPower10TableMaxExclusive range) is confirmed by + // https://github.com/google/wuffs/blob/315b2e52625ebd7b02d8fac13e3cd85ea374fb80/script/print-mpb-powers-of-10.go + return (217706 * n >> 16) - 63; } // Returns true if n is large enough that 10**n always results in an IEEE // overflow. -bool Power10Overflow(int n) { return n > kPower10TableMax; } +bool Power10Overflow(int n) { return n >= kPower10TableMaxExclusive; } // Returns true if n is small enough that 10**n times a ParsedFloat mantissa // always results in an IEEE underflow. -bool Power10Underflow(int n) { return n < kPower10TableMin; } +bool Power10Underflow(int n) { return n < kPower10TableMinInclusive; } // Returns true if Power10Mantissa(n) * 2**Power10Exponent(n) is exactly equal // to 10**n numerically. Put another way, this returns true if there is no @@ -242,9 +296,11 @@ struct CalculatedFloat { // Returns the bit width of the given uint128. (Equivalently, returns 128 // minus the number of leading zero bits.) -unsigned BitWidth(uint128 value) { +int BitWidth(uint128 value) { if (Uint128High64(value) == 0) { - return static_cast<unsigned>(bit_width(Uint128Low64(value))); + // This static_cast is only needed when using a std::bit_width() + // implementation that does not have the fix for LWG 3656 applied. + return static_cast<int>(bit_width(Uint128Low64(value))); } return 128 - countl_zero(Uint128High64(value)); } @@ -285,14 +341,19 @@ template <typename FloatType> bool HandleEdgeCase(const strings_internal::ParsedFloat& input, bool negative, FloatType* value) { if (input.type == strings_internal::FloatType::kNan) { - // A bug in both clang and gcc would cause the compiler to optimize away the - // buffer we are building below. Declaring the buffer volatile avoids the - // issue, and has no measurable performance impact in microbenchmarks. + // A bug in both clang < 7 and gcc would cause the compiler to optimize + // away the buffer we are building below. Declaring the buffer volatile + // avoids the issue, and has no measurable performance impact in + // microbenchmarks. // // https://bugs.llvm.org/show_bug.cgi?id=37778 // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=86113 constexpr ptrdiff_t kNanBufferSize = 128; +#if defined(__GNUC__) || (defined(__clang__) && __clang_major__ < 7) volatile char n_char_sequence[kNanBufferSize]; +#else + char n_char_sequence[kNanBufferSize]; +#endif if (input.subrange_begin == nullptr) { n_char_sequence[0] = '\0'; } else { @@ -337,8 +398,10 @@ void EncodeResult(const CalculatedFloat& calculated, bool negative, *value = negative ? -0.0 : 0.0; return; } - *value = FloatTraits<FloatType>::Make(calculated.mantissa, - calculated.exponent, negative); + *value = FloatTraits<FloatType>::Make( + static_cast<typename FloatTraits<FloatType>::mantissa_t>( + calculated.mantissa), + calculated.exponent, negative); } // Returns the given uint128 shifted to the right by `shift` bits, and rounds @@ -519,7 +582,9 @@ CalculatedFloat CalculateFromParsedHexadecimal( const strings_internal::ParsedFloat& parsed_hex) { uint64_t mantissa = parsed_hex.mantissa; int exponent = parsed_hex.exponent; - auto mantissa_width = static_cast<unsigned>(bit_width(mantissa)); + // This static_cast is only needed when using a std::bit_width() + // implementation that does not have the fix for LWG 3656 applied. + int mantissa_width = static_cast<int>(bit_width(mantissa)); const int shift = NormalizedShiftSize<FloatType>(mantissa_width, exponent); bool result_exact; exponent += shift; @@ -595,6 +660,185 @@ CalculatedFloat CalculateFromParsedDecimal( binary_exponent); } +// As discussed in https://nigeltao.github.io/blog/2020/eisel-lemire.html the +// primary goal of the Eisel-Lemire algorithm is speed, for 99+% of the cases, +// not 100% coverage. As long as Eisel-Lemire doesn’t claim false positives, +// the combined approach (falling back to an alternative implementation when +// 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) { + uint64_t man = input.mantissa; + int exp10 = input.exponent; + if (exp10 < FloatTraits<FloatType>::kEiselLemireMinInclusiveExp10) { + *value = negative ? -0.0 : 0.0; + *ec = std::errc::result_out_of_range; + return true; + } else if (exp10 >= FloatTraits<FloatType>::kEiselLemireMaxExclusiveExp10) { + // Return max (a finite value) consistent with from_chars and DR 3081. For + // SimpleAtod and SimpleAtof, post-processing will return infinity. + *value = negative ? -std::numeric_limits<FloatType>::max() + : std::numeric_limits<FloatType>::max(); + *ec = std::errc::result_out_of_range; + return true; + } + + // Assert kPower10TableMinInclusive <= exp10 < kPower10TableMaxExclusive. + // Equivalently, !Power10Underflow(exp10) and !Power10Overflow(exp10). + static_assert( + FloatTraits<FloatType>::kEiselLemireMinInclusiveExp10 >= + kPower10TableMinInclusive, + "(exp10-kPower10TableMinInclusive) in kPower10MantissaHighTable bounds"); + static_assert( + FloatTraits<FloatType>::kEiselLemireMaxExclusiveExp10 <= + kPower10TableMaxExclusive, + "(exp10-kPower10TableMinInclusive) in kPower10MantissaHighTable bounds"); + + // The terse (+) comments in this function body refer to sections of the + // https://nigeltao.github.io/blog/2020/eisel-lemire.html blog post. + // + // That blog post discusses double precision (11 exponent bits with a -1023 + // bias, 52 mantissa bits), but the same approach applies to single precision + // (8 exponent bits with a -127 bias, 23 mantissa bits). Either way, the + // computation here happens with 64-bit values (e.g. man) or 128-bit values + // (e.g. x) before finally converting to 64- or 32-bit floating point. + // + // See also "Number Parsing at a Gigabyte per Second, Software: Practice and + // Experience 51 (8), 2021" (https://arxiv.org/abs/2101.11408) for detail. + + // (+) Normalization. + int clz = countl_zero(man); + man <<= static_cast<unsigned int>(clz); + // The 217706 etc magic numbers are from the Power10Exponent function. + uint64_t ret_exp2 = + static_cast<uint64_t>((217706 * exp10 >> 16) + 64 + + FloatTraits<FloatType>::kExponentBias - clz); + + // (+) Multiplication. + uint128 x = static_cast<uint128>(man) * + static_cast<uint128>( + kPower10MantissaHighTable[exp10 - kPower10TableMinInclusive]); + + // (+) Wider Approximation. + static constexpr uint64_t high64_mask = + FloatTraits<FloatType>::kEiselLemireMask; + if (((Uint128High64(x) & high64_mask) == high64_mask) && + (man > (std::numeric_limits<uint64_t>::max() - Uint128Low64(x)))) { + uint128 y = + static_cast<uint128>(man) * + static_cast<uint128>( + kPower10MantissaLowTable[exp10 - kPower10TableMinInclusive]); + x += Uint128High64(y); + // For example, parsing "4503599627370497.5" will take the if-true + // branch here (for double precision), since: + // - x = 0x8000000000000BFF_FFFFFFFFFFFFFFFF + // - y = 0x8000000000000BFF_7FFFFFFFFFFFF400 + // - man = 0xA000000000000F00 + // Likewise, when parsing "0.0625" for single precision: + // - x = 0x7FFFFFFFFFFFFFFF_FFFFFFFFFFFFFFFF + // - y = 0x813FFFFFFFFFFFFF_8A00000000000000 + // - man = 0x9C40000000000000 + if (((Uint128High64(x) & high64_mask) == high64_mask) && + ((Uint128Low64(x) + 1) == 0) && + (man > (std::numeric_limits<uint64_t>::max() - Uint128Low64(y)))) { + return false; + } + } + + // (+) Shifting to 54 Bits (or for single precision, to 25 bits). + uint64_t msb = Uint128High64(x) >> 63; + uint64_t ret_man = + Uint128High64(x) >> (msb + FloatTraits<FloatType>::kEiselLemireShift); + ret_exp2 -= 1 ^ msb; + + // (+) Half-way Ambiguity. + // + // For example, parsing "1e+23" will take the if-true branch here (for double + // precision), since: + // - x = 0x54B40B1F852BDA00_0000000000000000 + // - ret_man = 0x002A5A058FC295ED + // Likewise, when parsing "20040229.0" for single precision: + // - x = 0x4C72894000000000_0000000000000000 + // - ret_man = 0x000000000131CA25 + if ((Uint128Low64(x) == 0) && ((Uint128High64(x) & high64_mask) == 0) && + ((ret_man & 3) == 1)) { + return false; + } + + // (+) From 54 to 53 Bits (or for single precision, from 25 to 24 bits). + ret_man += ret_man & 1; // Line From54a. + ret_man >>= 1; // Line From54b. + // Incrementing ret_man (at line From54a) may have overflowed 54 bits (53 + // bits after the right shift by 1 at line From54b), so adjust for that. + // + // For example, parsing "9223372036854775807" will take the if-true branch + // here (for double precision), since: + // - ret_man = 0x0020000000000000 = (1 << 53) + // Likewise, when parsing "2147483647.0" for single precision: + // - ret_man = 0x0000000001000000 = (1 << 24) + if ((ret_man >> FloatTraits<FloatType>::kTargetMantissaBits) > 0) { + ret_exp2 += 1; + // Conceptually, we need a "ret_man >>= 1" in this if-block to balance + // incrementing ret_exp2 in the line immediately above. However, we only + // get here when line From54a overflowed (after adding a 1), so ret_man + // here is (1 << 53). Its low 53 bits are therefore all zeroes. The only + // remaining use of ret_man is to mask it with ((1 << 52) - 1), so only its + // low 52 bits matter. A "ret_man >>= 1" would have no effect in practice. + // + // We omit the "ret_man >>= 1", even if it is cheap (and this if-branch is + // rarely taken) and technically 'more correct', so that mutation tests + // that would otherwise modify or omit that "ret_man >>= 1" don't complain + // that such code mutations have no observable effect. + } + + // ret_exp2 is a uint64_t. Zero or underflow means that we're in subnormal + // space. max_exp2 (0x7FF for double precision, 0xFF for single precision) or + // above means that we're in Inf/NaN space. + // + // The if block is equivalent to (but has fewer branches than): + // if ((ret_exp2 <= 0) || (ret_exp2 >= max_exp2)) { etc } + // + // For example, parsing "4.9406564584124654e-324" will take the if-true + // branch here, since ret_exp2 = -51. + static constexpr uint64_t max_exp2 = + (1 << FloatTraits<FloatType>::kTargetExponentBits) - 1; + if ((ret_exp2 - 1) >= (max_exp2 - 1)) { + return false; + } + +#ifndef ABSL_BIT_PACK_FLOATS + if (FloatTraits<FloatType>::kTargetBits == 64) { + *value = FloatTraits<FloatType>::Make( + (ret_man & 0x000FFFFFFFFFFFFFu) | 0x0010000000000000u, + static_cast<int>(ret_exp2) - 1023 - 52, negative); + return true; + } else if (FloatTraits<FloatType>::kTargetBits == 32) { + *value = FloatTraits<FloatType>::Make( + (static_cast<uint32_t>(ret_man) & 0x007FFFFFu) | 0x00800000u, + static_cast<int>(ret_exp2) - 127 - 23, negative); + return true; + } +#else + if (FloatTraits<FloatType>::kTargetBits == 64) { + uint64_t ret_bits = (ret_exp2 << 52) | (ret_man & 0x000FFFFFFFFFFFFFu); + if (negative) { + ret_bits |= 0x8000000000000000u; + } + *value = absl::bit_cast<double>(ret_bits); + return true; + } else if (FloatTraits<FloatType>::kTargetBits == 32) { + uint32_t ret_bits = (static_cast<uint32_t>(ret_exp2) << 23) | + (static_cast<uint32_t>(ret_man) & 0x007FFFFFu); + if (negative) { + ret_bits |= 0x80000000u; + } + *value = absl::bit_cast<float>(ret_bits); + return true; + } +#endif // ABSL_BIT_PACK_FLOATS + return false; +} + template <typename FloatType> from_chars_result FromCharsImpl(const char* first, const char* last, FloatType& value, chars_format fmt_flags) { @@ -668,6 +912,12 @@ from_chars_result FromCharsImpl(const char* first, const char* last, if (HandleEdgeCase(decimal_parse, negative, &value)) { return result; } + // A nullptr subrange_begin means that the decimal_parse.mantissa is exact + // (not truncated), a precondition of the Eisel-Lemire algorithm. + if ((decimal_parse.subrange_begin == nullptr) && + EiselLemire<FloatType>(decimal_parse, negative, &value, &result.ec)) { + return result; + } CalculatedFloat calculated = CalculateFromParsedDecimal<FloatType>(decimal_parse); EncodeResult(calculated, negative, &result, &value); @@ -688,15 +938,46 @@ from_chars_result from_chars(const char* first, const char* last, float& value, namespace { -// Table of powers of 10, from kPower10TableMin to kPower10TableMax. +// Table of powers of 10, from kPower10TableMinInclusive to +// kPower10TableMaxExclusive. +// +// kPower10MantissaHighTable[i - kPower10TableMinInclusive] stores the 64-bit +// mantissa. The high bit is always on. +// +// kPower10MantissaLowTable extends that 64-bit mantissa to 128 bits. +// +// Power10Exponent(i) calculates the power-of-two exponent. +// +// For a number i, this gives the unique mantissaHigh and exponent such that +// (mantissaHigh * 2**exponent) <= 10**i < ((mantissaHigh + 1) * 2**exponent). +// +// For example, Python can confirm that the exact hexadecimal value of 1e60 is: +// >>> a = 1000000000000000000000000000000000000000000000000000000000000 +// >>> hex(a) +// '0x9f4f2726179a224501d762422c946590d91000000000000000' +// Adding underscores at every 8th hex digit shows 50 hex digits: +// '0x9f4f2726_179a2245_01d76242_2c946590_d9100000_00000000_00'. +// In this case, the high bit of the first hex digit, 9, is coincidentally set, +// so we do not have to do further shifting to deduce the 128-bit mantissa: +// - kPower10MantissaHighTable[60 - kP10TMI] = 0x9f4f2726179a2245U +// - kPower10MantissaLowTable[ 60 - kP10TMI] = 0x01d762422c946590U +// where kP10TMI is kPower10TableMinInclusive. The low 18 of those 50 hex +// digits are truncated. +// +// 50 hex digits (with the high bit set) is 200 bits and mantissaHigh holds 64 +// bits, so Power10Exponent(60) = 200 - 64 = 136. Again, Python can confirm: +// >>> b = 0x9f4f2726179a2245 +// >>> ((b+0)<<136) <= a +// True +// >>> ((b+1)<<136) <= a +// False // -// kPower10MantissaTable[i - kPower10TableMin] stores the 64-bit mantissa (high -// bit always on), and kPower10ExponentTable[i - kPower10TableMin] stores the -// power-of-two exponent. For a given number i, this gives the unique mantissa -// and exponent such that mantissa * 2**exponent <= 10**i < (mantissa + 1) * -// 2**exponent. +// The tables were generated by +// https://github.com/google/wuffs/blob/315b2e52625ebd7b02d8fac13e3cd85ea374fb80/script/print-mpb-powers-of-10.go +// after re-formatting its output into two arrays of N uint64_t values (instead +// of an N element array of uint64_t pairs). -const uint64_t kPower10MantissaTable[] = { +const uint64_t kPower10MantissaHighTable[] = { 0xeef453d6923bd65aU, 0x9558b4661b6565f8U, 0xbaaee17fa23ebf76U, 0xe95a99df8ace6f53U, 0x91d8a02bb6c10594U, 0xb64ec836a47146f9U, 0xe3e27a444d8d98b7U, 0x8e6d8c6ab0787f72U, 0xb208ef855c969f4fU, @@ -916,67 +1197,224 @@ const uint64_t kPower10MantissaTable[] = { 0xb6472e511c81471dU, 0xe3d8f9e563a198e5U, 0x8e679c2f5e44ff8fU, }; -const int16_t kPower10ExponentTable[] = { - -1200, -1196, -1193, -1190, -1186, -1183, -1180, -1176, -1173, -1170, -1166, - -1163, -1160, -1156, -1153, -1150, -1146, -1143, -1140, -1136, -1133, -1130, - -1127, -1123, -1120, -1117, -1113, -1110, -1107, -1103, -1100, -1097, -1093, - -1090, -1087, -1083, -1080, -1077, -1073, -1070, -1067, -1063, -1060, -1057, - -1053, -1050, -1047, -1043, -1040, -1037, -1034, -1030, -1027, -1024, -1020, - -1017, -1014, -1010, -1007, -1004, -1000, -997, -994, -990, -987, -984, - -980, -977, -974, -970, -967, -964, -960, -957, -954, -950, -947, - -944, -940, -937, -934, -931, -927, -924, -921, -917, -914, -911, - -907, -904, -901, -897, -894, -891, -887, -884, -881, -877, -874, - -871, -867, -864, -861, -857, -854, -851, -847, -844, -841, -838, - -834, -831, -828, -824, -821, -818, -814, -811, -808, -804, -801, - -798, -794, -791, -788, -784, -781, -778, -774, -771, -768, -764, - -761, -758, -754, -751, -748, -744, -741, -738, -735, -731, -728, - -725, -721, -718, -715, -711, -708, -705, -701, -698, -695, -691, - -688, -685, -681, -678, -675, -671, -668, -665, -661, -658, -655, - -651, -648, -645, -642, -638, -635, -632, -628, -625, -622, -618, - -615, -612, -608, -605, -602, -598, -595, -592, -588, -585, -582, - -578, -575, -572, -568, -565, -562, -558, -555, -552, -549, -545, - -542, -539, -535, -532, -529, -525, -522, -519, -515, -512, -509, - -505, -502, -499, -495, -492, -489, -485, -482, -479, -475, -472, - -469, -465, -462, -459, -455, -452, -449, -446, -442, -439, -436, - -432, -429, -426, -422, -419, -416, -412, -409, -406, -402, -399, - -396, -392, -389, -386, -382, -379, -376, -372, -369, -366, -362, - -359, -356, -353, -349, -346, -343, -339, -336, -333, -329, -326, - -323, -319, -316, -313, -309, -306, -303, -299, -296, -293, -289, - -286, -283, -279, -276, -273, -269, -266, -263, -259, -256, -253, - -250, -246, -243, -240, -236, -233, -230, -226, -223, -220, -216, - -213, -210, -206, -203, -200, -196, -193, -190, -186, -183, -180, - -176, -173, -170, -166, -163, -160, -157, -153, -150, -147, -143, - -140, -137, -133, -130, -127, -123, -120, -117, -113, -110, -107, - -103, -100, -97, -93, -90, -87, -83, -80, -77, -73, -70, - -67, -63, -60, -57, -54, -50, -47, -44, -40, -37, -34, - -30, -27, -24, -20, -17, -14, -10, -7, -4, 0, 3, - 6, 10, 13, 16, 20, 23, 26, 30, 33, 36, 39, - 43, 46, 49, 53, 56, 59, 63, 66, 69, 73, 76, - 79, 83, 86, 89, 93, 96, 99, 103, 106, 109, 113, - 116, 119, 123, 126, 129, 132, 136, 139, 142, 146, 149, - 152, 156, 159, 162, 166, 169, 172, 176, 179, 182, 186, - 189, 192, 196, 199, 202, 206, 209, 212, 216, 219, 222, - 226, 229, 232, 235, 239, 242, 245, 249, 252, 255, 259, - 262, 265, 269, 272, 275, 279, 282, 285, 289, 292, 295, - 299, 302, 305, 309, 312, 315, 319, 322, 325, 328, 332, - 335, 338, 342, 345, 348, 352, 355, 358, 362, 365, 368, - 372, 375, 378, 382, 385, 388, 392, 395, 398, 402, 405, - 408, 412, 415, 418, 422, 425, 428, 431, 435, 438, 441, - 445, 448, 451, 455, 458, 461, 465, 468, 471, 475, 478, - 481, 485, 488, 491, 495, 498, 501, 505, 508, 511, 515, - 518, 521, 524, 528, 531, 534, 538, 541, 544, 548, 551, - 554, 558, 561, 564, 568, 571, 574, 578, 581, 584, 588, - 591, 594, 598, 601, 604, 608, 611, 614, 617, 621, 624, - 627, 631, 634, 637, 641, 644, 647, 651, 654, 657, 661, - 664, 667, 671, 674, 677, 681, 684, 687, 691, 694, 697, - 701, 704, 707, 711, 714, 717, 720, 724, 727, 730, 734, - 737, 740, 744, 747, 750, 754, 757, 760, 764, 767, 770, - 774, 777, 780, 784, 787, 790, 794, 797, 800, 804, 807, - 810, 813, 817, 820, 823, 827, 830, 833, 837, 840, 843, - 847, 850, 853, 857, 860, 863, 867, 870, 873, 877, 880, - 883, 887, 890, 893, 897, 900, 903, 907, 910, 913, 916, - 920, 923, 926, 930, 933, 936, 940, 943, 946, 950, 953, - 956, 960, +const uint64_t kPower10MantissaLowTable[] = { + 0x113faa2906a13b3fU, 0x4ac7ca59a424c507U, 0x5d79bcf00d2df649U, + 0xf4d82c2c107973dcU, 0x79071b9b8a4be869U, 0x9748e2826cdee284U, + 0xfd1b1b2308169b25U, 0xfe30f0f5e50e20f7U, 0xbdbd2d335e51a935U, + 0xad2c788035e61382U, 0x4c3bcb5021afcc31U, 0xdf4abe242a1bbf3dU, + 0xd71d6dad34a2af0dU, 0x8672648c40e5ad68U, 0x680efdaf511f18c2U, + 0x0212bd1b2566def2U, 0x014bb630f7604b57U, 0x419ea3bd35385e2dU, + 0x52064cac828675b9U, 0x7343efebd1940993U, 0x1014ebe6c5f90bf8U, + 0xd41a26e077774ef6U, 0x8920b098955522b4U, 0x55b46e5f5d5535b0U, + 0xeb2189f734aa831dU, 0xa5e9ec7501d523e4U, 0x47b233c92125366eU, + 0x999ec0bb696e840aU, 0xc00670ea43ca250dU, 0x380406926a5e5728U, + 0xc605083704f5ecf2U, 0xf7864a44c633682eU, 0x7ab3ee6afbe0211dU, + 0x5960ea05bad82964U, 0x6fb92487298e33bdU, 0xa5d3b6d479f8e056U, + 0x8f48a4899877186cU, 0x331acdabfe94de87U, 0x9ff0c08b7f1d0b14U, + 0x07ecf0ae5ee44dd9U, 0xc9e82cd9f69d6150U, 0xbe311c083a225cd2U, + 0x6dbd630a48aaf406U, 0x092cbbccdad5b108U, 0x25bbf56008c58ea5U, + 0xaf2af2b80af6f24eU, 0x1af5af660db4aee1U, 0x50d98d9fc890ed4dU, + 0xe50ff107bab528a0U, 0x1e53ed49a96272c8U, 0x25e8e89c13bb0f7aU, + 0x77b191618c54e9acU, 0xd59df5b9ef6a2417U, 0x4b0573286b44ad1dU, + 0x4ee367f9430aec32U, 0x229c41f793cda73fU, 0x6b43527578c1110fU, + 0x830a13896b78aaa9U, 0x23cc986bc656d553U, 0x2cbfbe86b7ec8aa8U, + 0x7bf7d71432f3d6a9U, 0xdaf5ccd93fb0cc53U, 0xd1b3400f8f9cff68U, + 0x23100809b9c21fa1U, 0xabd40a0c2832a78aU, 0x16c90c8f323f516cU, + 0xae3da7d97f6792e3U, 0x99cd11cfdf41779cU, 0x40405643d711d583U, + 0x482835ea666b2572U, 0xda3243650005eecfU, 0x90bed43e40076a82U, + 0x5a7744a6e804a291U, 0x711515d0a205cb36U, 0x0d5a5b44ca873e03U, + 0xe858790afe9486c2U, 0x626e974dbe39a872U, 0xfb0a3d212dc8128fU, + 0x7ce66634bc9d0b99U, 0x1c1fffc1ebc44e80U, 0xa327ffb266b56220U, + 0x4bf1ff9f0062baa8U, 0x6f773fc3603db4a9U, 0xcb550fb4384d21d3U, + 0x7e2a53a146606a48U, 0x2eda7444cbfc426dU, 0xfa911155fefb5308U, + 0x793555ab7eba27caU, 0x4bc1558b2f3458deU, 0x9eb1aaedfb016f16U, + 0x465e15a979c1cadcU, 0x0bfacd89ec191ec9U, 0xcef980ec671f667bU, + 0x82b7e12780e7401aU, 0xd1b2ecb8b0908810U, 0x861fa7e6dcb4aa15U, + 0x67a791e093e1d49aU, 0xe0c8bb2c5c6d24e0U, 0x58fae9f773886e18U, + 0xaf39a475506a899eU, 0x6d8406c952429603U, 0xc8e5087ba6d33b83U, + 0xfb1e4a9a90880a64U, 0x5cf2eea09a55067fU, 0xf42faa48c0ea481eU, + 0xf13b94daf124da26U, 0x76c53d08d6b70858U, 0x54768c4b0c64ca6eU, + 0xa9942f5dcf7dfd09U, 0xd3f93b35435d7c4cU, 0xc47bc5014a1a6dafU, + 0x359ab6419ca1091bU, 0xc30163d203c94b62U, 0x79e0de63425dcf1dU, + 0x985915fc12f542e4U, 0x3e6f5b7b17b2939dU, 0xa705992ceecf9c42U, + 0x50c6ff782a838353U, 0xa4f8bf5635246428U, 0x871b7795e136be99U, + 0x28e2557b59846e3fU, 0x331aeada2fe589cfU, 0x3ff0d2c85def7621U, + 0x0fed077a756b53a9U, 0xd3e8495912c62894U, 0x64712dd7abbbd95cU, + 0xbd8d794d96aacfb3U, 0xecf0d7a0fc5583a0U, 0xf41686c49db57244U, + 0x311c2875c522ced5U, 0x7d633293366b828bU, 0xae5dff9c02033197U, + 0xd9f57f830283fdfcU, 0xd072df63c324fd7bU, 0x4247cb9e59f71e6dU, + 0x52d9be85f074e608U, 0x67902e276c921f8bU, 0x00ba1cd8a3db53b6U, + 0x80e8a40eccd228a4U, 0x6122cd128006b2cdU, 0x796b805720085f81U, + 0xcbe3303674053bb0U, 0xbedbfc4411068a9cU, 0xee92fb5515482d44U, + 0x751bdd152d4d1c4aU, 0xd262d45a78a0635dU, 0x86fb897116c87c34U, + 0xd45d35e6ae3d4da0U, 0x8974836059cca109U, 0x2bd1a438703fc94bU, + 0x7b6306a34627ddcfU, 0x1a3bc84c17b1d542U, 0x20caba5f1d9e4a93U, + 0x547eb47b7282ee9cU, 0xe99e619a4f23aa43U, 0x6405fa00e2ec94d4U, + 0xde83bc408dd3dd04U, 0x9624ab50b148d445U, 0x3badd624dd9b0957U, + 0xe54ca5d70a80e5d6U, 0x5e9fcf4ccd211f4cU, 0x7647c3200069671fU, + 0x29ecd9f40041e073U, 0xf468107100525890U, 0x7182148d4066eeb4U, + 0xc6f14cd848405530U, 0xb8ada00e5a506a7cU, 0xa6d90811f0e4851cU, + 0x908f4a166d1da663U, 0x9a598e4e043287feU, 0x40eff1e1853f29fdU, + 0xd12bee59e68ef47cU, 0x82bb74f8301958ceU, 0xe36a52363c1faf01U, + 0xdc44e6c3cb279ac1U, 0x29ab103a5ef8c0b9U, 0x7415d448f6b6f0e7U, + 0x111b495b3464ad21U, 0xcab10dd900beec34U, 0x3d5d514f40eea742U, + 0x0cb4a5a3112a5112U, 0x47f0e785eaba72abU, 0x59ed216765690f56U, + 0x306869c13ec3532cU, 0x1e414218c73a13fbU, 0xe5d1929ef90898faU, + 0xdf45f746b74abf39U, 0x6b8bba8c328eb783U, 0x066ea92f3f326564U, + 0xc80a537b0efefebdU, 0xbd06742ce95f5f36U, 0x2c48113823b73704U, + 0xf75a15862ca504c5U, 0x9a984d73dbe722fbU, 0xc13e60d0d2e0ebbaU, + 0x318df905079926a8U, 0xfdf17746497f7052U, 0xfeb6ea8bedefa633U, + 0xfe64a52ee96b8fc0U, 0x3dfdce7aa3c673b0U, 0x06bea10ca65c084eU, + 0x486e494fcff30a62U, 0x5a89dba3c3efccfaU, 0xf89629465a75e01cU, + 0xf6bbb397f1135823U, 0x746aa07ded582e2cU, 0xa8c2a44eb4571cdcU, + 0x92f34d62616ce413U, 0x77b020baf9c81d17U, 0x0ace1474dc1d122eU, + 0x0d819992132456baU, 0x10e1fff697ed6c69U, 0xca8d3ffa1ef463c1U, + 0xbd308ff8a6b17cb2U, 0xac7cb3f6d05ddbdeU, 0x6bcdf07a423aa96bU, + 0x86c16c98d2c953c6U, 0xe871c7bf077ba8b7U, 0x11471cd764ad4972U, + 0xd598e40d3dd89bcfU, 0x4aff1d108d4ec2c3U, 0xcedf722a585139baU, + 0xc2974eb4ee658828U, 0x733d226229feea32U, 0x0806357d5a3f525fU, + 0xca07c2dcb0cf26f7U, 0xfc89b393dd02f0b5U, 0xbbac2078d443ace2U, + 0xd54b944b84aa4c0dU, 0x0a9e795e65d4df11U, 0x4d4617b5ff4a16d5U, + 0x504bced1bf8e4e45U, 0xe45ec2862f71e1d6U, 0x5d767327bb4e5a4cU, + 0x3a6a07f8d510f86fU, 0x890489f70a55368bU, 0x2b45ac74ccea842eU, + 0x3b0b8bc90012929dU, 0x09ce6ebb40173744U, 0xcc420a6a101d0515U, + 0x9fa946824a12232dU, 0x47939822dc96abf9U, 0x59787e2b93bc56f7U, + 0x57eb4edb3c55b65aU, 0xede622920b6b23f1U, 0xe95fab368e45ecedU, + 0x11dbcb0218ebb414U, 0xd652bdc29f26a119U, 0x4be76d3346f0495fU, + 0x6f70a4400c562ddbU, 0xcb4ccd500f6bb952U, 0x7e2000a41346a7a7U, + 0x8ed400668c0c28c8U, 0x728900802f0f32faU, 0x4f2b40a03ad2ffb9U, + 0xe2f610c84987bfa8U, 0x0dd9ca7d2df4d7c9U, 0x91503d1c79720dbbU, + 0x75a44c6397ce912aU, 0xc986afbe3ee11abaU, 0xfbe85badce996168U, + 0xfae27299423fb9c3U, 0xdccd879fc967d41aU, 0x5400e987bbc1c920U, + 0x290123e9aab23b68U, 0xf9a0b6720aaf6521U, 0xf808e40e8d5b3e69U, + 0xb60b1d1230b20e04U, 0xb1c6f22b5e6f48c2U, 0x1e38aeb6360b1af3U, + 0x25c6da63c38de1b0U, 0x579c487e5a38ad0eU, 0x2d835a9df0c6d851U, + 0xf8e431456cf88e65U, 0x1b8e9ecb641b58ffU, 0xe272467e3d222f3fU, + 0x5b0ed81dcc6abb0fU, 0x98e947129fc2b4e9U, 0x3f2398d747b36224U, + 0x8eec7f0d19a03aadU, 0x1953cf68300424acU, 0x5fa8c3423c052dd7U, + 0x3792f412cb06794dU, 0xe2bbd88bbee40bd0U, 0x5b6aceaeae9d0ec4U, + 0xf245825a5a445275U, 0xeed6e2f0f0d56712U, 0x55464dd69685606bU, + 0xaa97e14c3c26b886U, 0xd53dd99f4b3066a8U, 0xe546a8038efe4029U, + 0xde98520472bdd033U, 0x963e66858f6d4440U, 0xdde7001379a44aa8U, + 0x5560c018580d5d52U, 0xaab8f01e6e10b4a6U, 0xcab3961304ca70e8U, + 0x3d607b97c5fd0d22U, 0x8cb89a7db77c506aU, 0x77f3608e92adb242U, + 0x55f038b237591ed3U, 0x6b6c46dec52f6688U, 0x2323ac4b3b3da015U, + 0xabec975e0a0d081aU, 0x96e7bd358c904a21U, 0x7e50d64177da2e54U, + 0xdde50bd1d5d0b9e9U, 0x955e4ec64b44e864U, 0xbd5af13bef0b113eU, + 0xecb1ad8aeacdd58eU, 0x67de18eda5814af2U, 0x80eacf948770ced7U, + 0xa1258379a94d028dU, 0x096ee45813a04330U, 0x8bca9d6e188853fcU, + 0x775ea264cf55347dU, 0x95364afe032a819dU, 0x3a83ddbd83f52204U, + 0xc4926a9672793542U, 0x75b7053c0f178293U, 0x5324c68b12dd6338U, + 0xd3f6fc16ebca5e03U, 0x88f4bb1ca6bcf584U, 0x2b31e9e3d06c32e5U, + 0x3aff322e62439fcfU, 0x09befeb9fad487c2U, 0x4c2ebe687989a9b3U, + 0x0f9d37014bf60a10U, 0x538484c19ef38c94U, 0x2865a5f206b06fb9U, + 0xf93f87b7442e45d3U, 0xf78f69a51539d748U, 0xb573440e5a884d1bU, + 0x31680a88f8953030U, 0xfdc20d2b36ba7c3dU, 0x3d32907604691b4cU, + 0xa63f9a49c2c1b10fU, 0x0fcf80dc33721d53U, 0xd3c36113404ea4a8U, + 0x645a1cac083126e9U, 0x3d70a3d70a3d70a3U, 0xccccccccccccccccU, + 0x0000000000000000U, 0x0000000000000000U, 0x0000000000000000U, + 0x0000000000000000U, 0x0000000000000000U, 0x0000000000000000U, + 0x0000000000000000U, 0x0000000000000000U, 0x0000000000000000U, + 0x0000000000000000U, 0x0000000000000000U, 0x0000000000000000U, + 0x0000000000000000U, 0x0000000000000000U, 0x0000000000000000U, + 0x0000000000000000U, 0x0000000000000000U, 0x0000000000000000U, + 0x0000000000000000U, 0x0000000000000000U, 0x0000000000000000U, + 0x0000000000000000U, 0x0000000000000000U, 0x0000000000000000U, + 0x0000000000000000U, 0x0000000000000000U, 0x0000000000000000U, + 0x0000000000000000U, 0x4000000000000000U, 0x5000000000000000U, + 0xa400000000000000U, 0x4d00000000000000U, 0xf020000000000000U, + 0x6c28000000000000U, 0xc732000000000000U, 0x3c7f400000000000U, + 0x4b9f100000000000U, 0x1e86d40000000000U, 0x1314448000000000U, + 0x17d955a000000000U, 0x5dcfab0800000000U, 0x5aa1cae500000000U, + 0xf14a3d9e40000000U, 0x6d9ccd05d0000000U, 0xe4820023a2000000U, + 0xdda2802c8a800000U, 0xd50b2037ad200000U, 0x4526f422cc340000U, + 0x9670b12b7f410000U, 0x3c0cdd765f114000U, 0xa5880a69fb6ac800U, + 0x8eea0d047a457a00U, 0x72a4904598d6d880U, 0x47a6da2b7f864750U, + 0x999090b65f67d924U, 0xfff4b4e3f741cf6dU, 0xbff8f10e7a8921a4U, + 0xaff72d52192b6a0dU, 0x9bf4f8a69f764490U, 0x02f236d04753d5b4U, + 0x01d762422c946590U, 0x424d3ad2b7b97ef5U, 0xd2e0898765a7deb2U, + 0x63cc55f49f88eb2fU, 0x3cbf6b71c76b25fbU, 0x8bef464e3945ef7aU, + 0x97758bf0e3cbb5acU, 0x3d52eeed1cbea317U, 0x4ca7aaa863ee4bddU, + 0x8fe8caa93e74ef6aU, 0xb3e2fd538e122b44U, 0x60dbbca87196b616U, + 0xbc8955e946fe31cdU, 0x6babab6398bdbe41U, 0xc696963c7eed2dd1U, + 0xfc1e1de5cf543ca2U, 0x3b25a55f43294bcbU, 0x49ef0eb713f39ebeU, + 0x6e3569326c784337U, 0x49c2c37f07965404U, 0xdc33745ec97be906U, + 0x69a028bb3ded71a3U, 0xc40832ea0d68ce0cU, 0xf50a3fa490c30190U, + 0x792667c6da79e0faU, 0x577001b891185938U, 0xed4c0226b55e6f86U, + 0x544f8158315b05b4U, 0x696361ae3db1c721U, 0x03bc3a19cd1e38e9U, + 0x04ab48a04065c723U, 0x62eb0d64283f9c76U, 0x3ba5d0bd324f8394U, + 0xca8f44ec7ee36479U, 0x7e998b13cf4e1ecbU, 0x9e3fedd8c321a67eU, + 0xc5cfe94ef3ea101eU, 0xbba1f1d158724a12U, 0x2a8a6e45ae8edc97U, + 0xf52d09d71a3293bdU, 0x593c2626705f9c56U, 0x6f8b2fb00c77836cU, + 0x0b6dfb9c0f956447U, 0x4724bd4189bd5eacU, 0x58edec91ec2cb657U, + 0x2f2967b66737e3edU, 0xbd79e0d20082ee74U, 0xecd8590680a3aa11U, + 0xe80e6f4820cc9495U, 0x3109058d147fdcddU, 0xbd4b46f0599fd415U, + 0x6c9e18ac7007c91aU, 0x03e2cf6bc604ddb0U, 0x84db8346b786151cU, + 0xe612641865679a63U, 0x4fcb7e8f3f60c07eU, 0xe3be5e330f38f09dU, + 0x5cadf5bfd3072cc5U, 0x73d9732fc7c8f7f6U, 0x2867e7fddcdd9afaU, + 0xb281e1fd541501b8U, 0x1f225a7ca91a4226U, 0x3375788de9b06958U, + 0x0052d6b1641c83aeU, 0xc0678c5dbd23a49aU, 0xf840b7ba963646e0U, + 0xb650e5a93bc3d898U, 0xa3e51f138ab4cebeU, 0xc66f336c36b10137U, + 0xb80b0047445d4184U, 0xa60dc059157491e5U, 0x87c89837ad68db2fU, + 0x29babe4598c311fbU, 0xf4296dd6fef3d67aU, 0x1899e4a65f58660cU, + 0x5ec05dcff72e7f8fU, 0x76707543f4fa1f73U, 0x6a06494a791c53a8U, + 0x0487db9d17636892U, 0x45a9d2845d3c42b6U, 0x0b8a2392ba45a9b2U, + 0x8e6cac7768d7141eU, 0x3207d795430cd926U, 0x7f44e6bd49e807b8U, + 0x5f16206c9c6209a6U, 0x36dba887c37a8c0fU, 0xc2494954da2c9789U, + 0xf2db9baa10b7bd6cU, 0x6f92829494e5acc7U, 0xcb772339ba1f17f9U, + 0xff2a760414536efbU, 0xfef5138519684abaU, 0x7eb258665fc25d69U, + 0xef2f773ffbd97a61U, 0xaafb550ffacfd8faU, 0x95ba2a53f983cf38U, + 0xdd945a747bf26183U, 0x94f971119aeef9e4U, 0x7a37cd5601aab85dU, + 0xac62e055c10ab33aU, 0x577b986b314d6009U, 0xed5a7e85fda0b80bU, + 0x14588f13be847307U, 0x596eb2d8ae258fc8U, 0x6fca5f8ed9aef3bbU, + 0x25de7bb9480d5854U, 0xaf561aa79a10ae6aU, 0x1b2ba1518094da04U, + 0x90fb44d2f05d0842U, 0x353a1607ac744a53U, 0x42889b8997915ce8U, + 0x69956135febada11U, 0x43fab9837e699095U, 0x94f967e45e03f4bbU, + 0x1d1be0eebac278f5U, 0x6462d92a69731732U, 0x7d7b8f7503cfdcfeU, + 0x5cda735244c3d43eU, 0x3a0888136afa64a7U, 0x088aaa1845b8fdd0U, + 0x8aad549e57273d45U, 0x36ac54e2f678864bU, 0x84576a1bb416a7ddU, + 0x656d44a2a11c51d5U, 0x9f644ae5a4b1b325U, 0x873d5d9f0dde1feeU, + 0xa90cb506d155a7eaU, 0x09a7f12442d588f2U, 0x0c11ed6d538aeb2fU, + 0x8f1668c8a86da5faU, 0xf96e017d694487bcU, 0x37c981dcc395a9acU, + 0x85bbe253f47b1417U, 0x93956d7478ccec8eU, 0x387ac8d1970027b2U, + 0x06997b05fcc0319eU, 0x441fece3bdf81f03U, 0xd527e81cad7626c3U, + 0x8a71e223d8d3b074U, 0xf6872d5667844e49U, 0xb428f8ac016561dbU, + 0xe13336d701beba52U, 0xecc0024661173473U, 0x27f002d7f95d0190U, + 0x31ec038df7b441f4U, 0x7e67047175a15271U, 0x0f0062c6e984d386U, + 0x52c07b78a3e60868U, 0xa7709a56ccdf8a82U, 0x88a66076400bb691U, + 0x6acff893d00ea435U, 0x0583f6b8c4124d43U, 0xc3727a337a8b704aU, + 0x744f18c0592e4c5cU, 0x1162def06f79df73U, 0x8addcb5645ac2ba8U, + 0x6d953e2bd7173692U, 0xc8fa8db6ccdd0437U, 0x1d9c9892400a22a2U, + 0x2503beb6d00cab4bU, 0x2e44ae64840fd61dU, 0x5ceaecfed289e5d2U, + 0x7425a83e872c5f47U, 0xd12f124e28f77719U, 0x82bd6b70d99aaa6fU, + 0x636cc64d1001550bU, 0x3c47f7e05401aa4eU, 0x65acfaec34810a71U, + 0x7f1839a741a14d0dU, 0x1ede48111209a050U, 0x934aed0aab460432U, + 0xf81da84d5617853fU, 0x36251260ab9d668eU, 0xc1d72b7c6b426019U, + 0xb24cf65b8612f81fU, 0xdee033f26797b627U, 0x169840ef017da3b1U, + 0x8e1f289560ee864eU, 0xf1a6f2bab92a27e2U, 0xae10af696774b1dbU, + 0xacca6da1e0a8ef29U, 0x17fd090a58d32af3U, 0xddfc4b4cef07f5b0U, + 0x4abdaf101564f98eU, 0x9d6d1ad41abe37f1U, 0x84c86189216dc5edU, + 0x32fd3cf5b4e49bb4U, 0x3fbc8c33221dc2a1U, 0x0fabaf3feaa5334aU, + 0x29cb4d87f2a7400eU, 0x743e20e9ef511012U, 0x914da9246b255416U, + 0x1ad089b6c2f7548eU, 0xa184ac2473b529b1U, 0xc9e5d72d90a2741eU, + 0x7e2fa67c7a658892U, 0xddbb901b98feeab7U, 0x552a74227f3ea565U, + 0xd53a88958f87275fU, 0x8a892abaf368f137U, 0x2d2b7569b0432d85U, + 0x9c3b29620e29fc73U, 0x8349f3ba91b47b8fU, 0x241c70a936219a73U, + 0xed238cd383aa0110U, 0xf4363804324a40aaU, 0xb143c6053edcd0d5U, + 0xdd94b7868e94050aU, 0xca7cf2b4191c8326U, 0xfd1c2f611f63a3f0U, + 0xbc633b39673c8cecU, 0xd5be0503e085d813U, 0x4b2d8644d8a74e18U, + 0xddf8e7d60ed1219eU, 0xcabb90e5c942b503U, 0x3d6a751f3b936243U, + 0x0cc512670a783ad4U, 0x27fb2b80668b24c5U, 0xb1f9f660802dedf6U, + 0x5e7873f8a0396973U, 0xdb0b487b6423e1e8U, 0x91ce1a9a3d2cda62U, + 0x7641a140cc7810fbU, 0xa9e904c87fcb0a9dU, 0x546345fa9fbdcd44U, + 0xa97c177947ad4095U, 0x49ed8eabcccc485dU, 0x5c68f256bfff5a74U, + 0x73832eec6fff3111U, 0xc831fd53c5ff7eabU, 0xba3e7ca8b77f5e55U, + 0x28ce1bd2e55f35ebU, 0x7980d163cf5b81b3U, 0xd7e105bcc332621fU, + 0x8dd9472bf3fefaa7U, 0xb14f98f6f0feb951U, 0x6ed1bf9a569f33d3U, + 0x0a862f80ec4700c8U, 0xcd27bb612758c0faU, 0x8038d51cb897789cU, + 0xe0470a63e6bd56c3U, 0x1858ccfce06cac74U, 0x0f37801e0c43ebc8U, + 0xd30560258f54e6baU, 0x47c6b82ef32a2069U, 0x4cdc331d57fa5441U, + 0xe0133fe4adf8e952U, 0x58180fddd97723a6U, 0x570f09eaa7ea7648U, }; } // namespace diff --git a/absl/strings/cord.cc b/absl/strings/cord.cc index 85a67a08..1d33dd83 100644 --- a/absl/strings/cord.cc +++ b/absl/strings/cord.cc @@ -20,6 +20,7 @@ #include <cstdio> #include <cstdlib> #include <iomanip> +#include <ios> #include <iostream> #include <limits> #include <ostream> @@ -34,6 +35,7 @@ #include "absl/base/port.h" #include "absl/container/fixed_array.h" #include "absl/container/inlined_vector.h" +#include "absl/crc/internal/crc_cord_state.h" #include "absl/strings/cord_buffer.h" #include "absl/strings/escaping.h" #include "absl/strings/internal/cord_data_edge.h" @@ -166,9 +168,7 @@ constexpr unsigned char Cord::InlineRep::kMaxInline; inline void Cord::InlineRep::set_data(const char* data, size_t n) { static_assert(kMaxInline == 15, "set_data is hard-coded for a length of 15"); - - cord_internal::SmallMemmove<true>(data_.as_chars(), data, n); - set_inline_size(n); + data_.set_inline_data(data, n); } inline char* Cord::InlineRep::set_data(size_t n) { @@ -184,7 +184,7 @@ inline void Cord::InlineRep::reduce_size(size_t n) { assert(tag >= n); tag -= n; memset(data_.as_chars() + tag, 0, n); - set_inline_size(static_cast<char>(tag)); + set_inline_size(tag); } inline void Cord::InlineRep::remove_prefix(size_t n) { @@ -419,6 +419,7 @@ Cord& Cord::operator=(absl::string_view src) { // we keep it here to make diffs easier. void Cord::InlineRep::AppendArray(absl::string_view src, MethodIdentifier method) { + MaybeRemoveEmptyCrcNode(); if (src.empty()) return; // memcpy(_, nullptr, 0) is undefined. size_t appended = 0; @@ -436,8 +437,8 @@ void Cord::InlineRep::AppendArray(absl::string_view src, size_t inline_length = inline_size(); if (src.size() <= kMaxInline - inline_length) { // Append new data to embedded array - memcpy(data_.as_chars() + inline_length, src.data(), src.size()); set_inline_size(inline_length + src.size()); + memcpy(data_.as_chars() + inline_length, src.data(), src.size()); return; } @@ -478,6 +479,10 @@ inline CordRep* Cord::TakeRep() && { template <typename C> inline void Cord::AppendImpl(C&& src) { auto constexpr method = CordzUpdateTracker::kAppendCord; + + contents_.MaybeRemoveEmptyCrcNode(); + if (src.empty()) return; + if (empty()) { // Since destination is empty, we can avoid allocating a node, if (src.contents_.is_tree()) { @@ -537,18 +542,23 @@ static CordRep::ExtractResult ExtractAppendBuffer(CordRep* rep, } } -static CordBuffer CreateAppendBuffer(InlineData& data, size_t capacity) { +static CordBuffer CreateAppendBuffer(InlineData& data, size_t block_size, + size_t capacity) { // Watch out for overflow, people can ask for size_t::max(). const size_t size = data.inline_size(); - capacity = (std::min)(std::numeric_limits<size_t>::max() - size, capacity); - CordBuffer buffer = CordBuffer::CreateWithDefaultLimit(size + capacity); + const size_t max_capacity = std::numeric_limits<size_t>::max() - size; + capacity = (std::min)(max_capacity, capacity) + size; + CordBuffer buffer = + block_size ? CordBuffer::CreateWithCustomLimit(block_size, capacity) + : CordBuffer::CreateWithDefaultLimit(capacity); cord_internal::SmallMemmove(buffer.data(), data.as_chars(), size); buffer.SetLength(size); data = {}; return buffer; } -CordBuffer Cord::GetAppendBufferSlowPath(size_t capacity, size_t min_capacity) { +CordBuffer Cord::GetAppendBufferSlowPath(size_t block_size, size_t capacity, + size_t min_capacity) { auto constexpr method = CordzUpdateTracker::kGetAppendBuffer; CordRep* tree = contents_.tree(); if (tree != nullptr) { @@ -558,9 +568,10 @@ CordBuffer Cord::GetAppendBufferSlowPath(size_t capacity, size_t min_capacity) { contents_.SetTreeOrEmpty(result.tree, scope); return CordBuffer(result.extracted->flat()); } - return CordBuffer::CreateWithDefaultLimit(capacity); + return block_size ? CordBuffer::CreateWithCustomLimit(block_size, capacity) + : CordBuffer::CreateWithDefaultLimit(capacity); } - return CreateAppendBuffer(contents_.data_, capacity); + return CreateAppendBuffer(contents_.data_, block_size, capacity); } void Cord::Append(const Cord& src) { @@ -584,6 +595,9 @@ void Cord::Append(T&& src) { template void Cord::Append(std::string&& src); void Cord::Prepend(const Cord& src) { + contents_.MaybeRemoveEmptyCrcNode(); + if (src.empty()) return; + CordRep* src_tree = src.contents_.tree(); if (src_tree != nullptr) { CordRep::Ref(src_tree); @@ -598,16 +612,18 @@ void Cord::Prepend(const Cord& src) { } void Cord::PrependArray(absl::string_view src, MethodIdentifier method) { + contents_.MaybeRemoveEmptyCrcNode(); if (src.empty()) return; // memcpy(_, nullptr, 0) is undefined. + if (!contents_.is_tree()) { size_t cur_size = contents_.inline_size(); if (cur_size + src.size() <= InlineRep::kMaxInline) { // Use embedded storage. - char data[InlineRep::kMaxInline + 1] = {0}; - memcpy(data, src.data(), src.size()); - memcpy(data + src.size(), contents_.data(), cur_size); - memcpy(contents_.data_.as_chars(), data, InlineRep::kMaxInline + 1); - contents_.set_inline_size(cur_size + src.size()); + InlineData data; + data.set_inline_size(cur_size + src.size()); + memcpy(data.as_chars(), src.data(), src.size()); + memcpy(data.as_chars() + src.size(), contents_.data(), cur_size); + contents_.data_ = data; return; } } @@ -620,8 +636,8 @@ void Cord::AppendPrecise(absl::string_view src, MethodIdentifier method) { assert(src.size() <= cord_internal::kMaxFlatLength); if (contents_.remaining_inline_capacity() >= src.size()) { const size_t inline_length = contents_.inline_size(); - memcpy(contents_.data_.as_chars() + inline_length, src.data(), src.size()); contents_.set_inline_size(inline_length + src.size()); + memcpy(contents_.data_.as_chars() + inline_length, src.data(), src.size()); } else { contents_.AppendTree(CordRepFlat::Create(src), method); } @@ -631,12 +647,12 @@ void Cord::PrependPrecise(absl::string_view src, MethodIdentifier method) { assert(!src.empty()); assert(src.size() <= cord_internal::kMaxFlatLength); if (contents_.remaining_inline_capacity() >= src.size()) { - const size_t inline_length = contents_.inline_size(); - char data[InlineRep::kMaxInline + 1] = {0}; - memcpy(data, src.data(), src.size()); - memcpy(data + src.size(), contents_.data(), inline_length); - memcpy(contents_.data_.as_chars(), data, InlineRep::kMaxInline + 1); - contents_.set_inline_size(inline_length + src.size()); + const size_t cur_size = contents_.inline_size(); + InlineData data; + data.set_inline_size(cur_size + src.size()); + memcpy(data.as_chars(), src.data(), src.size()); + memcpy(data.as_chars() + src.size(), contents_.data(), cur_size); + contents_.data_ = data; } else { contents_.PrependTree(CordRepFlat::Create(src), method); } @@ -658,6 +674,7 @@ void Cord::RemovePrefix(size_t n) { ABSL_INTERNAL_CHECK(n <= size(), absl::StrCat("Requested prefix size ", n, " exceeds Cord's size ", size())); + contents_.MaybeRemoveEmptyCrcNode(); CordRep* tree = contents_.tree(); if (tree == nullptr) { contents_.remove_prefix(n); @@ -688,6 +705,7 @@ void Cord::RemoveSuffix(size_t n) { ABSL_INTERNAL_CHECK(n <= size(), absl::StrCat("Requested suffix size ", n, " exceeds Cord's size ", size())); + contents_.MaybeRemoveEmptyCrcNode(); CordRep* tree = contents_.tree(); if (tree == nullptr) { contents_.reduce_size(n); @@ -726,6 +744,7 @@ Cord Cord::Subcord(size_t pos, size_t new_size) const { } if (new_size <= InlineRep::kMaxInline) { + sub_cord.contents_.set_inline_size(new_size); char* dest = sub_cord.contents_.data_.as_chars(); Cord::ChunkIterator it = chunk_begin(); it.AdvanceBytes(pos); @@ -737,7 +756,6 @@ Cord Cord::Subcord(size_t pos, size_t new_size) const { ++it; } cord_internal::SmallMemmove(dest, it->data(), remaining_size); - sub_cord.contents_.set_inline_size(new_size); return sub_cord; } @@ -835,26 +853,44 @@ inline absl::string_view Cord::InlineRep::FindFlatStartPiece() const { return absl::string_view(node->external()->base + offset, length); } -void Cord::SetExpectedChecksum(uint32_t crc) { +void Cord::SetCrcCordState(crc_internal::CrcCordState state) { auto constexpr method = CordzUpdateTracker::kSetExpectedChecksum; - if (empty()) return; - - if (!contents_.is_tree()) { + if (empty()) { + contents_.MaybeRemoveEmptyCrcNode(); + CordRep* rep = CordRepCrc::New(nullptr, std::move(state)); + contents_.EmplaceTree(rep, method); + } else if (!contents_.is_tree()) { CordRep* rep = contents_.MakeFlatWithExtraCapacity(0); - rep = CordRepCrc::New(rep, crc); + rep = CordRepCrc::New(rep, std::move(state)); contents_.EmplaceTree(rep, method); } else { const CordzUpdateScope scope(contents_.data_.cordz_info(), method); - CordRep* rep = CordRepCrc::New(contents_.data_.as_tree(), crc); + CordRep* rep = CordRepCrc::New(contents_.data_.as_tree(), std::move(state)); contents_.SetTree(rep, scope); } } +void Cord::SetExpectedChecksum(uint32_t crc) { + // Construct a CrcCordState with a single chunk. + crc_internal::CrcCordState state; + state.mutable_rep()->prefix_crc.push_back( + crc_internal::CrcCordState::PrefixCrc(size(), absl::crc32c_t{crc})); + SetCrcCordState(std::move(state)); +} + +const crc_internal::CrcCordState* Cord::MaybeGetCrcCordState() const { + if (!contents_.is_tree() || !contents_.tree()->IsCrc()) { + return nullptr; + } + return &contents_.tree()->crc()->crc_cord_state; +} + absl::optional<uint32_t> Cord::ExpectedChecksum() const { if (!contents_.is_tree() || !contents_.tree()->IsCrc()) { return absl::nullopt; } - return contents_.tree()->crc()->crc; + return static_cast<uint32_t>( + contents_.tree()->crc()->crc_cord_state.Checksum()); } inline int Cord::CompareSlowPath(absl::string_view rhs, size_t compared_size, @@ -922,6 +958,7 @@ inline int Cord::CompareSlowPath(const Cord& rhs, size_t compared_size, } inline absl::string_view Cord::GetFirstChunk(const Cord& c) { + if (c.empty()) return {}; return c.contents_.FindFlatStartPiece(); } inline absl::string_view Cord::GetFirstChunk(absl::string_view sv) { @@ -1092,7 +1129,7 @@ Cord Cord::ChunkIterator::AdvanceAndReadBytes(size_t n) { : current_leaf_; const char* data = payload->IsExternal() ? payload->external()->base : payload->flat()->Data(); - const size_t offset = current_chunk_.data() - data; + const size_t offset = static_cast<size_t>(current_chunk_.data() - data); auto* tree = CordRepSubstring::Substring(payload, offset, n); subcord.contents_.EmplaceTree(VerifyTree(tree), method); @@ -1159,6 +1196,10 @@ absl::string_view Cord::FlattenSlowPath() { /* static */ bool Cord::GetFlatAux(CordRep* rep, absl::string_view* fragment) { assert(rep != nullptr); + if (rep->length == 0) { + *fragment = absl::string_view(); + return true; + } rep = cord_internal::SkipCrcNode(rep); if (rep->IsFlat()) { *fragment = absl::string_view(rep->flat()->Data(), rep->length); @@ -1190,6 +1231,7 @@ absl::string_view Cord::FlattenSlowPath() { absl::cord_internal::CordRep* rep, absl::FunctionRef<void(absl::string_view)> callback) { assert(rep != nullptr); + if (rep->length == 0) return; rep = cord_internal::SkipCrcNode(rep); if (rep->IsBtree()) { @@ -1223,8 +1265,12 @@ static void DumpNode(CordRep* rep, bool include_data, std::ostream* os, if (include_data) *os << static_cast<void*>(rep); *os << "]"; *os << " " << std::setw(indent) << ""; - if (rep->IsCrc()) { - *os << "CRC crc=" << rep->crc()->crc << "\n"; + bool leaf = false; + if (rep == nullptr) { + *os << "NULL\n"; + leaf = true; + } else if (rep->IsCrc()) { + *os << "CRC crc=" << rep->crc()->crc_cord_state.Checksum() << "\n"; indent += kIndentStep; rep = rep->crc()->child; } else if (rep->IsSubstring()) { @@ -1232,6 +1278,7 @@ static void DumpNode(CordRep* rep, bool include_data, std::ostream* os, indent += kIndentStep; rep = rep->substring()->child; } else { // Leaf or ring + leaf = true; if (rep->IsExternal()) { *os << "EXTERNAL ["; if (include_data) @@ -1245,6 +1292,8 @@ static void DumpNode(CordRep* rep, bool include_data, std::ostream* os, } else { CordRepBtree::Dump(rep, /*label=*/ "", include_data, *os); } + } + if (leaf) { if (stack.empty()) break; rep = stack.back(); stack.pop_back(); @@ -1290,11 +1339,14 @@ static bool VerifyNode(CordRep* root, CordRep* start_node, node->substring()->child->length, ReportError(root, node)); } else if (node->IsCrc()) { - ABSL_INTERNAL_CHECK(node->crc()->child != nullptr, - ReportError(root, node)); - ABSL_INTERNAL_CHECK(node->crc()->length == node->crc()->child->length, - ReportError(root, node)); - worklist.push_back(node->crc()->child); + ABSL_INTERNAL_CHECK( + node->crc()->child != nullptr || node->crc()->length == 0, + ReportError(root, node)); + if (node->crc()->child != nullptr) { + ABSL_INTERNAL_CHECK(node->crc()->length == node->crc()->child->length, + ReportError(root, node)); + worklist.push_back(node->crc()->child); + } } } while (!worklist.empty()); return true; @@ -1302,7 +1354,7 @@ static bool VerifyNode(CordRep* root, CordRep* start_node, std::ostream& operator<<(std::ostream& out, const Cord& cord) { for (absl::string_view chunk : cord.Chunks()) { - out.write(chunk.data(), chunk.size()); + out.write(chunk.data(), static_cast<std::streamsize>(chunk.size())); } return out; } diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 18d6ab85..c4a0d5aa 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h @@ -20,8 +20,7 @@ // structure. A Cord is a string-like sequence of characters optimized for // specific use cases. Unlike a `std::string`, which stores an array of // contiguous characters, Cord data is stored in a structure consisting of -// separate, reference-counted "chunks." (Currently, this implementation is a -// tree structure, though that implementation may change.) +// separate, reference-counted "chunks." // // Because a Cord consists of these chunks, data can be added to or removed from // a Cord during its lifetime. Chunks may also be shared between Cords. Unlike a @@ -77,6 +76,7 @@ #include "absl/base/macros.h" #include "absl/base/port.h" #include "absl/container/inlined_vector.h" +#include "absl/crc/internal/crc_cord_state.h" #include "absl/functional/function_ref.h" #include "absl/meta/type_traits.h" #include "absl/strings/cord_analysis.h" @@ -284,6 +284,19 @@ class Cord { // } CordBuffer GetAppendBuffer(size_t capacity, size_t min_capacity = 16); + // Returns a CordBuffer, re-using potential existing capacity in this cord. + // + // This function is identical to `GetAppendBuffer`, except that in the case + // where a new `CordBuffer` is allocated, it is allocated using the provided + // custom limit instead of the default limit. `GetAppendBuffer` will default + // to `CordBuffer::CreateWithDefaultLimit(capacity)` whereas this method + // will default to `CordBuffer::CreateWithCustomLimit(block_size, capacity)`. + // This method is equivalent to `GetAppendBuffer` if `block_size` is zero. + // See the documentation for `CreateWithCustomLimit` for more details on the + // restrictions and legal values for `block_size`. + CordBuffer GetCustomAppendBuffer(size_t block_size, size_t capacity, + size_t min_capacity = 16); + // Cord::Prepend() // // Prepends data to the Cord, which may come from another Cord or other string @@ -802,7 +815,7 @@ class Cord { InlineRep& operator=(const InlineRep& src); InlineRep& operator=(InlineRep&& src) noexcept; - explicit constexpr InlineRep(cord_internal::InlineData data); + explicit constexpr InlineRep(absl::string_view sv, CordRep* rep); void Swap(InlineRep* rhs); bool empty() const; @@ -861,33 +874,14 @@ class Cord { void PrependTreeToTree(CordRep* tree, MethodIdentifier method); void PrependTree(CordRep* tree, MethodIdentifier method); - template <bool has_length> - void GetAppendRegion(char** region, size_t* size, size_t length); + bool IsSame(const InlineRep& other) const { return data_ == other.data_; } - bool IsSame(const InlineRep& other) const { - return memcmp(&data_, &other.data_, sizeof(data_)) == 0; - } - int BitwiseCompare(const InlineRep& other) const { - uint64_t x, y; - // Use memcpy to avoid aliasing issues. - memcpy(&x, &data_, sizeof(x)); - memcpy(&y, &other.data_, sizeof(y)); - if (x == y) { - memcpy(&x, reinterpret_cast<const char*>(&data_) + 8, sizeof(x)); - memcpy(&y, reinterpret_cast<const char*>(&other.data_) + 8, sizeof(y)); - if (x == y) return 0; - } - return absl::big_endian::FromHost64(x) < absl::big_endian::FromHost64(y) - ? -1 - : 1; - } void CopyTo(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. - absl::strings_internal::STLStringResizeUninitialized(dst, - sizeof(data_) - 1); - memcpy(&(*dst)[0], &data_, sizeof(data_) - 1); + absl::strings_internal::STLStringResizeUninitialized(dst, kMaxInline); + data_.copy_max_inline_to(&(*dst)[0]); // erase is faster than resize because the logic for memory allocation is // not needed. dst->erase(inline_size()); @@ -932,6 +926,13 @@ class Cord { void set_inline_size(size_t size) { data_.set_inline_size(size); } size_t inline_size() const { return data_.inline_size(); } + // Empty cords that carry a checksum have a CordRepCrc node with a null + // child node. The code can avoid lots of special cases where it would + // otherwise transition from tree to inline storage if we just remove the + // CordRepCrc node before mutations. Must never be called inside a + // CordzUpdateScope since it untracks the cordz info. + void MaybeRemoveEmptyCrcNode(); + cord_internal::InlineData data_; }; InlineRep contents_; @@ -980,7 +981,8 @@ class Cord { void AppendPrecise(absl::string_view src, MethodIdentifier method); void PrependPrecise(absl::string_view src, MethodIdentifier method); - CordBuffer GetAppendBufferSlowPath(size_t capacity, size_t min_capacity); + CordBuffer GetAppendBufferSlowPath(size_t block_size, size_t capacity, + size_t min_capacity); // Prepends the provided data to this instance. `method` contains the public // API method for this action which is tracked for Cordz sampling purposes. @@ -1000,6 +1002,10 @@ class Cord { }); return H::combine(combiner.finalize(std::move(hash_state)), size()); } + + friend class CrcCord; + void SetCrcCordState(crc_internal::CrcCordState state); + const crc_internal::CrcCordState* MaybeGetCrcCordState() const; }; ABSL_NAMESPACE_END @@ -1016,46 +1022,6 @@ extern std::ostream& operator<<(std::ostream& out, const Cord& cord); namespace cord_internal { -// Fast implementation of memmove for up to 15 bytes. This implementation is -// safe for overlapping regions. If nullify_tail is true, the destination is -// padded with '\0' up to 16 bytes. -template <bool nullify_tail = false> -inline void SmallMemmove(char* dst, const char* src, size_t n) { - if (n >= 8) { - assert(n <= 16); - uint64_t buf1; - uint64_t buf2; - memcpy(&buf1, src, 8); - memcpy(&buf2, src + n - 8, 8); - if (nullify_tail) { - memset(dst + 8, 0, 8); - } - memcpy(dst, &buf1, 8); - memcpy(dst + n - 8, &buf2, 8); - } else if (n >= 4) { - uint32_t buf1; - uint32_t buf2; - memcpy(&buf1, src, 4); - memcpy(&buf2, src + n - 4, 4); - if (nullify_tail) { - memset(dst + 4, 0, 4); - memset(dst + 8, 0, 8); - } - memcpy(dst, &buf1, 4); - memcpy(dst + n - 4, &buf2, 4); - } else { - if (n != 0) { - dst[0] = src[0]; - dst[n / 2] = src[n / 2]; - dst[n - 1] = src[n - 1]; - } - if (nullify_tail) { - memset(dst + 8, 0, 8); - memset(dst + n, 0, 8); - } - } -} - // Does non-template-specific `CordRepExternal` initialization. // Requires `data` to be non-empty. void InitializeCordRepExternal(absl::string_view data, CordRepExternal* rep); @@ -1099,8 +1065,8 @@ Cord MakeCordFromExternal(absl::string_view data, Releaser&& releaser) { return cord; } -constexpr Cord::InlineRep::InlineRep(cord_internal::InlineData data) - : data_(data) {} +constexpr Cord::InlineRep::InlineRep(absl::string_view sv, CordRep* rep) + : data_(sv, rep) {} inline Cord::InlineRep::InlineRep(const Cord::InlineRep& src) : data_(InlineData::kDefaultInit) { @@ -1179,7 +1145,7 @@ inline cord_internal::CordRepFlat* Cord::InlineRep::MakeFlatWithExtraCapacity( size_t len = data_.inline_size(); auto* result = CordRepFlat::New(len + extra); result->length = len; - memcpy(result->Data(), data_.as_chars(), sizeof(data_)); + data_.copy_max_inline_to(result->Data()); return result; } @@ -1241,6 +1207,18 @@ inline void Cord::InlineRep::CopyToArray(char* dst) const { cord_internal::SmallMemmove(dst, data_.as_chars(), n); } +inline void Cord::InlineRep::MaybeRemoveEmptyCrcNode() { + CordRep* rep = tree(); + if (rep == nullptr || ABSL_PREDICT_TRUE(rep->length > 0)) { + return; + } + assert(rep->IsCrc()); + assert(rep->crc()->child == nullptr); + CordzInfo::MaybeUntrackCord(cordz_info()); + CordRep::Unref(rep); + ResetToEmpty(); +} + constexpr inline Cord::Cord() noexcept {} inline Cord::Cord(absl::string_view src) @@ -1248,13 +1226,12 @@ inline Cord::Cord(absl::string_view src) template <typename T> constexpr Cord::Cord(strings_internal::StringConstant<T>) - : contents_(strings_internal::StringConstant<T>::value.size() <= + : contents_(strings_internal::StringConstant<T>::value, + strings_internal::StringConstant<T>::value.size() <= cord_internal::kMaxInline - ? cord_internal::InlineData( - strings_internal::StringConstant<T>::value) - : cord_internal::InlineData( - &cord_internal::ConstInitExternalStorage< - strings_internal::StringConstant<T>>::value)) {} + ? nullptr + : &cord_internal::ConstInitExternalStorage< + strings_internal::StringConstant<T>>::value) {} inline Cord& Cord::operator=(const Cord& x) { contents_ = x.contents_; @@ -1290,7 +1267,7 @@ inline size_t Cord::size() const { return contents_.size(); } -inline bool Cord::empty() const { return contents_.empty(); } +inline bool Cord::empty() const { return size() == 0; } inline size_t Cord::EstimatedMemoryUsage( CordMemoryAccounting accounting_method) const { @@ -1360,7 +1337,17 @@ inline void Cord::Prepend(CordBuffer buffer) { inline CordBuffer Cord::GetAppendBuffer(size_t capacity, size_t min_capacity) { if (empty()) return CordBuffer::CreateWithDefaultLimit(capacity); - return GetAppendBufferSlowPath(capacity, min_capacity); + return GetAppendBufferSlowPath(0, capacity, min_capacity); +} + +inline CordBuffer Cord::GetCustomAppendBuffer(size_t block_size, + size_t capacity, + size_t min_capacity) { + if (empty()) { + return block_size ? CordBuffer::CreateWithCustomLimit(block_size, capacity) + : CordBuffer::CreateWithDefaultLimit(capacity); + } + return GetAppendBufferSlowPath(block_size, capacity, min_capacity); } extern template void Cord::Append(std::string&& src); @@ -1368,7 +1355,7 @@ extern template void Cord::Prepend(std::string&& src); inline int Cord::Compare(const Cord& rhs) const { if (!contents_.is_tree() && !rhs.contents_.is_tree()) { - return contents_.BitwiseCompare(rhs.contents_); + return contents_.data_.Compare(rhs.contents_.data_); } return CompareImpl(rhs); @@ -1406,7 +1393,11 @@ inline Cord::ChunkIterator::ChunkIterator(cord_internal::CordRep* tree) { inline Cord::ChunkIterator::ChunkIterator(const Cord* cord) { if (CordRep* tree = cord->contents_.tree()) { bytes_remaining_ = tree->length; - InitTree(tree); + if (ABSL_PREDICT_TRUE(bytes_remaining_ != 0)) { + InitTree(tree); + } else { + current_chunk_ = {}; + } } else { bytes_remaining_ = cord->contents_.inline_size(); current_chunk_ = {cord->contents_.data(), bytes_remaining_}; @@ -1575,7 +1566,7 @@ inline void Cord::ForEachChunk( if (rep == nullptr) { callback(absl::string_view(contents_.data(), contents_.size())); } else { - return ForEachChunkAux(rep, callback); + ForEachChunkAux(rep, callback); } } diff --git a/absl/strings/cord_buffer.h b/absl/strings/cord_buffer.h index 56a6ce6f..15494b31 100644 --- a/absl/strings/cord_buffer.h +++ b/absl/strings/cord_buffer.h @@ -330,8 +330,7 @@ class CordBuffer { // Returns the available area of the internal SSO data absl::Span<char> short_available() { - assert(is_short()); - const size_t length = (short_rep.raw_size >> 1); + const size_t length = short_length(); return absl::Span<char>(short_rep.data + length, kInlineCapacity - length); } @@ -347,7 +346,7 @@ class CordBuffer { // Returns the length of the internal SSO data. size_t short_length() const { assert(is_short()); - return short_rep.raw_size >> 1; + return static_cast<size_t>(short_rep.raw_size >> 1); } // Sets the length of the internal SSO data. @@ -412,8 +411,12 @@ class CordBuffer { // Power2 functions static bool IsPow2(size_t size) { return absl::has_single_bit(size); } - static size_t Log2Floor(size_t size) { return absl::bit_width(size) - 1; } - static size_t Log2Ceil(size_t size) { return absl::bit_width(size - 1); } + static size_t Log2Floor(size_t size) { + return static_cast<size_t>(absl::bit_width(size) - 1); + } + static size_t Log2Ceil(size_t size) { + return static_cast<size_t>(absl::bit_width(size - 1)); + } // Implementation of `CreateWithCustomLimit()`. // This implementation allows for future memory allocation hints to diff --git a/absl/strings/cord_test.cc b/absl/strings/cord_test.cc index 0862f69a..5603e94c 100644 --- a/absl/strings/cord_test.cc +++ b/absl/strings/cord_test.cc @@ -28,7 +28,6 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" -#include "absl/base/casts.h" #include "absl/base/config.h" #include "absl/base/internal/endian.h" #include "absl/base/internal/raw_logging.h" @@ -733,18 +732,48 @@ TEST_P(CordTest, PrependLargeBuffer) { EXPECT_THAT(cord.Chunks(), ::testing::ElementsAre(s2, s1)); } -TEST_P(CordTest, GetAppendBufferOnEmptyCord) { +class CordAppendBufferTest : public testing::TestWithParam<bool> { + public: + size_t is_default() const { return GetParam(); } + + // Returns human readable string representation of the test parameter. + static std::string ToString(testing::TestParamInfo<bool> param) { + return param.param ? "DefaultLimit" : "CustomLimit"; + } + + size_t limit() const { + return is_default() ? absl::CordBuffer::kDefaultLimit + : absl::CordBuffer::kCustomLimit; + } + + size_t maximum_payload() const { + return is_default() ? absl::CordBuffer::MaximumPayload() + : absl::CordBuffer::MaximumPayload(limit()); + } + + absl::CordBuffer GetAppendBuffer(absl::Cord& cord, size_t capacity, + size_t min_capacity = 16) { + return is_default() + ? cord.GetAppendBuffer(capacity, min_capacity) + : cord.GetCustomAppendBuffer(limit(), capacity, min_capacity); + } +}; + +INSTANTIATE_TEST_SUITE_P(WithParam, CordAppendBufferTest, testing::Bool(), + CordAppendBufferTest::ToString); + +TEST_P(CordAppendBufferTest, GetAppendBufferOnEmptyCord) { absl::Cord cord; - absl::CordBuffer buffer = cord.GetAppendBuffer(1000); + absl::CordBuffer buffer = GetAppendBuffer(cord, 1000); EXPECT_GE(buffer.capacity(), 1000); EXPECT_EQ(buffer.length(), 0); } -TEST_P(CordTest, GetAppendBufferOnInlinedCord) { +TEST_P(CordAppendBufferTest, GetAppendBufferOnInlinedCord) { static constexpr int kInlinedSize = sizeof(absl::CordBuffer) - 1; for (int size : {6, kInlinedSize - 3, kInlinedSize - 2, 1000}) { absl::Cord cord("Abc"); - absl::CordBuffer buffer = cord.GetAppendBuffer(size, 1); + absl::CordBuffer buffer = GetAppendBuffer(cord, size, 1); EXPECT_GE(buffer.capacity(), 3 + size); EXPECT_EQ(buffer.length(), 3); EXPECT_EQ(absl::string_view(buffer.data(), buffer.length()), "Abc"); @@ -752,7 +781,7 @@ TEST_P(CordTest, GetAppendBufferOnInlinedCord) { } } -TEST_P(CordTest, GetAppendBufferOnInlinedCordWithCapacityCloseToMax) { +TEST_P(CordAppendBufferTest, GetAppendBufferOnInlinedCordCapacityCloseToMax) { // Cover the use case where we have a non empty inlined cord with some size // 'n', and ask for something like 'uint64_max - k', assuming internal logic // could overflow on 'uint64_max - k + size', and return a valid, but @@ -760,30 +789,31 @@ TEST_P(CordTest, GetAppendBufferOnInlinedCordWithCapacityCloseToMax) { for (size_t dist_from_max = 0; dist_from_max <= 4; ++dist_from_max) { absl::Cord cord("Abc"); size_t size = std::numeric_limits<size_t>::max() - dist_from_max; - absl::CordBuffer buffer = cord.GetAppendBuffer(size, 1); - EXPECT_EQ(buffer.capacity(), absl::CordBuffer::kDefaultLimit); + absl::CordBuffer buffer = GetAppendBuffer(cord, size, 1); + EXPECT_GE(buffer.capacity(), maximum_payload()); EXPECT_EQ(buffer.length(), 3); EXPECT_EQ(absl::string_view(buffer.data(), buffer.length()), "Abc"); EXPECT_TRUE(cord.empty()); } } -TEST_P(CordTest, GetAppendBufferOnFlat) { +TEST_P(CordAppendBufferTest, GetAppendBufferOnFlat) { // Create a cord with a single flat and extra capacity absl::Cord cord; absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500); + const size_t expected_capacity = buffer.capacity(); buffer.SetLength(3); memcpy(buffer.data(), "Abc", 3); cord.Append(std::move(buffer)); - buffer = cord.GetAppendBuffer(6); - EXPECT_GE(buffer.capacity(), 500); + buffer = GetAppendBuffer(cord, 6); + EXPECT_EQ(buffer.capacity(), expected_capacity); EXPECT_EQ(buffer.length(), 3); EXPECT_EQ(absl::string_view(buffer.data(), buffer.length()), "Abc"); EXPECT_TRUE(cord.empty()); } -TEST_P(CordTest, GetAppendBufferOnFlatWithoutMinCapacity) { +TEST_P(CordAppendBufferTest, GetAppendBufferOnFlatWithoutMinCapacity) { // Create a cord with a single flat and extra capacity absl::Cord cord; absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500); @@ -791,13 +821,13 @@ TEST_P(CordTest, GetAppendBufferOnFlatWithoutMinCapacity) { memset(buffer.data(), 'x', 30); cord.Append(std::move(buffer)); - buffer = cord.GetAppendBuffer(1000, 900); + buffer = GetAppendBuffer(cord, 1000, 900); EXPECT_GE(buffer.capacity(), 1000); EXPECT_EQ(buffer.length(), 0); EXPECT_EQ(cord, std::string(30, 'x')); } -TEST_P(CordTest, GetAppendBufferOnTree) { +TEST_P(CordAppendBufferTest, GetAppendBufferOnTree) { RandomEngine rng; for (int num_flats : {2, 3, 100}) { // Create a cord with `num_flats` flats and extra capacity @@ -812,7 +842,7 @@ TEST_P(CordTest, GetAppendBufferOnTree) { memcpy(buffer.data(), last.data(), 10); cord.Append(std::move(buffer)); } - absl::CordBuffer buffer = cord.GetAppendBuffer(6); + absl::CordBuffer buffer = GetAppendBuffer(cord, 6); EXPECT_GE(buffer.capacity(), 500); EXPECT_EQ(buffer.length(), 10); EXPECT_EQ(absl::string_view(buffer.data(), buffer.length()), last); @@ -820,7 +850,7 @@ TEST_P(CordTest, GetAppendBufferOnTree) { } } -TEST_P(CordTest, GetAppendBufferOnTreeWithoutMinCapacity) { +TEST_P(CordAppendBufferTest, GetAppendBufferOnTreeWithoutMinCapacity) { absl::Cord cord; for (int i = 0; i < 2; ++i) { absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500); @@ -828,13 +858,13 @@ TEST_P(CordTest, GetAppendBufferOnTreeWithoutMinCapacity) { memcpy(buffer.data(), i ? "def" : "Abc", 3); cord.Append(std::move(buffer)); } - absl::CordBuffer buffer = cord.GetAppendBuffer(1000, 900); + absl::CordBuffer buffer = GetAppendBuffer(cord, 1000, 900); EXPECT_GE(buffer.capacity(), 1000); EXPECT_EQ(buffer.length(), 0); EXPECT_EQ(cord, "Abcdef"); } -TEST_P(CordTest, GetAppendBufferOnSubstring) { +TEST_P(CordAppendBufferTest, GetAppendBufferOnSubstring) { // Create a large cord with a single flat and some extra capacity absl::Cord cord; absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500); @@ -844,12 +874,12 @@ TEST_P(CordTest, GetAppendBufferOnSubstring) { cord.RemovePrefix(1); // Deny on substring - buffer = cord.GetAppendBuffer(6); + buffer = GetAppendBuffer(cord, 6); EXPECT_EQ(buffer.length(), 0); EXPECT_EQ(cord, std::string(449, 'x')); } -TEST_P(CordTest, GetAppendBufferOnSharedCord) { +TEST_P(CordAppendBufferTest, GetAppendBufferOnSharedCord) { // Create a shared cord with a single flat and extra capacity absl::Cord cord; absl::CordBuffer buffer = absl::CordBuffer::CreateWithDefaultLimit(500); @@ -859,7 +889,7 @@ TEST_P(CordTest, GetAppendBufferOnSharedCord) { absl::Cord shared_cord = cord; // Deny on flat - buffer = cord.GetAppendBuffer(6); + buffer = GetAppendBuffer(cord, 6); EXPECT_EQ(buffer.length(), 0); EXPECT_EQ(cord, "Abc"); @@ -870,7 +900,7 @@ TEST_P(CordTest, GetAppendBufferOnSharedCord) { shared_cord = cord; // Deny on tree - buffer = cord.GetAppendBuffer(6); + buffer = GetAppendBuffer(cord, 6); EXPECT_EQ(buffer.length(), 0); EXPECT_EQ(cord, "Abcdef"); } @@ -1958,6 +1988,12 @@ TEST_P(CordTest, HugeCord) { // Tests that Append() works ok when handed a self reference TEST_P(CordTest, AppendSelf) { + // Test the empty case. + absl::Cord empty; + MaybeHarden(empty); + empty.Append(empty); + ASSERT_EQ(empty, ""); + // We run the test until data is ~16K // This guarantees it covers small, medium and large data. std::string control_data = "Abc"; @@ -2682,7 +2718,7 @@ class CordMutator { // clang-format off // This array is constant-initialized in conformant compilers. -CordMutator cord_mutators[] ={ +CordMutator cord_mutators[] = { {"clear", [](absl::Cord& c) { c.Clear(); }}, {"overwrite", [](absl::Cord& c) { c = "overwritten"; }}, { @@ -2712,6 +2748,25 @@ CordMutator cord_mutators[] ={ [](absl::Cord& c) { c.RemoveSuffix(c.size() / 2); } }, { + "append empty string", + [](absl::Cord& c) { c.Append(""); }, + [](absl::Cord& c) { } + }, + { + "append empty cord", + [](absl::Cord& c) { c.Append(absl::Cord()); }, + [](absl::Cord& c) { } + }, + { + "append empty checksummed cord", + [](absl::Cord& c) { + absl::Cord to_append; + to_append.SetExpectedChecksum(999); + c.Append(to_append); + }, + [](absl::Cord& c) { } + }, + { "prepend string", [](absl::Cord& c) { c.Prepend("9876543210"); }, [](absl::Cord& c) { c.RemovePrefix(10); } @@ -2733,12 +2788,33 @@ CordMutator cord_mutators[] ={ [](absl::Cord& c) { c.RemovePrefix(10); } }, { + "prepend empty string", + [](absl::Cord& c) { c.Prepend(""); }, + [](absl::Cord& c) { } + }, + { + "prepend empty cord", + [](absl::Cord& c) { c.Prepend(absl::Cord()); }, + [](absl::Cord& c) { } + }, + { + "prepend empty checksummed cord", + [](absl::Cord& c) { + absl::Cord to_prepend; + to_prepend.SetExpectedChecksum(999); + c.Prepend(to_prepend); + }, + [](absl::Cord& c) { } + }, + { "prepend self", [](absl::Cord& c) { c.Prepend(c); }, [](absl::Cord& c) { c.RemovePrefix(c.size() / 2); } }, - {"remove prefix", [](absl::Cord& c) { c.RemovePrefix(2); }}, - {"remove suffix", [](absl::Cord& c) { c.RemoveSuffix(2); }}, + {"remove prefix", [](absl::Cord& c) { c.RemovePrefix(c.size() / 2); }}, + {"remove suffix", [](absl::Cord& c) { c.RemoveSuffix(c.size() / 2); }}, + {"remove 0-prefix", [](absl::Cord& c) { c.RemovePrefix(0); }}, + {"remove 0-suffix", [](absl::Cord& c) { c.RemoveSuffix(0); }}, {"subcord", [](absl::Cord& c) { c = c.Subcord(1, c.size() - 2); }}, { "swap inline", @@ -2780,6 +2856,12 @@ TEST_P(CordTest, ExpectedChecksum) { EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345); EXPECT_EQ(c1, base_value); + // Test that setting an expected checksum again doesn't crash or leak + // memory. + c1.SetExpectedChecksum(12345); + EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345); + EXPECT_EQ(c1, base_value); + // CRC persists through copies, assignments, and moves: absl::Cord c1_copy_construct = c1; EXPECT_EQ(c1_copy_construct.ExpectedChecksum().value_or(0), 12345); @@ -2804,6 +2886,13 @@ TEST_P(CordTest, ExpectedChecksum) { c2.SetExpectedChecksum(24680); mutator.Mutate(c2); + + if (c1 == c2) { + // Not a mutation (for example, appending the empty string). + // Whether the checksum is removed is not defined. + continue; + } + EXPECT_EQ(c2.ExpectedChecksum(), absl::nullopt); if (mutator.CanUndo()) { @@ -2873,3 +2962,164 @@ TEST_P(CordTest, ExpectedChecksum) { } } } + +// Test the special cases encountered with an empty checksummed cord. +TEST_P(CordTest, ChecksummedEmptyCord) { + absl::Cord c1; + EXPECT_FALSE(c1.ExpectedChecksum().has_value()); + + // Setting an expected checksum works. + c1.SetExpectedChecksum(12345); + EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345); + EXPECT_EQ(c1, ""); + EXPECT_TRUE(c1.empty()); + + // Test that setting an expected checksum again doesn't crash or leak memory. + c1.SetExpectedChecksum(12345); + EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345); + EXPECT_EQ(c1, ""); + EXPECT_TRUE(c1.empty()); + + // CRC persists through copies, assignments, and moves: + absl::Cord c1_copy_construct = c1; + EXPECT_EQ(c1_copy_construct.ExpectedChecksum().value_or(0), 12345); + + absl::Cord c1_copy_assign; + c1_copy_assign = c1; + EXPECT_EQ(c1_copy_assign.ExpectedChecksum().value_or(0), 12345); + + absl::Cord c1_move(std::move(c1_copy_assign)); + EXPECT_EQ(c1_move.ExpectedChecksum().value_or(0), 12345); + + EXPECT_EQ(c1.ExpectedChecksum().value_or(0), 12345); + + // A CRC Cord compares equal to its non-CRC value. + EXPECT_EQ(c1, absl::Cord()); + + for (const CordMutator& mutator : cord_mutators) { + SCOPED_TRACE(mutator.Name()); + + // Exercise mutating an empty checksummed cord to catch crashes and exercise + // memory sanitizers. + absl::Cord c2; + c2.SetExpectedChecksum(24680); + mutator.Mutate(c2); + + if (c2.empty()) { + // Not a mutation + continue; + } + EXPECT_EQ(c2.ExpectedChecksum(), absl::nullopt); + + if (mutator.CanUndo()) { + mutator.Undo(c2); + } + } + + absl::Cord c3; + c3.SetExpectedChecksum(999); + const absl::Cord& cc3 = c3; + + // Test that all cord reading operations function in the face of an + // expected checksum. + EXPECT_TRUE(cc3.StartsWith("")); + EXPECT_TRUE(cc3.EndsWith("")); + EXPECT_TRUE(cc3.empty()); + EXPECT_EQ(cc3, ""); + EXPECT_EQ(cc3, absl::Cord()); + EXPECT_EQ(cc3.size(), 0); + EXPECT_EQ(cc3.Compare(absl::Cord()), 0); + EXPECT_EQ(cc3.Compare(c1), 0); + EXPECT_EQ(cc3.Compare(cc3), 0); + EXPECT_EQ(cc3.Compare(""), 0); + EXPECT_EQ(cc3.Compare("wxyz"), -1); + EXPECT_EQ(cc3.Compare(absl::Cord("wxyz")), -1); + EXPECT_EQ(absl::Cord("wxyz").Compare(cc3), 1); + EXPECT_EQ(std::string(cc3), ""); + + std::string dest; + absl::CopyCordToString(cc3, &dest); + EXPECT_EQ(dest, ""); + + for (absl::string_view chunk : cc3.Chunks()) { // NOLINT(unreachable loop) + static_cast<void>(chunk); + GTEST_FAIL() << "no chunks expected"; + } + EXPECT_TRUE(cc3.chunk_begin() == cc3.chunk_end()); + + for (char ch : cc3.Chars()) { // NOLINT(unreachable loop) + static_cast<void>(ch); + GTEST_FAIL() << "no chars expected"; + } + EXPECT_TRUE(cc3.char_begin() == cc3.char_end()); + + EXPECT_EQ(cc3.TryFlat(), ""); + EXPECT_EQ(absl::HashOf(c3), absl::HashOf(absl::Cord())); + EXPECT_EQ(absl::HashOf(c3), absl::HashOf(absl::string_view())); +} + +#if defined(GTEST_HAS_DEATH_TEST) && defined(ABSL_INTERNAL_CORD_HAVE_SANITIZER) + +// Returns an expected poison / uninitialized death message expression. +const char* MASanDeathExpr() { + return "(use-after-poison|use-of-uninitialized-value)"; +} + +TEST(CordSanitizerTest, SanitizesEmptyCord) { + absl::Cord cord; + const char* data = cord.Flatten().data(); + EXPECT_DEATH(EXPECT_EQ(data[0], 0), MASanDeathExpr()); +} + +TEST(CordSanitizerTest, SanitizesSmallCord) { + absl::Cord cord("Hello"); + const char* data = cord.Flatten().data(); + EXPECT_DEATH(EXPECT_EQ(data[5], 0), MASanDeathExpr()); +} + +TEST(CordSanitizerTest, SanitizesCordOnSetSSOValue) { + absl::Cord cord("String that is too big to be an SSO value"); + cord = "Hello"; + const char* data = cord.Flatten().data(); + EXPECT_DEATH(EXPECT_EQ(data[5], 0), MASanDeathExpr()); +} + +TEST(CordSanitizerTest, SanitizesCordOnCopyCtor) { + absl::Cord src("hello"); + absl::Cord dst(src); + const char* data = dst.Flatten().data(); + EXPECT_DEATH(EXPECT_EQ(data[5], 0), MASanDeathExpr()); +} + +TEST(CordSanitizerTest, SanitizesCordOnMoveCtor) { + absl::Cord src("hello"); + absl::Cord dst(std::move(src)); + const char* data = dst.Flatten().data(); + EXPECT_DEATH(EXPECT_EQ(data[5], 0), MASanDeathExpr()); +} + +TEST(CordSanitizerTest, SanitizesCordOnAssign) { + absl::Cord src("hello"); + absl::Cord dst; + dst = src; + const char* data = dst.Flatten().data(); + EXPECT_DEATH(EXPECT_EQ(data[5], 0), MASanDeathExpr()); +} + +TEST(CordSanitizerTest, SanitizesCordOnMoveAssign) { + absl::Cord src("hello"); + absl::Cord dst; + dst = std::move(src); + const char* data = dst.Flatten().data(); + EXPECT_DEATH(EXPECT_EQ(data[5], 0), MASanDeathExpr()); +} + +TEST(CordSanitizerTest, SanitizesCordOnSsoAssign) { + absl::Cord src("hello"); + absl::Cord dst("String that is too big to be an SSO value"); + dst = src; + const char* data = dst.Flatten().data(); + EXPECT_DEATH(EXPECT_EQ(data[5], 0), MASanDeathExpr()); +} + +#endif // GTEST_HAS_DEATH_TEST && ABSL_INTERNAL_CORD_HAVE_SANITIZER diff --git a/absl/strings/escaping.cc b/absl/strings/escaping.cc index 18b20b83..93966846 100644 --- a/absl/strings/escaping.cc +++ b/absl/strings/escaping.cc @@ -42,11 +42,11 @@ constexpr bool kUnescapeNulls = false; inline bool is_octal_digit(char c) { return ('0' <= c) && (c <= '7'); } -inline int hex_digit_to_int(char c) { +inline unsigned int hex_digit_to_int(char c) { static_assert('0' == 0x30 && 'A' == 0x41 && 'a' == 0x61, "Character set must be ASCII."); - assert(absl::ascii_isxdigit(c)); - int x = static_cast<unsigned char>(c); + assert(absl::ascii_isxdigit(static_cast<unsigned char>(c))); + unsigned int x = static_cast<unsigned char>(c); if (x > '9') { x += 9; } @@ -121,27 +121,29 @@ bool CUnescapeInternal(absl::string_view source, bool leave_nulls_escaped, case '7': { // octal digit: 1 to 3 digits const char* octal_start = p; - unsigned int ch = *p - '0'; - if (p < last_byte && is_octal_digit(p[1])) ch = ch * 8 + *++p - '0'; + unsigned int ch = static_cast<unsigned int>(*p - '0'); // digit 1 if (p < last_byte && is_octal_digit(p[1])) - ch = ch * 8 + *++p - '0'; // now points at last digit + ch = ch * 8 + static_cast<unsigned int>(*++p - '0'); // digit 2 + if (p < last_byte && is_octal_digit(p[1])) + ch = ch * 8 + static_cast<unsigned int>(*++p - '0'); // digit 3 if (ch > 0xff) { if (error) { *error = "Value of \\" + - std::string(octal_start, p + 1 - octal_start) + + std::string(octal_start, + static_cast<size_t>(p + 1 - octal_start)) + " exceeds 0xff"; } return false; } if ((ch == 0) && leave_nulls_escaped) { // Copy the escape sequence for the null character - const ptrdiff_t octal_size = p + 1 - octal_start; + const size_t octal_size = static_cast<size_t>(p + 1 - octal_start); *d++ = '\\'; memmove(d, octal_start, octal_size); d += octal_size; break; } - *d++ = ch; + *d++ = static_cast<char>(ch); break; } case 'x': @@ -149,32 +151,34 @@ bool CUnescapeInternal(absl::string_view source, bool leave_nulls_escaped, if (p >= last_byte) { if (error) *error = "String cannot end with \\x"; return false; - } else if (!absl::ascii_isxdigit(p[1])) { + } else if (!absl::ascii_isxdigit(static_cast<unsigned char>(p[1]))) { if (error) *error = "\\x cannot be followed by a non-hex digit"; return false; } unsigned int ch = 0; const char* hex_start = p; - while (p < last_byte && absl::ascii_isxdigit(p[1])) + while (p < last_byte && + absl::ascii_isxdigit(static_cast<unsigned char>(p[1]))) // Arbitrarily many hex digits ch = (ch << 4) + hex_digit_to_int(*++p); if (ch > 0xFF) { if (error) { *error = "Value of \\" + - std::string(hex_start, p + 1 - hex_start) + + std::string(hex_start, + static_cast<size_t>(p + 1 - hex_start)) + " exceeds 0xff"; } return false; } if ((ch == 0) && leave_nulls_escaped) { // Copy the escape sequence for the null character - const ptrdiff_t hex_size = p + 1 - hex_start; + const size_t hex_size = static_cast<size_t>(p + 1 - hex_start); *d++ = '\\'; memmove(d, hex_start, hex_size); d += hex_size; break; } - *d++ = ch; + *d++ = static_cast<char>(ch); break; } case 'u': { @@ -184,18 +188,20 @@ bool CUnescapeInternal(absl::string_view source, bool leave_nulls_escaped, if (p + 4 >= end) { if (error) { *error = "\\u must be followed by 4 hex digits: \\" + - std::string(hex_start, p + 1 - hex_start); + std::string(hex_start, + static_cast<size_t>(p + 1 - hex_start)); } return false; } for (int i = 0; i < 4; ++i) { // Look one char ahead. - if (absl::ascii_isxdigit(p[1])) { + if (absl::ascii_isxdigit(static_cast<unsigned char>(p[1]))) { rune = (rune << 4) + hex_digit_to_int(*++p); // Advance p. } else { if (error) { *error = "\\u must be followed by 4 hex digits: \\" + - std::string(hex_start, p + 1 - hex_start); + std::string(hex_start, + static_cast<size_t>(p + 1 - hex_start)); } return false; } @@ -220,20 +226,22 @@ bool CUnescapeInternal(absl::string_view source, bool leave_nulls_escaped, if (p + 8 >= end) { if (error) { *error = "\\U must be followed by 8 hex digits: \\" + - std::string(hex_start, p + 1 - hex_start); + std::string(hex_start, + static_cast<size_t>(p + 1 - hex_start)); } return false; } for (int i = 0; i < 8; ++i) { // Look one char ahead. - if (absl::ascii_isxdigit(p[1])) { + if (absl::ascii_isxdigit(static_cast<unsigned char>(p[1]))) { // Don't change rune until we're sure this // is within the Unicode limit, but do advance p. uint32_t newrune = (rune << 4) + hex_digit_to_int(*++p); if (newrune > 0x10FFFF) { if (error) { *error = "Value of \\" + - std::string(hex_start, p + 1 - hex_start) + + std::string(hex_start, + static_cast<size_t>(p + 1 - hex_start)) + " exceeds Unicode limit (0x10FFFF)"; } return false; @@ -243,7 +251,8 @@ bool CUnescapeInternal(absl::string_view source, bool leave_nulls_escaped, } else { if (error) { *error = "\\U must be followed by 8 hex digits: \\" + - std::string(hex_start, p + 1 - hex_start); + std::string(hex_start, + static_cast<size_t>(p + 1 - hex_start)); } return false; } @@ -291,7 +300,7 @@ bool CUnescapeInternal(absl::string_view source, bool leave_nulls_escaped, error)) { return false; } - dest->erase(dest_size); + dest->erase(static_cast<size_t>(dest_size)); return true; } @@ -311,7 +320,7 @@ std::string CEscapeInternal(absl::string_view src, bool use_hex, std::string dest; bool last_hex_escape = false; // true if last output char was \xNN. - for (unsigned char c : src) { + for (char c : src) { bool is_hex_escape = false; switch (c) { case '\n': dest.append("\\" "n"); break; @@ -320,28 +329,30 @@ std::string CEscapeInternal(absl::string_view src, bool use_hex, case '\"': dest.append("\\" "\""); break; case '\'': dest.append("\\" "'"); break; case '\\': dest.append("\\" "\\"); break; - default: + default: { // Note that if we emit \xNN and the src character after that is a hex // digit then that digit must be escaped too to prevent it being // interpreted as part of the character code by C. - if ((!utf8_safe || c < 0x80) && - (!absl::ascii_isprint(c) || - (last_hex_escape && absl::ascii_isxdigit(c)))) { + const unsigned char uc = static_cast<unsigned char>(c); + if ((!utf8_safe || uc < 0x80) && + (!absl::ascii_isprint(uc) || + (last_hex_escape && absl::ascii_isxdigit(uc)))) { if (use_hex) { dest.append("\\" "x"); - dest.push_back(numbers_internal::kHexChar[c / 16]); - dest.push_back(numbers_internal::kHexChar[c % 16]); + dest.push_back(numbers_internal::kHexChar[uc / 16]); + dest.push_back(numbers_internal::kHexChar[uc % 16]); is_hex_escape = true; } else { dest.append("\\"); - dest.push_back(numbers_internal::kHexChar[c / 64]); - dest.push_back(numbers_internal::kHexChar[(c % 64) / 8]); - dest.push_back(numbers_internal::kHexChar[c % 8]); + dest.push_back(numbers_internal::kHexChar[uc / 64]); + dest.push_back(numbers_internal::kHexChar[(uc % 64) / 8]); + dest.push_back(numbers_internal::kHexChar[uc % 8]); } } else { dest.push_back(c); break; } + } } last_hex_escape = is_hex_escape; } @@ -350,7 +361,7 @@ std::string CEscapeInternal(absl::string_view src, bool use_hex, } /* clang-format off */ -constexpr char c_escaped_len[256] = { +constexpr unsigned char c_escaped_len[256] = { 4, 4, 4, 4, 4, 4, 4, 4, 4, 2, 2, 4, 4, 2, 4, 4, // \t, \n, \r 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 1, 1, 2, 1, 1, 1, 1, 2, 1, 1, 1, 1, 1, 1, 1, 1, // ", ' @@ -375,7 +386,8 @@ constexpr char c_escaped_len[256] = { // that UTF-8 bytes are not handled specially. inline size_t CEscapedLength(absl::string_view src) { size_t escaped_len = 0; - for (unsigned char c : src) escaped_len += c_escaped_len[c]; + for (char c : src) + escaped_len += c_escaped_len[static_cast<unsigned char>(c)]; return escaped_len; } @@ -391,8 +403,8 @@ void CEscapeAndAppendInternal(absl::string_view src, std::string* dest) { cur_dest_len + escaped_len); char* append_ptr = &(*dest)[cur_dest_len]; - for (unsigned char c : src) { - int char_len = c_escaped_len[c]; + for (char c : src) { + size_t char_len = c_escaped_len[static_cast<unsigned char>(c)]; if (char_len == 1) { *append_ptr++ = c; } else if (char_len == 2) { @@ -424,9 +436,9 @@ void CEscapeAndAppendInternal(absl::string_view src, std::string* dest) { } } else { *append_ptr++ = '\\'; - *append_ptr++ = '0' + c / 64; - *append_ptr++ = '0' + (c % 64) / 8; - *append_ptr++ = '0' + c % 8; + *append_ptr++ = '0' + static_cast<unsigned char>(c) / 64; + *append_ptr++ = '0' + (static_cast<unsigned char>(c) % 64) / 8; + *append_ptr++ = '0' + static_cast<unsigned char>(c) % 8; } } } @@ -440,7 +452,7 @@ bool Base64UnescapeInternal(const char* src_param, size_t szsrc, char* dest, size_t destidx = 0; int decode = 0; int state = 0; - unsigned int ch = 0; + unsigned char ch = 0; unsigned int temp = 0; // If "char" is signed by default, using *src as an array index results in @@ -500,13 +512,13 @@ bool Base64UnescapeInternal(const char* src_param, size_t szsrc, char* dest, // how to handle those cases. GET_INPUT(first, 4); - temp = decode; + temp = static_cast<unsigned char>(decode); GET_INPUT(second, 3); - temp = (temp << 6) | decode; + temp = (temp << 6) | static_cast<unsigned char>(decode); GET_INPUT(third, 2); - temp = (temp << 6) | decode; + temp = (temp << 6) | static_cast<unsigned char>(decode); GET_INPUT(fourth, 1); - temp = (temp << 6) | decode; + temp = (temp << 6) | static_cast<unsigned char>(decode); } else { // We really did have four good data bytes, so advance four // characters in the string. @@ -518,11 +530,11 @@ bool Base64UnescapeInternal(const char* src_param, size_t szsrc, char* dest, // temp has 24 bits of input, so write that out as three bytes. if (destidx + 3 > szdest) return false; - dest[destidx + 2] = temp; + dest[destidx + 2] = static_cast<char>(temp); temp >>= 8; - dest[destidx + 1] = temp; + dest[destidx + 1] = static_cast<char>(temp); temp >>= 8; - dest[destidx] = temp; + dest[destidx] = static_cast<char>(temp); destidx += 3; } } else { @@ -583,18 +595,18 @@ bool Base64UnescapeInternal(const char* src_param, size_t szsrc, char* dest, } // Each input character gives us six bits of output. - temp = (temp << 6) | decode; + temp = (temp << 6) | static_cast<unsigned char>(decode); ++state; if (state == 4) { // If we've accumulated 24 bits of output, write that out as // three bytes. if (dest) { if (destidx + 3 > szdest) return false; - dest[destidx + 2] = temp; + dest[destidx + 2] = static_cast<char>(temp); temp >>= 8; - dest[destidx + 1] = temp; + dest[destidx + 1] = static_cast<char>(temp); temp >>= 8; - dest[destidx] = temp; + dest[destidx] = static_cast<char>(temp); } destidx += 3; state = 0; @@ -619,7 +631,7 @@ bool Base64UnescapeInternal(const char* src_param, size_t szsrc, char* dest, if (dest) { if (destidx + 1 > szdest) return false; temp >>= 4; - dest[destidx] = temp; + dest[destidx] = static_cast<char>(temp); } ++destidx; expected_equals = 2; @@ -630,9 +642,9 @@ bool Base64UnescapeInternal(const char* src_param, size_t szsrc, char* dest, if (dest) { if (destidx + 2 > szdest) return false; temp >>= 2; - dest[destidx + 1] = temp; + dest[destidx + 1] = static_cast<char>(temp); temp >>= 8; - dest[destidx] = temp; + dest[destidx] = static_cast<char>(temp); } destidx += 2; expected_equals = 1; @@ -772,8 +784,7 @@ template <typename String> bool Base64UnescapeInternal(const char* src, size_t slen, String* dest, const signed char* unbase64) { // Determine the size of the output string. Base64 encodes every 3 bytes into - // 4 characters. any leftover chars are added directly for good measure. - // This is documented in the base64 RFC: http://tools.ietf.org/html/rfc3548 + // 4 characters. Any leftover chars are added directly for good measure. const size_t dest_len = 3 * (slen / 4) + (slen % 4); strings_internal::STLStringResizeUninitialized(dest, dest_len); @@ -821,9 +832,9 @@ constexpr char kHexValueLenient[256] = { // or a string. This works because we use the [] operator to access // individual characters at a time. template <typename T> -void HexStringToBytesInternal(const char* from, T to, ptrdiff_t num) { - for (int i = 0; i < num; i++) { - to[i] = (kHexValueLenient[from[i * 2] & 0xFF] << 4) + +void HexStringToBytesInternal(const char* from, T to, size_t num) { + for (size_t i = 0; i < num; i++) { + to[i] = static_cast<char>(kHexValueLenient[from[i * 2] & 0xFF] << 4) + (kHexValueLenient[from[i * 2 + 1] & 0xFF]); } } @@ -831,7 +842,7 @@ void HexStringToBytesInternal(const char* from, T to, ptrdiff_t num) { // This is a templated function so that T can be either a char* or a // std::string. template <typename T> -void BytesToHexStringInternal(const unsigned char* src, T dest, ptrdiff_t num) { +void BytesToHexStringInternal(const unsigned char* src, T dest, size_t num) { auto dest_ptr = &dest[0]; for (auto src_ptr = src; src_ptr != (src + num); ++src_ptr, dest_ptr += 2) { const char* hex_p = &numbers_internal::kHexTable[*src_ptr * 2]; @@ -876,8 +887,8 @@ std::string Utf8SafeCHexEscape(absl::string_view src) { // WebSafeBase64Escape() - Google's variation of base64 encoder // // Check out -// http://tools.ietf.org/html/rfc2045 for formal description, but what we -// care about is that... +// 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 diff --git a/absl/strings/escaping.h b/absl/strings/escaping.h index f5ca26c5..7c082fef 100644 --- a/absl/strings/escaping.h +++ b/absl/strings/escaping.h @@ -117,35 +117,40 @@ std::string Utf8SafeCEscape(absl::string_view src); // conversion. std::string Utf8SafeCHexEscape(absl::string_view src); -// Base64Unescape() -// -// Converts a `src` string encoded in Base64 to its binary equivalent, writing -// it to a `dest` buffer, returning `true` on success. If `src` contains invalid -// characters, `dest` is cleared and returns `false`. -bool Base64Unescape(absl::string_view src, std::string* dest); - -// WebSafeBase64Unescape() -// -// Converts a `src` string encoded in Base64 to its binary equivalent, writing -// it to a `dest` buffer, but using '-' instead of '+', and '_' instead of '/'. -// If `src` contains invalid characters, `dest` is cleared and returns `false`. -bool WebSafeBase64Unescape(absl::string_view src, std::string* dest); - // Base64Escape() // -// Encodes a `src` string into a base64-encoded string, with padding characters. -// This function conforms with RFC 4648 section 4 (base64). +// 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(). void Base64Escape(absl::string_view src, std::string* dest); std::string Base64Escape(absl::string_view src); // WebSafeBase64Escape() // -// Encodes a `src` string into a base64-like string, using '-' instead of '+' -// and '_' instead of '/', and without padding. This function conforms with RFC -// 4648 section 5 (base64url). +// Encodes a `src` string into a base64 string, like Base64Escape() does, but +// outputs '-' instead of '+' and '_' instead of '/', and does not pad 'dest'. +// This function conforms with RFC 4648 section 5 (base64url). void WebSafeBase64Escape(absl::string_view src, std::string* dest); std::string WebSafeBase64Escape(absl::string_view src); +// Base64Unescape() +// +// Converts a `src` string encoded in Base64 (RFC 4648 section 4) to its binary +// equivalent, writing it to a `dest` buffer, returning `true` on success. If +// `src` contains invalid characters, `dest` is cleared and returns `false`. +// If padding is included (note that `Base64Escape()` does produce it), it must +// be correct. In the padding, '=' and '.' are treated identically. +bool Base64Unescape(absl::string_view src, std::string* dest); + +// WebSafeBase64Unescape() +// +// Converts a `src` string encoded in "web safe" Base64 (RFC 4648 section 5) to +// its binary equivalent, writing it to a `dest` buffer. If `src` contains +// invalid characters, `dest` is cleared and returns `false`. If padding is +// included (note that `WebSafeBase64Escape()` does not produce it), it must be +// correct. In the padding, '=' and '.' are treated identically. +bool WebSafeBase64Unescape(absl::string_view src, std::string* dest); + // HexStringToBytes() // // Converts an ASCII hex string into bytes, returning binary data of length diff --git a/absl/strings/escaping_test.cc b/absl/strings/escaping_test.cc index 45671a0e..44ffcba7 100644 --- a/absl/strings/escaping_test.cc +++ b/absl/strings/escaping_test.cc @@ -617,6 +617,48 @@ TEST(Base64, EscapeAndUnescape) { TestEscapeAndUnescape<std::string>(); } +TEST(Base64, Padding) { + // Padding is optional. + // '.' is an acceptable padding character, just like '='. + std::initializer_list<absl::string_view> good_padding = { + "YQ", + "YQ==", + "YQ=.", + "YQ.=", + "YQ..", + }; + for (absl::string_view b64 : good_padding) { + std::string decoded; + EXPECT_TRUE(absl::Base64Unescape(b64, &decoded)); + EXPECT_EQ(decoded, "a"); + std::string websafe_decoded; + EXPECT_TRUE(absl::WebSafeBase64Unescape(b64, &websafe_decoded)); + EXPECT_EQ(websafe_decoded, "a"); + } + std::initializer_list<absl::string_view> bad_padding = { + "YQ=", + "YQ.", + "YQ===", + "YQ==.", + "YQ=.=", + "YQ=..", + "YQ.==", + "YQ.=.", + "YQ..=", + "YQ...", + "YQ====", + "YQ....", + "YQ=====", + "YQ.....", + }; + for (absl::string_view b64 : bad_padding) { + std::string decoded; + EXPECT_FALSE(absl::Base64Unescape(b64, &decoded)); + std::string websafe_decoded; + EXPECT_FALSE(absl::WebSafeBase64Unescape(b64, &websafe_decoded)); + } +} + TEST(Base64, DISABLED_HugeData) { const size_t kSize = size_t(3) * 1000 * 1000 * 1000; static_assert(kSize % 3 == 0, "kSize must be divisible by 3"); diff --git a/absl/strings/internal/char_map.h b/absl/strings/internal/char_map.h index 61484de0..70a90343 100644 --- a/absl/strings/internal/char_map.h +++ b/absl/strings/internal/char_map.h @@ -73,10 +73,10 @@ class Charmap { } // Containing all the chars in the C-string 's'. - // Note that this is expensively recursive because of the C++11 constexpr - // formulation. Use only in constexpr initializers. static constexpr Charmap FromString(const char* s) { - return *s == 0 ? Charmap() : (Char(*s) | FromString(s + 1)); + Charmap ret; + while (*s) ret = ret | Char(*s++); + return ret; } // Containing all the chars in the closed interval [lo,hi]. @@ -103,10 +103,9 @@ class Charmap { constexpr Charmap(uint64_t b0, uint64_t b1, uint64_t b2, uint64_t b3) : m_{b0, b1, b2, b3} {} - static constexpr uint64_t RangeForWord(unsigned char lo, unsigned char hi, - uint64_t word) { - return OpenRangeFromZeroForWord(hi + 1, word) & - ~OpenRangeFromZeroForWord(lo, word); + 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). @@ -119,13 +118,16 @@ class Charmap { : (~static_cast<uint64_t>(0) >> (64 - upper % 64)); } - static constexpr uint64_t CharMaskForWord(unsigned char x, uint64_t word) { - return (x / 64 == word) ? (static_cast<uint64_t>(1) << (x % 64)) : 0; + 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; } - private: - void SetChar(unsigned char c) { - m_[c / 64] |= static_cast<uint64_t>(1) << (c % 64); + 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]; diff --git a/absl/strings/internal/charconv_bigint.cc b/absl/strings/internal/charconv_bigint.cc index ebf8c079..282b639e 100644 --- a/absl/strings/internal/charconv_bigint.cc +++ b/absl/strings/internal/charconv_bigint.cc @@ -242,7 +242,7 @@ int BigUnsigned<max_words>::ReadDigits(const char* begin, const char* end, // decimal exponent to compensate. --exponent_adjust; } - int digit = (*begin - '0'); + char digit = (*begin - '0'); --significant_digits; if (significant_digits == 0 && std::next(begin) != end && (digit == 0 || digit == 5)) { @@ -255,7 +255,7 @@ int BigUnsigned<max_words>::ReadDigits(const char* begin, const char* end, // 500000...000000000001 to correctly round up, rather than to nearest. ++digit; } - queued = 10 * queued + digit; + queued = 10 * queued + static_cast<uint32_t>(digit); ++digits_queued; if (digits_queued == kMaxSmallPowerOfTen) { MultiplyBy(kTenToNth[kMaxSmallPowerOfTen]); @@ -341,8 +341,8 @@ std::string BigUnsigned<max_words>::ToString() const { std::string result; // Build result in reverse order while (copy.size() > 0) { - int next_digit = copy.DivMod<10>(); - result.push_back('0' + next_digit); + uint32_t next_digit = copy.DivMod<10>(); + result.push_back('0' + static_cast<char>(next_digit)); } if (result.empty()) { result.push_back('0'); diff --git a/absl/strings/internal/charconv_parse.cc b/absl/strings/internal/charconv_parse.cc index d29acaf4..98823def 100644 --- a/absl/strings/internal/charconv_parse.cc +++ b/absl/strings/internal/charconv_parse.cc @@ -190,11 +190,11 @@ bool IsDigit<16>(char ch) { template <> unsigned ToDigit<10>(char ch) { - return ch - '0'; + return static_cast<unsigned>(ch - '0'); } template <> unsigned ToDigit<16>(char ch) { - return kAsciiToInt[static_cast<unsigned char>(ch)]; + return static_cast<unsigned>(kAsciiToInt[static_cast<unsigned char>(ch)]); } template <> diff --git a/absl/strings/internal/cord_internal.h b/absl/strings/internal/cord_internal.h index b50fb79a..e6f0d544 100644 --- a/absl/strings/internal/cord_internal.h +++ b/absl/strings/internal/cord_internal.h @@ -27,9 +27,20 @@ #include "absl/base/internal/invoke.h" #include "absl/base/optimization.h" #include "absl/container/internal/compressed_tuple.h" +#include "absl/container/internal/container_memory.h" #include "absl/meta/type_traits.h" #include "absl/strings/string_view.h" +// We can only add poisoning if we can detect consteval executions. +#if defined(ABSL_HAVE_CONSTANT_EVALUATED) && \ + (defined(ABSL_HAVE_ADDRESS_SANITIZER) || \ + defined(ABSL_HAVE_MEMORY_SANITIZER)) +#define ABSL_INTERNAL_CORD_HAVE_SANITIZER 1 +#endif + +#define ABSL_CORD_INTERNAL_NO_SANITIZE \ + ABSL_ATTRIBUTE_NO_SANITIZE_ADDRESS ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY + namespace absl { ABSL_NAMESPACE_BEGIN namespace cord_internal { @@ -91,6 +102,46 @@ enum Constants { // Emits a fatal error "Unexpected node type: xyz" and aborts the program. ABSL_ATTRIBUTE_NORETURN void LogFatalNodeType(CordRep* rep); +// Fast implementation of memmove for up to 15 bytes. This implementation is +// safe for overlapping regions. If nullify_tail is true, the destination is +// padded with '\0' up to 15 bytes. +template <bool nullify_tail = false> +inline void SmallMemmove(char* dst, const char* src, size_t n) { + if (n >= 8) { + assert(n <= 15); + uint64_t buf1; + uint64_t buf2; + memcpy(&buf1, src, 8); + memcpy(&buf2, src + n - 8, 8); + if (nullify_tail) { + memset(dst + 7, 0, 8); + } + memcpy(dst, &buf1, 8); + memcpy(dst + n - 8, &buf2, 8); + } else if (n >= 4) { + uint32_t buf1; + uint32_t buf2; + memcpy(&buf1, src, 4); + memcpy(&buf2, src + n - 4, 4); + if (nullify_tail) { + memset(dst + 4, 0, 4); + memset(dst + 7, 0, 8); + } + memcpy(dst, &buf1, 4); + memcpy(dst + n - 4, &buf2, 4); + } else { + if (n != 0) { + dst[0] = src[0]; + dst[n / 2] = src[n / 2]; + dst[n - 1] = src[n - 1]; + } + if (nullify_tail) { + memset(dst + 7, 0, 8); + memset(dst + n, 0, 8); + } + } +} + // Compact class for tracking the reference count and state flags for CordRep // instances. Data is stored in an atomic int32_t for compactness and speed. class RefcountAndFlags { @@ -129,8 +180,9 @@ class RefcountAndFlags { } // Returns the current reference count using acquire semantics. - inline int32_t Get() const { - return count_.load(std::memory_order_acquire) >> kNumFlags; + inline size_t Get() const { + return static_cast<size_t>(count_.load(std::memory_order_acquire) >> + kNumFlags); } // Returns whether the atomic integer is 1. @@ -224,7 +276,11 @@ struct CordRep { : length(l), refcount(immortal), tag(EXTERNAL), storage{} {} // The following three fields have to be less than 32 bytes since - // that is the smallest supported flat node size. + // that is the smallest supported flat node size. Some code optimizations rely + // on the specific layout of these fields. Notably: the non-trivial field + // `refcount` being preceded by `length`, and being tailed by POD data + // members only. + // # LINT.IfChange size_t length; RefcountAndFlags refcount; // If tag < FLAT, it represents CordRepKind and indicates the type of node. @@ -240,6 +296,7 @@ struct CordRep { // allocate room for these in the derived class, as not all compilers reuse // padding space from the base class (clang and gcc do, MSVC does not, etc) uint8_t storage[3]; + // # 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; } @@ -422,25 +479,25 @@ constexpr char GetOrNull(absl::string_view data, size_t pos) { return pos < data.size() ? data[pos] : '\0'; } -// We store cordz_info as 64 bit pointer value in big endian format. This -// guarantees that the least significant byte of cordz_info matches the last -// byte of the inline data representation in as_chars_, which holds the inlined +// We store cordz_info as 64 bit pointer value in little endian format. This +// guarantees that the least significant byte of cordz_info matches the first +// byte of the inline data representation in `data`, which holds the inlined // size or the 'is_tree' bit. using cordz_info_t = int64_t; // Assert that the `cordz_info` pointer value perfectly overlaps the last half -// of `as_chars_` and can hold a pointer value. +// of `data` and can hold a pointer value. static_assert(sizeof(cordz_info_t) * 2 == kMaxInline + 1, ""); static_assert(sizeof(cordz_info_t) >= sizeof(intptr_t), ""); -// BigEndianByte() creates a big endian representation of 'value', i.e.: a big -// endian value where the last byte in the host's representation holds 'value`, -// with all other bytes being 0. -static constexpr cordz_info_t BigEndianByte(unsigned char value) { +// LittleEndianByte() creates a little endian representation of 'value', i.e.: +// a little endian value where the first byte in the host's representation +// holds 'value`, with all other bytes being 0. +static constexpr cordz_info_t LittleEndianByte(unsigned char value) { #if defined(ABSL_IS_BIG_ENDIAN) - return value; -#else return static_cast<cordz_info_t>(value) << ((sizeof(cordz_info_t) - 1) * 8); +#else + return value; #endif } @@ -449,38 +506,80 @@ class InlineData { // DefaultInitType forces the use of the default initialization constructor. enum DefaultInitType { kDefaultInit }; - // kNullCordzInfo holds the big endian representation of intptr_t(1) + // kNullCordzInfo holds the little endian representation of intptr_t(1) // This is the 'null' / initial value of 'cordz_info'. The null value // is specifically big endian 1 as with 64-bit pointers, the last // byte of cordz_info overlaps with the last byte holding the tag. - static constexpr cordz_info_t kNullCordzInfo = BigEndianByte(1); - - constexpr InlineData() : as_chars_{0} {} - explicit InlineData(DefaultInitType) {} - explicit constexpr InlineData(CordRep* rep) : as_tree_(rep) {} - explicit constexpr InlineData(absl::string_view chars) - : as_chars_{ - GetOrNull(chars, 0), GetOrNull(chars, 1), - GetOrNull(chars, 2), GetOrNull(chars, 3), - GetOrNull(chars, 4), GetOrNull(chars, 5), - GetOrNull(chars, 6), GetOrNull(chars, 7), - GetOrNull(chars, 8), GetOrNull(chars, 9), - GetOrNull(chars, 10), GetOrNull(chars, 11), - GetOrNull(chars, 12), GetOrNull(chars, 13), - GetOrNull(chars, 14), static_cast<char>((chars.size() << 1))} {} + static constexpr cordz_info_t kNullCordzInfo = LittleEndianByte(1); + + // kTagOffset contains the offset of the control byte / tag. This constant is + // intended mostly for debugging purposes: do not remove this constant as it + // is actively inspected and used by gdb pretty printing code. + static constexpr size_t kTagOffset = 0; + + // Implement `~InlineData()` conditionally: we only need this destructor to + // unpoison poisoned instances under *SAN, and it will only compile correctly + // if the current compiler supports `absl::is_constant_evaluated()`. +#ifdef ABSL_INTERNAL_CORD_HAVE_SANITIZER + ~InlineData() noexcept { unpoison(); } +#endif + + constexpr InlineData() noexcept { poison_this(); } + + explicit InlineData(DefaultInitType) noexcept : rep_(kDefaultInit) { + poison_this(); + } + + explicit InlineData(CordRep* rep) noexcept : rep_(rep) { + ABSL_ASSERT(rep != nullptr); + } + + // Explicit constexpr constructor to create a constexpr InlineData + // value. Creates an inlined SSO value if `rep` is null, otherwise + // creates a tree instance value. + constexpr InlineData(absl::string_view sv, CordRep* rep) noexcept + : rep_(rep ? Rep(rep) : Rep(sv)) { + poison(); + } + + constexpr InlineData(const InlineData& rhs) noexcept; + InlineData& operator=(const InlineData& rhs) noexcept; + + friend bool operator==(const InlineData& lhs, const InlineData& rhs) { +#ifdef ABSL_INTERNAL_CORD_HAVE_SANITIZER + const Rep l = lhs.rep_.SanitizerSafeCopy(); + const Rep r = rhs.rep_.SanitizerSafeCopy(); + return memcmp(&l, &r, sizeof(l)) == 0; +#else + return memcmp(&lhs, &rhs, sizeof(lhs)) == 0; +#endif + } + friend bool operator!=(const InlineData& lhs, const InlineData& rhs) { + return !operator==(lhs, rhs); + } + + // Poisons the unused inlined SSO data if the current instance + // is inlined, else un-poisons the entire instance. + constexpr void poison(); + + // Un-poisons this instance. + constexpr void unpoison(); + + // Poisons the current instance. This is used on default initialization. + constexpr void poison_this(); // Returns true if the current instance is empty. // The 'empty value' is an inlined data value of zero length. - bool is_empty() const { return tag() == 0; } + bool is_empty() const { return rep_.tag() == 0; } // Returns true if the current instance holds a tree value. - bool is_tree() const { return (tag() & 1) != 0; } + bool is_tree() const { return (rep_.tag() & 1) != 0; } // Returns true if the current instance holds a cordz_info value. // Requires the current instance to hold a tree value. bool is_profiled() const { assert(is_tree()); - return as_tree_.cordz_info != kNullCordzInfo; + return rep_.cordz_info() != kNullCordzInfo; } // Returns true if either of the provided instances hold a cordz_info value. @@ -489,7 +588,7 @@ class InlineData { static bool is_either_profiled(const InlineData& data1, const InlineData& data2) { assert(data1.is_tree() && data2.is_tree()); - return (data1.as_tree_.cordz_info | data2.as_tree_.cordz_info) != + return (data1.rep_.cordz_info() | data2.rep_.cordz_info()) != kNullCordzInfo; } @@ -498,8 +597,8 @@ class InlineData { // Requires the current instance to hold a tree value. CordzInfo* cordz_info() const { assert(is_tree()); - intptr_t info = static_cast<intptr_t>( - absl::big_endian::ToHost64(static_cast<uint64_t>(as_tree_.cordz_info))); + intptr_t info = static_cast<intptr_t>(absl::little_endian::ToHost64( + static_cast<uint64_t>(rep_.cordz_info()))); assert(info & 1); return reinterpret_cast<CordzInfo*>(info - 1); } @@ -510,21 +609,21 @@ class InlineData { void set_cordz_info(CordzInfo* cordz_info) { assert(is_tree()); uintptr_t info = reinterpret_cast<uintptr_t>(cordz_info) | 1; - as_tree_.cordz_info = - static_cast<cordz_info_t>(absl::big_endian::FromHost64(info)); + rep_.set_cordz_info( + static_cast<cordz_info_t>(absl::little_endian::FromHost64(info))); } // Resets the current cordz_info to null / empty. void clear_cordz_info() { assert(is_tree()); - as_tree_.cordz_info = kNullCordzInfo; + rep_.set_cordz_info(kNullCordzInfo); } // Returns a read only pointer to the character data inside this instance. // Requires the current instance to hold inline data. const char* as_chars() const { assert(!is_tree()); - return as_chars_; + return rep_.as_chars(); } // Returns a mutable pointer to the character data inside this instance. @@ -542,20 +641,33 @@ class InlineData { // // It's an error to read from the returned pointer without a preceding write // if the current instance does not hold inline data, i.e.: is_tree() == true. - char* as_chars() { return as_chars_; } + char* as_chars() { return rep_.as_chars(); } // Returns the tree value of this value. // Requires the current instance to hold a tree value. CordRep* as_tree() const { assert(is_tree()); - return as_tree_.rep; + return rep_.tree(); + } + + void set_inline_data(const char* data, size_t n) { + ABSL_ASSERT(n <= kMaxInline); + unpoison(); + rep_.set_tag(static_cast<int8_t>(n << 1)); + SmallMemmove<true>(rep_.as_chars(), data, n); + poison(); + } + + void copy_max_inline_to(char* dst) const { + assert(!is_tree()); + memcpy(dst, rep_.SanitizerSafeCopy().as_chars(), kMaxInline); } // Initialize this instance to holding the tree value `rep`, // initializing the cordz_info to null, i.e.: 'not profiled'. void make_tree(CordRep* rep) { - as_tree_.rep = rep; - as_tree_.cordz_info = kNullCordzInfo; + unpoison(); + rep_.make_tree(rep); } // Set the tree value of this instance to 'rep`. @@ -563,54 +675,202 @@ class InlineData { // Does not affect the value of cordz_info. void set_tree(CordRep* rep) { assert(is_tree()); - as_tree_.rep = rep; + rep_.set_tree(rep); } // Returns the size of the inlined character data inside this instance. // Requires the current instance to hold inline data. - size_t inline_size() const { - assert(!is_tree()); - return tag() >> 1; - } + size_t inline_size() const { return rep_.inline_size(); } // Sets the size of the inlined character data inside this instance. // Requires `size` to be <= kMaxInline. // See the documentation on 'as_chars()' for more information and examples. void set_inline_size(size_t size) { - ABSL_ASSERT(size <= kMaxInline); - tag() = static_cast<char>(size << 1); + unpoison(); + rep_.set_inline_size(size); + poison(); + } + + // Compares 'this' inlined data with rhs. The comparison is a straightforward + // lexicographic comparison. `Compare()` returns values as follows: + // + // -1 'this' InlineData instance is smaller + // 0 the InlineData instances are equal + // 1 'this' InlineData instance larger + int Compare(const InlineData& rhs) const { + return Compare(rep_.SanitizerSafeCopy(), rhs.rep_.SanitizerSafeCopy()); } private: - // See cordz_info_t for forced alignment and size of `cordz_info` details. - struct AsTree { - explicit constexpr AsTree(absl::cord_internal::CordRep* tree) - : rep(tree), cordz_info(kNullCordzInfo) {} - // This union uses up extra space so that whether rep is 32 or 64 bits, - // cordz_info will still start at the eighth byte, and the last - // byte of cordz_info will still be the last byte of InlineData. - union { + struct Rep { + // See cordz_info_t for forced alignment and size of `cordz_info` details. + struct AsTree { + explicit constexpr AsTree(absl::cord_internal::CordRep* tree) + : rep(tree) {} + cordz_info_t cordz_info = kNullCordzInfo; absl::cord_internal::CordRep* rep; - cordz_info_t unused_aligner; }; - cordz_info_t cordz_info; - }; - char& tag() { return reinterpret_cast<char*>(this)[kMaxInline]; } - char tag() const { return reinterpret_cast<const char*>(this)[kMaxInline]; } + explicit Rep(DefaultInitType) {} + constexpr Rep() : data{0} {} + constexpr Rep(const Rep&) = default; + constexpr Rep& operator=(const Rep&) = default; + + explicit constexpr Rep(CordRep* rep) : as_tree(rep) {} + + explicit constexpr Rep(absl::string_view chars) + : data{static_cast<char>((chars.size() << 1)), + GetOrNull(chars, 0), + GetOrNull(chars, 1), + GetOrNull(chars, 2), + GetOrNull(chars, 3), + GetOrNull(chars, 4), + GetOrNull(chars, 5), + GetOrNull(chars, 6), + GetOrNull(chars, 7), + GetOrNull(chars, 8), + GetOrNull(chars, 9), + GetOrNull(chars, 10), + GetOrNull(chars, 11), + GetOrNull(chars, 12), + GetOrNull(chars, 13), + GetOrNull(chars, 14)} {} + + // Disable sanitizer as we must always be able to read `tag`. + ABSL_CORD_INTERNAL_NO_SANITIZE + int8_t tag() const { return reinterpret_cast<const int8_t*>(this)[0]; } + void set_tag(int8_t rhs) { reinterpret_cast<int8_t*>(this)[0] = rhs; } + + char* as_chars() { return data + 1; } + const char* as_chars() const { return data + 1; } + + bool is_tree() const { return (tag() & 1) != 0; } + + size_t inline_size() const { + ABSL_ASSERT(!is_tree()); + return static_cast<size_t>(tag()) >> 1; + } + + void set_inline_size(size_t size) { + ABSL_ASSERT(size <= kMaxInline); + set_tag(static_cast<int8_t>(size << 1)); + } + + CordRep* tree() const { return as_tree.rep; } + void set_tree(CordRep* rhs) { as_tree.rep = rhs; } + + cordz_info_t cordz_info() const { return as_tree.cordz_info; } + void set_cordz_info(cordz_info_t rhs) { as_tree.cordz_info = rhs; } + + void make_tree(CordRep* tree) { + as_tree.rep = tree; + as_tree.cordz_info = kNullCordzInfo; + } + +#ifdef ABSL_INTERNAL_CORD_HAVE_SANITIZER + constexpr Rep SanitizerSafeCopy() const { + if (!absl::is_constant_evaluated()) { + Rep res; + if (is_tree()) { + res = *this; + } else { + res.set_tag(tag()); + memcpy(res.as_chars(), as_chars(), inline_size()); + } + return res; + } else { + return *this; + } + } +#else + constexpr const Rep& SanitizerSafeCopy() const { return *this; } +#endif - // If the data has length <= kMaxInline, we store it in `as_chars_`, and - // store the size in the last char of `as_chars_` shifted left + 1. - // Else we store it in a tree and store a pointer to that tree in - // `as_tree_.rep` and store a tag in `tagged_size`. - union { - char as_chars_[kMaxInline + 1]; - AsTree as_tree_; + // If the data has length <= kMaxInline, we store it in `data`, and + // store the size in the first char of `data` shifted left + 1. + // Else we store it in a tree and store a pointer to that tree in + // `as_tree.rep` with a tagged pointer to make `tag() & 1` non zero. + union { + char data[kMaxInline + 1]; + AsTree as_tree; + }; }; + + // Private implementation of `Compare()` + static inline int Compare(const Rep& lhs, const Rep& rhs) { + uint64_t x, y; + memcpy(&x, lhs.as_chars(), sizeof(x)); + memcpy(&y, rhs.as_chars(), sizeof(y)); + if (x == y) { + memcpy(&x, lhs.as_chars() + 7, sizeof(x)); + memcpy(&y, rhs.as_chars() + 7, sizeof(y)); + if (x == y) { + if (lhs.inline_size() == rhs.inline_size()) return 0; + return lhs.inline_size() < rhs.inline_size() ? -1 : 1; + } + } + x = absl::big_endian::FromHost64(x); + y = absl::big_endian::FromHost64(y); + return x < y ? -1 : 1; + } + + Rep rep_; }; static_assert(sizeof(InlineData) == kMaxInline + 1, ""); +#ifdef ABSL_INTERNAL_CORD_HAVE_SANITIZER + +constexpr InlineData::InlineData(const InlineData& rhs) noexcept + : rep_(rhs.rep_.SanitizerSafeCopy()) { + poison(); +} + +inline InlineData& InlineData::operator=(const InlineData& rhs) noexcept { + unpoison(); + rep_ = rhs.rep_.SanitizerSafeCopy(); + poison(); + return *this; +} + +constexpr void InlineData::poison_this() { + if (!absl::is_constant_evaluated()) { + container_internal::SanitizerPoisonObject(this); + } +} + +constexpr void InlineData::unpoison() { + if (!absl::is_constant_evaluated()) { + container_internal::SanitizerUnpoisonObject(this); + } +} + +constexpr void InlineData::poison() { + if (!absl::is_constant_evaluated()) { + if (is_tree()) { + container_internal::SanitizerUnpoisonObject(this); + } else if (const size_t size = inline_size()) { + if (size < kMaxInline) { + const char* end = rep_.as_chars() + size; + container_internal::SanitizerPoisonMemoryRegion(end, kMaxInline - size); + } + } else { + container_internal::SanitizerPoisonObject(this); + } + } +} + +#else // ABSL_INTERNAL_CORD_HAVE_SANITIZER + +constexpr InlineData::InlineData(const InlineData&) noexcept = default; +inline InlineData& InlineData::operator=(const InlineData&) noexcept = default; + +constexpr void InlineData::poison_this() {} +constexpr void InlineData::unpoison() {} +constexpr void InlineData::poison() {} + +#endif // ABSL_INTERNAL_CORD_HAVE_SANITIZER + inline CordRepSubstring* CordRep::substring() { assert(IsSubstring()); return static_cast<CordRepSubstring*>(this); diff --git a/absl/strings/internal/cord_rep_btree.cc b/absl/strings/internal/cord_rep_btree.cc index cacbf3da..a86fdc0b 100644 --- a/absl/strings/internal/cord_rep_btree.cc +++ b/absl/strings/internal/cord_rep_btree.cc @@ -17,11 +17,13 @@ #include <cassert> #include <cstdint> #include <iostream> +#include <ostream> #include <string> #include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" +#include "absl/base/optimization.h" #include "absl/strings/internal/cord_data_edge.h" #include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cord_rep_consume.h" @@ -55,8 +57,10 @@ inline bool exhaustive_validation() { // Prints the entire tree structure or 'rep'. External callers should // not specify 'depth' and leave it to its default (0) value. // Rep may be a CordRepBtree tree, or a SUBSTRING / EXTERNAL / FLAT node. -void DumpAll(const CordRep* rep, bool include_contents, std::ostream& stream, - int depth = 0) { +void DumpAll(const CordRep* rep, + bool include_contents, + std::ostream& stream, + size_t depth = 0) { // Allow for full height trees + substring -> flat / external nodes. assert(depth <= CordRepBtree::kMaxDepth + 2); std::string sharing = const_cast<CordRep*>(rep)->refcount.IsOne() @@ -283,7 +287,7 @@ struct StackOperations { case CordRepBtree::kSelf: return result.tree; } - ABSL_INTERNAL_UNREACHABLE; + ABSL_UNREACHABLE(); return result.tree; } @@ -499,7 +503,7 @@ OpResult CordRepBtree::SetEdge(bool owned, CordRep* edge, size_t delta) { // open interval [begin, back) or [begin + 1, end) depending on `edge_type`. // We conveniently cover both case using a constexpr `shift` being 0 or 1 // as `end :== back + 1`. - result = {CopyRaw(), kCopied}; + result = {CopyRaw(length), kCopied}; constexpr int shift = edge_type == kFront ? 1 : 0; for (CordRep* r : Edges(begin() + shift, back() + shift)) { CordRep::Ref(r); diff --git a/absl/strings/internal/cord_rep_btree.h b/absl/strings/internal/cord_rep_btree.h index 2cbc09ec..4209e512 100644 --- a/absl/strings/internal/cord_rep_btree.h +++ b/absl/strings/internal/cord_rep_btree.h @@ -95,8 +95,9 @@ class CordRepBtree : public CordRep { // local stack variable compared to Cord's current near 400 bytes stack use. // The maximum `height` value of a node is then `kMaxDepth - 1` as node height // values start with a value of 0 for leaf nodes. - static constexpr int kMaxDepth = 12; - static constexpr int kMaxHeight = kMaxDepth - 1; + static constexpr size_t kMaxDepth = 12; + // See comments on height() for why this is an int and not a size_t. + static constexpr int kMaxHeight = static_cast<int>(kMaxDepth - 1); // `Action` defines the action for unwinding changes done at the btree's leaf // level that need to be propagated up to the parent node(s). Each operation @@ -445,9 +446,9 @@ class CordRepBtree : public CordRep { template <EdgeType edge_type> static CordRepBtree* NewLeaf(absl::string_view data, size_t extra); - // Creates a raw copy of this Btree node, copying all properties, but - // without adding any references to existing edges. - CordRepBtree* CopyRaw() const; + // Creates a raw copy of this Btree node with the specified length, copying + // all properties, but without adding any references to existing edges. + CordRepBtree* CopyRaw(size_t new_length) const; // Creates a full copy of this Btree node, adding a reference on all edges. CordRepBtree* Copy() const; @@ -665,15 +666,28 @@ inline void CordRepBtree::Unref(absl::Span<CordRep* const> edges) { } } -inline CordRepBtree* CordRepBtree::CopyRaw() const { - auto* tree = static_cast<CordRepBtree*>(::operator new(sizeof(CordRepBtree))); - memcpy(static_cast<void*>(tree), this, sizeof(CordRepBtree)); - new (&tree->refcount) RefcountAndFlags; +inline CordRepBtree* CordRepBtree::CopyRaw(size_t new_length) const { + CordRepBtree* tree = new CordRepBtree; + + // `length` and `refcount` are the first members of `CordRepBtree`. + // We initialize `length` using the given length, have `refcount` be set to + // ref = 1 through its default constructor, and copy all data beyond + // 'refcount' which starts with `tag` using a single memcpy: all contents + // except `refcount` is trivially copyable, and the compiler does not + // efficiently coalesce member-wise copy of these members. + // See https://gcc.godbolt.org/z/qY8zsca6z + // # LINT.IfChange(copy_raw) + tree->length = new_length; + uint8_t* dst = &tree->tag; + const uint8_t* src = &tag; + const ptrdiff_t offset = src - reinterpret_cast<const uint8_t*>(this); + memcpy(dst, src, sizeof(CordRepBtree) - static_cast<size_t>(offset)); return tree; + // # LINT.ThenChange() } inline CordRepBtree* CordRepBtree::Copy() const { - CordRepBtree* tree = CopyRaw(); + CordRepBtree* tree = CopyRaw(length); for (CordRep* rep : Edges()) CordRep::Ref(rep); return tree; } @@ -682,8 +696,7 @@ inline CordRepBtree* CordRepBtree::CopyToEndFrom(size_t begin, size_t new_length) const { assert(begin >= this->begin()); assert(begin <= this->end()); - CordRepBtree* tree = CopyRaw(); - tree->length = new_length; + CordRepBtree* tree = CopyRaw(new_length); tree->set_begin(begin); for (CordRep* edge : tree->Edges()) CordRep::Ref(edge); return tree; @@ -693,8 +706,7 @@ inline CordRepBtree* CordRepBtree::CopyBeginTo(size_t end, size_t new_length) const { assert(end <= capacity()); assert(end >= this->begin()); - CordRepBtree* tree = CopyRaw(); - tree->length = new_length; + CordRepBtree* tree = CopyRaw(new_length); tree->set_end(end); for (CordRep* edge : tree->Edges()) CordRep::Ref(edge); return tree; diff --git a/absl/strings/internal/cord_rep_btree_navigator.cc b/absl/strings/internal/cord_rep_btree_navigator.cc index 9b896a3d..6ed20c23 100644 --- a/absl/strings/internal/cord_rep_btree_navigator.cc +++ b/absl/strings/internal/cord_rep_btree_navigator.cc @@ -90,7 +90,7 @@ CordRepBtreeNavigator::Position CordRepBtreeNavigator::Skip(size_t n) { // edges that must be skipped. while (height > 0) { node = edge->btree(); - index_[height] = index; + index_[height] = static_cast<uint8_t>(index); node_[--height] = node; index = node->begin(); edge = node->Edge(index); @@ -101,7 +101,7 @@ CordRepBtreeNavigator::Position CordRepBtreeNavigator::Skip(size_t n) { edge = node->Edge(index); } } - index_[0] = index; + index_[0] = static_cast<uint8_t>(index); return {edge, n}; } @@ -126,7 +126,7 @@ ReadResult CordRepBtreeNavigator::Read(size_t edge_offset, size_t n) { do { length -= edge->length; while (++index == node->end()) { - index_[height] = index; + index_[height] = static_cast<uint8_t>(index); if (++height > height_) { subtree->set_end(subtree_end); if (length == 0) return {subtree, 0}; @@ -154,7 +154,7 @@ ReadResult CordRepBtreeNavigator::Read(size_t edge_offset, size_t n) { // edges that must be read, adding 'down' nodes to `subtree`. while (height > 0) { node = edge->btree(); - index_[height] = index; + index_[height] = static_cast<uint8_t>(index); node_[--height] = node; index = node->begin(); edge = node->Edge(index); @@ -178,7 +178,7 @@ ReadResult CordRepBtreeNavigator::Read(size_t edge_offset, size_t n) { subtree->edges_[subtree_end++] = Substring(edge, 0, length); } subtree->set_end(subtree_end); - index_[0] = index; + index_[0] = static_cast<uint8_t>(index); return {tree, length}; } diff --git a/absl/strings/internal/cord_rep_btree_navigator_test.cc b/absl/strings/internal/cord_rep_btree_navigator_test.cc index 4f9bd4e5..bed75508 100644 --- a/absl/strings/internal/cord_rep_btree_navigator_test.cc +++ b/absl/strings/internal/cord_rep_btree_navigator_test.cc @@ -48,7 +48,7 @@ using Position = CordRepBtreeNavigator::Position; // CordRepBtreeNavigatorTest is a test fixture which automatically creates a // tree to test navigation logic on. The parameter `count' defines the number of // data edges in the test tree. -class CordRepBtreeNavigatorTest : public testing::TestWithParam<int> { +class CordRepBtreeNavigatorTest : public testing::TestWithParam<size_t> { public: using Flats = std::vector<CordRep*>; static constexpr size_t kCharsPerFlat = 3; @@ -71,12 +71,12 @@ class CordRepBtreeNavigatorTest : public testing::TestWithParam<int> { ~CordRepBtreeNavigatorTest() override { CordRep::Unref(tree_); } - int count() const { return GetParam(); } + size_t count() const { return GetParam(); } CordRepBtree* tree() { return tree_; } const std::string& data() const { return data_; } const std::vector<CordRep*>& flats() const { return flats_; } - static std::string ToString(testing::TestParamInfo<int> param) { + static std::string ToString(testing::TestParamInfo<size_t> param) { return absl::StrCat(param.param, "_Flats"); } @@ -131,15 +131,15 @@ TEST_P(CordRepBtreeNavigatorTest, NextPrev) { EXPECT_THAT(nav.Previous(), Eq(nullptr)); EXPECT_THAT(nav.Current(), Eq(flats.front())); - for (int i = 1; i < flats.size(); ++i) { + for (size_t i = 1; i < flats.size(); ++i) { ASSERT_THAT(nav.Next(), Eq(flats[i])); EXPECT_THAT(nav.Current(), Eq(flats[i])); } EXPECT_THAT(nav.Next(), Eq(nullptr)); EXPECT_THAT(nav.Current(), Eq(flats.back())); - for (int i = static_cast<int>(flats.size()) - 2; i >= 0; --i) { - ASSERT_THAT(nav.Previous(), Eq(flats[i])); - EXPECT_THAT(nav.Current(), Eq(flats[i])); + for (size_t i = flats.size() - 1; i > 0; --i) { + ASSERT_THAT(nav.Previous(), Eq(flats[i - 1])); + EXPECT_THAT(nav.Current(), Eq(flats[i - 1])); } EXPECT_THAT(nav.Previous(), Eq(nullptr)); EXPECT_THAT(nav.Current(), Eq(flats.front())); @@ -152,13 +152,13 @@ TEST_P(CordRepBtreeNavigatorTest, PrevNext) { EXPECT_THAT(nav.Next(), Eq(nullptr)); EXPECT_THAT(nav.Current(), Eq(flats.back())); - for (int i = static_cast<int>(flats.size()) - 2; i >= 0; --i) { - ASSERT_THAT(nav.Previous(), Eq(flats[i])); - EXPECT_THAT(nav.Current(), Eq(flats[i])); + for (size_t i = flats.size() - 1; i > 0; --i) { + ASSERT_THAT(nav.Previous(), Eq(flats[i - 1])); + EXPECT_THAT(nav.Current(), Eq(flats[i - 1])); } EXPECT_THAT(nav.Previous(), Eq(nullptr)); EXPECT_THAT(nav.Current(), Eq(flats.front())); - for (int i = 1; i < flats.size(); ++i) { + for (size_t i = 1; i < flats.size(); ++i) { ASSERT_THAT(nav.Next(), Eq(flats[i])); EXPECT_THAT(nav.Current(), Eq(flats[i])); } @@ -180,21 +180,21 @@ TEST(CordRepBtreeNavigatorTest, Reset) { } TEST_P(CordRepBtreeNavigatorTest, Skip) { - int count = this->count(); + size_t count = this->count(); const Flats& flats = this->flats(); CordRepBtreeNavigator nav; nav.InitFirst(tree()); - for (int char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) { + for (size_t char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) { Position pos = nav.Skip(char_offset); EXPECT_THAT(pos.edge, Eq(nav.Current())); EXPECT_THAT(pos.edge, Eq(flats[0])); EXPECT_THAT(pos.offset, Eq(char_offset)); } - for (int index1 = 0; index1 < count; ++index1) { - for (int index2 = index1; index2 < count; ++index2) { - for (int char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) { + for (size_t index1 = 0; index1 < count; ++index1) { + for (size_t index2 = index1; index2 < count; ++index2) { + for (size_t char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) { CordRepBtreeNavigator nav; nav.InitFirst(tree()); @@ -215,20 +215,20 @@ TEST_P(CordRepBtreeNavigatorTest, Skip) { } TEST_P(CordRepBtreeNavigatorTest, Seek) { - int count = this->count(); + size_t count = this->count(); const Flats& flats = this->flats(); CordRepBtreeNavigator nav; nav.InitFirst(tree()); - for (int char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) { + for (size_t char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) { Position pos = nav.Seek(char_offset); EXPECT_THAT(pos.edge, Eq(nav.Current())); EXPECT_THAT(pos.edge, Eq(flats[0])); EXPECT_THAT(pos.offset, Eq(char_offset)); } - for (int index = 0; index < count; ++index) { - for (int char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) { + for (size_t index = 0; index < count; ++index) { + for (size_t char_offset = 0; char_offset < kCharsPerFlat; ++char_offset) { size_t offset = index * kCharsPerFlat + char_offset; Position pos1 = nav.Seek(offset); ASSERT_THAT(pos1.edge, Eq(flats[index])); @@ -249,7 +249,7 @@ TEST(CordRepBtreeNavigatorTest, InitOffset) { EXPECT_THAT(nav.btree(), Eq(tree)); EXPECT_THAT(pos.edge, Eq(tree->Edges()[1])); EXPECT_THAT(pos.edge, Eq(nav.Current())); - EXPECT_THAT(pos.offset, Eq(2)); + EXPECT_THAT(pos.offset, Eq(2u)); CordRep::Unref(tree); } diff --git a/absl/strings/internal/cord_rep_btree_reader_test.cc b/absl/strings/internal/cord_rep_btree_reader_test.cc index 9b27a81f..b4cdd8e5 100644 --- a/absl/strings/internal/cord_rep_btree_reader_test.cc +++ b/absl/strings/internal/cord_rep_btree_reader_test.cc @@ -50,9 +50,9 @@ using ReadResult = CordRepBtreeReader::ReadResult; TEST(CordRepBtreeReaderTest, Next) { constexpr size_t kChars = 3; const size_t cap = CordRepBtree::kMaxCapacity; - int counts[] = {1, 2, cap, cap * cap, cap * cap + 1, cap * cap * 2 + 17}; + size_t counts[] = {1, 2, cap, cap * cap, cap * cap + 1, cap * cap * 2 + 17}; - for (int count : counts) { + for (size_t count : counts) { std::string data = CreateRandomString(count * kChars); std::vector<CordRep*> flats = CreateFlatsFromString(data, kChars); CordRepBtree* node = CordRepBtreeFromFlats(flats); @@ -74,7 +74,7 @@ TEST(CordRepBtreeReaderTest, Next) { EXPECT_THAT(reader.remaining(), Eq(remaining)); } - EXPECT_THAT(reader.remaining(), Eq(0)); + EXPECT_THAT(reader.remaining(), Eq(0u)); // Verify trying to read beyond EOF returns empty string_view EXPECT_THAT(reader.Next(), testing::IsEmpty()); @@ -86,9 +86,9 @@ TEST(CordRepBtreeReaderTest, Next) { TEST(CordRepBtreeReaderTest, Skip) { constexpr size_t kChars = 3; const size_t cap = CordRepBtree::kMaxCapacity; - int counts[] = {1, 2, cap, cap * cap, cap * cap + 1, cap * cap * 2 + 17}; + size_t counts[] = {1, 2, cap, cap * cap, cap * cap + 1, cap * cap * 2 + 17}; - for (int count : counts) { + for (size_t count : counts) { std::string data = CreateRandomString(count * kChars); std::vector<CordRep*> flats = CreateFlatsFromString(data, kChars); CordRepBtree* node = CordRepBtreeFromFlats(flats); @@ -125,16 +125,16 @@ TEST(CordRepBtreeReaderTest, SkipBeyondLength) { CordRepBtreeReader reader; reader.Init(tree); EXPECT_THAT(reader.Skip(100), IsEmpty()); - EXPECT_THAT(reader.remaining(), Eq(0)); + EXPECT_THAT(reader.remaining(), Eq(0u)); CordRep::Unref(tree); } TEST(CordRepBtreeReaderTest, Seek) { constexpr size_t kChars = 3; const size_t cap = CordRepBtree::kMaxCapacity; - int counts[] = {1, 2, cap, cap * cap, cap * cap + 1, cap * cap * 2 + 17}; + size_t counts[] = {1, 2, cap, cap * cap, cap * cap + 1, cap * cap * 2 + 17}; - for (int count : counts) { + for (size_t count : counts) { std::string data = CreateRandomString(count * kChars); std::vector<CordRep*> flats = CreateFlatsFromString(data, kChars); CordRepBtree* node = CordRepBtreeFromFlats(flats); @@ -159,9 +159,9 @@ TEST(CordRepBtreeReaderTest, SeekBeyondLength) { CordRepBtreeReader reader; reader.Init(tree); EXPECT_THAT(reader.Seek(6), IsEmpty()); - EXPECT_THAT(reader.remaining(), Eq(0)); + EXPECT_THAT(reader.remaining(), Eq(0u)); EXPECT_THAT(reader.Seek(100), IsEmpty()); - EXPECT_THAT(reader.remaining(), Eq(0)); + EXPECT_THAT(reader.remaining(), Eq(0u)); CordRep::Unref(tree); } @@ -179,7 +179,7 @@ TEST(CordRepBtreeReaderTest, Read) { chunk = reader.Read(0, chunk.length(), tree); EXPECT_THAT(tree, Eq(nullptr)); EXPECT_THAT(chunk, Eq("abcde")); - EXPECT_THAT(reader.remaining(), Eq(10)); + EXPECT_THAT(reader.remaining(), Eq(10u)); EXPECT_THAT(reader.Next(), Eq("fghij")); // Read in full @@ -188,7 +188,7 @@ TEST(CordRepBtreeReaderTest, Read) { EXPECT_THAT(tree, Ne(nullptr)); EXPECT_THAT(CordToString(tree), Eq("abcdefghijklmno")); EXPECT_THAT(chunk, Eq("")); - EXPECT_THAT(reader.remaining(), Eq(0)); + EXPECT_THAT(reader.remaining(), Eq(0u)); CordRep::Unref(tree); // Read < chunk bytes @@ -197,7 +197,7 @@ TEST(CordRepBtreeReaderTest, Read) { ASSERT_THAT(tree, Ne(nullptr)); EXPECT_THAT(CordToString(tree), Eq("abc")); EXPECT_THAT(chunk, Eq("de")); - EXPECT_THAT(reader.remaining(), Eq(10)); + EXPECT_THAT(reader.remaining(), Eq(10u)); EXPECT_THAT(reader.Next(), Eq("fghij")); CordRep::Unref(tree); @@ -207,7 +207,7 @@ TEST(CordRepBtreeReaderTest, Read) { ASSERT_THAT(tree, Ne(nullptr)); EXPECT_THAT(CordToString(tree), Eq("cd")); EXPECT_THAT(chunk, Eq("e")); - EXPECT_THAT(reader.remaining(), Eq(10)); + EXPECT_THAT(reader.remaining(), Eq(10u)); EXPECT_THAT(reader.Next(), Eq("fghij")); CordRep::Unref(tree); @@ -217,7 +217,7 @@ TEST(CordRepBtreeReaderTest, Read) { ASSERT_THAT(tree, Ne(nullptr)); EXPECT_THAT(CordToString(tree), Eq("fgh")); EXPECT_THAT(chunk, Eq("ij")); - EXPECT_THAT(reader.remaining(), Eq(5)); + EXPECT_THAT(reader.remaining(), Eq(5u)); EXPECT_THAT(reader.Next(), Eq("klmno")); CordRep::Unref(tree); @@ -227,7 +227,7 @@ TEST(CordRepBtreeReaderTest, Read) { ASSERT_THAT(tree, Ne(nullptr)); EXPECT_THAT(CordToString(tree), Eq("cdefghijklmn")); EXPECT_THAT(chunk, Eq("o")); - EXPECT_THAT(reader.remaining(), Eq(0)); + EXPECT_THAT(reader.remaining(), Eq(0u)); CordRep::Unref(tree); // Read across chunks landing on exact edge boundary @@ -236,7 +236,7 @@ TEST(CordRepBtreeReaderTest, Read) { ASSERT_THAT(tree, Ne(nullptr)); EXPECT_THAT(CordToString(tree), Eq("cdefghij")); EXPECT_THAT(chunk, Eq("klmno")); - EXPECT_THAT(reader.remaining(), Eq(0)); + EXPECT_THAT(reader.remaining(), Eq(0u)); CordRep::Unref(tree); CordRep::Unref(node); @@ -245,9 +245,9 @@ TEST(CordRepBtreeReaderTest, Read) { TEST(CordRepBtreeReaderTest, ReadExhaustive) { constexpr size_t kChars = 3; const size_t cap = CordRepBtree::kMaxCapacity; - int counts[] = {1, 2, cap, cap * cap + 1, cap * cap * cap * 2 + 17}; + size_t counts[] = {1, 2, cap, cap * cap + 1, cap * cap * cap * 2 + 17}; - for (int count : counts) { + for (size_t count : counts) { std::string data = CreateRandomString(count * kChars); std::vector<CordRep*> flats = CreateFlatsFromString(data, kChars); CordRepBtree* node = CordRepBtreeFromFlats(flats); diff --git a/absl/strings/internal/cord_rep_btree_test.cc b/absl/strings/internal/cord_rep_btree_test.cc index 51b90db1..9d6ce484 100644 --- a/absl/strings/internal/cord_rep_btree_test.cc +++ b/absl/strings/internal/cord_rep_btree_test.cc @@ -284,13 +284,14 @@ INSTANTIATE_TEST_SUITE_P(WithParam, CordRepBtreeDualTest, TEST(CordRepBtreeTest, SizeIsMultipleOf64) { // Only enforce for fully 64-bit platforms. if (sizeof(size_t) == 8 && sizeof(void*) == 8) { - EXPECT_THAT(sizeof(CordRepBtree) % 64, Eq(0)) << "Should be multiple of 64"; + EXPECT_THAT(sizeof(CordRepBtree) % 64, Eq(0u)) + << "Should be multiple of 64"; } } TEST(CordRepBtreeTest, NewDestroyEmptyTree) { auto* tree = CordRepBtree::New(); - EXPECT_THAT(tree->size(), Eq(0)); + EXPECT_THAT(tree->size(), Eq(0u)); EXPECT_THAT(tree->height(), Eq(0)); EXPECT_THAT(tree->Edges(), ElementsAre()); CordRepBtree::Destroy(tree); @@ -298,7 +299,7 @@ TEST(CordRepBtreeTest, NewDestroyEmptyTree) { TEST(CordRepBtreeTest, NewDestroyEmptyTreeAtHeight) { auto* tree = CordRepBtree::New(3); - EXPECT_THAT(tree->size(), Eq(0)); + EXPECT_THAT(tree->size(), Eq(0u)); EXPECT_THAT(tree->height(), Eq(3)); EXPECT_THAT(tree->Edges(), ElementsAre()); CordRepBtree::Destroy(tree); @@ -356,7 +357,7 @@ TEST(CordRepBtreeTest, EdgeData) { TEST(CordRepBtreeTest, CreateUnrefLeaf) { auto* flat = MakeFlat("a"); auto* leaf = CordRepBtree::Create(flat); - EXPECT_THAT(leaf->size(), Eq(1)); + EXPECT_THAT(leaf->size(), Eq(1u)); EXPECT_THAT(leaf->height(), Eq(0)); EXPECT_THAT(leaf->Edges(), ElementsAre(flat)); CordRepBtree::Unref(leaf); @@ -365,7 +366,7 @@ TEST(CordRepBtreeTest, CreateUnrefLeaf) { TEST(CordRepBtreeTest, NewUnrefNode) { auto* leaf = CordRepBtree::Create(MakeFlat("a")); CordRepBtree* tree = CordRepBtree::New(leaf); - EXPECT_THAT(tree->size(), Eq(1)); + EXPECT_THAT(tree->size(), Eq(1u)); EXPECT_THAT(tree->height(), Eq(1)); EXPECT_THAT(tree->Edges(), ElementsAre(leaf)); CordRepBtree::Unref(tree); @@ -653,7 +654,7 @@ TEST_P(CordRepBtreeDualTest, MergeEqualHeightTrees) { CordRepBtree* tree = use_append ? CordRepBtree::Append(left, right) : CordRepBtree::Prepend(right, left); EXPECT_THAT(tree, IsNode(1)); - EXPECT_THAT(tree->Edges(), SizeIs(5)); + EXPECT_THAT(tree->Edges(), SizeIs(5u)); // `tree` contains all flats originally belonging to `left` and `right`. EXPECT_THAT(GetLeafEdges(tree), ElementsAreArray(flats)); @@ -681,7 +682,7 @@ TEST_P(CordRepBtreeDualTest, MergeLeafWithTreeNotExceedingLeafCapacity) { CordRepBtree* tree = use_append ? CordRepBtree::Append(left, right) : CordRepBtree::Prepend(right, left); EXPECT_THAT(tree, IsNode(1)); - EXPECT_THAT(tree->Edges(), SizeIs(3)); + EXPECT_THAT(tree->Edges(), SizeIs(3u)); // `tree` contains all flats originally belonging to `left` and `right`. EXPECT_THAT(GetLeafEdges(tree), ElementsAreArray(flats)); @@ -709,7 +710,7 @@ TEST_P(CordRepBtreeDualTest, MergeLeafWithTreeExceedingLeafCapacity) { CordRepBtree* tree = use_append ? CordRepBtree::Append(left, right) : CordRepBtree::Prepend(right, left); EXPECT_THAT(tree, IsNode(1)); - EXPECT_THAT(tree->Edges(), SizeIs(4)); + EXPECT_THAT(tree->Edges(), SizeIs(4u)); // `tree` contains all flats originally belonging to `left` and `right`. EXPECT_THAT(GetLeafEdges(tree), ElementsAreArray(flats)); @@ -738,7 +739,7 @@ TEST(CordRepBtreeTest, MergeFuzzTest) { auto random_leaf_count = [&]() { std::uniform_int_distribution<int> dist_height(0, 3); std::uniform_int_distribution<int> dist_leaf(0, max_cap - 1); - const size_t height = dist_height(rnd); + const int height = dist_height(rnd); return (height ? pow(max_cap, height) : 0) + dist_leaf(rnd); }; @@ -749,14 +750,16 @@ TEST(CordRepBtreeTest, MergeFuzzTest) { CordRepBtree* left = MakeTree(random_leaf_count(), coin_flip(rnd)); GetLeafEdges(left, flats); if (dice_throw(rnd) == 1) { - std::uniform_int_distribution<int> dist(0, left->height()); + std::uniform_int_distribution<size_t> dist( + 0, static_cast<size_t>(left->height())); RefEdgesAt(dist(rnd), refs, left); } CordRepBtree* right = MakeTree(random_leaf_count(), coin_flip(rnd)); GetLeafEdges(right, flats); if (dice_throw(rnd) == 1) { - std::uniform_int_distribution<int> dist(0, right->height()); + std::uniform_int_distribution<size_t> dist( + 0, static_cast<size_t>(right->height())); RefEdgesAt(dist(rnd), refs, right); } @@ -784,7 +787,7 @@ TEST_P(CordRepBtreeTest, RemoveSuffix) { CordRep::Unref(node); } - for (int n = 1; n < data.length(); ++n) { + for (size_t n = 1; n < data.length(); ++n) { AutoUnref refs; auto flats = CreateFlatsFromString(data, 512); CordRepBtree* node = refs.RefIf(shared(), CreateTree(flats)); @@ -802,10 +805,10 @@ TEST_P(CordRepBtreeTest, RemoveSuffix) { const size_t last_length = rep->length - edges.size() * 512; // All flats except the last edge must be kept or copied 'as is' - int index = 0; + size_t index = 0; for (CordRep* edge : edges) { ASSERT_THAT(edge, Eq(flats[index++])); - ASSERT_THAT(edge->length, Eq(512)); + ASSERT_THAT(edge->length, Eq(512u)); } // CordRepBtree may optimize small substrings to avoid waste, so only @@ -813,7 +816,7 @@ TEST_P(CordRepBtreeTest, RemoveSuffix) { if (last_length >= 500) { EXPECT_THAT(last_edge, Eq(flats[index++])); if (shared()) { - EXPECT_THAT(last_edge->length, Eq(512)); + EXPECT_THAT(last_edge->length, Eq(512u)); } else { EXPECT_TRUE(last_edge->refcount.IsOne()); EXPECT_THAT(last_edge->length, Eq(last_length)); @@ -837,8 +840,8 @@ TEST(CordRepBtreeTest, SubTree) { node = CordRepBtree::Append(node, CordRep::Ref(flats[i])); } - for (int offset = 0; offset < data.length(); ++offset) { - for (int length = 1; length <= data.length() - offset; ++length) { + for (size_t offset = 0; offset < data.length(); ++offset) { + for (size_t length = 1; length <= data.length() - offset; ++length) { CordRep* rep = node->SubTree(offset, length); EXPECT_THAT(CordToString(rep), Eq(data.substr(offset, length))); CordRep::Unref(rep); @@ -865,12 +868,12 @@ TEST(CordRepBtreeTest, SubTreeOnExistingSubstring) { ASSERT_THAT(result->tag, Eq(BTREE)); CordRep::Unref(leaf); leaf = result->btree(); - ASSERT_THAT(leaf->Edges(), ElementsAre(_, IsSubstring(0, 990))); + ASSERT_THAT(leaf->Edges(), ElementsAre(_, IsSubstring(0u, 990u))); EXPECT_THAT(leaf->Edges()[1]->substring()->child, Eq(flat)); // Verify substring of substring. result = leaf->SubTree(3 + 5, 970); - ASSERT_THAT(result, IsSubstring(5, 970)); + ASSERT_THAT(result, IsSubstring(5u, 970u)); EXPECT_THAT(result->substring()->child, Eq(flat)); CordRep::Unref(result); @@ -1092,7 +1095,7 @@ TEST_P(CordRepBtreeHeightTest, GetAppendBufferNotFlat) { for (int i = 1; i <= height(); ++i) { tree = CordRepBtree::New(tree); } - EXPECT_THAT(tree->GetAppendBuffer(1), SizeIs(0)); + EXPECT_THAT(tree->GetAppendBuffer(1), SizeIs(0u)); CordRepBtree::Unref(tree); } @@ -1102,7 +1105,7 @@ TEST_P(CordRepBtreeHeightTest, GetAppendBufferFlatNotPrivate) { for (int i = 1; i <= height(); ++i) { tree = CordRepBtree::New(tree); } - EXPECT_THAT(tree->GetAppendBuffer(1), SizeIs(0)); + EXPECT_THAT(tree->GetAppendBuffer(1), SizeIs(0u)); CordRepBtree::Unref(tree); CordRep::Unref(flat); } @@ -1116,7 +1119,7 @@ TEST_P(CordRepBtreeHeightTest, GetAppendBufferTreeNotPrivate) { if (i == (height() + 1) / 2) refs.Ref(tree); tree = CordRepBtree::New(tree); } - EXPECT_THAT(tree->GetAppendBuffer(1), SizeIs(0)); + EXPECT_THAT(tree->GetAppendBuffer(1), SizeIs(0u)); CordRepBtree::Unref(tree); CordRep::Unref(flat); } @@ -1128,7 +1131,7 @@ TEST_P(CordRepBtreeHeightTest, GetAppendBufferFlatNoCapacity) { for (int i = 1; i <= height(); ++i) { tree = CordRepBtree::New(tree); } - EXPECT_THAT(tree->GetAppendBuffer(1), SizeIs(0)); + EXPECT_THAT(tree->GetAppendBuffer(1), SizeIs(0u)); CordRepBtree::Unref(tree); } @@ -1139,9 +1142,9 @@ TEST_P(CordRepBtreeHeightTest, GetAppendBufferFlatWithCapacity) { tree = CordRepBtree::New(tree); } absl::Span<char> span = tree->GetAppendBuffer(2); - EXPECT_THAT(span, SizeIs(2)); + EXPECT_THAT(span, SizeIs(2u)); EXPECT_THAT(span.data(), TypedEq<void*>(flat->Data() + 3)); - EXPECT_THAT(tree->length, Eq(5)); + EXPECT_THAT(tree->length, Eq(5u)); size_t avail = flat->Capacity() - 5; span = tree->GetAppendBuffer(avail + 100); @@ -1393,11 +1396,11 @@ TEST(CordRepBtreeTest, CheckAssertValidShallowVsDeep) { } TEST_P(CordRepBtreeTest, Rebuild) { - for (size_t size : {3, 8, 100, 10000, 1000000}) { + for (size_t size : {3u, 8u, 100u, 10000u, 1000000u}) { SCOPED_TRACE(absl::StrCat("Rebuild @", size)); std::vector<CordRepFlat*> flats; - for (int i = 0; i < size; ++i) { + for (size_t i = 0; i < size; ++i) { flats.push_back(CordRepFlat::New(2)); flats.back()->Data()[0] = 'x'; flats.back()->length = 1; diff --git a/absl/strings/internal/cord_rep_crc.cc b/absl/strings/internal/cord_rep_crc.cc index ee140354..dbe54cc4 100644 --- a/absl/strings/internal/cord_rep_crc.cc +++ b/absl/strings/internal/cord_rep_crc.cc @@ -16,6 +16,7 @@ #include <cassert> #include <cstdint> +#include <utility> #include "absl/base/config.h" #include "absl/strings/internal/cord_internal.h" @@ -24,11 +25,10 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace cord_internal { -CordRepCrc* CordRepCrc::New(CordRep* child, uint32_t crc) { - assert(child != nullptr); - if (child->IsCrc()) { +CordRepCrc* CordRepCrc::New(CordRep* child, crc_internal::CrcCordState state) { + if (child != nullptr && child->IsCrc()) { if (child->refcount.IsOne()) { - child->crc()->crc = crc; + child->crc()->crc_cord_state = std::move(state); return child->crc(); } CordRep* old = child; @@ -37,15 +37,17 @@ CordRepCrc* CordRepCrc::New(CordRep* child, uint32_t crc) { CordRep::Unref(old); } auto* new_cordrep = new CordRepCrc; - new_cordrep->length = child->length; + new_cordrep->length = child != nullptr ? child->length : 0; new_cordrep->tag = cord_internal::CRC; new_cordrep->child = child; - new_cordrep->crc = crc; + new_cordrep->crc_cord_state = std::move(state); return new_cordrep; } void CordRepCrc::Destroy(CordRepCrc* node) { - CordRep::Unref(node->child); + if (node->child != nullptr) { + CordRep::Unref(node->child); + } delete node; } diff --git a/absl/strings/internal/cord_rep_crc.h b/absl/strings/internal/cord_rep_crc.h index 5294b0d1..379d7a60 100644 --- a/absl/strings/internal/cord_rep_crc.h +++ b/absl/strings/internal/cord_rep_crc.h @@ -20,6 +20,7 @@ #include "absl/base/config.h" #include "absl/base/optimization.h" +#include "absl/crc/internal/crc_cord_state.h" #include "absl/strings/internal/cord_internal.h" namespace absl { @@ -34,14 +35,14 @@ namespace cord_internal { // the contained checksum is the user's responsibility. struct CordRepCrc : public CordRep { CordRep* child; - uint32_t crc; + absl::crc_internal::CrcCordState crc_cord_state; // Consumes `child` and returns a CordRepCrc prefixed tree containing `child`. // If the specified `child` is itself a CordRepCrc node, then this method - // either replaces the existing node, or directly updates the crc value in it + // either replaces the existing node, or directly updates the crc state in it // depending on the node being shared or not, i.e.: refcount.IsOne(). - // `child` must not be null. Never returns null. - static CordRepCrc* New(CordRep* child, uint32_t crc); + // `child` must only be null if the Cord is empty. Never returns null. + static CordRepCrc* New(CordRep* child, crc_internal::CrcCordState state); // Destroys (deletes) the provided node. `node` must not be null. static void Destroy(CordRepCrc* node); diff --git a/absl/strings/internal/cord_rep_crc_test.cc b/absl/strings/internal/cord_rep_crc_test.cc index 80f53485..3d27c33c 100644 --- a/absl/strings/internal/cord_rep_crc_test.cc +++ b/absl/strings/internal/cord_rep_crc_test.cc @@ -17,6 +17,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/config.h" +#include "absl/crc/internal/crc_cord_state.h" #include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cord_rep_test_util.h" @@ -27,47 +28,51 @@ namespace { using ::absl::cordrep_testing::MakeFlat; using ::testing::Eq; +using ::testing::IsNull; using ::testing::Ne; #if !defined(NDEBUG) && GTEST_HAS_DEATH_TEST -TEST(CordRepCrc, NewWithNullPtr) { - EXPECT_DEATH(CordRepCrc::New(nullptr, 0), ""); -} - TEST(CordRepCrc, RemoveCrcWithNullptr) { EXPECT_DEATH(RemoveCrcNode(nullptr), ""); } #endif // !NDEBUG && GTEST_HAS_DEATH_TEST +absl::crc_internal::CrcCordState MakeCrcCordState(uint32_t crc) { + crc_internal::CrcCordState state; + state.mutable_rep()->prefix_crc.push_back( + crc_internal::CrcCordState::PrefixCrc(42, crc32c_t{crc})); + return state; +} + TEST(CordRepCrc, NewDestroy) { CordRep* rep = cordrep_testing::MakeFlat("Hello world"); - CordRepCrc* crc = CordRepCrc::New(rep, 12345); + CordRepCrc* crc = CordRepCrc::New(rep, MakeCrcCordState(12345)); EXPECT_TRUE(crc->refcount.IsOne()); EXPECT_THAT(crc->child, Eq(rep)); - EXPECT_THAT(crc->crc, Eq(12345)); + EXPECT_THAT(crc->crc_cord_state.Checksum(), Eq(crc32c_t{12345u})); EXPECT_TRUE(rep->refcount.IsOne()); CordRepCrc::Destroy(crc); } TEST(CordRepCrc, NewExistingCrcNotShared) { CordRep* rep = cordrep_testing::MakeFlat("Hello world"); - CordRepCrc* crc = CordRepCrc::New(rep, 12345); - CordRepCrc* new_crc = CordRepCrc::New(crc, 54321); + CordRepCrc* crc = CordRepCrc::New(rep, MakeCrcCordState(12345)); + CordRepCrc* new_crc = CordRepCrc::New(crc, MakeCrcCordState(54321)); EXPECT_THAT(new_crc, Eq(crc)); EXPECT_TRUE(new_crc->refcount.IsOne()); EXPECT_THAT(new_crc->child, Eq(rep)); - EXPECT_THAT(new_crc->crc, Eq(54321)); + EXPECT_THAT(new_crc->crc_cord_state.Checksum(), Eq(crc32c_t{54321u})); EXPECT_TRUE(rep->refcount.IsOne()); CordRepCrc::Destroy(new_crc); } TEST(CordRepCrc, NewExistingCrcShared) { CordRep* rep = cordrep_testing::MakeFlat("Hello world"); - CordRepCrc* crc = CordRepCrc::New(rep, 12345); + CordRepCrc* crc = CordRepCrc::New(rep, MakeCrcCordState(12345)); CordRep::Ref(crc); - CordRepCrc* new_crc = CordRepCrc::New(crc, 54321); + CordRepCrc* new_crc = CordRepCrc::New(crc, MakeCrcCordState(54321)); EXPECT_THAT(new_crc, Ne(crc)); EXPECT_TRUE(new_crc->refcount.IsOne()); @@ -75,13 +80,23 @@ TEST(CordRepCrc, NewExistingCrcShared) { EXPECT_FALSE(rep->refcount.IsOne()); EXPECT_THAT(crc->child, Eq(rep)); EXPECT_THAT(new_crc->child, Eq(rep)); - EXPECT_THAT(crc->crc, Eq(12345)); - EXPECT_THAT(new_crc->crc, Eq(54321)); + EXPECT_THAT(crc->crc_cord_state.Checksum(), Eq(crc32c_t{12345u})); + EXPECT_THAT(new_crc->crc_cord_state.Checksum(), Eq(crc32c_t{54321u})); CordRep::Unref(crc); CordRep::Unref(new_crc); } +TEST(CordRepCrc, NewEmpty) { + CordRepCrc* crc = CordRepCrc::New(nullptr, MakeCrcCordState(12345)); + EXPECT_TRUE(crc->refcount.IsOne()); + EXPECT_THAT(crc->child, IsNull()); + EXPECT_THAT(crc->length, Eq(0u)); + EXPECT_THAT(crc->crc_cord_state.Checksum(), Eq(crc32c_t{12345u})); + EXPECT_TRUE(crc->refcount.IsOne()); + CordRepCrc::Destroy(crc); +} + TEST(CordRepCrc, RemoveCrcNotCrc) { CordRep* rep = cordrep_testing::MakeFlat("Hello world"); CordRep* nocrc = RemoveCrcNode(rep); @@ -91,7 +106,7 @@ TEST(CordRepCrc, RemoveCrcNotCrc) { TEST(CordRepCrc, RemoveCrcNotShared) { CordRep* rep = cordrep_testing::MakeFlat("Hello world"); - CordRepCrc* crc = CordRepCrc::New(rep, 12345); + CordRepCrc* crc = CordRepCrc::New(rep, MakeCrcCordState(12345)); CordRep* nocrc = RemoveCrcNode(crc); EXPECT_THAT(nocrc, Eq(rep)); EXPECT_TRUE(rep->refcount.IsOne()); @@ -100,7 +115,7 @@ TEST(CordRepCrc, RemoveCrcNotShared) { TEST(CordRepCrc, RemoveCrcShared) { CordRep* rep = cordrep_testing::MakeFlat("Hello world"); - CordRepCrc* crc = CordRepCrc::New(rep, 12345); + CordRepCrc* crc = CordRepCrc::New(rep, MakeCrcCordState(12345)); CordRep::Ref(crc); CordRep* nocrc = RemoveCrcNode(crc); EXPECT_THAT(nocrc, Eq(rep)); diff --git a/absl/strings/internal/cordz_functions.h b/absl/strings/internal/cordz_functions.h index c9ba1450..ed108bf1 100644 --- a/absl/strings/internal/cordz_functions.h +++ b/absl/strings/internal/cordz_functions.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef ABSL_STRINGS_CORDZ_FUNCTIONS_H_ -#define ABSL_STRINGS_CORDZ_FUNCTIONS_H_ +#ifndef ABSL_STRINGS_INTERNAL_CORDZ_FUNCTIONS_H_ +#define ABSL_STRINGS_INTERNAL_CORDZ_FUNCTIONS_H_ #include <stdint.h> @@ -32,18 +32,10 @@ int32_t get_cordz_mean_interval(); // Sets the sample rate with the average interval between samples. void set_cordz_mean_interval(int32_t mean_interval); -// Enable cordz unless any of the following applies: -// - no thread local support -// - MSVC build -// - Android build -// - Apple build -// - DLL build -// Hashtablez is turned off completely in opensource builds. -// MSVC's static atomics are dynamically initialized in debug mode, which breaks -// sampling. -#if defined(ABSL_HAVE_THREAD_LOCAL) && !defined(_MSC_VER) && \ - !defined(ABSL_BUILD_DLL) && !defined(ABSL_CONSUME_DLL) && \ - !defined(__ANDROID__) && !defined(__APPLE__) +// Cordz is only enabled on Linux with thread_local support. +#if defined(ABSL_INTERNAL_CORDZ_ENABLED) +#error ABSL_INTERNAL_CORDZ_ENABLED cannot be set directly +#elif defined(__linux__) && defined(ABSL_HAVE_THREAD_LOCAL) #define ABSL_INTERNAL_CORDZ_ENABLED 1 #endif @@ -82,4 +74,4 @@ inline void cordz_set_next_sample_for_testing(int64_t) {} ABSL_NAMESPACE_END } // namespace absl -#endif // ABSL_STRINGS_CORDZ_FUNCTIONS_H_ +#endif // ABSL_STRINGS_INTERNAL_CORDZ_FUNCTIONS_H_ diff --git a/absl/strings/internal/cordz_handle.h b/absl/strings/internal/cordz_handle.h index 5df53c78..3c800b43 100644 --- a/absl/strings/internal/cordz_handle.h +++ b/absl/strings/internal/cordz_handle.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef ABSL_STRINGS_CORDZ_HANDLE_H_ -#define ABSL_STRINGS_CORDZ_HANDLE_H_ +#ifndef ABSL_STRINGS_INTERNAL_CORDZ_HANDLE_H_ +#define ABSL_STRINGS_INTERNAL_CORDZ_HANDLE_H_ #include <atomic> #include <vector> @@ -128,4 +128,4 @@ class CordzSnapshot : public CordzHandle { ABSL_NAMESPACE_END } // namespace absl -#endif // ABSL_STRINGS_CORDZ_HANDLE_H_ +#endif // ABSL_STRINGS_INTERNAL_CORDZ_HANDLE_H_ diff --git a/absl/strings/internal/cordz_info.cc b/absl/strings/internal/cordz_info.cc index dac3fd8b..530f33be 100644 --- a/absl/strings/internal/cordz_info.cc +++ b/absl/strings/internal/cordz_info.cc @@ -35,7 +35,7 @@ namespace cord_internal { using ::absl::base_internal::SpinLockHolder; #ifdef ABSL_INTERNAL_NEED_REDUNDANT_CONSTEXPR_DECL -constexpr int CordzInfo::kMaxStackDepth; +constexpr size_t CordzInfo::kMaxStackDepth; #endif ABSL_CONST_INIT CordzInfo::List CordzInfo::global_list_{absl::kConstInit}; @@ -291,7 +291,7 @@ CordzInfo::MethodIdentifier CordzInfo::GetParentMethod(const CordzInfo* src) { : src->method_; } -int CordzInfo::FillParentStack(const CordzInfo* src, void** stack) { +size_t CordzInfo::FillParentStack(const CordzInfo* src, void** stack) { assert(stack); if (src == nullptr) return 0; if (src->parent_stack_depth_) { @@ -302,11 +302,14 @@ int CordzInfo::FillParentStack(const CordzInfo* src, void** stack) { return src->stack_depth_; } -CordzInfo::CordzInfo(CordRep* rep, const CordzInfo* src, +CordzInfo::CordzInfo(CordRep* rep, + const CordzInfo* src, MethodIdentifier method) : rep_(rep), - stack_depth_(absl::GetStackTrace(stack_, /*max_depth=*/kMaxStackDepth, - /*skip_count=*/1)), + stack_depth_( + static_cast<size_t>(absl::GetStackTrace(stack_, + /*max_depth=*/kMaxStackDepth, + /*skip_count=*/1))), parent_stack_depth_(FillParentStack(src, parent_stack_)), method_(method), parent_method_(GetParentMethod(src)), diff --git a/absl/strings/internal/cordz_info.h b/absl/strings/internal/cordz_info.h index 026d5b99..17eaa91c 100644 --- a/absl/strings/internal/cordz_info.h +++ b/absl/strings/internal/cordz_info.h @@ -12,8 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -#ifndef ABSL_STRINGS_CORDZ_INFO_H_ -#define ABSL_STRINGS_CORDZ_INFO_H_ +#ifndef ABSL_STRINGS_INTERNAL_CORDZ_INFO_H_ +#define ABSL_STRINGS_INTERNAL_CORDZ_INFO_H_ #include <atomic> #include <cstdint> @@ -196,7 +196,7 @@ class ABSL_LOCKABLE CordzInfo : public CordzHandle { std::atomic<CordzInfo*> head ABSL_GUARDED_BY(mutex){nullptr}; }; - static constexpr int kMaxStackDepth = 64; + static constexpr size_t kMaxStackDepth = 64; explicit CordzInfo(CordRep* rep, const CordzInfo* src, MethodIdentifier method); @@ -216,7 +216,7 @@ class ABSL_LOCKABLE CordzInfo : public CordzHandle { // `stack_` depending on `parent_stack_` being empty, returning the size of // the parent stack. // Returns 0 if `src` is null. - static int FillParentStack(const CordzInfo* src, void** stack); + static size_t FillParentStack(const CordzInfo* src, void** stack); void ODRCheck() const { #ifndef NDEBUG @@ -244,8 +244,8 @@ class ABSL_LOCKABLE CordzInfo : public CordzHandle { void* stack_[kMaxStackDepth]; void* parent_stack_[kMaxStackDepth]; - const int stack_depth_; - const int parent_stack_depth_; + const size_t stack_depth_; + const size_t parent_stack_depth_; const MethodIdentifier method_; const MethodIdentifier parent_method_; CordzUpdateTracker update_tracker_; @@ -295,4 +295,4 @@ inline CordRep* CordzInfo::RefCordRep() const ABSL_LOCKS_EXCLUDED(mutex_) { ABSL_NAMESPACE_END } // namespace absl -#endif // ABSL_STRINGS_CORDZ_INFO_H_ +#endif // ABSL_STRINGS_INTERNAL_CORDZ_INFO_H_ diff --git a/absl/strings/internal/cordz_info_statistics_test.cc b/absl/strings/internal/cordz_info_statistics_test.cc index 476c38d2..53d2f2ea 100644 --- a/absl/strings/internal/cordz_info_statistics_test.cc +++ b/absl/strings/internal/cordz_info_statistics_test.cc @@ -19,6 +19,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/config.h" +#include "absl/crc/internal/crc_cord_state.h" #include "absl/strings/cord.h" #include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cord_rep_btree.h" @@ -62,7 +63,7 @@ CordRepFlat* Flat(size_t size) { } // Creates an external of the specified length -CordRepExternal* External(int length = 512) { +CordRepExternal* External(size_t length = 512) { return static_cast<CordRepExternal*>( NewExternalRep(absl::string_view("", length), [](absl::string_view) {})); } @@ -352,7 +353,7 @@ TEST(CordzInfoStatisticsTest, SharedSubstringRing) { } TEST(CordzInfoStatisticsTest, BtreeLeaf) { - ASSERT_THAT(CordRepBtree::kMaxCapacity, Ge(3)); + ASSERT_THAT(CordRepBtree::kMaxCapacity, Ge(3u)); RefHelper ref; auto* flat1 = Flat(2000); auto* flat2 = Flat(200); @@ -392,7 +393,7 @@ TEST(CordzInfoStatisticsTest, BtreeNodeShared) { RefHelper ref; static constexpr int leaf_count = 3; const size_t flat3_count = CordRepBtree::kMaxCapacity - 3; - ASSERT_THAT(flat3_count, Ge(0)); + ASSERT_THAT(flat3_count, Ge(0u)); CordRepBtree* tree = nullptr; size_t mem_size = 0; @@ -451,7 +452,8 @@ TEST(CordzInfoStatisticsTest, BtreeNodeShared) { TEST(CordzInfoStatisticsTest, Crc) { RefHelper ref; auto* left = Flat(1000); - auto* crc = ref.NeedsUnref(CordRepCrc::New(left, 12345)); + auto* crc = + ref.NeedsUnref(CordRepCrc::New(left, crc_internal::CrcCordState())); CordzStatistics expected; expected.size = left->length; diff --git a/absl/strings/internal/cordz_info_test.cc b/absl/strings/internal/cordz_info_test.cc index b98343ae..cd226c3e 100644 --- a/absl/strings/internal/cordz_info_test.cc +++ b/absl/strings/internal/cordz_info_test.cc @@ -124,7 +124,7 @@ TEST(CordzInfoTest, UntrackCord) { CordzInfo* info = data.data.cordz_info(); info->Untrack(); - EXPECT_THAT(DeleteQueue(), SizeIs(0)); + EXPECT_THAT(DeleteQueue(), SizeIs(0u)); } TEST(CordzInfoTest, UntrackCordWithSnapshot) { @@ -263,8 +263,9 @@ TEST(CordzInfoTest, StackV2) { // resultant formatted stack will be "", but that still equals the stack // recorded in CordzInfo, which is also empty. The skip_count is 1 so that the // line number of the current stack isn't included in the HasSubstr check. - local_stack.resize(absl::GetStackTrace(local_stack.data(), kMaxStackDepth, - /*skip_count=*/1)); + local_stack.resize(static_cast<size_t>( + absl::GetStackTrace(local_stack.data(), kMaxStackDepth, + /*skip_count=*/1))); std::string got_stack = FormatStack(info->GetStack()); std::string expected_stack = FormatStack(local_stack); diff --git a/absl/strings/internal/cordz_sample_token.h b/absl/strings/internal/cordz_sample_token.h index 28a1d70c..b58022c3 100644 --- a/absl/strings/internal/cordz_sample_token.h +++ b/absl/strings/internal/cordz_sample_token.h @@ -16,8 +16,8 @@ #include "absl/strings/internal/cordz_handle.h" #include "absl/strings/internal/cordz_info.h" -#ifndef ABSL_STRINGS_CORDZ_SAMPLE_TOKEN_H_ -#define ABSL_STRINGS_CORDZ_SAMPLE_TOKEN_H_ +#ifndef ABSL_STRINGS_INTERNAL_CORDZ_SAMPLE_TOKEN_H_ +#define ABSL_STRINGS_INTERNAL_CORDZ_SAMPLE_TOKEN_H_ namespace absl { ABSL_NAMESPACE_BEGIN @@ -94,4 +94,4 @@ class CordzSampleToken : public CordzSnapshot { ABSL_NAMESPACE_END } // namespace absl -#endif // ABSL_STRINGS_CORDZ_SAMPLE_TOKEN_H_ +#endif // ABSL_STRINGS_INTERNAL_CORDZ_SAMPLE_TOKEN_H_ diff --git a/absl/strings/internal/cordz_statistics.h b/absl/strings/internal/cordz_statistics.h index 57071905..9f558df4 100644 --- a/absl/strings/internal/cordz_statistics.h +++ b/absl/strings/internal/cordz_statistics.h @@ -45,12 +45,12 @@ struct CordzStatistics { }; // The size of the cord in bytes. This matches the result of Cord::size(). - int64_t size = 0; + size_t size = 0; // The estimated memory used by the sampled cord. This value matches the // value as reported by Cord::EstimatedMemoryUsage(). // A value of 0 implies the property has not been recorded. - int64_t estimated_memory_usage = 0; + size_t estimated_memory_usage = 0; // The effective memory used by the sampled cord, inversely weighted by the // effective indegree of each allocated node. This is a representation of the @@ -59,14 +59,14 @@ struct CordzStatistics { // by multiple Cord instances, and for cases where a Cord includes the same // node multiple times (either directly or indirectly). // A value of 0 implies the property has not been recorded. - int64_t estimated_fair_share_memory_usage = 0; + size_t estimated_fair_share_memory_usage = 0; // The total number of nodes referenced by this cord. // For ring buffer Cords, this includes the 'ring buffer' node. // For btree Cords, this includes all 'CordRepBtree' tree nodes as well as all // the substring, flat and external nodes referenced by the tree. // A value of 0 implies the property has not been recorded. - int64_t node_count = 0; + size_t node_count = 0; // Detailed node counts per type NodeCounts node_counts; diff --git a/absl/strings/internal/damerau_levenshtein_distance.cc b/absl/strings/internal/damerau_levenshtein_distance.cc new file mode 100644 index 00000000..a084568f --- /dev/null +++ b/absl/strings/internal/damerau_levenshtein_distance.cc @@ -0,0 +1,93 @@ +// Copyright 2022 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/strings/internal/damerau_levenshtein_distance.h" + +#include <algorithm> +#include <array> +#include <numeric> + +#include "absl/strings/string_view.h" +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace strings_internal { +// Calculate DamerauLevenshtein (adjacent transpositions) distance +// between two strings, +// https://en.wikipedia.org/wiki/Damerau%E2%80%93Levenshtein_distance. The +// algorithm follows the condition that no substring is edited more than once. +// While this can reduce is larger distance, it's a) a much simpler algorithm +// and b) more realistic for the case that typographic mistakes should be +// detected. +// When the distance is larger than cutoff, or one of the strings has more +// than MAX_SIZE=100 characters, the code returns min(MAX_SIZE, cutoff) + 1. +uint8_t CappedDamerauLevenshteinDistance(absl::string_view s1, + absl::string_view s2, uint8_t cutoff) { + const uint8_t MAX_SIZE = 100; + const uint8_t _cutoff = std::min(MAX_SIZE, cutoff); + const uint8_t cutoff_plus_1 = static_cast<uint8_t>(_cutoff + 1); + + if (s1.size() > s2.size()) std::swap(s1, s2); + if (s1.size() + _cutoff < s2.size() || s2.size() > MAX_SIZE) + return cutoff_plus_1; + + if (s1.empty()) + return static_cast<uint8_t>(s2.size()); + + // Lower diagonal bound: y = x - lower_diag + const uint8_t lower_diag = + _cutoff - static_cast<uint8_t>(s2.size() - s1.size()); + // Upper diagonal bound: y = x + upper_diag + const uint8_t upper_diag = _cutoff; + + // d[i][j] is the number of edits required to convert s1[0, i] to s2[0, j] + std::array<std::array<uint8_t, MAX_SIZE + 2>, MAX_SIZE + 2> d; + std::iota(d[0].begin(), d[0].begin() + upper_diag + 1, 0); + d[0][cutoff_plus_1] = cutoff_plus_1; + for (size_t i = 1; i <= s1.size(); ++i) { + // Deduce begin of relevant window. + size_t j_begin = 1; + if (i > lower_diag) { + j_begin = i - lower_diag; + d[i][j_begin - 1] = cutoff_plus_1; + } else { + d[i][0] = static_cast<uint8_t>(i); + } + + // Deduce end of relevant window. + size_t j_end = i + upper_diag; + if (j_end > s2.size()) { + j_end = s2.size(); + } else { + d[i][j_end + 1] = cutoff_plus_1; + } + + for (size_t j = j_begin; j <= j_end; ++j) { + const uint8_t deletion_distance = d[i - 1][j] + 1; + const uint8_t insertion_distance = d[i][j - 1] + 1; + const uint8_t mismatched_tail_cost = s1[i - 1] == s2[j - 1] ? 0 : 1; + const uint8_t mismatch_distance = d[i - 1][j - 1] + mismatched_tail_cost; + uint8_t transposition_distance = _cutoff + 1; + if (i > 1 && j > 1 && s1[i - 1] == s2[j - 2] && s1[i - 2] == s2[j - 1]) + transposition_distance = d[i - 2][j - 2] + 1; + d[i][j] = std::min({cutoff_plus_1, deletion_distance, insertion_distance, + mismatch_distance, transposition_distance}); + } + } + return d[s1.size()][s2.size()]; +} + +} // namespace strings_internal + +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/strings/internal/damerau_levenshtein_distance.h b/absl/strings/internal/damerau_levenshtein_distance.h new file mode 100644 index 00000000..7a4bd644 --- /dev/null +++ b/absl/strings/internal/damerau_levenshtein_distance.h @@ -0,0 +1,34 @@ +// Copyright 2022 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_STRINGS_INTERNAL_DAMERAU_LEVENSHTEIN_DISTANCE_H_ +#define ABSL_STRINGS_INTERNAL_DAMERAU_LEVENSHTEIN_DISTANCE_H_ + +#include <cstdint> + +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace strings_internal { +// Calculate DamerauLevenshtein distance between two strings. +// When the distance is larger than cutoff, the code just returns cutoff + 1. +uint8_t CappedDamerauLevenshteinDistance(absl::string_view s1, + absl::string_view s2, uint8_t cutoff); + +} // namespace strings_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_DAMERAU_LEVENSHTEIN_DISTANCE_H_ diff --git a/absl/strings/internal/damerau_levenshtein_distance_test.cc b/absl/strings/internal/damerau_levenshtein_distance_test.cc new file mode 100644 index 00000000..a342b7db --- /dev/null +++ b/absl/strings/internal/damerau_levenshtein_distance_test.cc @@ -0,0 +1,99 @@ +// Copyright 2022 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/strings/internal/damerau_levenshtein_distance.h" + +#include <cstdint> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace { + +using absl::strings_internal::CappedDamerauLevenshteinDistance; + +TEST(Distance, TestDistances) { + EXPECT_THAT(CappedDamerauLevenshteinDistance("ab", "ab", 6), uint8_t{0}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("a", "b", 6), uint8_t{1}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("ca", "abc", 6), uint8_t{3}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "ad", 6), uint8_t{2}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "cadb", 6), uint8_t{4}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "bdac", 6), uint8_t{4}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("ab", "ab", 0), uint8_t{0}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("", "", 0), uint8_t{0}); + // combinations for 3-character strings: + // 1, 2, 3 removals, insertions or replacements and transpositions + EXPECT_THAT(CappedDamerauLevenshteinDistance("abc", "abc", 6), uint8_t{0}); + for (auto res : + {"", "ca", "efg", "ea", "ce", "ceb", "eca", "cae", "cea", "bea"}) { + EXPECT_THAT(CappedDamerauLevenshteinDistance("abc", res, 6), uint8_t{3}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(res, "abc", 6), uint8_t{3}); + } + for (auto res : + {"a", "b", "c", "ba", "cb", "bca", "cab", "cba", "ace", + "efc", "ebf", "aef", "ae", "be", "eb", "ec", "ecb", "bec", + "bce", "cbe", "ace", "eac", "aeb", "bae", "eab", "eba"}) { + EXPECT_THAT(CappedDamerauLevenshteinDistance("abc", res, 6), uint8_t{2}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(res, "abc", 6), uint8_t{2}); + } + for (auto res : {"ab", "ac", "bc", "acb", "bac", "ebc", "aec", "abe"}) { + EXPECT_THAT(CappedDamerauLevenshteinDistance("abc", res, 6), uint8_t{1}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(res, "abc", 6), uint8_t{1}); + } +} + +TEST(Distance, TestCutoff) { + // Returing cutoff + 1 if the value is larger than cutoff or string longer + // than MAX_SIZE. + EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "a", 3), uint8_t{3}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "a", 2), uint8_t{3}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("abcd", "a", 1), uint8_t{2}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("abcdefg", "a", 2), uint8_t{3}); + EXPECT_THAT(CappedDamerauLevenshteinDistance("a", "abcde", 2), uint8_t{3}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(102, 'a'), + std::string(102, 'a'), 105), + uint8_t{101}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(100, 'a'), + std::string(100, 'a'), 100), + uint8_t{0}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(100, 'a'), + std::string(100, 'b'), 100), + uint8_t{100}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(100, 'a'), + std::string(99, 'a'), 2), + uint8_t{1}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(100, 'a'), + std::string(101, 'a'), 2), + uint8_t{3}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(100, 'a'), + std::string(101, 'a'), 2), + uint8_t{3}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(UINT8_MAX + 1, 'a'), + std::string(UINT8_MAX + 1, 'b'), + UINT8_MAX), + uint8_t{101}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(UINT8_MAX - 1, 'a'), + std::string(UINT8_MAX - 1, 'b'), + UINT8_MAX), + uint8_t{101}); + EXPECT_THAT( + CappedDamerauLevenshteinDistance(std::string(UINT8_MAX, 'a'), + std::string(UINT8_MAX, 'b'), UINT8_MAX), + uint8_t{101}); + EXPECT_THAT(CappedDamerauLevenshteinDistance(std::string(UINT8_MAX - 1, 'a'), + std::string(UINT8_MAX - 1, 'a'), + UINT8_MAX), + uint8_t{101}); +} +} // namespace diff --git a/absl/strings/internal/escaping.cc b/absl/strings/internal/escaping.cc index cfea0961..8bd0890d 100644 --- a/absl/strings/internal/escaping.cc +++ b/absl/strings/internal/escaping.cc @@ -28,19 +28,11 @@ 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. // - // (from https://tools.ietf.org/html/rfc3548) - // Special processing is performed if fewer than 24 bits are available - // at the end of the data being encoded. A full encoding quantum is - // always completed at the end of a quantity. When fewer than 24 input - // bits are available in an input group, zero bits are added (on the - // right) to form an integral number of 6-bit groups. Padding at the - // end of the data is performed using the '=' character. Since all base - // 64 input is an integral number of octets, only the following cases - // can arise: - // Base64 encodes each three bytes of input into four bytes of output. size_t len = (input_len / 3) * 4; + // Since all base 64 input is an integral number of octets, only the following + // cases can arise: if (input_len % 3 == 0) { // (from https://tools.ietf.org/html/rfc3548) // (1) the final quantum of encoding input is an integral multiple of 24 @@ -83,6 +75,16 @@ size_t Base64EscapeInternal(const unsigned char* src, size_t szsrc, char* dest, char* const limit_dest = dest + szdest; const unsigned char* const limit_src = src + szsrc; + // (from https://tools.ietf.org/html/rfc3548) + // Special processing is performed if fewer than 24 bits are available + // at the end of the data being encoded. A full encoding quantum is + // always completed at the end of a quantity. When fewer than 24 input + // bits are available in an input group, zero bits are added (on the + // right) to form an integral number of 6-bit groups. + // + // If do_padding is true, padding at the end of the data is performed. This + // output padding uses the '=' character. + // Three bytes of data encodes to four characters of cyphertext. // So we can pump through three-byte chunks atomically. if (szsrc >= 3) { // "limit_src - 3" is UB if szsrc < 3. diff --git a/absl/strings/internal/escaping.h b/absl/strings/internal/escaping.h index 6a9ce602..b04033ff 100644 --- a/absl/strings/internal/escaping.h +++ b/absl/strings/internal/escaping.h @@ -25,19 +25,17 @@ namespace strings_internal { ABSL_CONST_INIT extern const char kBase64Chars[]; -// Calculates how long a string will be when it is base64 encoded given its -// length and whether or not the result should be padded. +// 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' +// encoding (section 5 of the RFC) does not change this length. size_t CalculateBase64EscapedLenInternal(size_t input_len, bool do_padding); -// Base64-encodes `src` using the alphabet provided in `base64` and writes the -// result to `dest`. If `do_padding` is true, `dest` is padded with '=' chars -// until its length is a multiple of 3. Returns the length of `dest`. +// Base64-encodes `src` using the alphabet provided in `base64` (which +// determines whether to do web-safe encoding or not) and writes the result to +// `dest`. If `do_padding` is true, `dest` is padded with '=' chars until its +// length is a multiple of 3. Returns the length of `dest`. size_t Base64EscapeInternal(const unsigned char* src, size_t szsrc, char* dest, size_t szdest, const char* base64, bool do_padding); - -// Base64-encodes `src` using the alphabet provided in `base64` and writes the -// result to `dest`. If `do_padding` is true, `dest` is padded with '=' chars -// until its length is a multiple of 3. template <typename String> void Base64EscapeInternal(const unsigned char* src, size_t szsrc, String* dest, bool do_padding, const char* base64_chars) { diff --git a/absl/strings/internal/has_absl_stringify.h b/absl/strings/internal/has_absl_stringify.h new file mode 100644 index 00000000..55a08508 --- /dev/null +++ b/absl/strings/internal/has_absl_stringify.h @@ -0,0 +1,55 @@ +// Copyright 2022 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_STRINGS_INTERNAL_HAS_ABSL_STRINGIFY_H_ +#define ABSL_STRINGS_INTERNAL_HAS_ABSL_STRINGIFY_H_ +#include <string> +#include <type_traits> +#include <utility> + +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +namespace strings_internal { + +// This is an empty class not intended to be used. It exists so that +// `HasAbslStringify` can reference a universal class rather than needing to be +// copied for each new sink. +class UnimplementedSink { + public: + void Append(size_t count, char ch); + + void Append(string_view v); + + // Support `absl::Format(&sink, format, args...)`. + friend void AbslFormatFlush(UnimplementedSink* sink, absl::string_view v); +}; + +template <typename T, typename = void> +struct HasAbslStringify : std::false_type {}; + +template <typename T> +struct HasAbslStringify< + T, std::enable_if_t<std::is_void<decltype(AbslStringify( + std::declval<strings_internal::UnimplementedSink&>(), + std::declval<const T&>()))>::value>> : std::true_type {}; + +} // namespace strings_internal + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_HAS_ABSL_STRINGIFY_H_ diff --git a/absl/strings/internal/memutil.cc b/absl/strings/internal/memutil.cc index 2519c688..44996a75 100644 --- a/absl/strings/internal/memutil.cc +++ b/absl/strings/internal/memutil.cc @@ -54,10 +54,11 @@ size_t memspn(const char* s, size_t slen, const char* accept) { cont: c = *p++; - if (slen-- == 0) return p - 1 - s; + if (slen-- == 0) + return static_cast<size_t>(p - 1 - s); for (spanp = accept; (sc = *spanp++) != '\0';) if (sc == c) goto cont; - return p - 1 - s; + return static_cast<size_t>(p - 1 - s); } size_t memcspn(const char* s, size_t slen, const char* reject) { @@ -68,9 +69,10 @@ size_t memcspn(const char* s, size_t slen, const char* reject) { while (slen-- != 0) { c = *p++; for (spanp = reject; (sc = *spanp++) != '\0';) - if (sc == c) return p - 1 - s; + if (sc == c) + return static_cast<size_t>(p - 1 - s); } - return p - s; + return static_cast<size_t>(p - s); } char* mempbrk(const char* s, size_t slen, const char* accept) { @@ -97,8 +99,9 @@ const char* memmatch(const char* phaystack, size_t haylen, const char* pneedle, 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], hayend - phaystack)))) { + 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 diff --git a/absl/strings/internal/ostringstream.cc b/absl/strings/internal/ostringstream.cc index dc6cfe16..a0e5ec08 100644 --- a/absl/strings/internal/ostringstream.cc +++ b/absl/strings/internal/ostringstream.cc @@ -14,20 +14,27 @@ #include "absl/strings/internal/ostringstream.h" +#include <cassert> +#include <cstddef> +#include <ios> +#include <streambuf> + namespace absl { ABSL_NAMESPACE_BEGIN namespace strings_internal { -OStringStream::Buf::int_type OStringStream::overflow(int c) { - assert(s_); - if (!Buf::traits_type::eq_int_type(c, Buf::traits_type::eof())) - s_->push_back(static_cast<char>(c)); +OStringStream::Streambuf::int_type OStringStream::Streambuf::overflow(int c) { + assert(str_); + if (!std::streambuf::traits_type::eq_int_type( + c, std::streambuf::traits_type::eof())) + str_->push_back(static_cast<char>(c)); return 1; } -std::streamsize OStringStream::xsputn(const char* s, std::streamsize n) { - assert(s_); - s_->append(s, static_cast<size_t>(n)); +std::streamsize OStringStream::Streambuf::xsputn(const char* s, + std::streamsize n) { + assert(str_); + str_->append(s, static_cast<size_t>(n)); return n; } diff --git a/absl/strings/internal/ostringstream.h b/absl/strings/internal/ostringstream.h index d25d6047..c0e237db 100644 --- a/absl/strings/internal/ostringstream.h +++ b/absl/strings/internal/ostringstream.h @@ -16,11 +16,13 @@ #define ABSL_STRINGS_INTERNAL_OSTRINGSTREAM_H_ #include <cassert> +#include <ios> #include <ostream> #include <streambuf> #include <string> +#include <utility> -#include "absl/base/port.h" +#include "absl/base/config.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -60,26 +62,49 @@ namespace strings_internal { // strm << 3.14; // // Note: flush() has no effect. No reason to call it. -class OStringStream : private std::basic_streambuf<char>, public std::ostream { +class OStringStream final : public std::ostream { public: // The argument can be null, in which case you'll need to call str(p) with a // non-null argument before you can write to the stream. // // The destructor of OStringStream doesn't use the std::string. It's OK to // destroy the std::string before the stream. - explicit OStringStream(std::string* s) : std::ostream(this), s_(s) {} + explicit OStringStream(std::string* str) + : std::ostream(&buf_), buf_(str) {} + OStringStream(OStringStream&& that) + : std::ostream(std::move(static_cast<std::ostream&>(that))), + buf_(that.buf_) { + rdbuf(&buf_); + } + OStringStream& operator=(OStringStream&& that) { + std::ostream::operator=(std::move(static_cast<std::ostream&>(that))); + buf_ = that.buf_; + rdbuf(&buf_); + return *this; + } - std::string* str() { return s_; } - const std::string* str() const { return s_; } - void str(std::string* s) { s_ = s; } + std::string* str() { return buf_.str(); } + const std::string* str() const { return buf_.str(); } + void str(std::string* str) { buf_.str(str); } private: - using Buf = std::basic_streambuf<char>; + class Streambuf final : public std::streambuf { + public: + explicit Streambuf(std::string* str) : str_(str) {} + Streambuf(const Streambuf&) = default; + Streambuf& operator=(const Streambuf&) = default; - Buf::int_type overflow(int c) override; - std::streamsize xsputn(const char* s, std::streamsize n) override; + std::string* str() { return str_; } + const std::string* str() const { return str_; } + void str(std::string* str) { str_ = str; } - std::string* s_; + protected: + int_type overflow(int c) override; + std::streamsize xsputn(const char* s, std::streamsize n) override; + + private: + std::string* str_; + } buf_; }; } // namespace strings_internal diff --git a/absl/strings/internal/ostringstream_test.cc b/absl/strings/internal/ostringstream_test.cc index 2879e50e..ef3ad573 100644 --- a/absl/strings/internal/ostringstream_test.cc +++ b/absl/strings/internal/ostringstream_test.cc @@ -14,10 +14,12 @@ #include "absl/strings/internal/ostringstream.h" +#include <ios> #include <memory> #include <ostream> #include <string> #include <type_traits> +#include <utility> #include "gtest/gtest.h" @@ -29,24 +31,51 @@ TEST(OStringStream, IsOStream) { ""); } -TEST(OStringStream, ConstructDestroy) { +TEST(OStringStream, ConstructNullptr) { + absl::strings_internal::OStringStream strm(nullptr); + EXPECT_EQ(nullptr, strm.str()); +} + +TEST(OStringStream, ConstructStr) { + std::string s = "abc"; { - absl::strings_internal::OStringStream strm(nullptr); - EXPECT_EQ(nullptr, strm.str()); + absl::strings_internal::OStringStream strm(&s); + EXPECT_EQ(&s, strm.str()); } + EXPECT_EQ("abc", s); +} + +TEST(OStringStream, Destroy) { + std::unique_ptr<std::string> s(new std::string); + absl::strings_internal::OStringStream strm(s.get()); + s.reset(); +} + +TEST(OStringStream, MoveConstruct) { + std::string s = "abc"; { - std::string s = "abc"; - { - absl::strings_internal::OStringStream strm(&s); - EXPECT_EQ(&s, strm.str()); - } - EXPECT_EQ("abc", s); + absl::strings_internal::OStringStream strm1(&s); + strm1 << std::hex << 16; + EXPECT_EQ(&s, strm1.str()); + absl::strings_internal::OStringStream strm2(std::move(strm1)); + strm2 << 16; // We should still be in base 16. + EXPECT_EQ(&s, strm2.str()); } + EXPECT_EQ("abc1010", s); +} + +TEST(OStringStream, MoveAssign) { + std::string s = "abc"; { - std::unique_ptr<std::string> s(new std::string); - absl::strings_internal::OStringStream strm(s.get()); - s.reset(); + absl::strings_internal::OStringStream strm1(&s); + strm1 << std::hex << 16; + EXPECT_EQ(&s, strm1.str()); + absl::strings_internal::OStringStream strm2(nullptr); + strm2 = std::move(strm1); + strm2 << 16; // We should still be in base 16. + EXPECT_EQ(&s, strm2.str()); } + EXPECT_EQ("abc1010", s); } TEST(OStringStream, Str) { diff --git a/absl/strings/internal/str_format/arg.cc b/absl/strings/internal/str_format/arg.cc index 02aeeebe..018dd052 100644 --- a/absl/strings/internal/str_format/arg.cc +++ b/absl/strings/internal/str_format/arg.cc @@ -77,7 +77,7 @@ class IntDigits { v >>= 3; } while (v); start_ = p; - size_ = storage_ + sizeof(storage_) - p; + size_ = static_cast<size_t>(storage_ + sizeof(storage_) - p); } // Print the signed or unsigned integer as decimal. @@ -86,7 +86,8 @@ class IntDigits { void PrintAsDec(T v) { static_assert(std::is_integral<T>::value, ""); start_ = storage_; - size_ = numbers_internal::FastIntToBuffer(v, storage_) - storage_; + size_ = static_cast<size_t>(numbers_internal::FastIntToBuffer(v, storage_) - + storage_); } void PrintAsDec(int128 v) { @@ -115,7 +116,7 @@ class IntDigits { if (add_neg) { *--p = '-'; } - size_ = storage_ + sizeof(storage_) - p; + size_ = static_cast<size_t>(storage_ + sizeof(storage_) - p); start_ = p; } @@ -138,7 +139,7 @@ class IntDigits { ++p; } start_ = p; - size_ = storage_ + sizeof(storage_) - p; + size_ = static_cast<size_t>(storage_ + sizeof(storage_) - p); } // Print the unsigned integer as hex using uppercase. @@ -154,7 +155,7 @@ class IntDigits { v >>= 4; } while (v); start_ = p; - size_ = storage_ + sizeof(storage_) - p; + size_ = static_cast<size_t>(storage_ + sizeof(storage_) - p); } // The printed value including the '-' sign if available. @@ -208,10 +209,12 @@ string_view SignColumn(bool neg, const FormatConversionSpecImpl conv) { return {}; } -bool ConvertCharImpl(unsigned char v, const FormatConversionSpecImpl conv, - FormatSinkImpl *sink) { +bool ConvertCharImpl(char v, + const FormatConversionSpecImpl conv, + FormatSinkImpl* sink) { size_t fill = 0; - if (conv.width() >= 0) fill = conv.width(); + if (conv.width() >= 0) + fill = static_cast<size_t>(conv.width()); ReducePadding(1, &fill); if (!conv.has_left_flag()) sink->Append(fill, ' '); sink->Append(1, v); @@ -225,7 +228,8 @@ bool ConvertIntImplInnerSlow(const IntDigits &as_digits, // Print as a sequence of Substrings: // [left_spaces][sign][base_indicator][zeroes][formatted][right_spaces] size_t fill = 0; - if (conv.width() >= 0) fill = conv.width(); + if (conv.width() >= 0) + fill = static_cast<size_t>(conv.width()); string_view formatted = as_digits.without_neg_or_zero(); ReducePadding(formatted, &fill); @@ -236,10 +240,9 @@ bool ConvertIntImplInnerSlow(const IntDigits &as_digits, string_view base_indicator = BaseIndicator(as_digits, conv); ReducePadding(base_indicator, &fill); - int precision = conv.precision(); - bool precision_specified = precision >= 0; - if (!precision_specified) - precision = 1; + bool precision_specified = conv.precision() >= 0; + size_t precision = + precision_specified ? static_cast<size_t>(conv.precision()) : size_t{1}; if (conv.has_alt_flag() && conv.conversion_char() == FormatConversionCharInternal::o) { @@ -247,7 +250,7 @@ bool ConvertIntImplInnerSlow(const IntDigits &as_digits, // "For o conversion, it increases the precision (if necessary) to // force the first digit of the result to be zero." if (formatted.empty() || *formatted.begin() != '0') { - int needed = static_cast<int>(formatted.size()) + 1; + size_t needed = formatted.size() + 1; precision = std::max(precision, needed); } } @@ -275,19 +278,71 @@ 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) { + conv.set_conversion_char(FormatConversionCharInternal::g); + } + + return FormatConversionCharIsFloat(conv.conversion_char()) && + ConvertFloatImpl(v, conv, sink); +} + +inline bool ConvertStringArg(string_view v, const FormatConversionSpecImpl conv, + FormatSinkImpl *sink) { + if (conv.is_basic()) { + sink->Append(v); + return true; + } + return sink->PutPaddedString(v, conv.width(), conv.precision(), + conv.has_left_flag()); +} + +} // namespace + +bool ConvertBoolArg(bool v, FormatSinkImpl *sink) { + if (v) { + sink->Append("true"); + } else { + sink->Append("false"); + } + return true; +} + template <typename T> -bool ConvertIntArg(T v, const FormatConversionSpecImpl conv, - FormatSinkImpl *sink) { +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 is declared, but not defined. switch (static_cast<uint8_t>(conv.conversion_char())) { case static_cast<uint8_t>(FormatConversionCharInternal::c): - return ConvertCharImpl(static_cast<unsigned char>(v), conv, sink); + return ConvertCharImpl(static_cast<char>(v), conv, sink); case static_cast<uint8_t>(FormatConversionCharInternal::o): as_digits.PrintAsOct(static_cast<U>(v)); @@ -320,7 +375,7 @@ bool ConvertIntArg(T v, const FormatConversionSpecImpl conv, return ConvertFloatImpl(static_cast<double>(v), conv, sink); default: - ABSL_ASSUME(false); + ABSL_ASSUME(false); } if (conv.is_basic()) { @@ -330,24 +385,37 @@ bool ConvertIntArg(T v, const FormatConversionSpecImpl conv, return ConvertIntImplInnerSlow(as_digits, conv, sink); } -template <typename T> -bool ConvertFloatArg(T v, const FormatConversionSpecImpl conv, - FormatSinkImpl *sink) { - return FormatConversionCharIsFloat(conv.conversion_char()) && - ConvertFloatImpl(v, conv, sink); -} - -inline bool ConvertStringArg(string_view v, const FormatConversionSpecImpl conv, - FormatSinkImpl *sink) { - if (conv.is_basic()) { - sink->Append(v); - return true; - } - return sink->PutPaddedString(v, conv.width(), conv.precision(), - conv.has_left_flag()); -} - -} // namespace +template bool ConvertIntArg<char>(char v, FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg<signed char>(signed char v, + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg<unsigned char>(unsigned char v, + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg<short>(short v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg<unsigned short>(unsigned short v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg<int>(int v, FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg<unsigned int>(unsigned int v, + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg<long>(long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg<unsigned long>(unsigned long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg<long long>(long long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); +template bool ConvertIntArg<unsigned long long>(unsigned long long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl *sink); // ==================== Strings ==================== StringConvertResult FormatConvertImpl(const std::string &v, @@ -375,7 +443,7 @@ FormatConvertImpl(const char *v, const FormatConversionSpecImpl conv, len = std::strlen(v); } else { // If precision is set, we look for the NUL-terminator on the valid range. - len = std::find(v, v + conv.precision(), '\0') - v; + len = static_cast<size_t>(std::find(v, v + conv.precision(), '\0') - v); } return {ConvertStringArg(string_view(v, len), conv, sink)}; } @@ -410,19 +478,18 @@ FloatingConvertResult FormatConvertImpl(long double v, } // ==================== Chars ==================== -IntegralConvertResult FormatConvertImpl(char v, - const FormatConversionSpecImpl conv, - FormatSinkImpl *sink) { +CharConvertResult FormatConvertImpl(char v, const FormatConversionSpecImpl conv, + FormatSinkImpl *sink) { return {ConvertIntArg(v, conv, sink)}; } -IntegralConvertResult FormatConvertImpl(signed char v, - const FormatConversionSpecImpl conv, - FormatSinkImpl *sink) { +CharConvertResult FormatConvertImpl(signed char v, + const FormatConversionSpecImpl conv, + FormatSinkImpl *sink) { return {ConvertIntArg(v, conv, sink)}; } -IntegralConvertResult FormatConvertImpl(unsigned char v, - const FormatConversionSpecImpl conv, - FormatSinkImpl *sink) { +CharConvertResult FormatConvertImpl(unsigned char v, + const FormatConversionSpecImpl conv, + FormatSinkImpl *sink) { return {ConvertIntArg(v, conv, sink)}; } diff --git a/absl/strings/internal/str_format/arg.h b/absl/strings/internal/str_format/arg.h index b9dda909..e4b16628 100644 --- a/absl/strings/internal/str_format/arg.h +++ b/absl/strings/internal/str_format/arg.h @@ -18,6 +18,7 @@ #include <string.h> #include <wchar.h> +#include <algorithm> #include <cstdio> #include <iomanip> #include <limits> @@ -25,10 +26,12 @@ #include <sstream> #include <string> #include <type_traits> +#include <utility> #include "absl/base/port.h" #include "absl/meta/type_traits.h" #include "absl/numeric/int128.h" +#include "absl/strings/internal/has_absl_stringify.h" #include "absl/strings/internal/str_format/extension.h" #include "absl/strings/string_view.h" @@ -45,6 +48,24 @@ class FormatConversionSpec; namespace str_format_internal { +template <FormatConversionCharSet C> +struct ArgConvertResult { + bool value; +}; + +using IntegralConvertResult = ArgConvertResult<FormatConversionCharSetUnion( + FormatConversionCharSetInternal::c, + FormatConversionCharSetInternal::kNumeric, + FormatConversionCharSetInternal::kStar, + FormatConversionCharSetInternal::v)>; +using FloatingConvertResult = ArgConvertResult<FormatConversionCharSetUnion( + FormatConversionCharSetInternal::kFloating, + FormatConversionCharSetInternal::v)>; +using CharConvertResult = ArgConvertResult<FormatConversionCharSetUnion( + FormatConversionCharSetInternal::c, + FormatConversionCharSetInternal::kNumeric, + FormatConversionCharSetInternal::kStar)>; + template <typename T, typename = void> struct HasUserDefinedConvert : std::false_type {}; @@ -55,7 +76,50 @@ struct HasUserDefinedConvert<T, void_t<decltype(AbslFormatConvert( std::declval<FormatSink*>()))>> : std::true_type {}; -void AbslFormatConvert(); // Stops the lexical name lookup +// These declarations prevent ADL lookup from continuing in absl namespaces, +// we are deliberately using these as ADL hooks and want them to consider +// non-absl namespaces only. +void AbslFormatConvert(); +void AbslStringify(); + +template <typename T> +bool ConvertIntArg(T v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); + +// Forward declarations of internal `ConvertIntArg` function template +// instantiations are here to avoid including the template body in the headers +// and instantiating it in large numbers of translation units. Explicit +// instantiations can be found in "absl/strings/internal/str_format/arg.cc" +extern template bool ConvertIntArg<char>(char v, FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg<signed char>(signed char v, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg<unsigned char>(unsigned char v, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg<short>(short v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg<unsigned short>( // NOLINT + unsigned short v, FormatConversionSpecImpl conv, // NOLINT + FormatSinkImpl* sink); +extern template bool ConvertIntArg<int>(int v, FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg<unsigned int>(unsigned int v, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg<long>( // NOLINT + long v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); // NOLINT +extern template bool ConvertIntArg<unsigned long>(unsigned long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg<long long>(long long v, // NOLINT + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +extern template bool ConvertIntArg<unsigned long long>( // NOLINT + unsigned long long v, FormatConversionSpecImpl conv, // NOLINT + FormatSinkImpl* sink); + template <typename T> auto FormatConvertImpl(const T& v, FormatConversionSpecImpl conv, FormatSinkImpl* sink) @@ -72,6 +136,39 @@ auto FormatConvertImpl(const T& v, FormatConversionSpecImpl conv, } template <typename T> +auto FormatConvertImpl(const T& v, FormatConversionSpecImpl conv, + FormatSinkImpl* sink) + -> std::enable_if_t<std::is_enum<T>::value && + std::is_void<decltype(AbslStringify( + std::declval<FormatSink&>(), v))>::value, + IntegralConvertResult> { + if (conv.conversion_char() == FormatConversionCharInternal::v) { + using FormatSinkT = + absl::enable_if_t<sizeof(const T& (*)()) != 0, FormatSink>; + auto fs = sink->Wrap<FormatSinkT>(); + AbslStringify(fs, v); + return {true}; + } else { + return {ConvertIntArg( + static_cast<typename std::underlying_type<T>::type>(v), conv, sink)}; + } +} + +template <typename T> +auto FormatConvertImpl(const T& v, FormatConversionSpecImpl, + FormatSinkImpl* sink) + -> std::enable_if_t<!std::is_enum<T>::value && + std::is_void<decltype(AbslStringify( + std::declval<FormatSink&>(), v))>::value, + ArgConvertResult<FormatConversionCharSetInternal::v>> { + using FormatSinkT = + absl::enable_if_t<sizeof(const T& (*)()) != 0, FormatSink>; + auto fs = sink->Wrap<FormatSinkT>(); + AbslStringify(fs, v); + return {true}; +} + +template <typename T> class StreamedWrapper; // If 'v' can be converted (in the printf sense) according to 'conv', @@ -96,11 +193,6 @@ struct VoidPtr { }; template <FormatConversionCharSet C> -struct ArgConvertResult { - bool value; -}; - -template <FormatConversionCharSet C> constexpr FormatConversionCharSet ExtractCharSet(FormatConvertResult<C>) { return C; } @@ -110,8 +202,8 @@ constexpr FormatConversionCharSet ExtractCharSet(ArgConvertResult<C>) { return C; } -using StringConvertResult = - ArgConvertResult<FormatConversionCharSetInternal::s>; +using StringConvertResult = ArgConvertResult<FormatConversionCharSetUnion( + FormatConversionCharSetInternal::s, FormatConversionCharSetInternal::v)>; ArgConvertResult<FormatConversionCharSetInternal::p> FormatConvertImpl( VoidPtr v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); @@ -173,12 +265,7 @@ StringConvertResult FormatConvertImpl(const AbslCord& value, return {true}; } -using IntegralConvertResult = ArgConvertResult<FormatConversionCharSetUnion( - FormatConversionCharSetInternal::c, - FormatConversionCharSetInternal::kNumeric, - FormatConversionCharSetInternal::kStar)>; -using FloatingConvertResult = - ArgConvertResult<FormatConversionCharSetInternal::kFloating>; +bool ConvertBoolArg(bool v, FormatSinkImpl* sink); // Floats. FloatingConvertResult FormatConvertImpl(float v, FormatConversionSpecImpl conv, @@ -190,14 +277,14 @@ FloatingConvertResult FormatConvertImpl(long double v, FormatSinkImpl* sink); // Chars. -IntegralConvertResult FormatConvertImpl(char v, FormatConversionSpecImpl conv, - FormatSinkImpl* sink); -IntegralConvertResult FormatConvertImpl(signed char v, - FormatConversionSpecImpl conv, - FormatSinkImpl* sink); -IntegralConvertResult FormatConvertImpl(unsigned char v, - FormatConversionSpecImpl conv, - FormatSinkImpl* sink); +CharConvertResult FormatConvertImpl(char v, FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +CharConvertResult FormatConvertImpl(signed char v, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); +CharConvertResult FormatConvertImpl(unsigned char v, + FormatConversionSpecImpl conv, + FormatSinkImpl* sink); // Ints. IntegralConvertResult FormatConvertImpl(short v, // NOLINT @@ -228,9 +315,16 @@ IntegralConvertResult FormatConvertImpl(int128 v, FormatConversionSpecImpl conv, IntegralConvertResult FormatConvertImpl(uint128 v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); + +// This function needs to be a template due to ambiguity regarding type +// conversions. template <typename T, enable_if_t<std::is_same<T, bool>::value, int> = 0> IntegralConvertResult FormatConvertImpl(T v, FormatConversionSpecImpl conv, FormatSinkImpl* sink) { + if (conv.conversion_char() == FormatConversionCharInternal::v) { + return {ConvertBoolArg(v, sink)}; + } + return FormatConvertImpl(static_cast<int>(v), conv, sink); } @@ -238,7 +332,8 @@ IntegralConvertResult FormatConvertImpl(T v, FormatConversionSpecImpl conv, // FormatArgImpl will use the underlying Convert functions instead. template <typename T> typename std::enable_if<std::is_enum<T>::value && - !HasUserDefinedConvert<T>::value, + !HasUserDefinedConvert<T>::value && + !strings_internal::HasAbslStringify<T>::value, IntegralConvertResult>::type FormatConvertImpl(T v, FormatConversionSpecImpl conv, FormatSinkImpl* sink); @@ -301,11 +396,11 @@ struct FormatArgImplFriend { template <typename Arg> constexpr FormatConversionCharSet ArgumentToConv() { - return absl::str_format_internal::ExtractCharSet( - decltype(str_format_internal::FormatConvertImpl( - std::declval<const Arg&>(), - std::declval<const FormatConversionSpecImpl&>(), - std::declval<FormatSinkImpl*>())){}); + using ConvResult = decltype(str_format_internal::FormatConvertImpl( + std::declval<const Arg&>(), + std::declval<const FormatConversionSpecImpl&>(), + std::declval<FormatSinkImpl*>())); + return absl::str_format_internal::ExtractCharSet(ConvResult{}); } // A type-erased handle to a format argument. @@ -351,7 +446,8 @@ class FormatArgImpl { template <typename T, typename = void> struct DecayType { static constexpr bool kHasUserDefined = - str_format_internal::HasUserDefinedConvert<T>::value; + str_format_internal::HasUserDefinedConvert<T>::value || + strings_internal::HasAbslStringify<T>::value; using type = typename std::conditional< !kHasUserDefined && std::is_convertible<T, const char*>::value, const char*, @@ -363,6 +459,7 @@ class FormatArgImpl { 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; }; diff --git a/absl/strings/internal/str_format/bind.cc b/absl/strings/internal/str_format/bind.cc index c988ba8f..77a42223 100644 --- a/absl/strings/internal/str_format/bind.cc +++ b/absl/strings/internal/str_format/bind.cc @@ -32,7 +32,8 @@ inline bool BindFromPosition(int position, int* value, return false; } // -1 because positions are 1-based - return FormatArgImplFriend::ToInt(pack[position - 1], value); + return FormatArgImplFriend::ToInt(pack[static_cast<size_t>(position) - 1], + value); } class ArgContext { @@ -56,7 +57,7 @@ inline bool ArgContext::Bind(const UnboundConversion* unbound, const FormatArgImpl* arg = nullptr; int arg_position = unbound->arg_position; if (static_cast<size_t>(arg_position - 1) >= pack_.size()) return false; - arg = &pack_[arg_position - 1]; // 1-based + arg = &pack_[static_cast<size_t>(arg_position - 1)]; // 1-based if (unbound->flags != Flags::kBasic) { int width = unbound->width.value(); diff --git a/absl/strings/internal/str_format/bind.h b/absl/strings/internal/str_format/bind.h index 80f29654..b73c5028 100644 --- a/absl/strings/internal/str_format/bind.h +++ b/absl/strings/internal/str_format/bind.h @@ -235,9 +235,10 @@ class StreamedWrapper { private: template <typename S> - friend ArgConvertResult<FormatConversionCharSetInternal::s> FormatConvertImpl( - const StreamedWrapper<S>& v, FormatConversionSpecImpl conv, - FormatSinkImpl* out); + friend ArgConvertResult<FormatConversionCharSetUnion( + FormatConversionCharSetInternal::s, FormatConversionCharSetInternal::v)> + FormatConvertImpl(const StreamedWrapper<S>& v, FormatConversionSpecImpl conv, + FormatSinkImpl* out); const T& v_; }; diff --git a/absl/strings/internal/str_format/checker.h b/absl/strings/internal/str_format/checker.h index 4fd19d13..eab6ab9d 100644 --- a/absl/strings/internal/str_format/checker.h +++ b/absl/strings/internal/str_format/checker.h @@ -15,8 +15,11 @@ #ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_ #define ABSL_STRINGS_INTERNAL_STR_FORMAT_CHECKER_H_ +#include <algorithm> + #include "absl/base/attributes.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" // Compile time check support for entry points. @@ -36,297 +39,56 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace str_format_internal { -constexpr bool AllOf() { return true; } - -template <typename... T> -constexpr bool AllOf(bool b, T... t) { - return b && AllOf(t...); -} - #ifdef ABSL_INTERNAL_ENABLE_FORMAT_CHECKER -constexpr bool ContainsChar(const char* chars, char c) { - return *chars == c || (*chars && ContainsChar(chars + 1, c)); -} - -// A constexpr compatible list of Convs. -struct ConvList { - const FormatConversionCharSet* array; - int count; - - // We do the bound check here to avoid having to do it on the callers. - // Returning an empty FormatConversionCharSet has the same effect as - // short circuiting because it will never match any conversion. - constexpr FormatConversionCharSet operator[](int i) const { - return i < count ? array[i] : FormatConversionCharSet{}; - } - - constexpr ConvList without_front() const { - return count != 0 ? ConvList{array + 1, count - 1} : *this; - } -}; - -template <size_t count> -struct ConvListT { - // Make sure the array has size > 0. - FormatConversionCharSet list[count ? count : 1]; -}; - -constexpr char GetChar(string_view str, size_t index) { - return index < str.size() ? str[index] : char{}; -} - -constexpr string_view ConsumeFront(string_view str, size_t len = 1) { - return len <= str.size() ? string_view(str.data() + len, str.size() - len) - : string_view(); -} - -constexpr string_view ConsumeAnyOf(string_view format, const char* chars) { - return ContainsChar(chars, GetChar(format, 0)) - ? ConsumeAnyOf(ConsumeFront(format), chars) - : format; -} - -constexpr bool IsDigit(char c) { return c >= '0' && c <= '9'; } - -// Helper class for the ParseDigits function. -// It encapsulates the two return values we need there. -struct Integer { - string_view format; - int value; - - // If the next character is a '$', consume it. - // Otherwise, make `this` an invalid positional argument. - constexpr Integer ConsumePositionalDollar() const { - return GetChar(format, 0) == '$' ? Integer{ConsumeFront(format), value} - : Integer{format, 0}; - } -}; - -constexpr Integer ParseDigits(string_view format, int value = 0) { - return IsDigit(GetChar(format, 0)) - ? ParseDigits(ConsumeFront(format), - 10 * value + GetChar(format, 0) - '0') - : Integer{format, value}; -} - -// Parse digits for a positional argument. -// The parsing also consumes the '$'. -constexpr Integer ParsePositional(string_view format) { - return ParseDigits(format).ConsumePositionalDollar(); -} - -// Parses a single conversion specifier. -// See ConvParser::Run() for post conditions. -class ConvParser { - constexpr ConvParser SetFormat(string_view format) const { - return ConvParser(format, args_, error_, arg_position_, is_positional_); - } - - constexpr ConvParser SetArgs(ConvList args) const { - return ConvParser(format_, args, error_, arg_position_, is_positional_); - } - - constexpr ConvParser SetError(bool error) const { - return ConvParser(format_, args_, error_ || error, arg_position_, - is_positional_); - } - - constexpr ConvParser SetArgPosition(int arg_position) const { - return ConvParser(format_, args_, error_, arg_position, is_positional_); - } - - // Consumes the next arg and verifies that it matches `conv`. - // `error_` is set if there is no next arg or if it doesn't match `conv`. - constexpr ConvParser ConsumeNextArg(char conv) const { - return SetArgs(args_.without_front()).SetError(!Contains(args_[0], conv)); - } - - // Verify that positional argument `i.value` matches `conv`. - // `error_` is set if `i.value` is not a valid argument or if it doesn't - // match. - constexpr ConvParser VerifyPositional(Integer i, char conv) const { - return SetFormat(i.format).SetError(!Contains(args_[i.value - 1], conv)); - } - - // Parse the position of the arg and store it in `arg_position_`. - constexpr ConvParser ParseArgPosition(Integer arg) const { - return SetFormat(arg.format).SetArgPosition(arg.value); - } - - // Consume the flags. - constexpr ConvParser ParseFlags() const { - return SetFormat(ConsumeAnyOf(format_, "-+ #0")); - } - - // Consume the width. - // If it is '*', we verify that it matches `args_`. `error_` is set if it - // doesn't match. - constexpr ConvParser ParseWidth() const { - return IsDigit(GetChar(format_, 0)) - ? SetFormat(ParseDigits(format_).format) - : GetChar(format_, 0) == '*' - ? is_positional_ - ? VerifyPositional( - ParsePositional(ConsumeFront(format_)), '*') - : SetFormat(ConsumeFront(format_)) - .ConsumeNextArg('*') - : *this; - } - - // Consume the precision. - // If it is '*', we verify that it matches `args_`. `error_` is set if it - // doesn't match. - constexpr ConvParser ParsePrecision() const { - return GetChar(format_, 0) != '.' - ? *this - : GetChar(format_, 1) == '*' - ? is_positional_ - ? VerifyPositional( - ParsePositional(ConsumeFront(format_, 2)), '*') - : SetFormat(ConsumeFront(format_, 2)) - .ConsumeNextArg('*') - : SetFormat(ParseDigits(ConsumeFront(format_)).format); - } - - // Consume the length characters. - constexpr ConvParser ParseLength() const { - return SetFormat(ConsumeAnyOf(format_, "lLhjztq")); - } - - // Consume the conversion character and verify that it matches `args_`. - // `error_` is set if it doesn't match. - constexpr ConvParser ParseConversion() const { - return is_positional_ - ? VerifyPositional({ConsumeFront(format_), arg_position_}, - GetChar(format_, 0)) - : ConsumeNextArg(GetChar(format_, 0)) - .SetFormat(ConsumeFront(format_)); - } - - constexpr ConvParser(string_view format, ConvList args, bool error, - int arg_position, bool is_positional) - : format_(format), - args_(args), - error_(error), - arg_position_(arg_position), - is_positional_(is_positional) {} - - public: - constexpr ConvParser(string_view format, ConvList args, bool is_positional) - : format_(format), - args_(args), - error_(false), - arg_position_(0), - is_positional_(is_positional) {} - - // Consume the whole conversion specifier. - // `format()` will be set to the character after the conversion character. - // `error()` will be set if any of the arguments do not match. - constexpr ConvParser Run() const { - return (is_positional_ ? ParseArgPosition(ParsePositional(format_)) : *this) - .ParseFlags() - .ParseWidth() - .ParsePrecision() - .ParseLength() - .ParseConversion(); - } - - constexpr string_view format() const { return format_; } - constexpr ConvList args() const { return args_; } - constexpr bool error() const { return error_; } - constexpr bool is_positional() const { return is_positional_; } - - private: - string_view format_; - // Current list of arguments. If we are not in positional mode we will consume - // from the front. - ConvList args_; - bool error_; - // Holds the argument position of the conversion character, if we are in - // positional mode. Otherwise, it is unspecified. - int arg_position_; - // Whether we are in positional mode. - // It changes the behavior of '*' and where to find the converted argument. - bool is_positional_; -}; - -// Parses a whole format expression. -// See FormatParser::Run(). -class FormatParser { - static constexpr bool FoundPercent(string_view format) { - return format.empty() || - (GetChar(format, 0) == '%' && GetChar(format, 1) != '%'); - } - - // We use an inner function to increase the recursion limit. - // The inner function consumes up to `limit` characters on every run. - // This increases the limit from 512 to ~512*limit. - static constexpr string_view ConsumeNonPercentInner(string_view format, - int limit = 20) { - return FoundPercent(format) || !limit - ? format - : ConsumeNonPercentInner( - ConsumeFront(format, GetChar(format, 0) == '%' && - GetChar(format, 1) == '%' - ? 2 - : 1), - limit - 1); - } - - // Consume characters until the next conversion spec %. - // It skips %%. - static constexpr string_view ConsumeNonPercent(string_view format) { - return FoundPercent(format) - ? format - : ConsumeNonPercent(ConsumeNonPercentInner(format)); - } - - static constexpr bool IsPositional(string_view format) { - return IsDigit(GetChar(format, 0)) ? IsPositional(ConsumeFront(format)) - : GetChar(format, 0) == '$'; - } - - constexpr bool RunImpl(bool is_positional) const { - // In non-positional mode we require all arguments to be consumed. - // In positional mode just reaching the end of the format without errors is - // enough. - return (format_.empty() && (is_positional || args_.count == 0)) || - (!format_.empty() && - ValidateArg( - ConvParser(ConsumeFront(format_), args_, is_positional).Run())); - } - - constexpr bool ValidateArg(ConvParser conv) const { - return !conv.error() && FormatParser(conv.format(), conv.args()) - .RunImpl(conv.is_positional()); - } - - public: - constexpr FormatParser(string_view format, ConvList args) - : format_(ConsumeNonPercent(format)), args_(args) {} - - // Runs the parser for `format` and `args`. - // It verifies that the format is valid and that all conversion specifiers - // match the arguments passed. - // In non-positional mode it also verfies that all arguments are consumed. - constexpr bool Run() const { - return RunImpl(!format_.empty() && IsPositional(ConsumeFront(format_))); - } - - private: - string_view format_; - // Current list of arguments. - // If we are not in positional mode we will consume from the front and will - // have to be empty in the end. - ConvList args_; -}; - template <FormatConversionCharSet... C> constexpr bool ValidFormatImpl(string_view format) { - return FormatParser(format, - {ConvListT<sizeof...(C)>{{C...}}.list, sizeof...(C)}) - .Run(); + int next_arg = 0; + const char* p = format.data(); + const char* const end = p + format.size(); + constexpr FormatConversionCharSet + kAllowedConvs[(std::max)(sizeof...(C), size_t{1})] = {C...}; + bool used[(std::max)(sizeof...(C), size_t{1})]{}; + constexpr int kNumArgs = sizeof...(C); + while (p != end) { + while (p != end && *p != '%') ++p; + if (p == end) { + break; + } + if (p + 1 >= end) return false; + if (p[1] == '%') { + // %% + p += 2; + continue; + } + + UnboundConversion conv(absl::kConstInit); + p = ConsumeUnboundConversion(p + 1, end, &conv, &next_arg); + if (p == nullptr) return false; + if (conv.arg_position <= 0 || conv.arg_position > kNumArgs) { + return false; + } + if (!Contains(kAllowedConvs[conv.arg_position - 1], conv.conv)) { + return false; + } + used[conv.arg_position - 1] = true; + for (auto extra : {conv.width, conv.precision}) { + if (extra.is_from_arg()) { + int pos = extra.get_from_arg(); + if (pos <= 0 || pos > kNumArgs) return false; + used[pos - 1] = true; + if (!Contains(kAllowedConvs[pos - 1], '*')) { + return false; + } + } + } + } + if (sizeof...(C) != 0) { + for (bool b : used) { + if (!b) return false; + } + } + return true; } #endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER diff --git a/absl/strings/internal/str_format/checker_test.cc b/absl/strings/internal/str_format/checker_test.cc index 7c70f47d..a86bed38 100644 --- a/absl/strings/internal/str_format/checker_test.cc +++ b/absl/strings/internal/str_format/checker_test.cc @@ -39,16 +39,16 @@ std::string ConvToString(FormatConversionCharSet conv) { TEST(StrFormatChecker, ArgumentToConv) { FormatConversionCharSet conv = ArgumentToConv<std::string>(); - EXPECT_EQ(ConvToString(conv), "s"); + EXPECT_EQ(ConvToString(conv), "sv"); conv = ArgumentToConv<const char*>(); EXPECT_EQ(ConvToString(conv), "sp"); conv = ArgumentToConv<double>(); - EXPECT_EQ(ConvToString(conv), "fFeEgGaA"); + EXPECT_EQ(ConvToString(conv), "fFeEgGaAv"); conv = ArgumentToConv<int>(); - EXPECT_EQ(ConvToString(conv), "cdiouxXfFeEgGaA*"); + EXPECT_EQ(ConvToString(conv), "cdiouxXfFeEgGaAv*"); conv = ArgumentToConv<std::string*>(); EXPECT_EQ(ConvToString(conv), "p"); @@ -93,6 +93,7 @@ TEST(StrFormatChecker, ValidFormat) { ValidFormat<void (*)(), volatile int*>("%p %p"), // ValidFormat<string_view, const char*, double, void*>( "string_view=%s const char*=%s double=%f void*=%p)"), + ValidFormat<int>("%v"), // ValidFormat<int>("%% %1$d"), // ValidFormat<int>("%1$ld"), // @@ -109,7 +110,9 @@ TEST(StrFormatChecker, ValidFormat) { ValidFormat<int, double>("%2$.*1$f"), // ValidFormat<void*, string_view, const char*, double>( "string_view=%2$s const char*=%3$s double=%4$f void*=%1$p " - "repeat=%3$s)")}; + "repeat=%3$s)"), + ValidFormat<std::string>("%1$v"), + }; for (Case c : trues) { EXPECT_TRUE(c.result) << c.format; @@ -130,6 +133,8 @@ TEST(StrFormatChecker, ValidFormat) { ValidFormat<int>("%*d"), // ValidFormat<std::string>("%p"), // ValidFormat<int (*)(int)>("%d"), // + ValidFormat<int>("%1v"), // + ValidFormat<int>("%.1v"), // ValidFormat<>("%3$d"), // ValidFormat<>("%1$r"), // @@ -138,13 +143,14 @@ TEST(StrFormatChecker, ValidFormat) { ValidFormat<int>("%1$*2$1d"), // ValidFormat<int>("%1$1-d"), // ValidFormat<std::string, int>("%2$*1$s"), // - ValidFormat<std::string>("%1$p"), + ValidFormat<std::string>("%1$p"), // + ValidFormat<int>("%1$*2$v"), // ValidFormat<int, int>("%d %2$d"), // }; for (Case c : falses) { - EXPECT_FALSE(c.result) << c.format; + EXPECT_FALSE(c.result) << "format<" << c.format << ">"; } } diff --git a/absl/strings/internal/str_format/constexpr_parser.h b/absl/strings/internal/str_format/constexpr_parser.h new file mode 100644 index 00000000..3dc1776b --- /dev/null +++ b/absl/strings/internal/str_format/constexpr_parser.h @@ -0,0 +1,351 @@ +// Copyright 2022 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_STRINGS_INTERNAL_STR_FORMAT_CONSTEXPR_PARSER_H_ +#define ABSL_STRINGS_INTERNAL_STR_FORMAT_CONSTEXPR_PARSER_H_ + +#include <cassert> +#include <cstdint> +#include <limits> + +#include "absl/base/const_init.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 + // initialization of parts of the object that are not necessary. + UnboundConversion() {} // NOLINT + + // This constructor is provided for the static checker. We don't want to do + // the unnecessary initialization in the normal case. + explicit constexpr UnboundConversion(absl::ConstInitType) + : arg_position{}, width{}, precision{} {} + + class InputValue { + public: + constexpr void set_value(int value) { + assert(value >= 0); + value_ = value; + } + constexpr int value() const { return value_; } + + // Marks the value as "from arg". aka the '*' format. + // Requires `value >= 1`. + // When set, is_from_arg() return true and get_from_arg() returns the + // original value. + // `value()`'s return value is unspecified in this state. + constexpr void set_from_arg(int value) { + assert(value > 0); + value_ = -value - 1; + } + constexpr bool is_from_arg() const { return value_ < -1; } + constexpr int get_from_arg() const { + assert(is_from_arg()); + return -value_ - 1; + } + + private: + int value_ = -1; + }; + + // No need to initialize. It will always be set in the parser. + int arg_position; + + InputValue width; + InputValue precision; + + Flags flags = Flags::kBasic; + LengthMod length_mod = LengthMod::none; + FormatConversionChar conv = FormatConversionCharInternal::kNone; +}; + +// Helper tag class for the table below. +// It allows fast `char -> ConversionChar/LengthMod/Flags` checking and +// conversions. +class ConvTag { + public: + constexpr ConvTag(FormatConversionChar conversion_char) // NOLINT + : tag_(static_cast<uint8_t>(conversion_char)) {} + constexpr ConvTag(LengthMod length_mod) // NOLINT + : tag_(0x80 | static_cast<uint8_t>(length_mod)) {} + constexpr ConvTag(Flags flags) // NOLINT + : tag_(0xc0 | static_cast<uint8_t>(flags)) {} + constexpr ConvTag() : tag_(0xFF) {} + + constexpr bool is_conv() const { return (tag_ & 0x80) == 0; } + constexpr bool is_length() const { return (tag_ & 0xC0) == 0x80; } + constexpr bool is_flags() const { return (tag_ & 0xE0) == 0xC0; } + + constexpr FormatConversionChar as_conv() const { + assert(is_conv()); + assert(!is_length()); + assert(!is_flags()); + return static_cast<FormatConversionChar>(tag_); + } + constexpr LengthMod as_length() const { + assert(!is_conv()); + assert(is_length()); + assert(!is_flags()); + return static_cast<LengthMod>(tag_ & 0x3F); + } + constexpr Flags as_flags() const { + assert(!is_conv()); + assert(!is_length()); + assert(is_flags()); + return static_cast<Flags>(tag_ & 0x1F); + } + + private: + uint8_t tag_; +}; + +struct ConvTagHolder { + using CC = FormatConversionCharInternal; + using LM = LengthMod; + + // Abbreviations to fit in the table below. + static constexpr auto kFSign = Flags::kSignCol; + static constexpr auto kFAlt = Flags::kAlt; + static constexpr auto kFPos = Flags::kShowPos; + static constexpr auto kFLeft = Flags::kLeft; + static constexpr auto kFZero = Flags::kZero; + + static constexpr ConvTag value[256] = { + {}, {}, {}, {}, {}, {}, {}, {}, // 00-07 + {}, {}, {}, {}, {}, {}, {}, {}, // 08-0f + {}, {}, {}, {}, {}, {}, {}, {}, // 10-17 + {}, {}, {}, {}, {}, {}, {}, {}, // 18-1f + kFSign, {}, {}, kFAlt, {}, {}, {}, {}, // !"#$%&' + {}, {}, {}, kFPos, {}, kFLeft, {}, {}, // ()*+,-./ + kFZero, {}, {}, {}, {}, {}, {}, {}, // 01234567 + {}, {}, {}, {}, {}, {}, {}, {}, // 89:;<=>? + {}, CC::A, {}, {}, {}, CC::E, CC::F, CC::G, // @ABCDEFG + {}, {}, {}, {}, LM::L, {}, {}, {}, // HIJKLMNO + {}, {}, {}, {}, {}, {}, {}, {}, // PQRSTUVW + CC::X, {}, {}, {}, {}, {}, {}, {}, // XYZ[\]^_ + {}, CC::a, {}, CC::c, CC::d, CC::e, CC::f, CC::g, // `abcdefg + LM::h, CC::i, LM::j, {}, LM::l, {}, CC::n, CC::o, // hijklmno + CC::p, LM::q, {}, CC::s, LM::t, CC::u, CC::v, {}, // pqrstuvw + CC::x, {}, LM::z, {}, {}, {}, {}, {}, // xyz{|}! + {}, {}, {}, {}, {}, {}, {}, {}, // 80-87 + {}, {}, {}, {}, {}, {}, {}, {}, // 88-8f + {}, {}, {}, {}, {}, {}, {}, {}, // 90-97 + {}, {}, {}, {}, {}, {}, {}, {}, // 98-9f + {}, {}, {}, {}, {}, {}, {}, {}, // a0-a7 + {}, {}, {}, {}, {}, {}, {}, {}, // a8-af + {}, {}, {}, {}, {}, {}, {}, {}, // b0-b7 + {}, {}, {}, {}, {}, {}, {}, {}, // b8-bf + {}, {}, {}, {}, {}, {}, {}, {}, // c0-c7 + {}, {}, {}, {}, {}, {}, {}, {}, // c8-cf + {}, {}, {}, {}, {}, {}, {}, {}, // d0-d7 + {}, {}, {}, {}, {}, {}, {}, {}, // d8-df + {}, {}, {}, {}, {}, {}, {}, {}, // e0-e7 + {}, {}, {}, {}, {}, {}, {}, {}, // e8-ef + {}, {}, {}, {}, {}, {}, {}, {}, // f0-f7 + {}, {}, {}, {}, {}, {}, {}, {}, // f8-ff + }; +}; + +// Keep a single table for all the conversion chars and length modifiers. +constexpr ConvTag GetTagForChar(char c) { + return ConvTagHolder::value[static_cast<unsigned char>(c)]; +} + +constexpr bool CheckFastPathSetting(const UnboundConversion& conv) { + bool width_precision_needed = + conv.width.value() >= 0 || conv.precision.value() >= 0; + if (width_precision_needed && conv.flags == Flags::kBasic) { +#if defined(__clang__) + // Some compilers complain about this in constexpr even when not executed, + // so only enable the error dump in clang. + fprintf(stderr, + "basic=%d left=%d show_pos=%d sign_col=%d alt=%d zero=%d " + "width=%d precision=%d\n", + conv.flags == Flags::kBasic ? 1 : 0, + FlagsContains(conv.flags, Flags::kLeft) ? 1 : 0, + FlagsContains(conv.flags, Flags::kShowPos) ? 1 : 0, + FlagsContains(conv.flags, Flags::kSignCol) ? 1 : 0, + FlagsContains(conv.flags, Flags::kAlt) ? 1 : 0, + FlagsContains(conv.flags, Flags::kZero) ? 1 : 0, conv.width.value(), + conv.precision.value()); +#endif // defined(__clang__) + return false; + } + return true; +} + +constexpr int ParseDigits(char& c, const char*& pos, const char* const end) { + int digits = c - '0'; + // We do not want to overflow `digits` so we consume at most digits10 + // digits. If there are more digits the parsing will fail later on when the + // digit doesn't match the expected characters. + int num_digits = std::numeric_limits<int>::digits10; + for (;;) { + if (ABSL_PREDICT_FALSE(pos == end)) break; + c = *pos++; + if ('0' > c || c > '9') break; + --num_digits; + if (ABSL_PREDICT_FALSE(!num_digits)) break; + digits = 10 * digits + c - '0'; + } + return digits; +} + +template <bool is_positional> +constexpr const char* ConsumeConversion(const char* pos, const char* const end, + UnboundConversion* conv, + int* next_arg) { + const char* const original_pos = pos; + char c = 0; + // Read the next char into `c` and update `pos`. Returns false if there are + // no more chars to read. +#define ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR() \ + do { \ + if (ABSL_PREDICT_FALSE(pos == end)) return nullptr; \ + c = *pos++; \ + } while (0) + + if (is_positional) { + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr; + conv->arg_position = ParseDigits(c, pos, end); + assert(conv->arg_position > 0); + if (ABSL_PREDICT_FALSE(c != '$')) return nullptr; + } + + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + + // We should start with the basic flag on. + assert(conv->flags == Flags::kBasic); + + // Any non alpha character makes this conversion not basic. + // This includes flags (-+ #0), width (1-9, *) or precision (.). + // All conversion characters and length modifiers are alpha characters. + if (c < 'A') { + while (c <= '0') { + auto tag = GetTagForChar(c); + if (tag.is_flags()) { + conv->flags = conv->flags | tag.as_flags(); + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + } else { + break; + } + } + + if (c <= '9') { + if (c >= '0') { + int maybe_width = ParseDigits(c, pos, end); + if (!is_positional && c == '$') { + if (ABSL_PREDICT_FALSE(*next_arg != 0)) return nullptr; + // Positional conversion. + *next_arg = -1; + return ConsumeConversion<true>(original_pos, end, conv, next_arg); + } + conv->flags = conv->flags | Flags::kNonBasic; + conv->width.set_value(maybe_width); + } else if (c == '*') { + conv->flags = conv->flags | Flags::kNonBasic; + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + if (is_positional) { + if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr; + conv->width.set_from_arg(ParseDigits(c, pos, end)); + if (ABSL_PREDICT_FALSE(c != '$')) return nullptr; + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + } else { + conv->width.set_from_arg(++*next_arg); + } + } + } + + if (c == '.') { + conv->flags = conv->flags | Flags::kNonBasic; + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + if ('0' <= c && c <= '9') { + conv->precision.set_value(ParseDigits(c, pos, end)); + } else if (c == '*') { + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + if (is_positional) { + if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr; + conv->precision.set_from_arg(ParseDigits(c, pos, end)); + if (c != '$') return nullptr; + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + } else { + conv->precision.set_from_arg(++*next_arg); + } + } else { + conv->precision.set_value(0); + } + } + } + + auto tag = GetTagForChar(c); + + if (ABSL_PREDICT_FALSE(c == 'v' && conv->flags != Flags::kBasic)) { + return nullptr; + } + + if (ABSL_PREDICT_FALSE(!tag.is_conv())) { + 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) { + conv->length_mod = LengthMod::hh; + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + } else if (c == 'l' && length_mod == LengthMod::l) { + conv->length_mod = LengthMod::ll; + ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); + } else { + conv->length_mod = length_mod; + } + tag = GetTagForChar(c); + + if (ABSL_PREDICT_FALSE(c == 'v')) return nullptr; + if (ABSL_PREDICT_FALSE(!tag.is_conv())) return nullptr; + } + + assert(CheckFastPathSetting(*conv)); + (void)(&CheckFastPathSetting); + + conv->conv = tag.as_conv(); + if (!is_positional) conv->arg_position = ++*next_arg; + return pos; +} + +// Consume conversion spec prefix (not including '%') of [p, end) if valid. +// Examples of valid specs would be e.g.: "s", "d", "-12.6f". +// If valid, it returns the first character following the conversion spec, +// and the spec part is broken down and returned in 'conv'. +// If invalid, returns nullptr. +constexpr const char* ConsumeUnboundConversion(const char* p, const char* end, + UnboundConversion* conv, + int* next_arg) { + if (*next_arg < 0) return ConsumeConversion<true>(p, end, conv, next_arg); + return ConsumeConversion<false>(p, end, conv, next_arg); +} + +} // namespace str_format_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_STR_FORMAT_CONSTEXPR_PARSER_H_ diff --git a/absl/strings/internal/str_format/extension.cc b/absl/strings/internal/str_format/extension.cc index f93153d5..2a0ceb13 100644 --- a/absl/strings/internal/str_format/extension.cc +++ b/absl/strings/internal/str_format/extension.cc @@ -58,7 +58,8 @@ constexpr FormatConversionCharSet FormatConversionCharSetInternal::kPointer; bool FormatSinkImpl::PutPaddedString(string_view value, int width, int precision, bool left) { size_t space_remaining = 0; - if (width >= 0) space_remaining = width; + if (width >= 0) + space_remaining = static_cast<size_t>(width); size_t n = value.size(); if (precision >= 0) n = std::min(n, static_cast<size_t>(precision)); string_view shown(value.data(), n); diff --git a/absl/strings/internal/str_format/extension.h b/absl/strings/internal/str_format/extension.h index 55e8ac88..603bd49d 100644 --- a/absl/strings/internal/str_format/extension.h +++ b/absl/strings/internal/str_format/extension.h @@ -169,7 +169,7 @@ inline std::ostream& operator<<(std::ostream& os, Flags v) { X_VAL(f) X_SEP X_VAL(F) X_SEP X_VAL(e) X_SEP X_VAL(E) X_SEP \ X_VAL(g) X_SEP X_VAL(G) X_SEP X_VAL(a) X_SEP X_VAL(A) X_SEP \ /* misc */ \ - X_VAL(n) X_SEP X_VAL(p) + X_VAL(n) X_SEP X_VAL(p) X_SEP X_VAL(v) // clang-format on // This type should not be referenced, it exists only to provide labels @@ -191,7 +191,7 @@ struct FormatConversionCharInternal { c, s, // text d, i, o, u, x, X, // int f, F, e, E, g, G, a, A, // float - n, p, // misc + n, p, v, // misc kNone }; // clang-format on @@ -292,6 +292,8 @@ class FormatConversionSpecImpl { return conv_; } + void set_conversion_char(FormatConversionChar c) { conv_ = c; } + // Returns the specified width. If width is unspecfied, it returns a negative // value. int width() const { return width_; } diff --git a/absl/strings/internal/str_format/extension_test.cc b/absl/strings/internal/str_format/extension_test.cc index 1c93fdb1..694c1264 100644 --- a/absl/strings/internal/str_format/extension_test.cc +++ b/absl/strings/internal/str_format/extension_test.cc @@ -19,6 +19,7 @@ #include <random> #include <string> +#include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/strings/str_format.h" #include "absl/strings/string_view.h" @@ -95,4 +96,14 @@ TEST(FormatExtensionTest, VerifyEnumEquality) { #undef X_VAL } +TEST(FormatExtensionTest, SetConversionChar) { + absl::str_format_internal::FormatConversionSpecImpl spec; + EXPECT_EQ(spec.conversion_char(), + absl::str_format_internal::FormatConversionCharInternal::kNone); + spec.set_conversion_char( + absl::str_format_internal::FormatConversionCharInternal::d); + EXPECT_EQ(spec.conversion_char(), + absl::str_format_internal::FormatConversionCharInternal::d); +} + } // namespace diff --git a/absl/strings/internal/str_format/float_conversion.cc b/absl/strings/internal/str_format/float_conversion.cc index b1c40684..8e497852 100644 --- a/absl/strings/internal/str_format/float_conversion.cc +++ b/absl/strings/internal/str_format/float_conversion.cc @@ -92,27 +92,30 @@ class StackArray { // Calculates `10 * (*v) + carry` and stores the result in `*v` and returns // the carry. +// Requires: `0 <= carry <= 9` template <typename Int> -inline Int MultiplyBy10WithCarry(Int *v, Int carry) { +inline char MultiplyBy10WithCarry(Int* v, char carry) { using BiggerInt = absl::conditional_t<sizeof(Int) == 4, uint64_t, uint128>; - BiggerInt tmp = 10 * static_cast<BiggerInt>(*v) + carry; + BiggerInt tmp = + 10 * static_cast<BiggerInt>(*v) + static_cast<BiggerInt>(carry); *v = static_cast<Int>(tmp); - return static_cast<Int>(tmp >> (sizeof(Int) * 8)); + return static_cast<char>(tmp >> (sizeof(Int) * 8)); } // Calculates `(2^64 * carry + *v) / 10`. // Stores the quotient in `*v` and returns the remainder. // Requires: `0 <= carry <= 9` -inline uint64_t DivideBy10WithCarry(uint64_t *v, uint64_t carry) { +inline char DivideBy10WithCarry(uint64_t* v, char carry) { constexpr uint64_t divisor = 10; // 2^64 / divisor = chunk_quotient + chunk_remainder / divisor constexpr uint64_t chunk_quotient = (uint64_t{1} << 63) / (divisor / 2); constexpr uint64_t chunk_remainder = uint64_t{} - chunk_quotient * divisor; + const uint64_t carry_u64 = static_cast<uint64_t>(carry); const uint64_t mod = *v % divisor; - const uint64_t next_carry = chunk_remainder * carry + mod; - *v = *v / divisor + carry * chunk_quotient + next_carry / divisor; - return next_carry % divisor; + const uint64_t next_carry = chunk_remainder * carry_u64 + mod; + *v = *v / divisor + carry_u64 * chunk_quotient + next_carry / divisor; + return static_cast<char>(next_carry % divisor); } using MaxFloatType = @@ -125,11 +128,11 @@ using MaxFloatType = // // Requires `0 <= exp` and `exp <= numeric_limits<MaxFloatType>::max_exponent`. class BinaryToDecimal { - static constexpr int ChunksNeeded(int exp) { + static constexpr size_t ChunksNeeded(int exp) { // We will left shift a uint128 by `exp` bits, so we need `128+exp` total // bits. Round up to 32. // See constructor for details about adding `10%` to the value. - return (128 + exp + 31) / 32 * 11 / 10; + return static_cast<size_t>((128 + exp + 31) / 32 * 11 / 10); } public: @@ -140,7 +143,7 @@ class BinaryToDecimal { assert(exp > 0); assert(exp <= std::numeric_limits<MaxFloatType>::max_exponent); static_assert( - static_cast<int>(StackArray::kMaxCapacity) >= + StackArray::kMaxCapacity >= ChunksNeeded(std::numeric_limits<MaxFloatType>::max_exponent), ""); @@ -149,9 +152,9 @@ class BinaryToDecimal { [=](absl::Span<uint32_t> input) { f(BinaryToDecimal(input, v, exp)); }); } - int TotalDigits() const { - return static_cast<int>((decimal_end_ - decimal_start_) * kDigitsPerChunk + - CurrentDigits().size()); + size_t TotalDigits() const { + return (decimal_end_ - decimal_start_) * kDigitsPerChunk + + CurrentDigits().size(); } // See the current block of digits. @@ -190,30 +193,31 @@ class BinaryToDecimal { // the decimal representation is around 7% less efficient in space than the // binary one. We allocate an extra 10% memory to account for this. See // ChunksNeeded for this calculation. - int chunk_index = exp / 32; + size_t after_chunk_index = static_cast<size_t>(exp / 32 + 1); decimal_start_ = decimal_end_ = ChunksNeeded(exp); const int offset = exp % 32; // Left shift v by exp bits. - data_[chunk_index] = static_cast<uint32_t>(v << offset); + data_[after_chunk_index - 1] = static_cast<uint32_t>(v << offset); for (v >>= (32 - offset); v; v >>= 32) - data_[++chunk_index] = static_cast<uint32_t>(v); + data_[++after_chunk_index - 1] = static_cast<uint32_t>(v); - while (chunk_index >= 0) { + while (after_chunk_index > 0) { // While we have more than one chunk available, go in steps of 1e9. - // `data_[chunk_index]` holds the highest non-zero binary chunk, so keep - // the variable updated. + // `data_[after_chunk_index - 1]` holds the highest non-zero binary chunk, + // so keep the variable updated. uint32_t carry = 0; - for (int i = chunk_index; i >= 0; --i) { - uint64_t tmp = uint64_t{data_[i]} + (uint64_t{carry} << 32); - data_[i] = static_cast<uint32_t>(tmp / uint64_t{1000000000}); + for (size_t i = after_chunk_index; i > 0; --i) { + uint64_t tmp = uint64_t{data_[i - 1]} + (uint64_t{carry} << 32); + data_[i - 1] = static_cast<uint32_t>(tmp / uint64_t{1000000000}); carry = static_cast<uint32_t>(tmp % uint64_t{1000000000}); } // If the highest chunk is now empty, remove it from view. - if (data_[chunk_index] == 0) --chunk_index; + if (data_[after_chunk_index - 1] == 0) + --after_chunk_index; --decimal_start_; - assert(decimal_start_ != chunk_index); + assert(decimal_start_ != after_chunk_index - 1); data_[decimal_start_] = carry; } @@ -225,13 +229,13 @@ class BinaryToDecimal { } private: - static constexpr int kDigitsPerChunk = 9; + static constexpr size_t kDigitsPerChunk = 9; - int decimal_start_; - int decimal_end_; + size_t decimal_start_; + size_t decimal_end_; char digits_[kDigitsPerChunk]; - int size_ = 0; + size_t size_ = 0; absl::Span<uint32_t> data_; }; @@ -251,25 +255,26 @@ class FractionalDigitGenerator { static_assert(StackArray::kMaxCapacity >= (Limits::digits + 128 - Limits::min_exponent + 31) / 32, ""); - StackArray::RunWithCapacity((Limits::digits + exp + 31) / 32, - [=](absl::Span<uint32_t> input) { - f(FractionalDigitGenerator(input, v, exp)); - }); + StackArray::RunWithCapacity( + static_cast<size_t>((Limits::digits + exp + 31) / 32), + [=](absl::Span<uint32_t> input) { + f(FractionalDigitGenerator(input, v, exp)); + }); } // Returns true if there are any more non-zero digits left. - bool HasMoreDigits() const { return next_digit_ != 0 || chunk_index_ >= 0; } + bool HasMoreDigits() const { return next_digit_ != 0 || after_chunk_index_; } // Returns true if the remainder digits are greater than 5000... bool IsGreaterThanHalf() const { - return next_digit_ > 5 || (next_digit_ == 5 && chunk_index_ >= 0); + return next_digit_ > 5 || (next_digit_ == 5 && after_chunk_index_); } // Returns true if the remainder digits are exactly 5000... - bool IsExactlyHalf() const { return next_digit_ == 5 && chunk_index_ < 0; } + bool IsExactlyHalf() const { return next_digit_ == 5 && !after_chunk_index_; } struct Digits { - int digit_before_nine; - int num_nines; + char digit_before_nine; + size_t num_nines; }; // Get the next set of digits. @@ -288,35 +293,37 @@ class FractionalDigitGenerator { private: // Return the next digit. - int GetOneDigit() { - if (chunk_index_ < 0) return 0; + char GetOneDigit() { + if (!after_chunk_index_) + return 0; - uint32_t carry = 0; - for (int i = chunk_index_; i >= 0; --i) { - carry = MultiplyBy10WithCarry(&data_[i], carry); + char carry = 0; + for (size_t i = after_chunk_index_; i > 0; --i) { + carry = MultiplyBy10WithCarry(&data_[i - 1], carry); } // If the lowest chunk is now empty, remove it from view. - if (data_[chunk_index_] == 0) --chunk_index_; + if (data_[after_chunk_index_ - 1] == 0) + --after_chunk_index_; return carry; } FractionalDigitGenerator(absl::Span<uint32_t> data, uint128 v, int exp) - : chunk_index_(exp / 32), data_(data) { + : after_chunk_index_(static_cast<size_t>(exp / 32 + 1)), data_(data) { const int offset = exp % 32; // Right shift `v` by `exp` bits. - data_[chunk_index_] = static_cast<uint32_t>(v << (32 - offset)); + data_[after_chunk_index_ - 1] = static_cast<uint32_t>(v << (32 - offset)); v >>= offset; // Make sure we don't overflow the data. We already calculated that // non-zero bits fit, so we might not have space for leading zero bits. - for (int pos = chunk_index_; v; v >>= 32) + for (size_t pos = after_chunk_index_ - 1; v; v >>= 32) data_[--pos] = static_cast<uint32_t>(v); // Fill next_digit_, as GetDigits expects it to be populated always. next_digit_ = GetOneDigit(); } - int next_digit_; - int chunk_index_; + char next_digit_; + size_t after_chunk_index_; absl::Span<uint32_t> data_; }; @@ -362,7 +369,7 @@ char *PrintIntegralDigitsFromRightFast(uint128 v, char *p) { auto low = static_cast<uint64_t>(v); while (high != 0) { - uint64_t carry = DivideBy10WithCarry(&high, 0); + char carry = DivideBy10WithCarry(&high, 0); carry = DivideBy10WithCarry(&low, carry); *--p = carry + '0'; } @@ -373,13 +380,15 @@ char *PrintIntegralDigitsFromRightFast(uint128 v, char *p) { // shifting. // Performs rounding if necessary to fit within `precision`. // Returns the pointer to one after the last character written. -char *PrintFractionalDigitsFast(uint64_t v, char *start, int exp, - int precision) { +char* PrintFractionalDigitsFast(uint64_t v, + char* start, + int exp, + size_t precision) { char *p = start; v <<= (64 - exp); while (precision > 0) { if (!v) return p; - *p++ = MultiplyBy10WithCarry(&v, uint64_t{0}) + '0'; + *p++ = MultiplyBy10WithCarry(&v, 0) + '0'; --precision; } @@ -393,8 +402,6 @@ char *PrintFractionalDigitsFast(uint64_t v, char *start, int exp, RoundToEven(p - 1); } - assert(precision == 0); - // Precision can only be zero here. return p; } @@ -402,8 +409,10 @@ char *PrintFractionalDigitsFast(uint64_t v, char *start, int exp, // after shifting. // Performs rounding if necessary to fit within `precision`. // Returns the pointer to one after the last character written. -char *PrintFractionalDigitsFast(uint128 v, char *start, int exp, - int precision) { +char* PrintFractionalDigitsFast(uint128 v, + char* start, + int exp, + size_t precision) { char *p = start; v <<= (128 - exp); auto high = static_cast<uint64_t>(v >> 64); @@ -412,7 +421,7 @@ char *PrintFractionalDigitsFast(uint128 v, char *start, int exp, // While we have digits to print and `low` is not empty, do the long // multiplication. while (precision > 0 && low != 0) { - uint64_t carry = MultiplyBy10WithCarry(&low, uint64_t{0}); + char carry = MultiplyBy10WithCarry(&low, 0); carry = MultiplyBy10WithCarry(&high, carry); *p++ = carry + '0'; @@ -424,7 +433,7 @@ char *PrintFractionalDigitsFast(uint128 v, char *start, int exp, // above. while (precision > 0) { if (!high) return p; - *p++ = MultiplyBy10WithCarry(&high, uint64_t{0}) + '0'; + *p++ = MultiplyBy10WithCarry(&high, 0) + '0'; --precision; } @@ -438,14 +447,12 @@ char *PrintFractionalDigitsFast(uint128 v, char *start, int exp, RoundToEven(p - 1); } - assert(precision == 0); - // Precision can only be zero here. return p; } struct FormatState { char sign_char; - int precision; + size_t precision; const FormatConversionSpecImpl &conv; FormatSinkImpl *sink; @@ -455,9 +462,9 @@ struct FormatState { }; struct Padding { - int left_spaces; - int zeros; - int right_spaces; + size_t left_spaces; + size_t zeros; + size_t right_spaces; }; Padding ExtraWidthToPadding(size_t total_size, const FormatState &state) { @@ -465,7 +472,7 @@ Padding ExtraWidthToPadding(size_t total_size, const FormatState &state) { static_cast<size_t>(state.conv.width()) <= total_size) { return {0, 0, 0}; } - int missing_chars = state.conv.width() - total_size; + size_t missing_chars = static_cast<size_t>(state.conv.width()) - total_size; if (state.conv.has_left_flag()) { return {0, 0, missing_chars}; } else if (state.conv.has_zero_flag()) { @@ -475,8 +482,10 @@ Padding ExtraWidthToPadding(size_t total_size, const FormatState &state) { } } -void FinalPrint(const FormatState &state, absl::string_view data, - int padding_offset, int trailing_zeros, +void FinalPrint(const FormatState& state, + absl::string_view data, + size_t padding_offset, + size_t trailing_zeros, absl::string_view data_postfix) { if (state.conv.width() < 0) { // No width specified. Fast-path. @@ -487,10 +496,10 @@ void FinalPrint(const FormatState &state, absl::string_view data, return; } - auto padding = ExtraWidthToPadding((state.sign_char != '\0' ? 1 : 0) + - data.size() + data_postfix.size() + - static_cast<size_t>(trailing_zeros), - state); + auto padding = + ExtraWidthToPadding((state.sign_char != '\0' ? 1 : 0) + data.size() + + data_postfix.size() + trailing_zeros, + state); state.sink->Append(padding.left_spaces, ' '); if (state.sign_char != '\0') state.sink->Append(1, state.sign_char); @@ -547,15 +556,16 @@ void FormatFFast(Int v, int exp, const FormatState &state) { if (integral_digits_start[-1] != '0') --integral_digits_start; } - size_t size = fractional_digits_end - integral_digits_start; + size_t size = + static_cast<size_t>(fractional_digits_end - integral_digits_start); // In `alt` mode (flag #) we keep the `.` even if there are no fractional // digits. In non-alt mode, we strip it. if (!state.ShouldPrintDot()) --size; FinalPrint(state, absl::string_view(integral_digits_start, size), /*padding_offset=*/0, - static_cast<int>(state.precision - (fractional_digits_end - - fractional_digits_start)), + state.precision - static_cast<size_t>(fractional_digits_end - + fractional_digits_start), /*data_postfix=*/""); } @@ -567,21 +577,22 @@ void FormatFFast(Int v, int exp, const FormatState &state) { void FormatFPositiveExpSlow(uint128 v, int exp, const FormatState &state) { BinaryToDecimal::RunConversion(v, exp, [&](BinaryToDecimal btd) { const size_t total_digits = - btd.TotalDigits() + - (state.ShouldPrintDot() ? static_cast<size_t>(state.precision) + 1 : 0); + btd.TotalDigits() + (state.ShouldPrintDot() ? state.precision + 1 : 0); const auto padding = ExtraWidthToPadding( total_digits + (state.sign_char != '\0' ? 1 : 0), state); state.sink->Append(padding.left_spaces, ' '); - if (state.sign_char != '\0') state.sink->Append(1, state.sign_char); + if (state.sign_char != '\0') + state.sink->Append(1, state.sign_char); state.sink->Append(padding.zeros, '0'); do { state.sink->Append(btd.CurrentDigits()); } while (btd.AdvanceDigits()); - if (state.ShouldPrintDot()) state.sink->Append(1, '.'); + if (state.ShouldPrintDot()) + state.sink->Append(1, '.'); state.sink->Append(state.precision, '0'); state.sink->Append(padding.right_spaces, ' '); }); @@ -594,8 +605,7 @@ void FormatFPositiveExpSlow(uint128 v, int exp, const FormatState &state) { // digits. void FormatFNegativeExpSlow(uint128 v, int exp, const FormatState &state) { const size_t total_digits = - /* 0 */ 1 + - (state.ShouldPrintDot() ? static_cast<size_t>(state.precision) + 1 : 0); + /* 0 */ 1 + (state.ShouldPrintDot() ? state.precision + 1 : 0); auto padding = ExtraWidthToPadding(total_digits + (state.sign_char ? 1 : 0), state); padding.zeros += 1; @@ -606,7 +616,7 @@ void FormatFNegativeExpSlow(uint128 v, int exp, const FormatState &state) { if (state.ShouldPrintDot()) state.sink->Append(1, '.'); // Print digits - int digits_to_go = state.precision; + size_t digits_to_go = state.precision; FractionalDigitGenerator::RunConversion( v, exp, [&](FractionalDigitGenerator digit_gen) { @@ -666,7 +676,8 @@ void FormatFNegativeExpSlow(uint128 v, int exp, const FormatState &state) { template <typename Int> void FormatF(Int mantissa, int exp, const FormatState &state) { if (exp >= 0) { - const int total_bits = sizeof(Int) * 8 - LeadingZeros(mantissa) + exp; + const int total_bits = + static_cast<int>(sizeof(Int) * 8) - LeadingZeros(mantissa) + exp; // Fallback to the slow stack-based approach if we can't do it in a 64 or // 128 bit state. @@ -686,9 +697,9 @@ void FormatF(Int mantissa, int exp, const FormatState &state) { // Grab the group of four bits (nibble) from `n`. E.g., nibble 1 corresponds to // bits 4-7. template <typename Int> -uint8_t GetNibble(Int n, int nibble_index) { +uint8_t GetNibble(Int n, size_t nibble_index) { constexpr Int mask_low_nibble = Int{0xf}; - int shift = nibble_index * 4; + int shift = static_cast<int>(nibble_index * 4); n &= mask_low_nibble << shift; return static_cast<uint8_t>((n >> shift) & 0xf); } @@ -696,9 +707,9 @@ uint8_t GetNibble(Int n, int nibble_index) { // Add one to the given nibble, applying carry to higher nibbles. Returns true // if overflow, false otherwise. template <typename Int> -bool IncrementNibble(int nibble_index, Int *n) { - constexpr int kShift = sizeof(Int) * 8 - 1; - constexpr int kNumNibbles = sizeof(Int) * 8 / 4; +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, @@ -706,28 +717,32 @@ bool IncrementNibble(int nibble_index, Int *n) { // 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} << (nibble_index * 4))); + *n += ((nibble_index >= kNumNibbles) + ? 0 + : (Int{1} << static_cast<int>(nibble_index * 4))); Int after = *n >> kShift; return (before && !after) || (nibble_index >= kNumNibbles); } // Return a mask with 1's in the given nibble and all lower nibbles. template <typename Int> -Int MaskUpToNibbleInclusive(int nibble_index) { - constexpr int kNumNibbles = sizeof(Int) * 8 / 4; +Int MaskUpToNibbleInclusive(size_t nibble_index) { + constexpr size_t kNumNibbles = sizeof(Int) * 8 / 4; static const Int ones = ~Int{0}; - return ones >> std::max(0, 4 * (kNumNibbles - nibble_index - 1)); + ++nibble_index; + return ones >> static_cast<int>( + 4 * (std::max(kNumNibbles, nibble_index) - nibble_index)); } // Return a mask with 1's below the given nibble. template <typename Int> -Int MaskUpToNibbleExclusive(int nibble_index) { - return nibble_index <= 0 ? 0 : MaskUpToNibbleInclusive<Int>(nibble_index - 1); +Int MaskUpToNibbleExclusive(size_t nibble_index) { + return nibble_index == 0 ? 0 : MaskUpToNibbleInclusive<Int>(nibble_index - 1); } template <typename Int> -Int MoveToNibble(uint8_t nibble, int nibble_index) { - return Int{nibble} << (4 * nibble_index); +Int MoveToNibble(uint8_t nibble, size_t nibble_index) { + return Int{nibble} << static_cast<int>(4 * nibble_index); } // Given mantissa size, find optimal # of mantissa bits to put in initial digit. @@ -744,10 +759,10 @@ Int MoveToNibble(uint8_t nibble, int nibble_index) { // a multiple of four. Once again, the goal is to have all fractional digits // represent real precision. template <typename Float> -constexpr int HexFloatLeadingDigitSizeInBits() { +constexpr size_t HexFloatLeadingDigitSizeInBits() { return std::numeric_limits<Float>::digits % 4 > 0 - ? std::numeric_limits<Float>::digits % 4 - : 4; + ? static_cast<size_t>(std::numeric_limits<Float>::digits % 4) + : size_t{4}; } // This function captures the rounding behavior of glibc for hex float @@ -757,16 +772,17 @@ constexpr int HexFloatLeadingDigitSizeInBits() { // point that is not followed by 800000..., it disregards the parity and rounds // up if > 8 and rounds down if < 8. template <typename Int> -bool HexFloatNeedsRoundUp(Int mantissa, int final_nibble_displayed, +bool HexFloatNeedsRoundUp(Int mantissa, + size_t final_nibble_displayed, uint8_t leading) { // If the last nibble (hex digit) to be displayed is the lowest on in the // mantissa then that means that we don't have any further nibbles to inform // rounding, so don't round. - if (final_nibble_displayed <= 0) { + if (final_nibble_displayed == 0) { return false; } - int rounding_nibble_idx = final_nibble_displayed - 1; - constexpr int kTotalNibbles = sizeof(Int) * 8 / 4; + size_t rounding_nibble_idx = final_nibble_displayed - 1; + constexpr size_t kTotalNibbles = sizeof(Int) * 8 / 4; assert(final_nibble_displayed <= kTotalNibbles); Int mantissa_up_to_rounding_nibble_inclusive = mantissa & MaskUpToNibbleInclusive<Int>(rounding_nibble_idx); @@ -793,7 +809,7 @@ struct HexFloatTypeParams { } int min_exponent; - int leading_digit_size_bits; + size_t leading_digit_size_bits; }; // Hex Float Rounding. First check if we need to round; if so, then we do that @@ -803,10 +819,12 @@ struct HexFloatTypeParams { template <typename Int> void FormatARound(bool precision_specified, const FormatState &state, uint8_t *leading, Int *mantissa, int *exp) { - constexpr int kTotalNibbles = sizeof(Int) * 8 / 4; + constexpr size_t kTotalNibbles = sizeof(Int) * 8 / 4; // Index of the last nibble that we could display given precision. - int final_nibble_displayed = - precision_specified ? std::max(0, (kTotalNibbles - state.precision)) : 0; + size_t final_nibble_displayed = + precision_specified + ? (std::max(kTotalNibbles, state.precision) - state.precision) + : 0; if (HexFloatNeedsRoundUp(*mantissa, final_nibble_displayed, *leading)) { // Need to round up. bool overflow = IncrementNibble(final_nibble_displayed, mantissa); @@ -830,9 +848,9 @@ void FormatARound(bool precision_specified, const FormatState &state, template <typename Int> void FormatANormalize(const HexFloatTypeParams float_traits, uint8_t *leading, Int *mantissa, int *exp) { - constexpr int kIntBits = sizeof(Int) * 8; + constexpr size_t kIntBits = sizeof(Int) * 8; static const Int kHighIntBit = Int{1} << (kIntBits - 1); - const int kLeadDigitBitsCount = float_traits.leading_digit_size_bits; + const size_t kLeadDigitBitsCount = float_traits.leading_digit_size_bits; // Normalize mantissa so that highest bit set is in MSB position, unless we // get interrupted by the exponent threshold. while (*mantissa && !(*mantissa & kHighIntBit)) { @@ -846,18 +864,18 @@ void FormatANormalize(const HexFloatTypeParams float_traits, uint8_t *leading, } // Extract bits for leading digit then shift them away leaving the // fractional part. - *leading = - static_cast<uint8_t>(*mantissa >> (kIntBits - kLeadDigitBitsCount)); - *exp -= (*mantissa != 0) ? kLeadDigitBitsCount : *exp; - *mantissa <<= kLeadDigitBitsCount; + *leading = static_cast<uint8_t>( + *mantissa >> static_cast<int>(kIntBits - kLeadDigitBitsCount)); + *exp -= (*mantissa != 0) ? static_cast<int>(kLeadDigitBitsCount) : *exp; + *mantissa <<= static_cast<int>(kLeadDigitBitsCount); } template <typename Int> void FormatA(const HexFloatTypeParams float_traits, Int mantissa, int exp, bool uppercase, const FormatState &state) { // Int properties. - constexpr int kIntBits = sizeof(Int) * 8; - constexpr int kTotalNibbles = sizeof(Int) * 8 / 4; + constexpr size_t kIntBits = sizeof(Int) * 8; + constexpr size_t kTotalNibbles = sizeof(Int) * 8 / 4; // Did the user specify a precision explicitly? const bool precision_specified = state.conv.precision() >= 0; @@ -903,16 +921,19 @@ void FormatA(const HexFloatTypeParams float_traits, Int mantissa, int exp, } // ============ Fractional Digits ============ - int digits_emitted = 0; + size_t digits_emitted = 0; while (mantissa > 0) { *digits_iter++ = digits[GetNibble(mantissa, kTotalNibbles - 1)]; mantissa <<= 4; ++digits_emitted; } - int trailing_zeros = - precision_specified ? state.precision - digits_emitted : 0; - assert(trailing_zeros >= 0); - auto digits_result = string_view(digits_buffer, digits_iter - digits_buffer); + size_t trailing_zeros = 0; + if (precision_specified) { + assert(state.precision >= digits_emitted); + trailing_zeros = state.precision - digits_emitted; + } + auto digits_result = string_view( + digits_buffer, static_cast<size_t>(digits_iter - digits_buffer)); // =============== Exponent ================== constexpr size_t kBufSizeForExpDecRepr = @@ -925,11 +946,11 @@ void FormatA(const HexFloatTypeParams float_traits, Int mantissa, int exp, numbers_internal::FastIntToBuffer(exp < 0 ? -exp : exp, exp_buffer + 2); // ============ Assemble Result ============== - FinalPrint(state, // - digits_result, // 0xN.NNN... - 2, // offset in `data` to start padding if needed. - trailing_zeros, // num remaining mantissa padding zeros - exp_buffer); // exponent + FinalPrint(state, + digits_result, // 0xN.NNN... + 2, // offset of any padding + static_cast<size_t>(trailing_zeros), // remaining mantissa padding + exp_buffer); // exponent } char *CopyStringTo(absl::string_view v, char *out) { @@ -961,10 +982,10 @@ bool FallbackToSnprintf(const Float v, const FormatConversionSpecImpl &conv, int n = snprintf(&space[0], space.size(), fmt, w, p, v); if (n < 0) return false; if (static_cast<size_t>(n) < space.size()) { - result = absl::string_view(space.data(), n); + result = absl::string_view(space.data(), static_cast<size_t>(n)); break; } - space.resize(n + 1); + space.resize(static_cast<size_t>(n) + 1); } sink->Append(result); return true; @@ -972,13 +993,13 @@ bool FallbackToSnprintf(const Float v, const FormatConversionSpecImpl &conv, // 128-bits in decimal: ceil(128*log(2)/log(10)) // or std::numeric_limits<__uint128_t>::digits10 -constexpr int kMaxFixedPrecision = 39; +constexpr size_t kMaxFixedPrecision = 39; -constexpr int kBufferLength = /*sign*/ 1 + - /*integer*/ kMaxFixedPrecision + - /*point*/ 1 + - /*fraction*/ kMaxFixedPrecision + - /*exponent e+123*/ 5; +constexpr size_t kBufferLength = /*sign*/ 1 + + /*integer*/ kMaxFixedPrecision + + /*point*/ 1 + + /*fraction*/ kMaxFixedPrecision + + /*exponent e+123*/ 5; struct Buffer { void push_front(char c) { @@ -1001,7 +1022,7 @@ struct Buffer { char last_digit() const { return end[-1] == '.' ? end[-2] : end[-1]; } - int size() const { return static_cast<int>(end - begin); } + size_t size() const { return static_cast<size_t>(end - begin); } char data[kBufferLength]; char *begin; @@ -1030,8 +1051,9 @@ bool ConvertNonNumericFloats(char sign_char, Float v, return false; } - return sink->PutPaddedString(string_view(text, ptr - text), conv.width(), -1, - conv.has_left_flag()); + return sink->PutPaddedString( + string_view(text, static_cast<size_t>(ptr - text)), conv.width(), -1, + conv.has_left_flag()); } // Round up the last digit of the value. @@ -1068,12 +1090,12 @@ void PrintExponent(int exp, char e, Buffer *out) { } // Exponent digits. if (exp > 99) { - out->push_back(exp / 100 + '0'); - out->push_back(exp / 10 % 10 + '0'); - out->push_back(exp % 10 + '0'); + out->push_back(static_cast<char>(exp / 100 + '0')); + out->push_back(static_cast<char>(exp / 10 % 10 + '0')); + out->push_back(static_cast<char>(exp % 10 + '0')); } else { - out->push_back(exp / 10 + '0'); - out->push_back(exp % 10 + '0'); + out->push_back(static_cast<char>(exp / 10 + '0')); + out->push_back(static_cast<char>(exp % 10 + '0')); } } @@ -1115,8 +1137,8 @@ Decomposed<Float> Decompose(Float v) { // In Fixed mode, we add a '.' at the end. // In Precision mode, we add a '.' after the first digit. template <FormatStyle mode, typename Int> -int PrintIntegralDigits(Int digits, Buffer *out) { - int printed = 0; +size_t PrintIntegralDigits(Int digits, Buffer* out) { + size_t printed = 0; if (digits) { for (; digits; digits /= 10) out->push_front(digits % 10 + '0'); printed = out->size(); @@ -1135,10 +1157,10 @@ int PrintIntegralDigits(Int digits, Buffer *out) { } // Back out 'extra_digits' digits and round up if necessary. -bool RemoveExtraPrecision(int extra_digits, bool has_leftover_value, - Buffer *out, int *exp_out) { - if (extra_digits <= 0) return false; - +void RemoveExtraPrecision(size_t extra_digits, + bool has_leftover_value, + Buffer* out, + int* exp_out) { // Back out the extra digits out->end -= extra_digits; @@ -1158,15 +1180,17 @@ bool RemoveExtraPrecision(int extra_digits, bool has_leftover_value, if (needs_to_round_up) { RoundUp<FormatStyle::Precision>(out, exp_out); } - return true; } // Print the value into the buffer. // This will not include the exponent, which will be returned in 'exp_out' for // Precision mode. template <typename Int, typename Float, FormatStyle mode> -bool FloatToBufferImpl(Int int_mantissa, int exp, int precision, Buffer *out, - int *exp_out) { +bool FloatToBufferImpl(Int int_mantissa, + int exp, + size_t precision, + Buffer* out, + int* exp_out) { assert((CanFitMantissa<Float, Int>())); const int int_bits = std::numeric_limits<Int>::digits; @@ -1182,14 +1206,16 @@ bool FloatToBufferImpl(Int int_mantissa, int exp, int precision, Buffer *out, // The value will overflow the Int return false; } - int digits_printed = PrintIntegralDigits<mode>(int_mantissa << exp, out); - int digits_to_zero_pad = precision; + size_t digits_printed = PrintIntegralDigits<mode>(int_mantissa << exp, out); + size_t digits_to_zero_pad = precision; if (mode == FormatStyle::Precision) { - *exp_out = digits_printed - 1; - digits_to_zero_pad -= digits_printed - 1; - if (RemoveExtraPrecision(-digits_to_zero_pad, false, out, exp_out)) { + *exp_out = static_cast<int>(digits_printed - 1); + if (digits_to_zero_pad < digits_printed - 1) { + RemoveExtraPrecision(digits_printed - 1 - digits_to_zero_pad, false, + out, exp_out); return true; } + digits_to_zero_pad -= digits_printed - 1; } for (; digits_to_zero_pad-- > 0;) out->push_back('0'); return true; @@ -1203,10 +1229,10 @@ bool FloatToBufferImpl(Int int_mantissa, int exp, int precision, Buffer *out, const Int mask = (Int{1} << exp) - 1; // Print the integral part first. - int digits_printed = PrintIntegralDigits<mode>(int_mantissa >> exp, out); + size_t digits_printed = PrintIntegralDigits<mode>(int_mantissa >> exp, out); int_mantissa &= mask; - int fractional_count = precision; + size_t fractional_count = precision; if (mode == FormatStyle::Precision) { if (digits_printed == 0) { // Find the first non-zero digit, when in Precision mode. @@ -1222,20 +1248,21 @@ bool FloatToBufferImpl(Int int_mantissa, int exp, int precision, Buffer *out, int_mantissa &= mask; } else { // We already have a digit, and a '.' - *exp_out = digits_printed - 1; - fractional_count -= *exp_out; - if (RemoveExtraPrecision(-fractional_count, int_mantissa != 0, out, - exp_out)) { + *exp_out = static_cast<int>(digits_printed - 1); + if (fractional_count < digits_printed - 1) { // If we had enough digits, return right away. // The code below will try to round again otherwise. + RemoveExtraPrecision(digits_printed - 1 - fractional_count, + int_mantissa != 0, out, exp_out); return true; } + fractional_count -= digits_printed - 1; } } auto get_next_digit = [&] { int_mantissa *= 10; - int digit = static_cast<int>(int_mantissa >> exp); + char digit = static_cast<char>(int_mantissa >> exp); int_mantissa &= mask; return digit; }; @@ -1245,7 +1272,7 @@ bool FloatToBufferImpl(Int int_mantissa, int exp, int precision, Buffer *out, out->push_back(get_next_digit() + '0'); } - int next_digit = get_next_digit(); + char next_digit = get_next_digit(); if (next_digit > 5 || (next_digit == 5 && (int_mantissa || out->last_digit() % 2 == 1))) { RoundUp<mode>(out, exp_out); @@ -1255,24 +1282,25 @@ bool FloatToBufferImpl(Int int_mantissa, int exp, int precision, Buffer *out, } template <FormatStyle mode, typename Float> -bool FloatToBuffer(Decomposed<Float> decomposed, int precision, Buffer *out, - int *exp) { +bool FloatToBuffer(Decomposed<Float> decomposed, + size_t precision, + Buffer* out, + int* exp) { if (precision > kMaxFixedPrecision) return false; // Try with uint64_t. if (CanFitMantissa<Float, std::uint64_t>() && FloatToBufferImpl<std::uint64_t, Float, mode>( - static_cast<std::uint64_t>(decomposed.mantissa), - static_cast<std::uint64_t>(decomposed.exponent), precision, out, exp)) + static_cast<std::uint64_t>(decomposed.mantissa), decomposed.exponent, + precision, out, exp)) return true; #if defined(ABSL_HAVE_INTRINSIC_INT128) // If that is not enough, try with __uint128_t. return CanFitMantissa<Float, __uint128_t>() && FloatToBufferImpl<__uint128_t, Float, mode>( - static_cast<__uint128_t>(decomposed.mantissa), - static_cast<__uint128_t>(decomposed.exponent), precision, out, - exp); + static_cast<__uint128_t>(decomposed.mantissa), decomposed.exponent, + precision, out, exp); #endif return false; } @@ -1280,12 +1308,15 @@ bool FloatToBuffer(Decomposed<Float> decomposed, int precision, Buffer *out, void WriteBufferToSink(char sign_char, absl::string_view str, const FormatConversionSpecImpl &conv, FormatSinkImpl *sink) { - int left_spaces = 0, zeros = 0, right_spaces = 0; - int missing_chars = - conv.width() >= 0 ? std::max(conv.width() - static_cast<int>(str.size()) - - static_cast<int>(sign_char != 0), - 0) - : 0; + size_t left_spaces = 0, zeros = 0, right_spaces = 0; + size_t missing_chars = 0; + if (conv.width() >= 0) { + const size_t conv_width_size_t = static_cast<size_t>(conv.width()); + const size_t existing_chars = + str.size() + static_cast<size_t>(sign_char != 0); + if (conv_width_size_t > existing_chars) + missing_chars = conv_width_size_t - existing_chars; + } if (conv.has_left_flag()) { right_spaces = missing_chars; } else if (conv.has_zero_flag()) { @@ -1321,7 +1352,8 @@ bool FloatToSink(const Float v, const FormatConversionSpecImpl &conv, return true; } - int precision = conv.precision() < 0 ? 6 : conv.precision(); + size_t precision = + conv.precision() < 0 ? 6 : static_cast<size_t>(conv.precision()); int exp = 0; @@ -1348,12 +1380,12 @@ bool FloatToSink(const Float v, const FormatConversionSpecImpl &conv, &buffer); } else if (c == FormatConversionCharInternal::g || c == FormatConversionCharInternal::G) { - precision = std::max(0, precision - 1); + precision = std::max(precision, size_t{1}) - 1; if (!FloatToBuffer<FormatStyle::Precision>(decomposed, precision, &buffer, &exp)) { return FallbackToSnprintf(v, conv, sink); } - if (precision + 1 > exp && exp >= -4) { + if ((exp < 0 || precision + 1 > static_cast<size_t>(exp)) && exp >= -4) { if (exp < 0) { // Have 1.23456, needs 0.00123456 // Move the first digit @@ -1388,9 +1420,11 @@ bool FloatToSink(const Float v, const FormatConversionSpecImpl &conv, return false; } - WriteBufferToSink(sign_char, - absl::string_view(buffer.begin, buffer.end - buffer.begin), - conv, sink); + WriteBufferToSink( + sign_char, + absl::string_view(buffer.begin, + static_cast<size_t>(buffer.end - buffer.begin)), + conv, sink); return true; } diff --git a/absl/strings/internal/str_format/parser.cc b/absl/strings/internal/str_format/parser.cc index 2c9c07da..5aaab698 100644 --- a/absl/strings/internal/str_format/parser.cc +++ b/absl/strings/internal/str_format/parser.cc @@ -31,207 +31,14 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace str_format_internal { -using CC = FormatConversionCharInternal; -using LM = LengthMod; +// Define the array for non-constexpr uses. +constexpr ConvTag ConvTagHolder::value[256]; -// Abbreviations to fit in the table below. -constexpr auto f_sign = Flags::kSignCol; -constexpr auto f_alt = Flags::kAlt; -constexpr auto f_pos = Flags::kShowPos; -constexpr auto f_left = Flags::kLeft; -constexpr auto f_zero = Flags::kZero; - -ABSL_CONST_INIT const ConvTag kTags[256] = { - {}, {}, {}, {}, {}, {}, {}, {}, // 00-07 - {}, {}, {}, {}, {}, {}, {}, {}, // 08-0f - {}, {}, {}, {}, {}, {}, {}, {}, // 10-17 - {}, {}, {}, {}, {}, {}, {}, {}, // 18-1f - f_sign, {}, {}, f_alt, {}, {}, {}, {}, // !"#$%&' - {}, {}, {}, f_pos, {}, f_left, {}, {}, // ()*+,-./ - f_zero, {}, {}, {}, {}, {}, {}, {}, // 01234567 - {}, {}, {}, {}, {}, {}, {}, {}, // 89:;<=>? - {}, CC::A, {}, {}, {}, CC::E, CC::F, CC::G, // @ABCDEFG - {}, {}, {}, {}, LM::L, {}, {}, {}, // HIJKLMNO - {}, {}, {}, {}, {}, {}, {}, {}, // PQRSTUVW - CC::X, {}, {}, {}, {}, {}, {}, {}, // XYZ[\]^_ - {}, CC::a, {}, CC::c, CC::d, CC::e, CC::f, CC::g, // `abcdefg - LM::h, CC::i, LM::j, {}, LM::l, {}, CC::n, CC::o, // hijklmno - CC::p, LM::q, {}, CC::s, LM::t, CC::u, {}, {}, // pqrstuvw - CC::x, {}, LM::z, {}, {}, {}, {}, {}, // xyz{|}! - {}, {}, {}, {}, {}, {}, {}, {}, // 80-87 - {}, {}, {}, {}, {}, {}, {}, {}, // 88-8f - {}, {}, {}, {}, {}, {}, {}, {}, // 90-97 - {}, {}, {}, {}, {}, {}, {}, {}, // 98-9f - {}, {}, {}, {}, {}, {}, {}, {}, // a0-a7 - {}, {}, {}, {}, {}, {}, {}, {}, // a8-af - {}, {}, {}, {}, {}, {}, {}, {}, // b0-b7 - {}, {}, {}, {}, {}, {}, {}, {}, // b8-bf - {}, {}, {}, {}, {}, {}, {}, {}, // c0-c7 - {}, {}, {}, {}, {}, {}, {}, {}, // c8-cf - {}, {}, {}, {}, {}, {}, {}, {}, // d0-d7 - {}, {}, {}, {}, {}, {}, {}, {}, // d8-df - {}, {}, {}, {}, {}, {}, {}, {}, // e0-e7 - {}, {}, {}, {}, {}, {}, {}, {}, // e8-ef - {}, {}, {}, {}, {}, {}, {}, {}, // f0-f7 - {}, {}, {}, {}, {}, {}, {}, {}, // f8-ff -}; - -namespace { - -bool CheckFastPathSetting(const UnboundConversion& conv) { - bool width_precision_needed = - conv.width.value() >= 0 || conv.precision.value() >= 0; - if (width_precision_needed && conv.flags == Flags::kBasic) { - fprintf(stderr, - "basic=%d left=%d show_pos=%d sign_col=%d alt=%d zero=%d " - "width=%d precision=%d\n", - conv.flags == Flags::kBasic ? 1 : 0, - FlagsContains(conv.flags, Flags::kLeft) ? 1 : 0, - FlagsContains(conv.flags, Flags::kShowPos) ? 1 : 0, - FlagsContains(conv.flags, Flags::kSignCol) ? 1 : 0, - FlagsContains(conv.flags, Flags::kAlt) ? 1 : 0, - FlagsContains(conv.flags, Flags::kZero) ? 1 : 0, conv.width.value(), - conv.precision.value()); - return false; - } - return true; -} - -template <bool is_positional> -const char *ConsumeConversion(const char *pos, const char *const end, - UnboundConversion *conv, int *next_arg) { - const char* const original_pos = pos; - char c; - // Read the next char into `c` and update `pos`. Returns false if there are - // no more chars to read. -#define ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR() \ - do { \ - if (ABSL_PREDICT_FALSE(pos == end)) return nullptr; \ - c = *pos++; \ - } while (0) - - const auto parse_digits = [&] { - int digits = c - '0'; - // We do not want to overflow `digits` so we consume at most digits10 - // digits. If there are more digits the parsing will fail later on when the - // digit doesn't match the expected characters. - int num_digits = std::numeric_limits<int>::digits10; - for (;;) { - if (ABSL_PREDICT_FALSE(pos == end)) break; - c = *pos++; - if (!std::isdigit(c)) break; - --num_digits; - if (ABSL_PREDICT_FALSE(!num_digits)) break; - digits = 10 * digits + c - '0'; - } - return digits; - }; - - if (is_positional) { - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr; - conv->arg_position = parse_digits(); - assert(conv->arg_position > 0); - if (ABSL_PREDICT_FALSE(c != '$')) return nullptr; - } - - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - - // We should start with the basic flag on. - assert(conv->flags == Flags::kBasic); - - // Any non alpha character makes this conversion not basic. - // This includes flags (-+ #0), width (1-9, *) or precision (.). - // All conversion characters and length modifiers are alpha characters. - if (c < 'A') { - while (c <= '0') { - auto tag = GetTagForChar(c); - if (tag.is_flags()) { - conv->flags = conv->flags | tag.as_flags(); - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - } else { - break; - } - } - - if (c <= '9') { - if (c >= '0') { - int maybe_width = parse_digits(); - if (!is_positional && c == '$') { - if (ABSL_PREDICT_FALSE(*next_arg != 0)) return nullptr; - // Positional conversion. - *next_arg = -1; - return ConsumeConversion<true>(original_pos, end, conv, next_arg); - } - conv->flags = conv->flags | Flags::kNonBasic; - conv->width.set_value(maybe_width); - } else if (c == '*') { - conv->flags = conv->flags | Flags::kNonBasic; - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - if (is_positional) { - if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr; - conv->width.set_from_arg(parse_digits()); - if (ABSL_PREDICT_FALSE(c != '$')) return nullptr; - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - } else { - conv->width.set_from_arg(++*next_arg); - } - } - } - - if (c == '.') { - conv->flags = conv->flags | Flags::kNonBasic; - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - if (std::isdigit(c)) { - conv->precision.set_value(parse_digits()); - } else if (c == '*') { - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - if (is_positional) { - if (ABSL_PREDICT_FALSE(c < '1' || c > '9')) return nullptr; - conv->precision.set_from_arg(parse_digits()); - if (c != '$') return nullptr; - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - } else { - conv->precision.set_from_arg(++*next_arg); - } - } else { - conv->precision.set_value(0); - } - } - } - - auto tag = GetTagForChar(c); - - if (ABSL_PREDICT_FALSE(!tag.is_conv())) { - 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) { - conv->length_mod = LengthMod::hh; - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - } else if (c == 'l' && length_mod == LengthMod::l) { - conv->length_mod = LengthMod::ll; - ABSL_FORMAT_PARSER_INTERNAL_GET_CHAR(); - } else { - conv->length_mod = length_mod; - } - tag = GetTagForChar(c); - if (ABSL_PREDICT_FALSE(!tag.is_conv())) return nullptr; - } - - assert(CheckFastPathSetting(*conv)); - (void)(&CheckFastPathSetting); - - conv->conv = tag.as_conv(); - if (!is_positional) conv->arg_position = ++*next_arg; - return pos; +ABSL_ATTRIBUTE_NOINLINE const char* ConsumeUnboundConversionNoInline( + const char* p, const char* end, UnboundConversion* conv, int* next_arg) { + return ConsumeUnboundConversion(p, end, conv, next_arg); } -} // namespace - std::string LengthModToString(LengthMod v) { switch (v) { case LengthMod::h: @@ -258,12 +65,6 @@ std::string LengthModToString(LengthMod v) { return ""; } -const char *ConsumeUnboundConversion(const char *p, const char *end, - UnboundConversion *conv, int *next_arg) { - if (*next_arg < 0) return ConsumeConversion<true>(p, end, conv, next_arg); - return ConsumeConversion<false>(p, end, conv, next_arg); -} - struct ParsedFormatBase::ParsedFormatConsumer { explicit ParsedFormatConsumer(ParsedFormatBase *parsedformat) : parsed(parsedformat), data_pos(parsedformat->data_.get()) {} @@ -312,11 +113,11 @@ bool ParsedFormatBase::MatchesConversions( std::initializer_list<FormatConversionCharSet> convs) const { std::unordered_set<int> used; auto add_if_valid_conv = [&](int pos, char c) { - if (static_cast<size_t>(pos) > convs.size() || - !Contains(convs.begin()[pos - 1], c)) - return false; - used.insert(pos); - return true; + if (static_cast<size_t>(pos) > convs.size() || + !Contains(convs.begin()[pos - 1], c)) + return false; + used.insert(pos); + return true; }; for (const ConversionItem &item : items_) { if (!item.is_conversion) continue; diff --git a/absl/strings/internal/str_format/parser.h b/absl/strings/internal/str_format/parser.h index 32b91d03..35b6d49c 100644 --- a/absl/strings/internal/str_format/parser.h +++ b/absl/strings/internal/str_format/parser.h @@ -29,111 +29,18 @@ #include <vector> #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" 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 }; - std::string LengthModToString(LengthMod v); -// The analyzed properties of a single specified conversion. -struct UnboundConversion { - UnboundConversion() {} - - class InputValue { - public: - void set_value(int value) { - assert(value >= 0); - value_ = value; - } - int value() const { return value_; } - - // Marks the value as "from arg". aka the '*' format. - // Requires `value >= 1`. - // When set, is_from_arg() return true and get_from_arg() returns the - // original value. - // `value()`'s return value is unspecfied in this state. - void set_from_arg(int value) { - assert(value > 0); - value_ = -value - 1; - } - bool is_from_arg() const { return value_ < -1; } - int get_from_arg() const { - assert(is_from_arg()); - return -value_ - 1; - } - - private: - int value_ = -1; - }; - - // No need to initialize. It will always be set in the parser. - int arg_position; - - InputValue width; - InputValue precision; - - Flags flags = Flags::kBasic; - LengthMod length_mod = LengthMod::none; - FormatConversionChar conv = FormatConversionCharInternal::kNone; -}; - -// Consume conversion spec prefix (not including '%') of [p, end) if valid. -// Examples of valid specs would be e.g.: "s", "d", "-12.6f". -// If valid, it returns the first character following the conversion spec, -// and the spec part is broken down and returned in 'conv'. -// If invalid, returns nullptr. -const char* ConsumeUnboundConversion(const char* p, const char* end, - UnboundConversion* conv, int* next_arg); - -// Helper tag class for the table below. -// It allows fast `char -> ConversionChar/LengthMod/Flags` checking and -// conversions. -class ConvTag { - public: - constexpr ConvTag(FormatConversionChar conversion_char) // NOLINT - : tag_(static_cast<uint8_t>(conversion_char)) {} - constexpr ConvTag(LengthMod length_mod) // NOLINT - : tag_(0x80 | static_cast<uint8_t>(length_mod)) {} - constexpr ConvTag(Flags flags) // NOLINT - : tag_(0xc0 | static_cast<uint8_t>(flags)) {} - constexpr ConvTag() : tag_(0xFF) {} - - bool is_conv() const { return (tag_ & 0x80) == 0; } - bool is_length() const { return (tag_ & 0xC0) == 0x80; } - bool is_flags() const { return (tag_ & 0xE0) == 0xC0; } - - FormatConversionChar as_conv() const { - assert(is_conv()); - assert(!is_length()); - assert(!is_flags()); - return static_cast<FormatConversionChar>(tag_); - } - LengthMod as_length() const { - assert(!is_conv()); - assert(is_length()); - assert(!is_flags()); - return static_cast<LengthMod>(tag_ & 0x3F); - } - Flags as_flags() const { - assert(!is_conv()); - assert(!is_length()); - assert(is_flags()); - return static_cast<Flags>(tag_ & 0x1F); - } - - private: - uint8_t tag_; -}; - -extern const ConvTag kTags[256]; -// Keep a single table for all the conversion chars and length modifiers. -inline ConvTag GetTagForChar(char c) { - return kTags[static_cast<unsigned char>(c)]; -} +const char* ConsumeUnboundConversionNoInline(const char* p, const char* end, + UnboundConversion* conv, + int* next_arg); // Parse the format string provided in 'src' and pass the identified items into // 'consumer'. @@ -155,10 +62,11 @@ bool ParseFormatString(string_view src, Consumer consumer) { static_cast<const char*>(memchr(p, '%', static_cast<size_t>(end - p))); if (!percent) { // We found the last substring. - return consumer.Append(string_view(p, end - p)); + return consumer.Append(string_view(p, static_cast<size_t>(end - p))); } // We found a percent, so push the text run then process the percent. - if (ABSL_PREDICT_FALSE(!consumer.Append(string_view(p, percent - p)))) { + if (ABSL_PREDICT_FALSE(!consumer.Append( + string_view(p, static_cast<size_t>(percent - p))))) { return false; } if (ABSL_PREDICT_FALSE(percent + 1 >= end)) return false; @@ -186,10 +94,11 @@ bool ParseFormatString(string_view src, Consumer consumer) { } } else if (percent[1] != '%') { UnboundConversion conv; - p = ConsumeUnboundConversion(percent + 1, end, &conv, &next_arg); + p = ConsumeUnboundConversionNoInline(percent + 1, end, &conv, &next_arg); if (ABSL_PREDICT_FALSE(p == nullptr)) return false; if (ABSL_PREDICT_FALSE(!consumer.ConvertOne( - conv, string_view(percent + 1, p - (percent + 1))))) { + conv, string_view(percent + 1, + static_cast<size_t>(p - (percent + 1)))))) { return false; } } else { diff --git a/absl/strings/internal/str_format/parser_test.cc b/absl/strings/internal/str_format/parser_test.cc index fe0d2963..021f6a87 100644 --- a/absl/strings/internal/str_format/parser_test.cc +++ b/absl/strings/internal/str_format/parser_test.cc @@ -110,10 +110,14 @@ TEST_F(ConsumeUnboundConversionTest, ConsumeSpecification) { {__LINE__, "ba", "", "ba"}, // 'b' is invalid {__LINE__, "l", "", "l" }, // just length mod isn't okay {__LINE__, "d", "d", "" }, // basic + {__LINE__, "v", "v", "" }, // basic {__LINE__, "d ", "d", " " }, // leave suffix {__LINE__, "dd", "d", "d" }, // don't be greedy {__LINE__, "d9", "d", "9" }, // leave non-space suffix {__LINE__, "dzz", "d", "zz"}, // length mod as suffix + {__LINE__, "3v", "", "3v"}, // 'v' cannot have modifiers + {__LINE__, "hv", "", "hv"}, // 'v' cannot have modifiers + {__LINE__, "1$v", "1$v", ""}, // 'v' can have use posix syntax {__LINE__, "1$*2$d", "1$*2$d", "" }, // arg indexing and * allowed. {__LINE__, "0-14.3hhd", "0-14.3hhd", ""}, // precision, width {__LINE__, " 0-+#14.3hhd", " 0-+#14.3hhd", ""}, // flags diff --git a/absl/strings/internal/str_split_internal.h b/absl/strings/internal/str_split_internal.h index e7664216..35edf3aa 100644 --- a/absl/strings/internal/str_split_internal.h +++ b/absl/strings/internal/str_split_internal.h @@ -132,7 +132,8 @@ class SplitIterator { const absl::string_view text = splitter_->text(); const absl::string_view d = delimiter_.Find(text, pos_); if (d.data() == text.data() + text.size()) state_ = kLastState; - curr_ = text.substr(pos_, d.data() - (text.data() + pos_)); + curr_ = text.substr(pos_, + static_cast<size_t>(d.data() - (text.data() + pos_))); pos_ += curr_.size() + d.size(); } while (!predicate_(curr_)); return *this; diff --git a/absl/strings/internal/stringify_sink.cc b/absl/strings/internal/stringify_sink.cc new file mode 100644 index 00000000..7c6995ab --- /dev/null +++ b/absl/strings/internal/stringify_sink.cc @@ -0,0 +1,28 @@ +// Copyright 2022 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/strings/internal/stringify_sink.h" +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace strings_internal { + +void StringifySink::Append(size_t count, char ch) { buffer_.append(count, ch); } + +void StringifySink::Append(string_view v) { + buffer_.append(v.data(), v.size()); +} + +} // namespace strings_internal +ABSL_NAMESPACE_END +} // namespace absl diff --git a/absl/strings/internal/stringify_sink.h b/absl/strings/internal/stringify_sink.h new file mode 100644 index 00000000..fc3747bb --- /dev/null +++ b/absl/strings/internal/stringify_sink.h @@ -0,0 +1,57 @@ +// Copyright 2022 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_STRINGS_INTERNAL_STRINGIFY_SINK_H_ +#define ABSL_STRINGS_INTERNAL_STRINGIFY_SINK_H_ + +#include <string> +#include <type_traits> +#include <utility> + +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +namespace strings_internal { +class StringifySink { + public: + void Append(size_t count, char ch); + + void Append(string_view v); + + // Support `absl::Format(&sink, format, args...)`. + friend void AbslFormatFlush(StringifySink* sink, absl::string_view v) { + sink->Append(v); + } + + private: + template <typename T> + friend string_view ExtractStringification(StringifySink& sink, const T& v); + + std::string buffer_; +}; + +template <typename T> +string_view ExtractStringification(StringifySink& sink, const T& v) { + AbslStringify(sink, v); + return sink.buffer_; +} + +} // namespace strings_internal + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_STRINGIFY_SINK_H_ diff --git a/absl/strings/numbers.cc b/absl/strings/numbers.cc index e798fc69..2987158e 100644 --- a/absl/strings/numbers.cc +++ b/absl/strings/numbers.cc @@ -190,32 +190,32 @@ char* numbers_internal::FastIntToBuffer(uint32_t i, char* buffer) { if (i >= 1000) goto lt10_000; digits = i / 100; i -= digits * 100; - *buffer++ = '0' + digits; + *buffer++ = '0' + static_cast<char>(digits); goto lt100; } if (i < 1000000) { // 1,000,000 if (i >= 100000) goto lt1_000_000; digits = i / 10000; // 10,000 i -= digits * 10000; - *buffer++ = '0' + digits; + *buffer++ = '0' + static_cast<char>(digits); goto lt10_000; } if (i < 100000000) { // 100,000,000 if (i >= 10000000) goto lt100_000_000; digits = i / 1000000; // 1,000,000 i -= digits * 1000000; - *buffer++ = '0' + digits; + *buffer++ = '0' + static_cast<char>(digits); goto lt1_000_000; } // we already know that i < 1,000,000,000 digits = i / 100000000; // 100,000,000 i -= digits * 100000000; - *buffer++ = '0' + digits; + *buffer++ = '0' + static_cast<char>(digits); goto lt100_000_000; } char* numbers_internal::FastIntToBuffer(int32_t i, char* buffer) { - uint32_t u = i; + uint32_t u = static_cast<uint32_t>(i); if (i < 0) { *buffer++ = '-'; // We need to do the negation in modular (i.e., "unsigned") @@ -268,7 +268,7 @@ char* numbers_internal::FastIntToBuffer(uint64_t i, char* buffer) { } char* numbers_internal::FastIntToBuffer(int64_t i, char* buffer) { - uint64_t u = i; + uint64_t u = static_cast<uint64_t>(i); if (i < 0) { *buffer++ = '-'; u = 0 - u; @@ -329,7 +329,7 @@ static std::pair<uint64_t, uint64_t> PowFive(uint64_t num, int expfive) { result = Mul32(result, 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5 * 5); expfive -= 13; } - constexpr int powers_of_five[13] = { + constexpr uint32_t powers_of_five[13] = { 1, 5, 5 * 5, @@ -404,14 +404,14 @@ static ExpDigits SplitToSix(const double value) { // we multiply it by 65536 and see if the fractional part is close to 32768. // (The number doesn't have to be a power of two,but powers of two are faster) uint64_t d64k = d * 65536; - int dddddd; // A 6-digit decimal integer. + uint32_t dddddd; // A 6-digit decimal integer. if ((d64k % 65536) == 32767 || (d64k % 65536) == 32768) { // OK, it's fairly likely that precision was lost above, which is // not a surprise given only 52 mantissa bits are available. Therefore // redo the calculation using 128-bit numbers. (64 bits are not enough). // Start out with digits rounded down; maybe add one below. - dddddd = static_cast<int>(d64k / 65536); + dddddd = static_cast<uint32_t>(d64k / 65536); // mantissa is a 64-bit integer representing M.mmm... * 2^63. The actual // value we're representing, of course, is M.mmm... * 2^exp2. @@ -461,7 +461,7 @@ static ExpDigits SplitToSix(const double value) { } } else { // Here, we are not close to the edge. - dddddd = static_cast<int>((d64k + 32768) / 65536); + dddddd = static_cast<uint32_t>((d64k + 32768) / 65536); } if (dddddd == 1000000) { dddddd = 100000; @@ -469,7 +469,7 @@ static ExpDigits SplitToSix(const double value) { } exp_dig.exponent = exp; - int two_digits = dddddd / 10000; + uint32_t two_digits = dddddd / 10000; dddddd -= two_digits * 10000; numbers_internal::PutTwoDigits(two_digits, &exp_dig.digits[0]); @@ -499,7 +499,7 @@ size_t numbers_internal::SixDigitsToBuffer(double d, char* const buffer) { if (std::signbit(d)) *out++ = '-'; *out++ = '0'; *out = 0; - return out - buffer; + return static_cast<size_t>(out - buffer); } if (d < 0) { *out++ = '-'; @@ -507,7 +507,7 @@ size_t numbers_internal::SixDigitsToBuffer(double d, char* const buffer) { } if (d > std::numeric_limits<double>::max()) { strcpy(out, "inf"); // NOLINT(runtime/printf) - return out + 3 - buffer; + return static_cast<size_t>(out + 3 - buffer); } auto exp_dig = SplitToSix(d); @@ -519,7 +519,7 @@ size_t numbers_internal::SixDigitsToBuffer(double d, char* const buffer) { case 5: memcpy(out, &digits[0], 6), out += 6; *out = 0; - return out - buffer; + return static_cast<size_t>(out - buffer); case 4: memcpy(out, &digits[0], 5), out += 5; if (digits[5] != '0') { @@ -527,7 +527,7 @@ size_t numbers_internal::SixDigitsToBuffer(double d, char* const buffer) { *out++ = digits[5]; } *out = 0; - return out - buffer; + return static_cast<size_t>(out - buffer); case 3: memcpy(out, &digits[0], 4), out += 4; if ((digits[5] | digits[4]) != '0') { @@ -536,7 +536,7 @@ size_t numbers_internal::SixDigitsToBuffer(double d, char* const buffer) { if (digits[5] != '0') *out++ = digits[5]; } *out = 0; - return out - buffer; + return static_cast<size_t>(out - buffer); case 2: memcpy(out, &digits[0], 3), out += 3; *out++ = '.'; @@ -545,7 +545,7 @@ size_t numbers_internal::SixDigitsToBuffer(double d, char* const buffer) { while (out[-1] == '0') --out; if (out[-1] == '.') --out; *out = 0; - return out - buffer; + return static_cast<size_t>(out - buffer); case 1: memcpy(out, &digits[0], 2), out += 2; *out++ = '.'; @@ -554,7 +554,7 @@ size_t numbers_internal::SixDigitsToBuffer(double d, char* const buffer) { while (out[-1] == '0') --out; if (out[-1] == '.') --out; *out = 0; - return out - buffer; + return static_cast<size_t>(out - buffer); case 0: memcpy(out, &digits[0], 1), out += 1; *out++ = '.'; @@ -563,7 +563,7 @@ size_t numbers_internal::SixDigitsToBuffer(double d, char* const buffer) { while (out[-1] == '0') --out; if (out[-1] == '.') --out; *out = 0; - return out - buffer; + return static_cast<size_t>(out - buffer); case -4: out[2] = '0'; ++out; @@ -582,7 +582,7 @@ size_t numbers_internal::SixDigitsToBuffer(double d, char* const buffer) { out += 6; while (out[-1] == '0') --out; *out = 0; - return out - buffer; + return static_cast<size_t>(out - buffer); } assert(exp < -4 || exp >= 6); out[0] = digits[0]; @@ -601,12 +601,12 @@ size_t numbers_internal::SixDigitsToBuffer(double d, char* const buffer) { if (exp > 99) { int dig1 = exp / 100; exp -= dig1 * 100; - *out++ = '0' + dig1; + *out++ = '0' + static_cast<char>(dig1); } - PutTwoDigits(exp, out); + PutTwoDigits(static_cast<uint32_t>(exp), out); out += 2; *out = 0; - return out - buffer; + return static_cast<size_t>(out - buffer); } namespace { @@ -642,10 +642,12 @@ inline bool safe_parse_sign_and_base(absl::string_view* text /*inout*/, int base = *base_ptr; // Consume whitespace. - while (start < end && absl::ascii_isspace(start[0])) { + while (start < end && + absl::ascii_isspace(static_cast<unsigned char>(start[0]))) { ++start; } - while (start < end && absl::ascii_isspace(end[-1])) { + while (start < end && + absl::ascii_isspace(static_cast<unsigned char>(end[-1]))) { --end; } if (start >= end) { @@ -694,7 +696,7 @@ inline bool safe_parse_sign_and_base(absl::string_view* text /*inout*/, } else { return false; } - *text = absl::string_view(start, end - start); + *text = absl::string_view(start, static_cast<size_t>(end - start)); *base_ptr = base; return true; } @@ -920,17 +922,18 @@ inline bool safe_parse_positive_int(absl::string_view text, int base, const IntType vmax = std::numeric_limits<IntType>::max(); assert(vmax > 0); assert(base >= 0); - assert(vmax >= static_cast<IntType>(base)); + const IntType base_inttype = static_cast<IntType>(base); + assert(vmax >= base_inttype); const IntType vmax_over_base = LookupTables<IntType>::kVmaxOverBase[base]; assert(base < 2 || - std::numeric_limits<IntType>::max() / base == vmax_over_base); + std::numeric_limits<IntType>::max() / base_inttype == vmax_over_base); const char* start = text.data(); const char* end = start + text.size(); // loop over digits for (; start < end; ++start) { unsigned char c = static_cast<unsigned char>(start[0]); - int digit = kAsciiToInt[c]; - if (digit >= base) { + IntType digit = static_cast<IntType>(kAsciiToInt[c]); + if (digit >= base_inttype) { *value_p = value; return false; } @@ -938,7 +941,7 @@ inline bool safe_parse_positive_int(absl::string_view text, int base, *value_p = vmax; return false; } - value *= base; + value *= base_inttype; if (value > vmax - digit) { *value_p = vmax; return false; diff --git a/absl/strings/numbers_test.cc b/absl/strings/numbers_test.cc index 498c210d..b3c098d1 100644 --- a/absl/strings/numbers_test.cc +++ b/absl/strings/numbers_test.cc @@ -19,6 +19,7 @@ #include <sys/types.h> #include <cfenv> // NOLINT(build/c++11) +#include <cfloat> #include <cinttypes> #include <climits> #include <cmath> @@ -388,9 +389,209 @@ TEST(NumbersTest, Atoi) { } TEST(NumbersTest, Atod) { + // DBL_TRUE_MIN and FLT_TRUE_MIN were not mandated in <cfloat> before C++17. +#if !defined(DBL_TRUE_MIN) + static constexpr double DBL_TRUE_MIN = + 4.940656458412465441765687928682213723650598026143247644255856825e-324; +#endif +#if !defined(FLT_TRUE_MIN) + static constexpr float FLT_TRUE_MIN = + 1.401298464324817070923729583289916131280261941876515771757068284e-45f; +#endif + double d; - EXPECT_TRUE(absl::SimpleAtod("nan", &d)); + float f; + + // NaN can be spelled in multiple ways. + EXPECT_TRUE(absl::SimpleAtod("NaN", &d)); + EXPECT_TRUE(std::isnan(d)); + EXPECT_TRUE(absl::SimpleAtod("nAN", &d)); EXPECT_TRUE(std::isnan(d)); + EXPECT_TRUE(absl::SimpleAtod("-nan", &d)); + EXPECT_TRUE(std::isnan(d)); + + // Likewise for Infinity. + EXPECT_TRUE(absl::SimpleAtod("inf", &d)); + EXPECT_TRUE(std::isinf(d) && (d > 0)); + EXPECT_TRUE(absl::SimpleAtod("+Infinity", &d)); + EXPECT_TRUE(std::isinf(d) && (d > 0)); + EXPECT_TRUE(absl::SimpleAtod("-INF", &d)); + EXPECT_TRUE(std::isinf(d) && (d < 0)); + + // Parse DBL_MAX. Parsing something more than twice as big should also + // produce infinity. + EXPECT_TRUE(absl::SimpleAtod("1.7976931348623157e+308", &d)); + EXPECT_EQ(d, 1.7976931348623157e+308); + EXPECT_TRUE(absl::SimpleAtod("5e308", &d)); + EXPECT_TRUE(std::isinf(d) && (d > 0)); + // Ditto, but for FLT_MAX. + EXPECT_TRUE(absl::SimpleAtof("3.4028234663852886e+38", &f)); + EXPECT_EQ(f, 3.4028234663852886e+38f); + EXPECT_TRUE(absl::SimpleAtof("7e38", &f)); + EXPECT_TRUE(std::isinf(f) && (f > 0)); + + // Parse the largest N such that parsing 1eN produces a finite value and the + // smallest M = N + 1 such that parsing 1eM produces infinity. + // + // The 309 exponent (and 39) confirms the "definition of + // kEiselLemireMaxExclExp10" comment in charconv.cc. + EXPECT_TRUE(absl::SimpleAtod("1e308", &d)); + EXPECT_EQ(d, 1e308); + EXPECT_FALSE(std::isinf(d)); + EXPECT_TRUE(absl::SimpleAtod("1e309", &d)); + EXPECT_TRUE(std::isinf(d)); + // Ditto, but for Atof instead of Atod. + EXPECT_TRUE(absl::SimpleAtof("1e38", &f)); + EXPECT_EQ(f, 1e38f); + EXPECT_FALSE(std::isinf(f)); + EXPECT_TRUE(absl::SimpleAtof("1e39", &f)); + EXPECT_TRUE(std::isinf(f)); + + // Parse the largest N such that parsing 9.999999999999999999eN, with 19 + // nines, produces a finite value. + // + // 9999999999999999999, with 19 nines but no decimal point, is the largest + // "repeated nines" integer that fits in a uint64_t. + EXPECT_TRUE(absl::SimpleAtod("9.999999999999999999e307", &d)); + EXPECT_EQ(d, 9.999999999999999999e307); + EXPECT_FALSE(std::isinf(d)); + EXPECT_TRUE(absl::SimpleAtod("9.999999999999999999e308", &d)); + EXPECT_TRUE(std::isinf(d)); + // Ditto, but for Atof instead of Atod. + EXPECT_TRUE(absl::SimpleAtof("9.999999999999999999e37", &f)); + EXPECT_EQ(f, 9.999999999999999999e37f); + EXPECT_FALSE(std::isinf(f)); + EXPECT_TRUE(absl::SimpleAtof("9.999999999999999999e38", &f)); + EXPECT_TRUE(std::isinf(f)); + + // Parse DBL_MIN (normal), DBL_TRUE_MIN (subnormal) and (DBL_TRUE_MIN / 10) + // (effectively zero). + EXPECT_TRUE(absl::SimpleAtod("2.2250738585072014e-308", &d)); + EXPECT_EQ(d, 2.2250738585072014e-308); + EXPECT_TRUE(absl::SimpleAtod("4.9406564584124654e-324", &d)); + EXPECT_EQ(d, 4.9406564584124654e-324); + EXPECT_TRUE(absl::SimpleAtod("4.9406564584124654e-325", &d)); + EXPECT_EQ(d, 0); + // Ditto, but for FLT_MIN, FLT_TRUE_MIN and (FLT_TRUE_MIN / 10). + EXPECT_TRUE(absl::SimpleAtof("1.1754943508222875e-38", &f)); + EXPECT_EQ(f, 1.1754943508222875e-38f); + EXPECT_TRUE(absl::SimpleAtof("1.4012984643248171e-45", &f)); + EXPECT_EQ(f, 1.4012984643248171e-45f); + EXPECT_TRUE(absl::SimpleAtof("1.4012984643248171e-46", &f)); + EXPECT_EQ(f, 0); + + // Parse the largest N (the most negative -N) such that parsing 1e-N produces + // a normal or subnormal (but still positive) or zero value. + EXPECT_TRUE(absl::SimpleAtod("1e-307", &d)); + EXPECT_EQ(d, 1e-307); + EXPECT_GE(d, DBL_MIN); + EXPECT_LT(d, DBL_MIN * 10); + EXPECT_TRUE(absl::SimpleAtod("1e-323", &d)); + EXPECT_EQ(d, 1e-323); + EXPECT_GE(d, DBL_TRUE_MIN); + EXPECT_LT(d, DBL_TRUE_MIN * 10); + EXPECT_TRUE(absl::SimpleAtod("1e-324", &d)); + EXPECT_EQ(d, 0); + // Ditto, but for Atof instead of Atod. + EXPECT_TRUE(absl::SimpleAtof("1e-37", &f)); + EXPECT_EQ(f, 1e-37f); + EXPECT_GE(f, FLT_MIN); + EXPECT_LT(f, FLT_MIN * 10); + EXPECT_TRUE(absl::SimpleAtof("1e-45", &f)); + EXPECT_EQ(f, 1e-45f); + EXPECT_GE(f, FLT_TRUE_MIN); + EXPECT_LT(f, FLT_TRUE_MIN * 10); + EXPECT_TRUE(absl::SimpleAtof("1e-46", &f)); + EXPECT_EQ(f, 0); + + // Parse the largest N (the most negative -N) such that parsing + // 9.999999999999999999e-N, with 19 nines, produces a normal or subnormal + // (but still positive) or zero value. + // + // 9999999999999999999, with 19 nines but no decimal point, is the largest + // "repeated nines" integer that fits in a uint64_t. + // + // The -324/-325 exponents (and -46/-47) confirms the "definition of + // kEiselLemireMinInclExp10" comment in charconv.cc. + EXPECT_TRUE(absl::SimpleAtod("9.999999999999999999e-308", &d)); + EXPECT_EQ(d, 9.999999999999999999e-308); + EXPECT_GE(d, DBL_MIN); + EXPECT_LT(d, DBL_MIN * 10); + EXPECT_TRUE(absl::SimpleAtod("9.999999999999999999e-324", &d)); + EXPECT_EQ(d, 9.999999999999999999e-324); + EXPECT_GE(d, DBL_TRUE_MIN); + EXPECT_LT(d, DBL_TRUE_MIN * 10); + EXPECT_TRUE(absl::SimpleAtod("9.999999999999999999e-325", &d)); + EXPECT_EQ(d, 0); + // Ditto, but for Atof instead of Atod. + EXPECT_TRUE(absl::SimpleAtof("9.999999999999999999e-38", &f)); + EXPECT_EQ(f, 9.999999999999999999e-38f); + EXPECT_GE(f, FLT_MIN); + EXPECT_LT(f, FLT_MIN * 10); + EXPECT_TRUE(absl::SimpleAtof("9.999999999999999999e-46", &f)); + EXPECT_EQ(f, 9.999999999999999999e-46f); + EXPECT_GE(f, FLT_TRUE_MIN); + EXPECT_LT(f, FLT_TRUE_MIN * 10); + EXPECT_TRUE(absl::SimpleAtof("9.999999999999999999e-47", &f)); + EXPECT_EQ(f, 0); + + // Leading and/or trailing whitespace is OK. + EXPECT_TRUE(absl::SimpleAtod(" \t\r\n 2.718", &d)); + EXPECT_EQ(d, 2.718); + EXPECT_TRUE(absl::SimpleAtod(" 3.141 ", &d)); + EXPECT_EQ(d, 3.141); + + // Leading or trailing not-whitespace is not OK. + EXPECT_FALSE(absl::SimpleAtod("n 0", &d)); + EXPECT_FALSE(absl::SimpleAtod("0n ", &d)); + + // Multiple leading 0s are OK. + EXPECT_TRUE(absl::SimpleAtod("000123", &d)); + EXPECT_EQ(d, 123); + EXPECT_TRUE(absl::SimpleAtod("000.456", &d)); + EXPECT_EQ(d, 0.456); + + // An absent leading 0 (for a fraction < 1) is OK. + EXPECT_TRUE(absl::SimpleAtod(".5", &d)); + EXPECT_EQ(d, 0.5); + EXPECT_TRUE(absl::SimpleAtod("-.707", &d)); + EXPECT_EQ(d, -0.707); + + // Unary + is OK. + EXPECT_TRUE(absl::SimpleAtod("+6.0221408e+23", &d)); + EXPECT_EQ(d, 6.0221408e+23); + + // Underscores are not OK. + EXPECT_FALSE(absl::SimpleAtod("123_456", &d)); + + // The decimal separator must be '.' and is never ','. + EXPECT_TRUE(absl::SimpleAtod("8.9", &d)); + EXPECT_FALSE(absl::SimpleAtod("8,9", &d)); + + // These examples are called out in the EiselLemire function's comments. + EXPECT_TRUE(absl::SimpleAtod("4503599627370497.5", &d)); + EXPECT_EQ(d, 4503599627370497.5); + EXPECT_TRUE(absl::SimpleAtod("1e+23", &d)); + EXPECT_EQ(d, 1e+23); + EXPECT_TRUE(absl::SimpleAtod("9223372036854775807", &d)); + EXPECT_EQ(d, 9223372036854775807); + // Ditto, but for Atof instead of Atod. + EXPECT_TRUE(absl::SimpleAtof("0.0625", &f)); + EXPECT_EQ(f, 0.0625f); + EXPECT_TRUE(absl::SimpleAtof("20040229.0", &f)); + EXPECT_EQ(f, 20040229.0f); + EXPECT_TRUE(absl::SimpleAtof("2147483647.0", &f)); + EXPECT_EQ(f, 2147483647.0f); + + // Some parsing algorithms don't always round correctly (but absl::SimpleAtod + // should). This test case comes from + // https://github.com/serde-rs/json/issues/707 + // + // See also atod_manual_test.cc for running many more test cases. + EXPECT_TRUE(absl::SimpleAtod("122.416294033786585", &d)); + EXPECT_EQ(d, 122.416294033786585); + EXPECT_TRUE(absl::SimpleAtof("122.416294033786585", &f)); + EXPECT_EQ(f, 122.416294033786585f); } TEST(NumbersTest, Prefixes) { diff --git a/absl/strings/str_cat.cc b/absl/strings/str_cat.cc index f4a77493..114a2ff2 100644 --- a/absl/strings/str_cat.cc +++ b/absl/strings/str_cat.cc @@ -17,12 +17,15 @@ #include <assert.h> #include <algorithm> +#include <cstddef> #include <cstdint> #include <cstring> +#include <string> #include "absl/strings/ascii.h" #include "absl/strings/internal/resize_uninitialized.h" #include "absl/strings/numbers.h" +#include "absl/strings/string_view.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -56,7 +59,7 @@ AlphaNum::AlphaNum(Dec dec) { *--writer = '0' + (value % 10); value /= 10; } - *--writer = '0' + value; + *--writer = '0' + static_cast<char>(value); if (neg) *--writer = '-'; ptrdiff_t fillers = writer - minfill; @@ -73,7 +76,7 @@ AlphaNum::AlphaNum(Dec dec) { if (add_sign_again) *--writer = '-'; } - piece_ = absl::string_view(writer, end - writer); + piece_ = absl::string_view(writer, static_cast<size_t>(end - writer)); } // ---------------------------------------------------------------------- @@ -141,12 +144,12 @@ namespace strings_internal { std::string CatPieces(std::initializer_list<absl::string_view> pieces) { std::string result; size_t total_size = 0; - for (const absl::string_view& piece : pieces) total_size += piece.size(); + for (absl::string_view piece : pieces) total_size += piece.size(); strings_internal::STLStringResizeUninitialized(&result, total_size); char* const begin = &result[0]; char* out = begin; - for (const absl::string_view& piece : pieces) { + for (absl::string_view piece : pieces) { const size_t this_size = piece.size(); if (this_size != 0) { memcpy(out, piece.data(), this_size); @@ -170,7 +173,7 @@ void AppendPieces(std::string* dest, std::initializer_list<absl::string_view> pieces) { size_t old_size = dest->size(); size_t total_size = old_size; - for (const absl::string_view& piece : pieces) { + for (absl::string_view piece : pieces) { ASSERT_NO_OVERLAP(*dest, piece); total_size += piece.size(); } @@ -178,7 +181,7 @@ void AppendPieces(std::string* dest, char* const begin = &(*dest)[0]; char* out = begin + old_size; - for (const absl::string_view& piece : pieces) { + for (absl::string_view piece : pieces) { const size_t this_size = piece.size(); if (this_size != 0) { memcpy(out, piece.data(), this_size); diff --git a/absl/strings/str_cat.h b/absl/strings/str_cat.h index a94bc5df..730b4d8c 100644 --- a/absl/strings/str_cat.h +++ b/absl/strings/str_cat.h @@ -48,6 +48,40 @@ // `StrCat()` or `StrAppend()`. You may specify a minimum hex field width using // a `PadSpec` enum. // +// User-defined types can be formatted with the `AbslStringify()` customization +// point. The API relies on detecting an overload in the user-defined type's +// namespace of a free (non-member) `AbslStringify()` function as a definition +// (typically declared as a friend and implemented in-line. +// with the following signature: +// +// class MyClass { ... }; +// +// template <typename Sink> +// void AbslStringify(Sink& sink, const MyClass& value); +// +// An `AbslStringify()` overload for a type should only be declared in the same +// file and namespace as said type. +// +// Note that `AbslStringify()` also supports use with `absl::StrFormat()` and +// `absl::Substitute()`. +// +// Example: +// +// struct Point { +// // To add formatting support to `Point`, we simply need to add a free +// // (non-member) function `AbslStringify()`. This method specifies how +// // Point should be printed when absl::StrCat() is called on it. You can add +// // such a free function using a friend declaration within the body of the +// // class. The sink parameter is a templated type to avoid requiring +// // dependencies. +// template <typename Sink> friend void AbslStringify(Sink& +// sink, const Point& p) { +// absl::Format(&sink, "(%v, %v)", p.x, p.y); +// } +// +// int x; +// int y; +// }; // ----------------------------------------------------------------------------- #ifndef ABSL_STRINGS_STR_CAT_H_ @@ -57,9 +91,12 @@ #include <cstdint> #include <string> #include <type_traits> +#include <utility> #include <vector> #include "absl/base/port.h" +#include "absl/strings/internal/has_absl_stringify.h" +#include "absl/strings/internal/stringify_sink.h" #include "absl/strings/numbers.h" #include "absl/strings/string_view.h" @@ -205,8 +242,10 @@ struct Dec { // ----------------------------------------------------------------------------- // // The `AlphaNum` class acts as the main parameter type for `StrCat()` and -// `StrAppend()`, providing efficient conversion of numeric, boolean, and -// hexadecimal values (through the `Hex` type) into strings. +// `StrAppend()`, providing efficient conversion of numeric, boolean, decimal, +// and hexadecimal values (through the `Dec` and `Hex` types) into strings. +// `AlphaNum` should only be used as a function parameter. Do not instantiate +// `AlphaNum` directly as a stack variable. class AlphaNum { public: @@ -255,6 +294,13 @@ class AlphaNum { : piece_(NullSafeStringView(c_str)) {} // NOLINT(runtime/explicit) AlphaNum(absl::string_view pc) : piece_(pc) {} // NOLINT(runtime/explicit) + 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) + : piece_(strings_internal::ExtractStringification(sink, v)) {} + template <typename Allocator> AlphaNum( // NOLINT(runtime/explicit) const std::basic_string<char, std::char_traits<char>, Allocator>& str) @@ -274,7 +320,8 @@ class AlphaNum { // This overload matches only scoped enums. template <typename T, typename = typename std::enable_if< - std::is_enum<T>{} && !std::is_convertible<T, int>{}>::type> + std::is_enum<T>{} && !std::is_convertible<T, int>{} && + !strings_internal::HasAbslStringify<T>::value>::type> AlphaNum(T e) // NOLINT(runtime/explicit) : AlphaNum(static_cast<typename std::underlying_type<T>::type>(e)) {} diff --git a/absl/strings/str_cat_test.cc b/absl/strings/str_cat_test.cc index 69df2502..2d74245e 100644 --- a/absl/strings/str_cat_test.cc +++ b/absl/strings/str_cat_test.cc @@ -21,6 +21,7 @@ #include <vector> #include "gtest/gtest.h" +#include "absl/strings/str_format.h" #include "absl/strings/substitute.h" #ifdef __ANDROID__ @@ -442,7 +443,7 @@ TEST(StrCat, AvoidsMemcpyWithNullptr) { EXPECT_EQ(result, "12345"); } -#ifdef GTEST_HAS_DEATH_TEST +#if GTEST_HAS_DEATH_TEST TEST(StrAppend, Death) { std::string s = "self"; // on linux it's "assertion", on mac it's "Assertion", @@ -612,4 +613,53 @@ TEST(Numbers, TestFunctionsMovedOverFromNumbersMain) { TestFastPrints(); } +struct PointStringify { + template <typename FormatSink> + friend void AbslStringify(FormatSink& sink, const PointStringify& p) { + sink.Append("("); + sink.Append(absl::StrCat(p.x)); + sink.Append(", "); + sink.Append(absl::StrCat(p.y)); + sink.Append(")"); + } + + double x = 10.0; + double y = 20.0; +}; + +TEST(StrCat, AbslStringifyExample) { + PointStringify p; + EXPECT_EQ(absl::StrCat(p), "(10, 20)"); + EXPECT_EQ(absl::StrCat("a ", p, " z"), "a (10, 20) z"); +} + +struct PointStringifyUsingFormat { + template <typename FormatSink> + friend void AbslStringify(FormatSink& sink, + const PointStringifyUsingFormat& p) { + absl::Format(&sink, "(%g, %g)", p.x, p.y); + } + + double x = 10.0; + double y = 20.0; +}; + +TEST(StrCat, AbslStringifyExampleUsingFormat) { + PointStringifyUsingFormat p; + EXPECT_EQ(absl::StrCat(p), "(10, 20)"); + EXPECT_EQ(absl::StrCat("a ", p, " z"), "a (10, 20) z"); +} + +enum class EnumWithStringify { Many = 0, Choices = 1 }; + +template <typename Sink> +void AbslStringify(Sink& sink, EnumWithStringify e) { + absl::Format(&sink, "%s", e == EnumWithStringify::Many ? "Many" : "Choices"); +} + +TEST(StrCat, AbslStringifyWithEnum) { + const auto e = EnumWithStringify::Choices; + EXPECT_EQ(absl::StrCat(e), "Choices"); +} + } // namespace diff --git a/absl/strings/str_format.h b/absl/strings/str_format.h index 4b05c70c..3536b70e 100644 --- a/absl/strings/str_format.h +++ b/absl/strings/str_format.h @@ -191,9 +191,9 @@ class FormatCountCapture { // absl::StrFormat(formatString, "TheVillage", 6); // // A format string generally follows the POSIX syntax as used within the POSIX -// `printf` specification. +// `printf` specification. (Exceptions are noted below.) // -// (See http://pubs.opengroup.org/onlinepubs/9699919799/functions/fprintf.html.) +// (See http://pubs.opengroup.org/onlinepubs/9699919799/functions/fprintf.html) // // In specific, the `FormatSpec` supports the following type specifiers: // * `c` for characters @@ -211,6 +211,10 @@ class FormatCountCapture { // * `n` for the special case of writing out the number of characters // written to this point. The resulting value must be captured within an // `absl::FormatCountCapture` type. +// * `v` for values using the default format for a deduced type. These deduced +// types include many of the primitive types denoted here as well as +// user-defined types containing the proper extensions. (See below for more +// information.) // // Implementation-defined behavior: // * A null pointer provided to "%s" or "%p" is output as "(nil)". @@ -239,6 +243,15 @@ class FormatCountCapture { // "%s%d%n", "hello", 123, absl::FormatCountCapture(&n)); // EXPECT_EQ(8, n); // +// NOTE: the `v` specifier (for "value") is a type specifier not present in the +// POSIX specification. %v will format values according to their deduced type. +// `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 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` @@ -570,6 +583,41 @@ ABSL_MUST_USE_RESULT inline bool FormatUntyped( // StrFormat Extensions //------------------------------------------------------------------------------ // +// AbslStringify() +// +// A simpler customization API for formatting user-defined types using +// absl::StrFormat(). The API relies on detecting an overload in the +// user-defined type's namespace of a free (non-member) `AbslStringify()` +// function as a friend definition with the following signature: +// +// template <typename Sink> +// void AbslStringify(Sink& sink, const X& value); +// +// An `AbslStringify()` overload for a type should only be declared in the same +// file and namespace as said type. +// +// Note that unlike with AbslFormatConvert(), AbslStringify() does not allow +// customization of allowed conversion characters. AbslStringify() uses `%v` as +// the underlying conversion specififer. Additionally, AbslStringify() supports +// use with absl::StrCat while AbslFormatConvert() does not. +// +// Example: +// +// struct Point { +// // To add formatting support to `Point`, we simply need to add a free +// // (non-member) function `AbslStringify()`. This method prints in the +// // request format using the underlying `%v` specifier. You can add such a +// // free function using a friend declaration within the body of the class. +// // The sink parameter is a templated type to avoid requiring dependencies. +// template <typename Sink> +// friend void AbslStringify(Sink& sink, const Point& p) { +// absl::Format(&sink, "(%v, %v)", p.x, p.y); +// } +// +// int x; +// int y; +// }; +// // AbslFormatConvert() // // The StrFormat library provides a customization API for formatting @@ -616,9 +664,9 @@ ABSL_MUST_USE_RESULT inline bool FormatUntyped( // AbslFormatConvert(const Point& p, const absl::FormatConversionSpec& spec, // absl::FormatSink* s) { // if (spec.conversion_char() == absl::FormatConversionChar::s) { -// s->Append(absl::StrCat("x=", p.x, " y=", p.y)); +// absl::Format(s, "x=%vy=%v", p.x, p.y); // } else { -// s->Append(absl::StrCat(p.x, ",", p.y)); +// absl::Format(s, "%v,%v", p.x, p.y); // } // return {true}; // } @@ -637,7 +685,7 @@ enum class FormatConversionChar : uint8_t { c, s, // text d, i, o, u, x, X, // int f, F, e, E, g, G, a, A, // float - n, p // misc + n, p, v // misc }; // clang-format on @@ -757,6 +805,7 @@ enum class FormatConversionCharSet : uint64_t { // misc n = str_format_internal::FormatConversionCharToConvInt('n'), p = str_format_internal::FormatConversionCharToConvInt('p'), + v = str_format_internal::FormatConversionCharToConvInt('v'), // Used for width/precision '*' specification. kStar = static_cast<uint64_t>( @@ -771,23 +820,36 @@ enum class FormatConversionCharSet : uint64_t { // FormatSink // -// An abstraction to which conversions write their string data. +// A format sink is a generic abstraction to which conversions may write their +// formatted string data. `absl::FormatConvert()` uses this sink to write its +// formatted string. // class FormatSink { public: - // Appends `count` copies of `ch`. + // FormatSink::Append() + // + // Appends `count` copies of `ch` to the format sink. void Append(size_t count, char ch) { sink_->Append(count, ch); } + // Overload of FormatSink::Append() for appending the characters of a string + // view to a format sink. void Append(string_view v) { sink_->Append(v); } - // Appends the first `precision` bytes of `v`. If this is less than - // `width`, spaces will be appended first (if `left` is false), or + // FormatSink::PutPaddedString() + // + // Appends `precision` number of bytes of `v` to the format sink. If this is + // less than `width`, spaces will be appended first (if `left` is false), or // after (if `left` is true) to ensure the total amount appended is // at least `width`. bool PutPaddedString(string_view v, int width, int precision, bool left) { return sink_->PutPaddedString(v, width, precision, left); } + // Support `absl::Format(&sink, format, args...)`. + friend void AbslFormatFlush(FormatSink* sink, absl::string_view v) { + sink->Append(v); + } + private: friend str_format_internal::FormatSinkImpl; explicit FormatSink(str_format_internal::FormatSinkImpl* s) : sink_(s) {} diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc index 804e6c22..5198fb33 100644 --- a/absl/strings/str_format_test.cc +++ b/absl/strings/str_format_test.cc @@ -42,6 +42,18 @@ TEST_F(FormatEntryPointTest, Format) { EXPECT_TRUE(Format(&sink, pc, 123)); EXPECT_EQ("A format 123", sink); } + +TEST_F(FormatEntryPointTest, FormatWithV) { + std::string sink; + EXPECT_TRUE(Format(&sink, "A format %v", 123)); + EXPECT_EQ("A format 123", sink); + sink.clear(); + + ParsedFormat<'v'> pc("A format %v"); + EXPECT_TRUE(Format(&sink, pc, 123)); + EXPECT_EQ("A format 123", sink); +} + TEST_F(FormatEntryPointTest, UntypedFormat) { constexpr const char* formats[] = { "", @@ -84,6 +96,14 @@ TEST_F(FormatEntryPointTest, StringFormat) { EXPECT_EQ("=123=", StrFormat(view, 123)); } +TEST_F(FormatEntryPointTest, StringFormatV) { + std::string hello = "hello"; + EXPECT_EQ("hello", StrFormat("%v", hello)); + EXPECT_EQ("123", StrFormat("%v", 123)); + constexpr absl::string_view view("=%v=", 4); + EXPECT_EQ("=123=", StrFormat(view, 123)); +} + TEST_F(FormatEntryPointTest, AppendFormat) { std::string s; std::string& r = StrAppendFormat(&s, "%d", 123); @@ -91,6 +111,13 @@ TEST_F(FormatEntryPointTest, AppendFormat) { EXPECT_EQ("123", r); } +TEST_F(FormatEntryPointTest, AppendFormatWithV) { + std::string s; + std::string& r = StrAppendFormat(&s, "%v", 123); + EXPECT_EQ(&s, &r); // should be same object + EXPECT_EQ("123", r); +} + TEST_F(FormatEntryPointTest, AppendFormatFail) { std::string s = "orig"; @@ -103,15 +130,33 @@ TEST_F(FormatEntryPointTest, AppendFormatFail) { {&arg, 1})); } +TEST_F(FormatEntryPointTest, AppendFormatFailWithV) { + std::string s = "orig"; + + UntypedFormatSpec format(" more %v"); + FormatArgImpl arg("not an int"); + + EXPECT_EQ("orig", + str_format_internal::AppendPack( + &s, str_format_internal::UntypedFormatSpecImpl::Extract(format), + {&arg, 1})); +} TEST_F(FormatEntryPointTest, ManyArgs) { - EXPECT_EQ("24", StrFormat("%24$d", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24)); - EXPECT_EQ("60", StrFormat("%60$d", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, - 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, - 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, - 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, - 53, 54, 55, 56, 57, 58, 59, 60)); + EXPECT_EQ( + "60 59 58 57 56 55 54 53 52 51 50 49 48 47 46 45 44 43 42 41 40 39 38 37 " + "36 35 34 33 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", + StrFormat("%60$d %59$d %58$d %57$d %56$d %55$d %54$d %53$d %52$d %51$d " + "%50$d %49$d %48$d %47$d %46$d %45$d %44$d %43$d %42$d %41$d " + "%40$d %39$d %38$d %37$d %36$d %35$d %34$d %33$d %32$d %31$d " + "%30$d %29$d %28$d %27$d %26$d %25$d %24$d %23$d %22$d %21$d " + "%20$d %19$d %18$d %17$d %16$d %15$d %14$d %13$d %12$d %11$d " + "%10$d %9$d %8$d %7$d %6$d %5$d %4$d %3$d %2$d %1$d", + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, + 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, + 51, 52, 53, 54, 55, 56, 57, 58, 59, 60)); } TEST_F(FormatEntryPointTest, Preparsed) { @@ -123,6 +168,15 @@ TEST_F(FormatEntryPointTest, Preparsed) { EXPECT_EQ("=123=", StrFormat(ParsedFormat<'d'>(view), 123)); } +TEST_F(FormatEntryPointTest, PreparsedWithV) { + ParsedFormat<'v'> pc("%v"); + EXPECT_EQ("123", StrFormat(pc, 123)); + // rvalue ok? + EXPECT_EQ("123", StrFormat(ParsedFormat<'v'>("%v"), 123)); + constexpr absl::string_view view("=%v=", 4); + EXPECT_EQ("=123=", StrFormat(ParsedFormat<'v'>(view), 123)); +} + TEST_F(FormatEntryPointTest, FormatCountCapture) { int n = 0; EXPECT_EQ("", StrFormat("%n", FormatCountCapture(&n))); @@ -131,6 +185,14 @@ TEST_F(FormatEntryPointTest, FormatCountCapture) { EXPECT_EQ(3, n); } +TEST_F(FormatEntryPointTest, FormatCountCaptureWithV) { + int n = 0; + EXPECT_EQ("", StrFormat("%n", FormatCountCapture(&n))); + EXPECT_EQ(0, n); + EXPECT_EQ("123", StrFormat("%v%n", 123, FormatCountCapture(&n))); + EXPECT_EQ(3, n); +} + TEST_F(FormatEntryPointTest, FormatCountCaptureWrongType) { // Should reject int*. int n = 0; @@ -143,6 +205,18 @@ TEST_F(FormatEntryPointTest, FormatCountCaptureWrongType) { absl::MakeSpan(args))); } +TEST_F(FormatEntryPointTest, FormatCountCaptureWrongTypeWithV) { + // Should reject int*. + int n = 0; + UntypedFormatSpec format("%v%n"); + int i = 123, *ip = &n; + FormatArgImpl args[2] = {FormatArgImpl(i), FormatArgImpl(ip)}; + + EXPECT_EQ("", str_format_internal::FormatPack( + str_format_internal::UntypedFormatSpecImpl::Extract(format), + absl::MakeSpan(args))); +} + TEST_F(FormatEntryPointTest, FormatCountCaptureMultiple) { int n1 = 0; int n2 = 0; @@ -165,6 +239,21 @@ TEST_F(FormatEntryPointTest, FormatCountCaptureExample) { s); } +TEST_F(FormatEntryPointTest, FormatCountCaptureExampleWithV) { + int n; + std::string s; + std::string a1 = "(1,1)"; + std::string a2 = "(1,2)"; + std::string a3 = "(2,2)"; + StrAppendFormat(&s, "%v: %n%v\n", a1, FormatCountCapture(&n), a2); + StrAppendFormat(&s, "%*s%v\n", n, "", a3); + EXPECT_EQ(7, n); + EXPECT_EQ( + "(1,1): (1,2)\n" + " (2,2)\n", + s); +} + TEST_F(FormatEntryPointTest, Stream) { const std::string formats[] = { "", @@ -183,7 +272,7 @@ TEST_F(FormatEntryPointTest, Stream) { std::ostringstream oss; oss << StreamFormat(*parsed, 123, 3, 49, "multistreaming!!!", 1.01, 1.01); int fmt_result = snprintf(&*buf.begin(), buf.size(), fmt.c_str(), // - 123, 3, 49, "multistreaming!!!", 1.01, 1.01); + 123, 3, 49, "multistreaming!!!", 1.01, 1.01); ASSERT_TRUE(oss) << fmt; ASSERT_TRUE(fmt_result >= 0 && static_cast<size_t>(fmt_result) < buf.size()) << fmt_result; @@ -191,6 +280,36 @@ TEST_F(FormatEntryPointTest, Stream) { } } +TEST_F(FormatEntryPointTest, StreamWithV) { + const std::string formats[] = { + "", + "a", + "%v %u %c %v %f %v", + }; + + const std::string formats_for_buf[] = { + "", + "a", + "%d %u %c %s %f %g", + }; + + std::string buf(4096, '\0'); + for (auto i = 0; i < ABSL_ARRAYSIZE(formats); ++i) { + const auto parsed = + ParsedFormat<'v', 'u', 'c', 'v', 'f', 'v'>::NewAllowIgnored(formats[i]); + std::ostringstream oss; + oss << StreamFormat(*parsed, 123, 3, 49, + absl::string_view("multistreaming!!!"), 1.01, 1.01); + int fmt_result = + snprintf(&*buf.begin(), buf.size(), formats_for_buf[i].c_str(), // + 123, 3, 49, "multistreaming!!!", 1.01, 1.01); + ASSERT_TRUE(oss) << formats[i]; + ASSERT_TRUE(fmt_result >= 0 && static_cast<size_t>(fmt_result) < buf.size()) + << fmt_result; + EXPECT_EQ(buf.c_str(), oss.str()); + } +} + TEST_F(FormatEntryPointTest, StreamOk) { std::ostringstream oss; oss << StreamFormat("hello %d", 123); @@ -198,6 +317,13 @@ TEST_F(FormatEntryPointTest, StreamOk) { EXPECT_TRUE(oss.good()); } +TEST_F(FormatEntryPointTest, StreamOkWithV) { + std::ostringstream oss; + oss << StreamFormat("hello %v", 123); + EXPECT_EQ("hello 123", oss.str()); + EXPECT_TRUE(oss.good()); +} + TEST_F(FormatEntryPointTest, StreamFail) { std::ostringstream oss; UntypedFormatSpec format("hello %d"); @@ -208,6 +334,16 @@ TEST_F(FormatEntryPointTest, StreamFail) { EXPECT_TRUE(oss.fail()); } +TEST_F(FormatEntryPointTest, StreamFailWithV) { + std::ostringstream oss; + UntypedFormatSpec format("hello %v"); + FormatArgImpl arg("non-numeric"); + oss << str_format_internal::Streamable( + str_format_internal::UntypedFormatSpecImpl::Extract(format), {&arg, 1}); + EXPECT_EQ("hello ", oss.str()); // partial write + EXPECT_TRUE(oss.fail()); +} + std::string WithSnprintf(const char* fmt, ...) { std::string buf; buf.resize(128); @@ -249,6 +385,12 @@ TEST_F(FormatEntryPointTest, FormatStreamed) { EXPECT_EQ("123", StrFormat("%s", FormatStreamed(StreamFormat("%d", 123)))); } +TEST_F(FormatEntryPointTest, FormatStreamedWithV) { + EXPECT_EQ("123", StrFormat("%v", FormatStreamed(123))); + EXPECT_EQ("X", StrFormat("%v", FormatStreamed(streamed_test::X()))); + EXPECT_EQ("123", StrFormat("%v", FormatStreamed(StreamFormat("%d", 123)))); +} + // Helper class that creates a temporary file and exposes a FILE* to it. // It will close the file on destruction. class TempFile { @@ -284,6 +426,14 @@ TEST_F(FormatEntryPointTest, FPrintF) { EXPECT_EQ(tmp.ReadFile(), "STRING: ABC NUMBER: -000000019"); } +TEST_F(FormatEntryPointTest, FPrintFWithV) { + TempFile tmp; + int result = + FPrintF(tmp.file(), "STRING: %v NUMBER: %010d", std::string("ABC"), -19); + EXPECT_EQ(result, 30); + EXPECT_EQ(tmp.ReadFile(), "STRING: ABC NUMBER: -000000019"); +} + TEST_F(FormatEntryPointTest, FPrintFError) { errno = 0; int result = FPrintF(stdin, "ABC"); @@ -318,6 +468,23 @@ TEST_F(FormatEntryPointTest, PrintF) { EXPECT_EQ(result, 30); EXPECT_EQ(tmp.ReadFile(), "STRING: ABC NUMBER: -000000019"); } + +TEST_F(FormatEntryPointTest, PrintFWithV) { + int stdout_tmp = dup(STDOUT_FILENO); + + TempFile tmp; + std::fflush(stdout); + dup2(fileno(tmp.file()), STDOUT_FILENO); + + int result = PrintF("STRING: %v NUMBER: %010d", std::string("ABC"), -19); + + std::fflush(stdout); + dup2(stdout_tmp, STDOUT_FILENO); + close(stdout_tmp); + + EXPECT_EQ(result, 30); + EXPECT_EQ(tmp.ReadFile(), "STRING: ABC NUMBER: -000000019"); +} #endif // __GLIBC__ TEST_F(FormatEntryPointTest, SNPrintF) { @@ -347,9 +514,41 @@ TEST_F(FormatEntryPointTest, SNPrintF) { EXPECT_EQ(result, 37); } +TEST_F(FormatEntryPointTest, SNPrintFWithV) { + char buffer[16]; + int result = + SNPrintF(buffer, sizeof(buffer), "STRING: %v", std::string("ABC")); + EXPECT_EQ(result, 11); + EXPECT_EQ(std::string(buffer), "STRING: ABC"); + + result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %v", 123456); + EXPECT_EQ(result, 14); + EXPECT_EQ(std::string(buffer), "NUMBER: 123456"); + + result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %v", 1234567); + EXPECT_EQ(result, 15); + EXPECT_EQ(std::string(buffer), "NUMBER: 1234567"); + + result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %v", 12345678); + EXPECT_EQ(result, 16); + EXPECT_EQ(std::string(buffer), "NUMBER: 1234567"); + + result = SNPrintF(buffer, sizeof(buffer), "NUMBER: %v", 123456789); + EXPECT_EQ(result, 17); + EXPECT_EQ(std::string(buffer), "NUMBER: 1234567"); + + std::string size = "size"; + + result = SNPrintF(nullptr, 0, "Just checking the %v of the output.", size); + EXPECT_EQ(result, 37); +} + TEST(StrFormat, BehavesAsDocumented) { std::string s = absl::StrFormat("%s, %d!", "Hello", 123); EXPECT_EQ("Hello, 123!", s); + std::string hello = "Hello"; + std::string s2 = absl::StrFormat("%v, %v!", hello, 123); + EXPECT_EQ("Hello, 123!", s2); // The format of a replacement is // '%'[position][flags][width['.'precision]][length_modifier][format] EXPECT_EQ(absl::StrFormat("%1$+3.2Lf", 1.1), "+1.10"); @@ -364,22 +563,31 @@ TEST(StrFormat, BehavesAsDocumented) { // "s" - string Eg: "C" -> "C", std::string("C++") -> "C++" // Formats std::string, char*, string_view, and Cord. EXPECT_EQ(StrFormat("%s", "C"), "C"); + EXPECT_EQ(StrFormat("%v", std::string("C")), "C"); EXPECT_EQ(StrFormat("%s", std::string("C++")), "C++"); + EXPECT_EQ(StrFormat("%v", std::string("C++")), "C++"); EXPECT_EQ(StrFormat("%s", string_view("view")), "view"); + EXPECT_EQ(StrFormat("%v", string_view("view")), "view"); EXPECT_EQ(StrFormat("%s", absl::Cord("cord")), "cord"); + EXPECT_EQ(StrFormat("%v", absl::Cord("cord")), "cord"); // Integral Conversion // These format integral types: char, int, long, uint64_t, etc. EXPECT_EQ(StrFormat("%d", char{10}), "10"); EXPECT_EQ(StrFormat("%d", int{10}), "10"); EXPECT_EQ(StrFormat("%d", long{10}), "10"); // NOLINT EXPECT_EQ(StrFormat("%d", uint64_t{10}), "10"); + EXPECT_EQ(StrFormat("%v", int{10}), "10"); + EXPECT_EQ(StrFormat("%v", long{10}), "10"); // NOLINT + EXPECT_EQ(StrFormat("%v", uint64_t{10}), "10"); // d,i - signed decimal Eg: -10 -> "-10" EXPECT_EQ(StrFormat("%d", -10), "-10"); EXPECT_EQ(StrFormat("%i", -10), "-10"); + EXPECT_EQ(StrFormat("%v", -10), "-10"); // o - octal Eg: 10 -> "12" EXPECT_EQ(StrFormat("%o", 10), "12"); // u - unsigned decimal Eg: 10 -> "10" EXPECT_EQ(StrFormat("%u", 10), "10"); + EXPECT_EQ(StrFormat("%v", 10), "10"); // x/X - lower,upper case hex Eg: 10 -> "a"/"A" EXPECT_EQ(StrFormat("%x", 10), "a"); EXPECT_EQ(StrFormat("%X", 10), "A"); @@ -404,6 +612,8 @@ TEST(StrFormat, BehavesAsDocumented) { EXPECT_EQ(StrFormat("%g", .01), "0.01"); EXPECT_EQ(StrFormat("%g", 1e10), "1e+10"); EXPECT_EQ(StrFormat("%G", 1e10), "1E+10"); + EXPECT_EQ(StrFormat("%v", .01), "0.01"); + EXPECT_EQ(StrFormat("%v", 1e10), "1e+10"); // a/A - lower,upper case hex Eg: -3.0 -> "-0x1.8p+1"/"-0X1.8P+1" // On Android platform <=21, there is a regression in hexfloat formatting. @@ -441,6 +651,11 @@ TEST(StrFormat, BehavesAsDocumented) { EXPECT_EQ(StrFormat("%zd", int{1}), "1"); EXPECT_EQ(StrFormat("%td", int{1}), "1"); EXPECT_EQ(StrFormat("%qd", int{1}), "1"); + + // Bool is handled correctly depending on whether %v is used + EXPECT_EQ(StrFormat("%v", true), "true"); + EXPECT_EQ(StrFormat("%v", false), "false"); + EXPECT_EQ(StrFormat("%d", true), "1"); } using str_format_internal::ExtendedParsedFormat; @@ -490,6 +705,15 @@ TEST_F(ParsedFormatTest, SimpleChecked) { SummarizeParsedFormat(ParsedFormat<'s', '*', 'd'>("%s %.*d"))); } +TEST_F(ParsedFormatTest, SimpleCheckedWithV) { + EXPECT_EQ("[ABC]{v:1$v}[DEF]", + SummarizeParsedFormat(ParsedFormat<'v'>("ABC%vDEF"))); + EXPECT_EQ("{v:1$v}[FFF]{v:2$v}[ZZZ]{f:3$f}", + SummarizeParsedFormat(ParsedFormat<'v', 'v', 'f'>("%vFFF%vZZZ%f"))); + EXPECT_EQ("{v:1$v}[ ]{.*d:3$.2$*d}", + SummarizeParsedFormat(ParsedFormat<'v', '*', 'd'>("%v %.*d"))); +} + TEST_F(ParsedFormatTest, SimpleUncheckedCorrect) { auto f = ParsedFormat<'d'>::New("ABC%dDEF"); ASSERT_TRUE(f); @@ -520,6 +744,23 @@ TEST_F(ParsedFormatTest, SimpleUncheckedCorrect) { SummarizeParsedFormat(*dollar)); } +TEST_F(ParsedFormatTest, SimpleUncheckedCorrectWithV) { + auto f = ParsedFormat<'v'>::New("ABC%vDEF"); + ASSERT_TRUE(f); + EXPECT_EQ("[ABC]{v:1$v}[DEF]", SummarizeParsedFormat(*f)); + + std::string format = "%vFFF%vZZZ%f"; + auto f2 = ParsedFormat<'v', 'v', 'f'>::New(format); + + ASSERT_TRUE(f2); + EXPECT_EQ("{v:1$v}[FFF]{v:2$v}[ZZZ]{f:3$f}", SummarizeParsedFormat(*f2)); + + f2 = ParsedFormat<'v', 'v', 'f'>::New("%v %v %f"); + + ASSERT_TRUE(f2); + EXPECT_EQ("{v:1$v}[ ]{v:2$v}[ ]{f:3$f}", SummarizeParsedFormat(*f2)); +} + TEST_F(ParsedFormatTest, SimpleUncheckedIgnoredArgs) { EXPECT_FALSE((ParsedFormat<'d', 's'>::New("ABC"))); EXPECT_FALSE((ParsedFormat<'d', 's'>::New("%dABC"))); @@ -535,6 +776,18 @@ TEST_F(ParsedFormatTest, SimpleUncheckedIgnoredArgs) { EXPECT_EQ("[ABC]{2$s:2$s}", SummarizeParsedFormat(*f)); } +TEST_F(ParsedFormatTest, SimpleUncheckedIgnoredArgsWithV) { + EXPECT_FALSE((ParsedFormat<'v', 'v'>::New("ABC"))); + EXPECT_FALSE((ParsedFormat<'v', 'v'>::New("%vABC"))); + EXPECT_FALSE((ParsedFormat<'v', 's'>::New("ABC%2$s"))); + auto f = ParsedFormat<'v', 'v'>::NewAllowIgnored("ABC"); + ASSERT_TRUE(f); + EXPECT_EQ("[ABC]", SummarizeParsedFormat(*f)); + f = ParsedFormat<'v', 'v'>::NewAllowIgnored("%vABC"); + ASSERT_TRUE(f); + EXPECT_EQ("{v:1$v}[ABC]", SummarizeParsedFormat(*f)); +} + TEST_F(ParsedFormatTest, SimpleUncheckedUnsupported) { EXPECT_FALSE(ParsedFormat<'d'>::New("%1$d %1$x")); EXPECT_FALSE(ParsedFormat<'x'>::New("%1$d %1$x")); @@ -549,6 +802,15 @@ TEST_F(ParsedFormatTest, SimpleUncheckedIncorrect) { EXPECT_FALSE((ParsedFormat<'s', 'd', 'g'>::New(format))); } +TEST_F(ParsedFormatTest, SimpleUncheckedIncorrectWithV) { + EXPECT_FALSE(ParsedFormat<'v'>::New("")); + + EXPECT_FALSE(ParsedFormat<'v'>::New("ABC%vDEF%v")); + + std::string format = "%vFFF%vZZZ%f"; + EXPECT_FALSE((ParsedFormat<'v', 'v', 'g'>::New(format))); +} + #if defined(__cpp_nontype_template_parameter_auto) template <auto T> @@ -595,6 +857,23 @@ TEST_F(ParsedFormatTest, ExtendedTyping) { 's'>::New("%s%s"); ASSERT_TRUE(v4); } + +TEST_F(ParsedFormatTest, ExtendedTypingWithV) { + EXPECT_FALSE(ParsedFormat<FormatConversionCharSet::v>::New("")); + ASSERT_TRUE(ParsedFormat<absl::FormatConversionCharSet::v>::New("%v")); + auto v1 = ParsedFormat<'v', absl::FormatConversionCharSet::v>::New("%v%v"); + ASSERT_TRUE(v1); + auto v2 = ParsedFormat<absl::FormatConversionCharSet::v, 'v'>::New("%v%v"); + ASSERT_TRUE(v2); + auto v3 = ParsedFormat<absl::FormatConversionCharSet::v | + absl::FormatConversionCharSet::v, + 'v'>::New("%v%v"); + ASSERT_TRUE(v3); + auto v4 = ParsedFormat<absl::FormatConversionCharSet::v | + absl::FormatConversionCharSet::v, + 'v'>::New("%v%v"); + ASSERT_TRUE(v4); +} #endif TEST_F(ParsedFormatTest, UncheckedCorrect) { @@ -638,6 +917,28 @@ TEST_F(ParsedFormatTest, UncheckedCorrect) { SummarizeParsedFormat(*dollar)); } +TEST_F(ParsedFormatTest, UncheckedCorrectWithV) { + auto f = + ExtendedParsedFormat<absl::FormatConversionCharSet::v>::New("ABC%vDEF"); + ASSERT_TRUE(f); + EXPECT_EQ("[ABC]{v:1$v}[DEF]", SummarizeParsedFormat(*f)); + + std::string format = "%vFFF%vZZZ%f"; + auto f2 = ExtendedParsedFormat< + absl::FormatConversionCharSet::v, absl::FormatConversionCharSet::v, + absl::FormatConversionCharSet::kFloating>::New(format); + + ASSERT_TRUE(f2); + EXPECT_EQ("{v:1$v}[FFF]{v:2$v}[ZZZ]{f:3$f}", SummarizeParsedFormat(*f2)); + + f2 = ExtendedParsedFormat< + absl::FormatConversionCharSet::v, absl::FormatConversionCharSet::v, + absl::FormatConversionCharSet::kFloating>::New("%v %v %f"); + + ASSERT_TRUE(f2); + EXPECT_EQ("{v:1$v}[ ]{v:2$v}[ ]{f:3$f}", SummarizeParsedFormat(*f2)); +} + TEST_F(ParsedFormatTest, UncheckedIgnoredArgs) { EXPECT_FALSE( (ExtendedParsedFormat<absl::FormatConversionCharSet::d, @@ -665,6 +966,28 @@ TEST_F(ParsedFormatTest, UncheckedIgnoredArgs) { EXPECT_EQ("[ABC]{2$s:2$s}", SummarizeParsedFormat(*f)); } +TEST_F(ParsedFormatTest, UncheckedIgnoredArgsWithV) { + EXPECT_FALSE( + (ExtendedParsedFormat<absl::FormatConversionCharSet::v, + absl::FormatConversionCharSet::v>::New("ABC"))); + EXPECT_FALSE( + (ExtendedParsedFormat<absl::FormatConversionCharSet::v, + absl::FormatConversionCharSet::v>::New("%vABC"))); + EXPECT_FALSE((ExtendedParsedFormat<absl::FormatConversionCharSet::v, + absl::FormatConversionCharSet::s>:: + New("ABC%2$s"))); + auto f = ExtendedParsedFormat< + absl::FormatConversionCharSet::v, + absl::FormatConversionCharSet::v>::NewAllowIgnored("ABC"); + ASSERT_TRUE(f); + EXPECT_EQ("[ABC]", SummarizeParsedFormat(*f)); + f = ExtendedParsedFormat< + absl::FormatConversionCharSet::v, + absl::FormatConversionCharSet::v>::NewAllowIgnored("%vABC"); + ASSERT_TRUE(f); + EXPECT_EQ("{v:1$v}[ABC]", SummarizeParsedFormat(*f)); +} + TEST_F(ParsedFormatTest, UncheckedMultipleTypes) { auto dx = ExtendedParsedFormat<absl::FormatConversionCharSet::d | @@ -691,12 +1014,35 @@ TEST_F(ParsedFormatTest, UncheckedIncorrect) { absl::FormatConversionCharSet::g>::New(format))); } +TEST_F(ParsedFormatTest, UncheckedIncorrectWithV) { + EXPECT_FALSE(ExtendedParsedFormat<absl::FormatConversionCharSet::v>::New("")); + + EXPECT_FALSE(ExtendedParsedFormat<absl::FormatConversionCharSet::v>::New( + "ABC%vDEF%v")); + + std::string format = "%vFFF%vZZZ%f"; + EXPECT_FALSE( + (ExtendedParsedFormat<absl::FormatConversionCharSet::v, + absl::FormatConversionCharSet::g>::New(format))); +} + TEST_F(ParsedFormatTest, RegressionMixPositional) { EXPECT_FALSE( (ExtendedParsedFormat<absl::FormatConversionCharSet::d, absl::FormatConversionCharSet::o>::New("%1$d %o"))); } +TEST_F(ParsedFormatTest, DisallowModifiersWithV) { + auto f = ParsedFormat<'v'>::New("ABC%80vDEF"); + EXPECT_EQ(f, nullptr); + + f = ParsedFormat<'v'>::New("ABC%0vDEF"); + EXPECT_EQ(f, nullptr); + + f = ParsedFormat<'v'>::New("ABC%.1vDEF"); + EXPECT_EQ(f, nullptr); +} + using FormatWrapperTest = ::testing::Test; // Plain wrapper for StrFormat. @@ -710,11 +1056,22 @@ TEST_F(FormatWrapperTest, ConstexprStringFormat) { EXPECT_EQ(WrappedFormat("%s there", "hello"), "hello there"); } +TEST_F(FormatWrapperTest, ConstexprStringFormatWithV) { + std::string hello = "hello"; + EXPECT_EQ(WrappedFormat("%v there", hello), "hello there"); +} + TEST_F(FormatWrapperTest, ParsedFormat) { ParsedFormat<'s'> format("%s there"); EXPECT_EQ(WrappedFormat(format, "hello"), "hello there"); } +TEST_F(FormatWrapperTest, ParsedFormatWithV) { + std::string hello = "hello"; + ParsedFormat<'v'> format("%v there"); + EXPECT_EQ(WrappedFormat(format, hello), "hello there"); +} + } // namespace ABSL_NAMESPACE_END } // namespace absl @@ -724,7 +1081,8 @@ using FormatExtensionTest = ::testing::Test; struct Point { friend absl::FormatConvertResult<absl::FormatConversionCharSet::kString | - absl::FormatConversionCharSet::kIntegral> + absl::FormatConversionCharSet::kIntegral | + absl::FormatConversionCharSet::v> AbslFormatConvert(const Point& p, const absl::FormatConversionSpec& spec, absl::FormatSink* s) { if (spec.conversion_char() == absl::FormatConversionChar::s) { @@ -743,6 +1101,7 @@ TEST_F(FormatExtensionTest, AbslFormatConvertExample) { Point p; EXPECT_EQ(absl::StrFormat("a %s z", p), "a x=10 y=20 z"); EXPECT_EQ(absl::StrFormat("a %d z", p), "a 10,20 z"); + EXPECT_EQ(absl::StrFormat("a %v z", p), "a 10,20 z"); // Typed formatting will fail to compile an invalid format. // StrFormat("%f", p); // Does not compile. @@ -751,6 +1110,83 @@ TEST_F(FormatExtensionTest, AbslFormatConvertExample) { // FormatUntyped will return false for bad character. EXPECT_FALSE(absl::FormatUntyped(&actual, f1, {absl::FormatArg(p)})); } + +struct PointStringify { + template <typename FormatSink> + friend void AbslStringify(FormatSink& sink, const PointStringify& p) { + sink.Append(absl::StrCat("(", p.x, ", ", p.y, ")")); + } + + double x = 10.0; + double y = 20.0; +}; + +TEST_F(FormatExtensionTest, AbslStringifyExample) { + PointStringify p; + EXPECT_EQ(absl::StrFormat("a %v z", p), "a (10, 20) z"); +} + +struct PointStringifyUsingFormat { + template <typename FormatSink> + friend void AbslStringify(FormatSink& sink, + const PointStringifyUsingFormat& p) { + absl::Format(&sink, "(%g, %g)", p.x, p.y); + } + + double x = 10.0; + double y = 20.0; +}; + +TEST_F(FormatExtensionTest, AbslStringifyExampleUsingFormat) { + PointStringifyUsingFormat p; + EXPECT_EQ(absl::StrFormat("a %v z", p), "a (10, 20) z"); +} + +enum class EnumClassWithStringify { Many = 0, Choices = 1 }; + +template <typename Sink> +void AbslStringify(Sink& sink, EnumClassWithStringify e) { + absl::Format(&sink, "%s", + e == EnumClassWithStringify::Many ? "Many" : "Choices"); +} + +enum EnumWithStringify { Many, Choices }; + +template <typename Sink> +void AbslStringify(Sink& sink, EnumWithStringify e) { + absl::Format(&sink, "%s", e == EnumWithStringify::Many ? "Many" : "Choices"); +} + +TEST_F(FormatExtensionTest, AbslStringifyWithEnumWithV) { + const auto e_class = EnumClassWithStringify::Choices; + EXPECT_EQ(absl::StrFormat("My choice is %v", e_class), + "My choice is Choices"); + + const auto e = EnumWithStringify::Choices; + EXPECT_EQ(absl::StrFormat("My choice is %v", e), "My choice is Choices"); +} + +TEST_F(FormatExtensionTest, AbslStringifyEnumWithD) { + const auto e_class = EnumClassWithStringify::Many; + EXPECT_EQ(absl::StrFormat("My choice is %d", e_class), "My choice is 0"); + + const auto e = EnumWithStringify::Choices; + EXPECT_EQ(absl::StrFormat("My choice is %d", e), "My choice is 1"); +} + +enum class EnumWithLargerValue { x = 32 }; + +template <typename Sink> +void AbslStringify(Sink& sink, EnumWithLargerValue e) { + absl::Format(&sink, "%s", "Many"); +} + +TEST_F(FormatExtensionTest, AbslStringifyEnumOtherSpecifiers) { + const auto e = EnumWithLargerValue::x; + EXPECT_EQ(absl::StrFormat("My choice is %g", e), "My choice is 32"); + EXPECT_EQ(absl::StrFormat("My choice is %x", e), "My choice is 20"); +} + } // namespace // Some codegen thunks that we can use to easily dump the generated assembly for diff --git a/absl/strings/str_split_test.cc b/absl/strings/str_split_test.cc index 1b4427b8..04a64a42 100644 --- a/absl/strings/str_split_test.cc +++ b/absl/strings/str_split_test.cc @@ -369,7 +369,7 @@ TEST(SplitIterator, EqualityAsEndCondition) { TEST(Splitter, RangeIterators) { auto splitter = absl::StrSplit("a,b,c", ','); std::vector<absl::string_view> output; - for (const absl::string_view& p : splitter) { + for (absl::string_view p : splitter) { output.push_back(p); } EXPECT_THAT(output, ElementsAre("a", "b", "c")); diff --git a/absl/strings/string_view.cc b/absl/strings/string_view.cc index adce3be9..e2261625 100644 --- a/absl/strings/string_view.cc +++ b/absl/strings/string_view.cc @@ -32,7 +32,7 @@ void WritePadding(std::ostream& o, size_t pad) { memset(fill_buf, o.fill(), sizeof(fill_buf)); while (pad) { size_t n = std::min(pad, sizeof(fill_buf)); - o.write(fill_buf, n); + o.write(fill_buf, static_cast<std::streamsize>(n)); pad -= n; } } @@ -63,7 +63,7 @@ std::ostream& operator<<(std::ostream& o, string_view piece) { size_t lpad = 0; size_t rpad = 0; if (static_cast<size_t>(o.width()) > piece.size()) { - size_t pad = o.width() - piece.size(); + size_t pad = static_cast<size_t>(o.width()) - piece.size(); if ((o.flags() & o.adjustfield) == o.left) { rpad = pad; } else { @@ -71,7 +71,7 @@ std::ostream& operator<<(std::ostream& o, string_view piece) { } } if (lpad) WritePadding(o, lpad); - o.write(piece.data(), piece.size()); + o.write(piece.data(), static_cast<std::streamsize>(piece.size())); if (rpad) WritePadding(o, rpad); o.width(0); } @@ -86,7 +86,7 @@ string_view::size_type string_view::find(string_view s, } const char* result = strings_internal::memmatch(ptr_ + pos, length_ - pos, s.ptr_, s.length_); - return result ? result - ptr_ : npos; + return result ? static_cast<size_type>(result - ptr_) : npos; } string_view::size_type string_view::find(char c, size_type pos) const noexcept { @@ -95,7 +95,7 @@ string_view::size_type string_view::find(char c, size_type pos) const noexcept { } const char* result = static_cast<const char*>(memchr(ptr_ + pos, c, length_ - pos)); - return result != nullptr ? result - ptr_ : npos; + return result != nullptr ? static_cast<size_type>(result - ptr_) : npos; } string_view::size_type string_view::rfind(string_view s, @@ -104,7 +104,7 @@ string_view::size_type string_view::rfind(string_view s, if (s.empty()) return std::min(length_, pos); const char* last = ptr_ + std::min(length_ - s.length_, pos) + s.length_; const char* result = std::find_end(ptr_, last, s.ptr_, s.ptr_ + s.length_); - return result != last ? result - ptr_ : npos; + return result != last ? static_cast<size_type>(result - ptr_) : npos; } // Search range is [0..pos] inclusive. If pos == npos, search everything. diff --git a/absl/strings/string_view.h b/absl/strings/string_view.h index e3239f57..eae11b2a 100644 --- a/absl/strings/string_view.h +++ b/absl/strings/string_view.h @@ -63,12 +63,6 @@ ABSL_NAMESPACE_END #define ABSL_INTERNAL_STRING_VIEW_MEMCMP memcmp #endif // ABSL_HAVE_BUILTIN(__builtin_memcmp) -#if defined(__cplusplus) && __cplusplus >= 201402L -#define ABSL_INTERNAL_STRING_VIEW_CXX14_CONSTEXPR constexpr -#else -#define ABSL_INTERNAL_STRING_VIEW_CXX14_CONSTEXPR -#endif - namespace absl { ABSL_NAMESPACE_BEGIN @@ -341,7 +335,7 @@ class string_view { // // Removes the first `n` characters from the `string_view`. Note that the // underlying string is not changed, only the view. - ABSL_INTERNAL_STRING_VIEW_CXX14_CONSTEXPR void remove_prefix(size_type n) { + constexpr void remove_prefix(size_type n) { ABSL_HARDENING_ASSERT(n <= length_); ptr_ += n; length_ -= n; @@ -351,7 +345,7 @@ class string_view { // // Removes the last `n` characters from the `string_view`. Note that the // underlying string is not changed, only the view. - ABSL_INTERNAL_STRING_VIEW_CXX14_CONSTEXPR void remove_suffix(size_type n) { + constexpr void remove_suffix(size_type n) { ABSL_HARDENING_ASSERT(n <= length_); length_ -= n; } @@ -359,7 +353,7 @@ class string_view { // string_view::swap() // // Swaps this `string_view` with another `string_view`. - ABSL_INTERNAL_STRING_VIEW_CXX14_CONSTEXPR void swap(string_view& s) noexcept { + constexpr void swap(string_view& s) noexcept { auto t = *this; *this = s; s = t; @@ -678,7 +672,6 @@ std::ostream& operator<<(std::ostream& o, string_view piece); ABSL_NAMESPACE_END } // namespace absl -#undef ABSL_INTERNAL_STRING_VIEW_CXX14_CONSTEXPR #undef ABSL_INTERNAL_STRING_VIEW_MEMCMP #endif // ABSL_USES_STD_STRING_VIEW diff --git a/absl/strings/string_view_test.cc b/absl/strings/string_view_test.cc index 9d5463a1..990c211a 100644 --- a/absl/strings/string_view_test.cc +++ b/absl/strings/string_view_test.cc @@ -82,7 +82,7 @@ TEST(StringViewTest, Ctor) { // Null. absl::string_view s10; EXPECT_TRUE(s10.data() == nullptr); - EXPECT_EQ(0, s10.length()); + EXPECT_EQ(0u, s10.length()); } { @@ -90,17 +90,17 @@ TEST(StringViewTest, Ctor) { const char* hello = "hello"; absl::string_view s20(hello); EXPECT_TRUE(s20.data() == hello); - EXPECT_EQ(5, s20.length()); + EXPECT_EQ(5u, s20.length()); // const char* with length. absl::string_view s21(hello, 4); EXPECT_TRUE(s21.data() == hello); - EXPECT_EQ(4, s21.length()); + EXPECT_EQ(4u, s21.length()); // Not recommended, but valid C++ absl::string_view s22(hello, 6); EXPECT_TRUE(s22.data() == hello); - EXPECT_EQ(6, s22.length()); + EXPECT_EQ(6u, s22.length()); } { @@ -108,7 +108,7 @@ TEST(StringViewTest, Ctor) { std::string hola = "hola"; absl::string_view s30(hola); EXPECT_TRUE(s30.data() == hola.data()); - EXPECT_EQ(4, s30.length()); + EXPECT_EQ(4u, s30.length()); // std::string with embedded '\0'. hola.push_back('\0'); @@ -116,7 +116,7 @@ TEST(StringViewTest, Ctor) { hola.push_back('\0'); absl::string_view s31(hola); EXPECT_TRUE(s31.data() == hola.data()); - EXPECT_EQ(8, s31.length()); + EXPECT_EQ(8u, s31.length()); } { @@ -165,7 +165,7 @@ TEST(StringViewTest, STLComparator) { map.insert(std::make_pair(p1, 0)); map.insert(std::make_pair(p2, 1)); map.insert(std::make_pair(p3, 2)); - EXPECT_EQ(map.size(), 3); + EXPECT_EQ(map.size(), 3u); TestMap::const_iterator iter = map.begin(); EXPECT_EQ(iter->second, 1); @@ -183,7 +183,7 @@ TEST(StringViewTest, STLComparator) { EXPECT_TRUE(new_iter != map.end()); map.erase(new_iter); - EXPECT_EQ(map.size(), 2); + EXPECT_EQ(map.size(), 2u); iter = map.begin(); EXPECT_EQ(iter->second, 2); @@ -261,11 +261,11 @@ TEST(StringViewTest, ComparisonOperators) { TEST(StringViewTest, ComparisonOperatorsByCharacterPosition) { std::string x; - for (int i = 0; i < 256; i++) { + for (size_t i = 0; i < 256; i++) { x += 'a'; std::string y = x; COMPARE(true, ==, x, y); - for (int j = 0; j < i; j++) { + for (size_t j = 0; j < i; j++) { std::string z = x; z[j] = 'b'; // Differs in position 'j' COMPARE(false, ==, x, z); @@ -341,12 +341,12 @@ TEST(StringViewTest, STL1) { EXPECT_EQ(*(c.rend() - 1), 'x'); EXPECT_TRUE(a.rbegin() + 26 == a.rend()); - EXPECT_EQ(a.size(), 26); - EXPECT_EQ(b.size(), 3); - EXPECT_EQ(c.size(), 3); - EXPECT_EQ(d.size(), 6); - EXPECT_EQ(e.size(), 0); - EXPECT_EQ(f.size(), 7); + EXPECT_EQ(a.size(), 26u); + EXPECT_EQ(b.size(), 3u); + EXPECT_EQ(c.size(), 3u); + EXPECT_EQ(d.size(), 6u); + EXPECT_EQ(e.size(), 0u); + EXPECT_EQ(f.size(), 7u); EXPECT_TRUE(!d.empty()); EXPECT_TRUE(d.begin() != d.end()); @@ -356,17 +356,17 @@ TEST(StringViewTest, STL1) { EXPECT_TRUE(e.begin() == e.end()); char buf[4] = { '%', '%', '%', '%' }; - EXPECT_EQ(a.copy(buf, 4), 4); + EXPECT_EQ(a.copy(buf, 4), 4u); EXPECT_EQ(buf[0], a[0]); EXPECT_EQ(buf[1], a[1]); EXPECT_EQ(buf[2], a[2]); EXPECT_EQ(buf[3], a[3]); - EXPECT_EQ(a.copy(buf, 3, 7), 3); + EXPECT_EQ(a.copy(buf, 3, 7), 3u); EXPECT_EQ(buf[0], a[7]); EXPECT_EQ(buf[1], a[8]); EXPECT_EQ(buf[2], a[9]); EXPECT_EQ(buf[3], a[3]); - EXPECT_EQ(c.copy(buf, 99), 3); + EXPECT_EQ(c.copy(buf, 99), 3u); EXPECT_EQ(buf[0], c[0]); EXPECT_EQ(buf[1], c[1]); EXPECT_EQ(buf[2], c[2]); @@ -393,22 +393,22 @@ TEST(StringViewTest, STL2) { 7); d = absl::string_view(); - EXPECT_EQ(d.size(), 0); + EXPECT_EQ(d.size(), 0u); EXPECT_TRUE(d.empty()); EXPECT_TRUE(d.data() == nullptr); EXPECT_TRUE(d.begin() == d.end()); - EXPECT_EQ(a.find(b), 0); + EXPECT_EQ(a.find(b), 0u); EXPECT_EQ(a.find(b, 1), absl::string_view::npos); - EXPECT_EQ(a.find(c), 23); - EXPECT_EQ(a.find(c, 9), 23); + EXPECT_EQ(a.find(c), 23u); + EXPECT_EQ(a.find(c, 9), 23u); EXPECT_EQ(a.find(c, absl::string_view::npos), absl::string_view::npos); EXPECT_EQ(b.find(c), absl::string_view::npos); EXPECT_EQ(b.find(c, absl::string_view::npos), absl::string_view::npos); - EXPECT_EQ(a.find(d), 0); - EXPECT_EQ(a.find(e), 0); - EXPECT_EQ(a.find(d, 12), 12); - EXPECT_EQ(a.find(e, 17), 17); + EXPECT_EQ(a.find(d), 0u); + EXPECT_EQ(a.find(e), 0u); + EXPECT_EQ(a.find(d, 12), 12u); + EXPECT_EQ(a.find(e, 17), 17u); absl::string_view g("xx not found bb"); EXPECT_EQ(a.find(g), absl::string_view::npos); // empty string nonsense @@ -427,17 +427,17 @@ TEST(StringViewTest, STL2) { EXPECT_EQ(e.find(d, 4), std::string().find(std::string(), 4)); EXPECT_EQ(e.find(e, 4), std::string().find(std::string(), 4)); - EXPECT_EQ(a.find('a'), 0); - EXPECT_EQ(a.find('c'), 2); - EXPECT_EQ(a.find('z'), 25); + EXPECT_EQ(a.find('a'), 0u); + EXPECT_EQ(a.find('c'), 2u); + EXPECT_EQ(a.find('z'), 25u); EXPECT_EQ(a.find('$'), absl::string_view::npos); EXPECT_EQ(a.find('\0'), absl::string_view::npos); - EXPECT_EQ(f.find('\0'), 3); - EXPECT_EQ(f.find('3'), 2); - EXPECT_EQ(f.find('5'), 5); - EXPECT_EQ(g.find('o'), 4); - EXPECT_EQ(g.find('o', 4), 4); - EXPECT_EQ(g.find('o', 5), 8); + EXPECT_EQ(f.find('\0'), 3u); + EXPECT_EQ(f.find('3'), 2u); + EXPECT_EQ(f.find('5'), 5u); + EXPECT_EQ(g.find('o'), 4u); + EXPECT_EQ(g.find('o', 4), 4u); + EXPECT_EQ(g.find('o', 5), 8u); EXPECT_EQ(a.find('b', 5), absl::string_view::npos); // empty string nonsense EXPECT_EQ(d.find('\0'), absl::string_view::npos); @@ -449,8 +449,8 @@ TEST(StringViewTest, STL2) { EXPECT_EQ(d.find('x', 4), absl::string_view::npos); EXPECT_EQ(e.find('x', 7), absl::string_view::npos); - EXPECT_EQ(a.find(b.data(), 1, 0), 1); - EXPECT_EQ(a.find(c.data(), 9, 0), 9); + EXPECT_EQ(a.find(b.data(), 1, 0), 1u); + EXPECT_EQ(a.find(c.data(), 9, 0), 9u); EXPECT_EQ(a.find(c.data(), absl::string_view::npos, 0), absl::string_view::npos); EXPECT_EQ(b.find(c.data(), absl::string_view::npos, 0), @@ -460,16 +460,16 @@ TEST(StringViewTest, STL2) { EXPECT_EQ(e.find(b.data(), 7, 0), absl::string_view::npos); EXPECT_EQ(a.find(b.data(), 1), absl::string_view::npos); - EXPECT_EQ(a.find(c.data(), 9), 23); + EXPECT_EQ(a.find(c.data(), 9), 23u); EXPECT_EQ(a.find(c.data(), absl::string_view::npos), absl::string_view::npos); EXPECT_EQ(b.find(c.data(), absl::string_view::npos), absl::string_view::npos); // empty string nonsense EXPECT_EQ(d.find(b.data(), 4), absl::string_view::npos); EXPECT_EQ(e.find(b.data(), 7), absl::string_view::npos); - EXPECT_EQ(a.rfind(b), 0); - EXPECT_EQ(a.rfind(b, 1), 0); - EXPECT_EQ(a.rfind(c), 23); + EXPECT_EQ(a.rfind(b), 0u); + EXPECT_EQ(a.rfind(b, 1), 0u); + EXPECT_EQ(a.rfind(c), 23u); EXPECT_EQ(a.rfind(c, 22), absl::string_view::npos); EXPECT_EQ(a.rfind(c, 1), absl::string_view::npos); EXPECT_EQ(a.rfind(c, 0), absl::string_view::npos); @@ -477,8 +477,8 @@ TEST(StringViewTest, STL2) { EXPECT_EQ(b.rfind(c, 0), absl::string_view::npos); EXPECT_EQ(a.rfind(d), std::string(a).rfind(std::string())); EXPECT_EQ(a.rfind(e), std::string(a).rfind(std::string())); - EXPECT_EQ(a.rfind(d, 12), 12); - EXPECT_EQ(a.rfind(e, 17), 17); + EXPECT_EQ(a.rfind(d, 12), 12u); + EXPECT_EQ(a.rfind(e, 17), 17u); EXPECT_EQ(a.rfind(g), absl::string_view::npos); EXPECT_EQ(d.rfind(b), absl::string_view::npos); EXPECT_EQ(e.rfind(b), absl::string_view::npos); @@ -494,28 +494,28 @@ TEST(StringViewTest, STL2) { EXPECT_EQ(d.rfind(e), std::string().rfind(std::string())); EXPECT_EQ(e.rfind(e), std::string().rfind(std::string())); - EXPECT_EQ(g.rfind('o'), 8); + EXPECT_EQ(g.rfind('o'), 8u); EXPECT_EQ(g.rfind('q'), absl::string_view::npos); - EXPECT_EQ(g.rfind('o', 8), 8); - EXPECT_EQ(g.rfind('o', 7), 4); + EXPECT_EQ(g.rfind('o', 8), 8u); + EXPECT_EQ(g.rfind('o', 7), 4u); EXPECT_EQ(g.rfind('o', 3), absl::string_view::npos); - EXPECT_EQ(f.rfind('\0'), 3); - EXPECT_EQ(f.rfind('\0', 12), 3); - EXPECT_EQ(f.rfind('3'), 2); - EXPECT_EQ(f.rfind('5'), 5); + EXPECT_EQ(f.rfind('\0'), 3u); + EXPECT_EQ(f.rfind('\0', 12), 3u); + EXPECT_EQ(f.rfind('3'), 2u); + EXPECT_EQ(f.rfind('5'), 5u); // empty string nonsense EXPECT_EQ(d.rfind('o'), absl::string_view::npos); EXPECT_EQ(e.rfind('o'), absl::string_view::npos); EXPECT_EQ(d.rfind('o', 4), absl::string_view::npos); EXPECT_EQ(e.rfind('o', 7), absl::string_view::npos); - EXPECT_EQ(a.rfind(b.data(), 1, 0), 1); - EXPECT_EQ(a.rfind(c.data(), 22, 0), 22); - EXPECT_EQ(a.rfind(c.data(), 1, 0), 1); - EXPECT_EQ(a.rfind(c.data(), 0, 0), 0); - EXPECT_EQ(b.rfind(c.data(), 0, 0), 0); - EXPECT_EQ(d.rfind(b.data(), 4, 0), 0); - EXPECT_EQ(e.rfind(b.data(), 7, 0), 0); + EXPECT_EQ(a.rfind(b.data(), 1, 0), 1u); + EXPECT_EQ(a.rfind(c.data(), 22, 0), 22u); + EXPECT_EQ(a.rfind(c.data(), 1, 0), 1u); + EXPECT_EQ(a.rfind(c.data(), 0, 0), 0u); + EXPECT_EQ(b.rfind(c.data(), 0, 0), 0u); + EXPECT_EQ(d.rfind(b.data(), 4, 0), 0u); + EXPECT_EQ(e.rfind(b.data(), 7, 0), 0u); } // Continued from STL2 @@ -533,18 +533,18 @@ TEST(StringViewTest, STL2FindFirst) { absl::string_view g("xx not found bb"); d = absl::string_view(); - EXPECT_EQ(a.find_first_of(b), 0); - EXPECT_EQ(a.find_first_of(b, 0), 0); - EXPECT_EQ(a.find_first_of(b, 1), 1); - EXPECT_EQ(a.find_first_of(b, 2), 2); + EXPECT_EQ(a.find_first_of(b), 0u); + EXPECT_EQ(a.find_first_of(b, 0), 0u); + EXPECT_EQ(a.find_first_of(b, 1), 1u); + EXPECT_EQ(a.find_first_of(b, 2), 2u); EXPECT_EQ(a.find_first_of(b, 3), absl::string_view::npos); - EXPECT_EQ(a.find_first_of(c), 23); - EXPECT_EQ(a.find_first_of(c, 23), 23); - EXPECT_EQ(a.find_first_of(c, 24), 24); - EXPECT_EQ(a.find_first_of(c, 25), 25); + EXPECT_EQ(a.find_first_of(c), 23u); + EXPECT_EQ(a.find_first_of(c, 23), 23u); + EXPECT_EQ(a.find_first_of(c, 24), 24u); + EXPECT_EQ(a.find_first_of(c, 25), 25u); EXPECT_EQ(a.find_first_of(c, 26), absl::string_view::npos); - EXPECT_EQ(g.find_first_of(b), 13); - EXPECT_EQ(g.find_first_of(c), 0); + EXPECT_EQ(g.find_first_of(b), 13u); + EXPECT_EQ(g.find_first_of(c), 0u); EXPECT_EQ(a.find_first_of(f), absl::string_view::npos); EXPECT_EQ(f.find_first_of(a), absl::string_view::npos); // empty string nonsense @@ -557,19 +557,19 @@ TEST(StringViewTest, STL2FindFirst) { EXPECT_EQ(d.find_first_of(e), absl::string_view::npos); EXPECT_EQ(e.find_first_of(e), absl::string_view::npos); - EXPECT_EQ(a.find_first_not_of(b), 3); - EXPECT_EQ(a.find_first_not_of(c), 0); + EXPECT_EQ(a.find_first_not_of(b), 3u); + EXPECT_EQ(a.find_first_not_of(c), 0u); EXPECT_EQ(b.find_first_not_of(a), absl::string_view::npos); EXPECT_EQ(c.find_first_not_of(a), absl::string_view::npos); - EXPECT_EQ(f.find_first_not_of(a), 0); - EXPECT_EQ(a.find_first_not_of(f), 0); - EXPECT_EQ(a.find_first_not_of(d), 0); - EXPECT_EQ(a.find_first_not_of(e), 0); + EXPECT_EQ(f.find_first_not_of(a), 0u); + EXPECT_EQ(a.find_first_not_of(f), 0u); + EXPECT_EQ(a.find_first_not_of(d), 0u); + EXPECT_EQ(a.find_first_not_of(e), 0u); // empty string nonsense - EXPECT_EQ(a.find_first_not_of(d), 0); - EXPECT_EQ(a.find_first_not_of(e), 0); - EXPECT_EQ(a.find_first_not_of(d, 1), 1); - EXPECT_EQ(a.find_first_not_of(e, 1), 1); + EXPECT_EQ(a.find_first_not_of(d), 0u); + EXPECT_EQ(a.find_first_not_of(e), 0u); + EXPECT_EQ(a.find_first_not_of(d, 1), 1u); + EXPECT_EQ(a.find_first_not_of(e, 1), 1u); EXPECT_EQ(a.find_first_not_of(d, a.size() - 1), a.size() - 1); EXPECT_EQ(a.find_first_not_of(e, a.size() - 1), a.size() - 1); EXPECT_EQ(a.find_first_not_of(d, a.size()), absl::string_view::npos); @@ -588,11 +588,11 @@ TEST(StringViewTest, STL2FindFirst) { absl::string_view h("===="); EXPECT_EQ(h.find_first_not_of('='), absl::string_view::npos); EXPECT_EQ(h.find_first_not_of('=', 3), absl::string_view::npos); - EXPECT_EQ(h.find_first_not_of('\0'), 0); - EXPECT_EQ(g.find_first_not_of('x'), 2); - EXPECT_EQ(f.find_first_not_of('\0'), 0); - EXPECT_EQ(f.find_first_not_of('\0', 3), 4); - EXPECT_EQ(f.find_first_not_of('\0', 2), 2); + EXPECT_EQ(h.find_first_not_of('\0'), 0u); + EXPECT_EQ(g.find_first_not_of('x'), 2u); + EXPECT_EQ(f.find_first_not_of('\0'), 0u); + EXPECT_EQ(f.find_first_not_of('\0', 3), 4u); + EXPECT_EQ(f.find_first_not_of('\0', 2), 2u); // empty string nonsense EXPECT_EQ(d.find_first_not_of('x'), absl::string_view::npos); EXPECT_EQ(e.find_first_not_of('x'), absl::string_view::npos); @@ -618,20 +618,20 @@ TEST(StringViewTest, STL2FindLast) { d = absl::string_view(); EXPECT_EQ(h.find_last_of(a), absl::string_view::npos); - EXPECT_EQ(g.find_last_of(a), g.size()-1); - EXPECT_EQ(a.find_last_of(b), 2); - EXPECT_EQ(a.find_last_of(c), a.size()-1); - EXPECT_EQ(f.find_last_of(i), 6); - EXPECT_EQ(a.find_last_of('a'), 0); - EXPECT_EQ(a.find_last_of('b'), 1); - EXPECT_EQ(a.find_last_of('z'), 25); - EXPECT_EQ(a.find_last_of('a', 5), 0); - EXPECT_EQ(a.find_last_of('b', 5), 1); + EXPECT_EQ(g.find_last_of(a), g.size() - 1); + EXPECT_EQ(a.find_last_of(b), 2u); + EXPECT_EQ(a.find_last_of(c), a.size() - 1); + EXPECT_EQ(f.find_last_of(i), 6u); + EXPECT_EQ(a.find_last_of('a'), 0u); + EXPECT_EQ(a.find_last_of('b'), 1u); + EXPECT_EQ(a.find_last_of('z'), 25u); + EXPECT_EQ(a.find_last_of('a', 5), 0u); + EXPECT_EQ(a.find_last_of('b', 5), 1u); EXPECT_EQ(a.find_last_of('b', 0), absl::string_view::npos); - EXPECT_EQ(a.find_last_of('z', 25), 25); + EXPECT_EQ(a.find_last_of('z', 25), 25u); EXPECT_EQ(a.find_last_of('z', 24), absl::string_view::npos); - EXPECT_EQ(f.find_last_of(i, 5), 5); - EXPECT_EQ(f.find_last_of(i, 6), 6); + EXPECT_EQ(f.find_last_of(i, 5), 5u); + EXPECT_EQ(f.find_last_of(i, 6), 6u); EXPECT_EQ(f.find_last_of(a, 4), absl::string_view::npos); // empty string nonsense EXPECT_EQ(f.find_last_of(d), absl::string_view::npos); @@ -651,19 +651,19 @@ TEST(StringViewTest, STL2FindLast) { EXPECT_EQ(d.find_last_of(f, 4), absl::string_view::npos); EXPECT_EQ(e.find_last_of(f, 4), absl::string_view::npos); - EXPECT_EQ(a.find_last_not_of(b), a.size()-1); - EXPECT_EQ(a.find_last_not_of(c), 22); + EXPECT_EQ(a.find_last_not_of(b), a.size() - 1); + EXPECT_EQ(a.find_last_not_of(c), 22u); EXPECT_EQ(b.find_last_not_of(a), absl::string_view::npos); EXPECT_EQ(b.find_last_not_of(b), absl::string_view::npos); - EXPECT_EQ(f.find_last_not_of(i), 4); - EXPECT_EQ(a.find_last_not_of(c, 24), 22); - EXPECT_EQ(a.find_last_not_of(b, 3), 3); + EXPECT_EQ(f.find_last_not_of(i), 4u); + EXPECT_EQ(a.find_last_not_of(c, 24), 22u); + EXPECT_EQ(a.find_last_not_of(b, 3), 3u); EXPECT_EQ(a.find_last_not_of(b, 2), absl::string_view::npos); // empty string nonsense - EXPECT_EQ(f.find_last_not_of(d), f.size()-1); - EXPECT_EQ(f.find_last_not_of(e), f.size()-1); - EXPECT_EQ(f.find_last_not_of(d, 4), 4); - EXPECT_EQ(f.find_last_not_of(e, 4), 4); + EXPECT_EQ(f.find_last_not_of(d), f.size() - 1); + EXPECT_EQ(f.find_last_not_of(e), f.size() - 1); + EXPECT_EQ(f.find_last_not_of(d, 4), 4u); + EXPECT_EQ(f.find_last_not_of(e, 4), 4u); EXPECT_EQ(d.find_last_not_of(d), absl::string_view::npos); EXPECT_EQ(d.find_last_not_of(e), absl::string_view::npos); EXPECT_EQ(e.find_last_not_of(d), absl::string_view::npos); @@ -679,10 +679,10 @@ TEST(StringViewTest, STL2FindLast) { EXPECT_EQ(h.find_last_not_of('x'), h.size() - 1); EXPECT_EQ(h.find_last_not_of('='), absl::string_view::npos); - EXPECT_EQ(b.find_last_not_of('c'), 1); - EXPECT_EQ(h.find_last_not_of('x', 2), 2); + EXPECT_EQ(b.find_last_not_of('c'), 1u); + EXPECT_EQ(h.find_last_not_of('x', 2), 2u); EXPECT_EQ(h.find_last_not_of('=', 2), absl::string_view::npos); - EXPECT_EQ(b.find_last_not_of('b', 1), 0); + EXPECT_EQ(b.find_last_not_of('b', 1), 0u); // empty string nonsense EXPECT_EQ(d.find_last_not_of('x'), absl::string_view::npos); EXPECT_EQ(e.find_last_not_of('x'), absl::string_view::npos); @@ -734,7 +734,7 @@ TEST(StringViewTest, TruncSubstr) { TEST(StringViewTest, UTF8) { std::string utf8 = "\u00E1"; std::string utf8_twice = utf8 + " " + utf8; - int utf8_len = strlen(utf8.data()); + size_t utf8_len = strlen(utf8.data()); EXPECT_EQ(utf8_len, absl::string_view(utf8_twice).find_first_of(" ")); EXPECT_EQ(utf8_len, absl::string_view(utf8_twice).find_first_of(" \t")); } @@ -879,12 +879,12 @@ TEST(StringViewTest, FrontBackEmpty) { TEST(StringViewTest, NULLInput) { absl::string_view s; EXPECT_EQ(s.data(), nullptr); - EXPECT_EQ(s.size(), 0); + EXPECT_EQ(s.size(), 0u); #ifdef ABSL_HAVE_STRING_VIEW_FROM_NULLPTR s = absl::string_view(nullptr); EXPECT_EQ(s.data(), nullptr); - EXPECT_EQ(s.size(), 0); + EXPECT_EQ(s.size(), 0u); // .ToString() on a absl::string_view with nullptr should produce the empty // string. @@ -959,7 +959,7 @@ TEST(StringViewTest, NullSafeStringView) { { absl::string_view s = absl::NullSafeStringView(nullptr); EXPECT_EQ(nullptr, s.data()); - EXPECT_EQ(0, s.size()); + EXPECT_EQ(0u, s.size()); EXPECT_EQ(absl::string_view(), s); } { @@ -975,7 +975,7 @@ TEST(StringViewTest, ConstexprNullSafeStringView) { { constexpr absl::string_view s = absl::NullSafeStringView(nullptr); EXPECT_EQ(nullptr, s.data()); - EXPECT_EQ(0, s.size()); + EXPECT_EQ(0u, s.size()); EXPECT_EQ(absl::string_view(), s); } #if !defined(_MSC_VER) || _MSC_VER >= 1910 @@ -990,7 +990,7 @@ TEST(StringViewTest, ConstexprNullSafeStringView) { } { constexpr absl::string_view s = absl::NullSafeStringView("hello"); - EXPECT_EQ(s.size(), 5); + EXPECT_EQ(s.size(), 5u); EXPECT_EQ("hello", s); } #endif @@ -1036,7 +1036,7 @@ TEST(StringViewTest, ConstexprCompiles) { #ifdef ABSL_HAVE_CONSTEXPR_STRING_VIEW_FROM_CSTR constexpr absl::string_view cstr_strlen("foo"); - EXPECT_EQ(cstr_strlen.length(), 3); + EXPECT_EQ(cstr_strlen.length(), 3u); constexpr absl::string_view cstr_strlen2 = "bar"; EXPECT_EQ(cstr_strlen2, "bar"); @@ -1111,7 +1111,7 @@ TEST(StringViewTest, ConstexprCompiles) { EXPECT_NE(cstr_ptr, nullptr); constexpr size_t sp_npos = sp.npos; - EXPECT_EQ(sp_npos, -1); + EXPECT_EQ(sp_npos, static_cast<size_t>(-1)); } constexpr char ConstexprMethodsHelper() { @@ -1179,7 +1179,7 @@ TEST(StringViewTest, BoundsCheck) { // Abseil's string_view implementation has bounds-checking in debug mode. absl::string_view h = "hello"; ABSL_EXPECT_DEATH_IF_SUPPORTED(h[5], ""); - ABSL_EXPECT_DEATH_IF_SUPPORTED(h[-1], ""); + ABSL_EXPECT_DEATH_IF_SUPPORTED(h[static_cast<size_t>(-1)], ""); #endif #endif } @@ -1201,17 +1201,17 @@ TEST(FindOneCharTest, EdgeCases) { a.remove_prefix(1); a.remove_suffix(1); - EXPECT_EQ(0, a.find('x')); - EXPECT_EQ(0, a.find('x', 0)); - EXPECT_EQ(4, a.find('x', 1)); - EXPECT_EQ(4, a.find('x', 4)); + EXPECT_EQ(0u, a.find('x')); + EXPECT_EQ(0u, a.find('x', 0)); + EXPECT_EQ(4u, a.find('x', 1)); + EXPECT_EQ(4u, a.find('x', 4)); EXPECT_EQ(absl::string_view::npos, a.find('x', 5)); - EXPECT_EQ(4, a.rfind('x')); - EXPECT_EQ(4, a.rfind('x', 5)); - EXPECT_EQ(4, a.rfind('x', 4)); - EXPECT_EQ(0, a.rfind('x', 3)); - EXPECT_EQ(0, a.rfind('x', 0)); + EXPECT_EQ(4u, a.rfind('x')); + EXPECT_EQ(4u, a.rfind('x', 5)); + EXPECT_EQ(4u, a.rfind('x', 4)); + EXPECT_EQ(0u, a.rfind('x', 3)); + EXPECT_EQ(0u, a.rfind('x', 0)); // Set a = "yyy". a.remove_prefix(1); @@ -1239,8 +1239,8 @@ TEST(HugeStringView, TwoPointTwoGB) { #if !defined(NDEBUG) && !defined(ABSL_USES_STD_STRING_VIEW) TEST(NonNegativeLenTest, NonNegativeLen) { - ABSL_EXPECT_DEATH_IF_SUPPORTED(absl::string_view("xyz", -1), - "len <= kMaxSize"); + ABSL_EXPECT_DEATH_IF_SUPPORTED( + absl::string_view("xyz", static_cast<size_t>(-1)), "len <= kMaxSize"); } TEST(LenExceedsMaxSizeTest, LenExceedsMaxSize) { diff --git a/absl/strings/substitute.cc b/absl/strings/substitute.cc index 8980b198..33a39305 100644 --- a/absl/strings/substitute.cc +++ b/absl/strings/substitute.cc @@ -40,7 +40,8 @@ void SubstituteAndAppendArray(std::string* output, absl::string_view format, absl::CEscape(format).c_str()); #endif return; - } else if (absl::ascii_isdigit(format[i + 1])) { + } else if (absl::ascii_isdigit( + static_cast<unsigned char>(format[i + 1]))) { int index = format[i + 1] - '0'; if (static_cast<size_t>(index) >= num_args) { #ifndef NDEBUG @@ -80,7 +81,7 @@ void SubstituteAndAppendArray(std::string* output, absl::string_view format, char* target = &(*output)[original_size]; for (size_t i = 0; i < format.size(); i++) { if (format[i] == '$') { - if (absl::ascii_isdigit(format[i + 1])) { + if (absl::ascii_isdigit(static_cast<unsigned char>(format[i + 1]))) { const absl::string_view src = args_array[format[i + 1] - '0']; target = std::copy(src.begin(), src.end(), target); ++i; // Skip next char. @@ -110,7 +111,8 @@ Arg::Arg(const void* value) { } while (num != 0); *--ptr = 'x'; *--ptr = '0'; - piece_ = absl::string_view(ptr, scratch_ + sizeof(scratch_) - ptr); + piece_ = absl::string_view( + ptr, static_cast<size_t>(scratch_ + sizeof(scratch_) - ptr)); } } @@ -132,7 +134,7 @@ Arg::Arg(Hex hex) { beg = writer; } - piece_ = absl::string_view(beg, end - beg); + piece_ = absl::string_view(beg, static_cast<size_t>(end - beg)); } // TODO(jorg): Don't duplicate so much code between here and str_cat.cc @@ -147,7 +149,7 @@ Arg::Arg(Dec dec) { *--writer = '0' + (value % 10); value /= 10; } - *--writer = '0' + value; + *--writer = '0' + static_cast<char>(value); if (neg) *--writer = '-'; ptrdiff_t fillers = writer - minfill; @@ -164,7 +166,7 @@ Arg::Arg(Dec dec) { if (add_sign_again) *--writer = '-'; } - piece_ = absl::string_view(writer, end - writer); + piece_ = absl::string_view(writer, static_cast<size_t>(end - writer)); } } // namespace substitute_internal diff --git a/absl/strings/substitute.h b/absl/strings/substitute.h index 6d2b08ab..d6a5a690 100644 --- a/absl/strings/substitute.h +++ b/absl/strings/substitute.h @@ -55,6 +55,8 @@ // * bool (Printed as "true" or "false") // * pointer types other than char* (Printed as "0x<lower case hex string>", // except that null is printed as "NULL") +// * user-defined types via the `AbslStringify()` customization point. See the +// documentation for `absl::StrCat` for an explanation on how to use this. // // If an invalid format string is provided, Substitute returns an empty string // and SubstituteAndAppend does not change the provided output string. @@ -79,6 +81,7 @@ #include "absl/base/port.h" #include "absl/strings/ascii.h" #include "absl/strings/escaping.h" +#include "absl/strings/internal/stringify_sink.h" #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" #include "absl/strings/str_split.h" @@ -102,14 +105,14 @@ class Arg { // Overloads for string-y things // // Explicitly overload `const char*` so the compiler doesn't cast to `bool`. - Arg(const char* value) // NOLINT(runtime/explicit) + Arg(const char* value) // NOLINT(google-explicit-constructor) : piece_(absl::NullSafeStringView(value)) {} template <typename Allocator> Arg( // NOLINT const std::basic_string<char, std::char_traits<char>, Allocator>& value) noexcept : piece_(value) {} - Arg(absl::string_view value) // NOLINT(runtime/explicit) + Arg(absl::string_view value) // NOLINT(google-explicit-constructor) : piece_(value) {} // Overloads for primitives @@ -119,45 +122,67 @@ class Arg { // probably using them as 8-bit integers and would probably prefer an integer // representation. However, we can't really know, so we make the caller decide // what to do. - Arg(char value) // NOLINT(runtime/explicit) + Arg(char value) // NOLINT(google-explicit-constructor) : piece_(scratch_, 1) { scratch_[0] = value; } Arg(short value) // NOLINT(*) : piece_(scratch_, - numbers_internal::FastIntToBuffer(value, scratch_) - scratch_) {} + static_cast<size_t>( + numbers_internal::FastIntToBuffer(value, scratch_) - + scratch_)) {} Arg(unsigned short value) // NOLINT(*) : piece_(scratch_, - numbers_internal::FastIntToBuffer(value, scratch_) - scratch_) {} - Arg(int value) // NOLINT(runtime/explicit) + static_cast<size_t>( + numbers_internal::FastIntToBuffer(value, scratch_) - + scratch_)) {} + Arg(int value) // NOLINT(google-explicit-constructor) : piece_(scratch_, - numbers_internal::FastIntToBuffer(value, scratch_) - scratch_) {} - Arg(unsigned int value) // NOLINT(runtime/explicit) + static_cast<size_t>( + numbers_internal::FastIntToBuffer(value, scratch_) - + scratch_)) {} + Arg(unsigned int value) // NOLINT(google-explicit-constructor) : piece_(scratch_, - numbers_internal::FastIntToBuffer(value, scratch_) - scratch_) {} + static_cast<size_t>( + numbers_internal::FastIntToBuffer(value, scratch_) - + scratch_)) {} Arg(long value) // NOLINT(*) : piece_(scratch_, - numbers_internal::FastIntToBuffer(value, scratch_) - scratch_) {} + static_cast<size_t>( + numbers_internal::FastIntToBuffer(value, scratch_) - + scratch_)) {} Arg(unsigned long value) // NOLINT(*) : piece_(scratch_, - numbers_internal::FastIntToBuffer(value, scratch_) - scratch_) {} + static_cast<size_t>( + numbers_internal::FastIntToBuffer(value, scratch_) - + scratch_)) {} Arg(long long value) // NOLINT(*) : piece_(scratch_, - numbers_internal::FastIntToBuffer(value, scratch_) - scratch_) {} + static_cast<size_t>( + numbers_internal::FastIntToBuffer(value, scratch_) - + scratch_)) {} Arg(unsigned long long value) // NOLINT(*) : piece_(scratch_, - numbers_internal::FastIntToBuffer(value, scratch_) - scratch_) {} - Arg(float value) // NOLINT(runtime/explicit) + static_cast<size_t>( + numbers_internal::FastIntToBuffer(value, scratch_) - + scratch_)) {} + Arg(float value) // NOLINT(google-explicit-constructor) : piece_(scratch_, numbers_internal::SixDigitsToBuffer(value, scratch_)) { } - Arg(double value) // NOLINT(runtime/explicit) + Arg(double value) // NOLINT(google-explicit-constructor) : piece_(scratch_, numbers_internal::SixDigitsToBuffer(value, scratch_)) { } - Arg(bool value) // NOLINT(runtime/explicit) + Arg(bool value) // NOLINT(google-explicit-constructor) : piece_(value ? "true" : "false") {} - Arg(Hex hex); // NOLINT(runtime/explicit) - Arg(Dec dec); // NOLINT(runtime/explicit) + template <typename T, typename = typename std::enable_if< + strings_internal::HasAbslStringify<T>::value>::type> + Arg( // NOLINT(google-explicit-constructor) + const T& v, strings_internal::StringifySink&& sink = {}) + : piece_(strings_internal::ExtractStringification(sink, v)) {} + + Arg(Hex hex); // NOLINT(google-explicit-constructor) + Arg(Dec dec); // NOLINT(google-explicit-constructor) // vector<bool>::reference and const_reference require special help to convert // to `Arg` because it requires two user defined conversions. @@ -172,13 +197,14 @@ class Arg { // `void*` values, with the exception of `char*`, are printed as // "0x<hex value>". However, in the case of `nullptr`, "NULL" is printed. - Arg(const void* value); // NOLINT(runtime/explicit) + Arg(const void* value); // NOLINT(google-explicit-constructor) // Normal enums are already handled by the integer formatters. // This overload matches only scoped enums. template <typename T, typename = typename std::enable_if< - std::is_enum<T>{} && !std::is_convertible<T, int>{}>::type> + std::is_enum<T>{} && !std::is_convertible<T, int>{} && + !strings_internal::HasAbslStringify<T>::value>::type> Arg(T value) // NOLINT(google-explicit-constructor) : Arg(static_cast<typename std::underlying_type<T>::type>(value)) {} diff --git a/absl/strings/substitute_test.cc b/absl/strings/substitute_test.cc index 9e6b9403..ecf78d6b 100644 --- a/absl/strings/substitute_test.cc +++ b/absl/strings/substitute_test.cc @@ -22,6 +22,16 @@ namespace { +struct MyStruct { + template <typename Sink> + friend void AbslStringify(Sink& sink, const MyStruct& s) { + sink.Append("MyStruct{.value = "); + sink.Append(absl::StrCat(s.value)); + sink.Append("}"); + } + int value; +}; + TEST(SubstituteTest, Substitute) { // Basic. EXPECT_EQ("Hello, world!", absl::Substitute("$0, $1!", "Hello", "world")); @@ -70,7 +80,7 @@ TEST(SubstituteTest, Substitute) { // Volatile Pointer. // Like C++ streamed I/O, such pointers implicitly become bool volatile int vol = 237; - volatile int *volatile volptr = &vol; + volatile int* volatile volptr = &vol; str = absl::Substitute("$0", volptr); EXPECT_EQ("true", str); @@ -128,6 +138,11 @@ TEST(SubstituteTest, Substitute) { const char* null_cstring = nullptr; EXPECT_EQ("Text: ''", absl::Substitute("Text: '$0'", null_cstring)); + + MyStruct s1 = MyStruct{17}; + MyStruct s2 = MyStruct{1043}; + EXPECT_EQ("MyStruct{.value = 17}, MyStruct{.value = 1043}", + absl::Substitute("$0, $1", s1, s2)); } TEST(SubstituteTest, SubstituteAndAppend) { @@ -171,6 +186,12 @@ TEST(SubstituteTest, SubstituteAndAppend) { absl::SubstituteAndAppend(&str, "$0 $1 $2 $3 $4 $5 $6 $7 $8 $9", "a", "b", "c", "d", "e", "f", "g", "h", "i", "j"); EXPECT_EQ("a b c d e f g h i j", str); + + str.clear(); + MyStruct s1 = MyStruct{17}; + MyStruct s2 = MyStruct{1043}; + absl::SubstituteAndAppend(&str, "$0, $1", s1, s2); + EXPECT_EQ("MyStruct{.value = 17}, MyStruct{.value = 1043}", str); } TEST(SubstituteTest, VectorBoolRef) { @@ -232,7 +253,19 @@ TEST(SubstituteTest, Enums) { ScopedEnumUInt16::kEnum1)); } -#ifdef GTEST_HAS_DEATH_TEST +enum class EnumWithStringify { Many = 0, Choices = 1 }; + +template <typename Sink> +void AbslStringify(Sink& sink, EnumWithStringify e) { + sink.Append(e == EnumWithStringify::Many ? "Many" : "Choices"); +} + +TEST(SubstituteTest, AbslStringifyWithEnum) { + const auto e = EnumWithStringify::Choices; + EXPECT_EQ(absl::Substitute("$0", e), "Choices"); +} + +#if GTEST_HAS_DEATH_TEST TEST(SubstituteDeathTest, SubstituteDeath) { EXPECT_DEBUG_DEATH( diff --git a/absl/synchronization/BUILD.bazel b/absl/synchronization/BUILD.bazel index 64d3b929..ccaee796 100644 --- a/absl/synchronization/BUILD.bazel +++ b/absl/synchronization/BUILD.bazel @@ -200,6 +200,7 @@ cc_library( deps = [ ":synchronization", "//absl/base:core_headers", + "//absl/functional:any_invocable", ], ) @@ -223,6 +224,18 @@ cc_test( ], ) +cc_test( + name = "mutex_method_pointer_test", + srcs = ["mutex_method_pointer_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":synchronization", + "//absl/base:config", + "@com_google_googletest//:gtest_main", + ], +) + cc_library( name = "mutex_benchmark_common", testonly = 1, @@ -259,6 +272,7 @@ cc_test( srcs = ["notification_test.cc"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + tags = ["no_test_lexan"], deps = [ ":synchronization", "//absl/time", @@ -285,7 +299,7 @@ cc_library( cc_test( name = "per_thread_sem_test", - size = "medium", + size = "large", copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, tags = [ diff --git a/absl/synchronization/CMakeLists.txt b/absl/synchronization/CMakeLists.txt index 9335c264..f64653bb 100644 --- a/absl/synchronization/CMakeLists.txt +++ b/absl/synchronization/CMakeLists.txt @@ -136,8 +136,9 @@ absl_cc_library( COPTS ${ABSL_DEFAULT_COPTS} DEPS - absl::synchronization + absl::any_invocable absl::core_headers + absl::synchronization TESTONLY ) @@ -162,6 +163,20 @@ absl_cc_test( absl_cc_test( NAME + mutex_method_pointer_test + SRCS + "mutex_method_pointer_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::base + absl::config + absl::synchronization + GTest::gmock_main +) + +absl_cc_test( + NAME notification_test SRCS "notification_test.cc" diff --git a/absl/synchronization/internal/futex.h b/absl/synchronization/internal/futex.h index 06fbd6d0..cb97da09 100644 --- a/absl/synchronization/internal/futex.h +++ b/absl/synchronization/internal/futex.h @@ -87,7 +87,7 @@ class FutexImpl { public: static int WaitUntil(std::atomic<int32_t> *v, int32_t val, KernelTimeout t) { - int err = 0; + 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. @@ -105,41 +105,44 @@ class FutexImpl { FUTEX_WAIT | FUTEX_PRIVATE_FLAG, val, nullptr); } if (ABSL_PREDICT_FALSE(err != 0)) { - err = -errno; + return -errno; } - return err; + return 0; } static int WaitBitsetAbsoluteTimeout(std::atomic<int32_t> *v, int32_t val, int32_t bits, const struct timespec *abstime) { - int err = syscall(SYS_futex, reinterpret_cast<int32_t *>(v), - FUTEX_WAIT_BITSET | FUTEX_PRIVATE_FLAG, val, abstime, - nullptr, bits); + // 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)) { - err = -errno; + return -errno; } - return err; + return 0; } static int Wake(std::atomic<int32_t> *v, int32_t count) { - int err = syscall(SYS_futex, reinterpret_cast<int32_t *>(v), - FUTEX_WAKE | FUTEX_PRIVATE_FLAG, count); + // NOLINTNEXTLINE(runtime/int) + long err = syscall(SYS_futex, reinterpret_cast<int32_t*>(v), + FUTEX_WAKE | FUTEX_PRIVATE_FLAG, count); if (ABSL_PREDICT_FALSE(err < 0)) { - err = -errno; + return -errno; } - return err; + return 0; } // FUTEX_WAKE_BITSET static int WakeBitset(std::atomic<int32_t> *v, int32_t count, int32_t bits) { - int err = syscall(SYS_futex, reinterpret_cast<int32_t *>(v), - FUTEX_WAKE_BITSET | FUTEX_PRIVATE_FLAG, count, nullptr, - nullptr, 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)) { - err = -errno; + return -errno; } - return err; + return 0; } }; diff --git a/absl/synchronization/internal/graphcycles.cc b/absl/synchronization/internal/graphcycles.cc index 27fec216..feec4581 100644 --- a/absl/synchronization/internal/graphcycles.cc +++ b/absl/synchronization/internal/graphcycles.cc @@ -181,9 +181,9 @@ class NodeSet { return true; } - void erase(uint32_t v) { + void erase(int32_t v) { uint32_t i = FindIndex(v); - if (static_cast<uint32_t>(table_[i]) == v) { + if (table_[i] == v) { table_[i] = kDel; } } @@ -195,7 +195,7 @@ class NodeSet { for (int32_t elem, _cursor = 0; (eset).Next(&_cursor, &elem); ) bool Next(int32_t* cursor, int32_t* elem) { while (static_cast<uint32_t>(*cursor) < table_.size()) { - int32_t v = table_[*cursor]; + int32_t v = table_[static_cast<uint32_t>(*cursor)]; (*cursor)++; if (v >= 0) { *elem = v; @@ -210,24 +210,26 @@ class NodeSet { Vec<int32_t> table_; uint32_t occupied_; // Count of non-empty slots (includes deleted slots) - static uint32_t Hash(uint32_t a) { return a * 41; } + static uint32_t Hash(int32_t a) { return static_cast<uint32_t>(a * 41); } // Return index for storing v. May return an empty index or deleted index - int FindIndex(int32_t v) const { + uint32_t FindIndex(int32_t v) const { // Search starting at hash index. const uint32_t mask = table_.size() - 1; uint32_t i = Hash(v) & mask; - int deleted_index = -1; // If >= 0, index of first deleted element we see + uint32_t deleted_index = 0; // index of first deleted element we see + bool seen_deleted_element = false; while (true) { int32_t e = table_[i]; if (v == e) { return i; } else if (e == kEmpty) { // Return any previously encountered deleted slot. - return (deleted_index >= 0) ? deleted_index : i; - } else if (e == kDel && deleted_index < 0) { + return seen_deleted_element ? deleted_index : i; + } else if (e == kDel && !seen_deleted_element) { // Keep searching since v might be present later. deleted_index = i; + seen_deleted_element = true; } i = (i + 1) & mask; // Linear probing; quadratic is slightly slower. } @@ -268,7 +270,7 @@ inline GraphId MakeId(int32_t index, uint32_t version) { } inline int32_t NodeIndex(GraphId id) { - return static_cast<uint32_t>(id.handle & 0xfffffffful); + return static_cast<int32_t>(id.handle); } inline uint32_t NodeVersion(GraphId id) { @@ -298,7 +300,7 @@ class PointerMap { int32_t Find(void* ptr) { auto masked = base_internal::HidePtr(ptr); for (int32_t i = table_[Hash(ptr)]; i != -1;) { - Node* n = (*nodes_)[i]; + Node* n = (*nodes_)[static_cast<uint32_t>(i)]; if (n->masked_ptr == masked) return i; i = n->next_hash; } @@ -307,7 +309,7 @@ class PointerMap { void Add(void* ptr, int32_t i) { int32_t* head = &table_[Hash(ptr)]; - (*nodes_)[i]->next_hash = *head; + (*nodes_)[static_cast<uint32_t>(i)]->next_hash = *head; *head = i; } @@ -317,7 +319,7 @@ class PointerMap { auto masked = base_internal::HidePtr(ptr); for (int32_t* slot = &table_[Hash(ptr)]; *slot != -1; ) { int32_t index = *slot; - Node* n = (*nodes_)[index]; + Node* n = (*nodes_)[static_cast<uint32_t>(index)]; if (n->masked_ptr == masked) { *slot = n->next_hash; // Remove n from linked list n->next_hash = -1; @@ -358,7 +360,7 @@ struct GraphCycles::Rep { }; static Node* FindNode(GraphCycles::Rep* rep, GraphId id) { - Node* n = rep->nodes_[NodeIndex(id)]; + Node* n = rep->nodes_[static_cast<uint32_t>(NodeIndex(id))]; return (n->version == NodeVersion(id)) ? n : nullptr; } @@ -393,7 +395,7 @@ bool GraphCycles::CheckInvariants() const { ABSL_RAW_LOG(FATAL, "Duplicate occurrence of rank %d", nx->rank); } HASH_FOR_EACH(y, nx->out) { - Node* ny = r->nodes_[y]; + 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); @@ -406,14 +408,14 @@ bool GraphCycles::CheckInvariants() const { GraphId GraphCycles::GetId(void* ptr) { int32_t i = rep_->ptrmap_.Find(ptr); if (i != -1) { - return MakeId(i, rep_->nodes_[i]->version); + return MakeId(i, rep_->nodes_[static_cast<uint32_t>(i)]->version); } else if (rep_->free_nodes_.empty()) { Node* n = new (base_internal::LowLevelAlloc::AllocWithArena(sizeof(Node), arena)) Node; n->version = 1; // Avoid 0 since it is used by InvalidGraphId() n->visited = false; - n->rank = rep_->nodes_.size(); + n->rank = static_cast<int32_t>(rep_->nodes_.size()); n->masked_ptr = base_internal::HidePtr(ptr); n->nstack = 0; n->priority = 0; @@ -425,7 +427,7 @@ GraphId GraphCycles::GetId(void* ptr) { // a permutation of [0,rep_->nodes_.size()-1]. int32_t r = rep_->free_nodes_.back(); rep_->free_nodes_.pop_back(); - Node* n = rep_->nodes_[r]; + Node* n = rep_->nodes_[static_cast<uint32_t>(r)]; n->masked_ptr = base_internal::HidePtr(ptr); n->nstack = 0; n->priority = 0; @@ -439,12 +441,12 @@ void GraphCycles::RemoveNode(void* ptr) { if (i == -1) { return; } - Node* x = rep_->nodes_[i]; + Node* x = rep_->nodes_[static_cast<uint32_t>(i)]; HASH_FOR_EACH(y, x->out) { - rep_->nodes_[y]->in.erase(i); + rep_->nodes_[static_cast<uint32_t>(y)]->in.erase(i); } HASH_FOR_EACH(y, x->in) { - rep_->nodes_[y]->out.erase(i); + rep_->nodes_[static_cast<uint32_t>(y)]->out.erase(i); } x->in.clear(); x->out.clear(); @@ -520,7 +522,7 @@ bool GraphCycles::InsertEdge(GraphId idx, GraphId idy) { // Since we do not call Reorder() on this path, clear any visited // markers left by ForwardDFS. for (const auto& d : r->deltaf_) { - r->nodes_[d]->visited = false; + r->nodes_[static_cast<uint32_t>(d)]->visited = false; } return false; } @@ -538,14 +540,14 @@ static bool ForwardDFS(GraphCycles::Rep* r, int32_t n, int32_t upper_bound) { while (!r->stack_.empty()) { n = r->stack_.back(); r->stack_.pop_back(); - Node* nn = r->nodes_[n]; + Node* nn = r->nodes_[static_cast<uint32_t>(n)]; if (nn->visited) continue; nn->visited = true; r->deltaf_.push_back(n); HASH_FOR_EACH(w, nn->out) { - Node* nw = r->nodes_[w]; + Node* nw = r->nodes_[static_cast<uint32_t>(w)]; if (nw->rank == upper_bound) { return false; // Cycle } @@ -564,14 +566,14 @@ static void BackwardDFS(GraphCycles::Rep* r, int32_t n, int32_t lower_bound) { while (!r->stack_.empty()) { n = r->stack_.back(); r->stack_.pop_back(); - Node* nn = r->nodes_[n]; + Node* nn = r->nodes_[static_cast<uint32_t>(n)]; if (nn->visited) continue; nn->visited = true; r->deltab_.push_back(n); HASH_FOR_EACH(w, nn->in) { - Node* nw = r->nodes_[w]; + Node* nw = r->nodes_[static_cast<uint32_t>(w)]; if (!nw->visited && lower_bound < nw->rank) { r->stack_.push_back(w); } @@ -596,7 +598,7 @@ static void Reorder(GraphCycles::Rep* r) { // Assign the ranks in order to the collected list. for (uint32_t i = 0; i < r->list_.size(); i++) { - r->nodes_[r->list_[i]]->rank = r->merged_[i]; + r->nodes_[static_cast<uint32_t>(r->list_[i])]->rank = r->merged_[i]; } } @@ -604,7 +606,8 @@ static void Sort(const Vec<Node*>& nodes, Vec<int32_t>* delta) { struct ByRank { const Vec<Node*>* nodes; bool operator()(int32_t a, int32_t b) const { - return (*nodes)[a]->rank < (*nodes)[b]->rank; + return (*nodes)[static_cast<uint32_t>(a)]->rank < + (*nodes)[static_cast<uint32_t>(b)]->rank; } }; ByRank cmp; @@ -616,8 +619,10 @@ static void MoveToList( GraphCycles::Rep* r, Vec<int32_t>* src, Vec<int32_t>* dst) { for (auto& v : *src) { int32_t w = v; - v = r->nodes_[w]->rank; // Replace v entry with its rank - r->nodes_[w]->visited = false; // Prepare for future DFS calls + // Replace v entry with its rank + v = r->nodes_[static_cast<uint32_t>(w)]->rank; + // Prepare for future DFS calls + r->nodes_[static_cast<uint32_t>(w)]->visited = false; dst->push_back(w); } } @@ -647,7 +652,8 @@ int GraphCycles::FindPath(GraphId idx, GraphId idy, int max_path_len, } if (path_len < max_path_len) { - path[path_len] = MakeId(n, rep_->nodes_[n]->version); + path[path_len] = + MakeId(n, rep_->nodes_[static_cast<uint32_t>(n)]->version); } path_len++; r->stack_.push_back(-1); // Will remove tentative path entry @@ -656,7 +662,7 @@ int GraphCycles::FindPath(GraphId idx, GraphId idy, int max_path_len, return path_len; } - HASH_FOR_EACH(w, r->nodes_[n]->out) { + HASH_FOR_EACH(w, r->nodes_[static_cast<uint32_t>(n)]->out) { if (seen.insert(w)) { r->stack_.push_back(w); } diff --git a/absl/synchronization/internal/kernel_timeout.h b/absl/synchronization/internal/kernel_timeout.h index bbd4d2d7..f5c2c0ef 100644 --- a/absl/synchronization/internal/kernel_timeout.h +++ b/absl/synchronization/internal/kernel_timeout.h @@ -19,8 +19,8 @@ // 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 that are friends of this class. Higher-level components -// should build APIs based on absl::Time and absl::Duration. +// 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_ @@ -28,6 +28,7 @@ #include <time.h> #include <algorithm> +#include <cstdint> #include <limits> #include "absl/base/internal/raw_logging.h" @@ -38,7 +39,6 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace synchronization_internal { -class Futex; class Waiter; class KernelTimeout { @@ -60,7 +60,10 @@ class KernelTimeout { // Convert to parameter for sem_timedwait/futex/similar. Only for approved // users. Do not call if !has_timeout. - struct timespec MakeAbsTimespec(); + struct timespec MakeAbsTimespec() const; + + // Convert to unix epoch nanos. Do not call if !has_timeout. + int64_t MakeAbsNanos() const; private: // internal rep, not user visible: ns after unix epoch. @@ -111,7 +114,8 @@ class KernelTimeout { constexpr uint64_t max_nanos = (std::numeric_limits<int64_t>::max)() - 999999u; uint64_t ms_from_now = - (std::min<uint64_t>(max_nanos, ns_ - now) + 999999u) / 1000000u; + ((std::min)(max_nanos, static_cast<uint64_t>(ns_ - now)) + 999999u) / + 1000000u; if (ms_from_now > kInfinite) { return kInfinite; } @@ -119,13 +123,12 @@ class KernelTimeout { } return 0; } -#endif - friend class Futex; friend class Waiter; +#endif }; -inline struct timespec KernelTimeout::MakeAbsTimespec() { +inline struct timespec KernelTimeout::MakeAbsTimespec() const { int64_t n = ns_; static const int64_t kNanosPerSecond = 1000 * 1000 * 1000; if (n == 0) { @@ -149,6 +152,17 @@ inline struct timespec KernelTimeout::MakeAbsTimespec() { 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_; +} + } // namespace synchronization_internal ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/synchronization/internal/thread_pool.h b/absl/synchronization/internal/thread_pool.h index 0cb96dac..5eb0bb60 100644 --- a/absl/synchronization/internal/thread_pool.h +++ b/absl/synchronization/internal/thread_pool.h @@ -20,9 +20,11 @@ #include <functional> #include <queue> #include <thread> // NOLINT(build/c++11) +#include <utility> #include <vector> #include "absl/base/thread_annotations.h" +#include "absl/functional/any_invocable.h" #include "absl/synchronization/mutex.h" namespace absl { @@ -33,6 +35,7 @@ namespace synchronization_internal { class ThreadPool { public: explicit ThreadPool(int num_threads) { + threads_.reserve(num_threads); for (int i = 0; i < num_threads; ++i) { threads_.push_back(std::thread(&ThreadPool::WorkLoop, this)); } @@ -54,7 +57,7 @@ class ThreadPool { } // Schedule a function to be run on a ThreadPool thread immediately. - void Schedule(std::function<void()> func) { + void Schedule(absl::AnyInvocable<void()> func) { assert(func != nullptr); absl::MutexLock l(&mu_); queue_.push(std::move(func)); @@ -67,7 +70,7 @@ class ThreadPool { void WorkLoop() { while (true) { - std::function<void()> func; + absl::AnyInvocable<void()> func; { absl::MutexLock l(&mu_); mu_.Await(absl::Condition(this, &ThreadPool::WorkAvailable)); @@ -82,7 +85,7 @@ class ThreadPool { } absl::Mutex mu_; - std::queue<std::function<void()>> queue_ ABSL_GUARDED_BY(mu_); + std::queue<absl::AnyInvocable<void()>> queue_ ABSL_GUARDED_BY(mu_); std::vector<std::thread> threads_; }; diff --git a/absl/synchronization/lifetime_test.cc b/absl/synchronization/lifetime_test.cc index cc973a32..e6274232 100644 --- a/absl/synchronization/lifetime_test.cc +++ b/absl/synchronization/lifetime_test.cc @@ -123,10 +123,10 @@ class OnDestruction { }; // These tests require that the compiler correctly supports C++11 constant -// initialization... but MSVC has a known regression since v19.10: +// initialization... but MSVC has a known regression since v19.10 till v19.25: // https://developercommunity.visualstudio.com/content/problem/336946/class-with-constexpr-constructor-not-using-static.html -// TODO(epastor): Limit the affected range once MSVC fixes this bug. -#if defined(__clang__) || !(defined(_MSC_VER) && _MSC_VER > 1900) +#if defined(__clang__) || \ + !(defined(_MSC_VER) && _MSC_VER > 1900 && _MSC_VER < 1925) // kConstInit // Test early usage. (Declaration comes first; definitions must appear after // the test runner.) diff --git a/absl/synchronization/mutex.cc b/absl/synchronization/mutex.cc index 52e2455d..064ccb74 100644 --- a/absl/synchronization/mutex.cc +++ b/absl/synchronization/mutex.cc @@ -36,6 +36,9 @@ #include <algorithm> #include <atomic> #include <cinttypes> +#include <cstddef> +#include <cstring> +#include <iterator> #include <thread> // NOLINT(build/c++11) #include "absl/base/attributes.h" @@ -51,6 +54,7 @@ #include "absl/base/internal/sysinfo.h" #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" @@ -134,25 +138,42 @@ enum DelayMode { AGGRESSIVE, GENTLE }; struct ABSL_CACHELINE_ALIGNED MutexGlobals { absl::once_flag once; int spinloop_iterations = 0; - int32_t mutex_sleep_limit[2] = {}; + int32_t mutex_sleep_spins[2] = {}; + absl::Duration mutex_sleep_time; }; +absl::Duration MeasureTimeToYield() { + absl::Time before = absl::Now(); + ABSL_INTERNAL_C_SYMBOL(AbslInternalMutexYield)(); + return absl::Now() - before; +} + const MutexGlobals &GetMutexGlobals() { ABSL_CONST_INIT static MutexGlobals data; absl::base_internal::LowLevelCallOnce(&data.once, [&]() { const int num_cpus = absl::base_internal::NumCPUs(); data.spinloop_iterations = num_cpus > 1 ? 1500 : 0; - // If this a uniprocessor, only yield/sleep. Otherwise, if the mode is + // If this a uniprocessor, only yield/sleep. + // Real-time threads are often unable to yield, so the sleep time needs + // to be long enough to keep the calling thread asleep until scheduling + // happens. + // If this is multiprocessor, allow spinning. If the mode is // aggressive then spin many times before yielding. If the mode is // gentle then spin only a few times before yielding. Aggressive spinning // is used to ensure that an Unlock() call, which must get the spin lock // for any thread to make progress gets it without undue delay. if (num_cpus > 1) { - data.mutex_sleep_limit[AGGRESSIVE] = 5000; - data.mutex_sleep_limit[GENTLE] = 250; + data.mutex_sleep_spins[AGGRESSIVE] = 5000; + data.mutex_sleep_spins[GENTLE] = 250; + data.mutex_sleep_time = absl::Microseconds(10); } else { - data.mutex_sleep_limit[AGGRESSIVE] = 0; - data.mutex_sleep_limit[GENTLE] = 0; + data.mutex_sleep_spins[AGGRESSIVE] = 0; + data.mutex_sleep_spins[GENTLE] = 0; + data.mutex_sleep_time = MeasureTimeToYield() * 5; + data.mutex_sleep_time = + std::min(data.mutex_sleep_time, absl::Milliseconds(1)); + data.mutex_sleep_time = + std::max(data.mutex_sleep_time, absl::Microseconds(10)); } }); return data; @@ -163,7 +184,8 @@ namespace synchronization_internal { // Returns the Mutex delay on iteration `c` depending on the given `mode`. // The returned value should be used as `c` for the next call to `MutexDelay`. int MutexDelay(int32_t c, int mode) { - const int32_t limit = GetMutexGlobals().mutex_sleep_limit[mode]; + const int32_t limit = GetMutexGlobals().mutex_sleep_spins[mode]; + const absl::Duration sleep_time = GetMutexGlobals().mutex_sleep_time; if (c < limit) { // Spin. c++; @@ -176,7 +198,7 @@ int MutexDelay(int32_t c, int mode) { c++; } else { // Then wait. - absl::SleepFor(absl::Microseconds(10)); + absl::SleepFor(sleep_time); c = 0; } ABSL_TSAN_MUTEX_POST_DIVERT(nullptr, 0); @@ -325,7 +347,7 @@ static struct SynchEvent { // this is a trivial hash table for the events static SynchEvent *EnsureSynchEvent(std::atomic<intptr_t> *addr, const char *name, intptr_t bits, intptr_t lockbit) { - uint32_t h = reinterpret_cast<intptr_t>(addr) % kNSynchEvent; + uint32_t h = reinterpret_cast<uintptr_t>(addr) % kNSynchEvent; SynchEvent *e; // first look for existing SynchEvent struct.. synch_event_mu.Lock(); @@ -378,7 +400,7 @@ static void UnrefSynchEvent(SynchEvent *e) { // is clear before doing so). static void ForgetSynchEvent(std::atomic<intptr_t> *addr, intptr_t bits, intptr_t lockbit) { - uint32_t h = reinterpret_cast<intptr_t>(addr) % kNSynchEvent; + uint32_t h = reinterpret_cast<uintptr_t>(addr) % kNSynchEvent; SynchEvent **pe; SynchEvent *e; synch_event_mu.Lock(); @@ -402,7 +424,7 @@ static void ForgetSynchEvent(std::atomic<intptr_t> *addr, intptr_t bits, // "addr", if any. The pointer returned is valid until the UnrefSynchEvent() is // called. static SynchEvent *GetSynchEvent(const void *addr) { - uint32_t h = reinterpret_cast<intptr_t>(addr) % kNSynchEvent; + uint32_t h = reinterpret_cast<uintptr_t>(addr) % kNSynchEvent; SynchEvent *e; synch_event_mu.Lock(); for (e = synch_event[h]; @@ -430,7 +452,13 @@ static void PostSynchEvent(void *obj, int ev) { char buffer[ABSL_ARRAYSIZE(pcs) * 24]; int pos = snprintf(buffer, sizeof (buffer), " @"); for (int i = 0; i != n; i++) { - pos += snprintf(&buffer[pos], sizeof (buffer) - pos, " %p", pcs[i]); + int b = snprintf(&buffer[pos], sizeof(buffer) - static_cast<size_t>(pos), + " %p", pcs[i]); + if (b < 0 || + static_cast<size_t>(b) >= sizeof(buffer) - static_cast<size_t>(pos)) { + break; + } + pos += b; } ABSL_RAW_LOG(INFO, "%s%p %s %s", event_properties[ev].msg, obj, (e == nullptr ? "" : e->name), buffer); @@ -486,7 +514,8 @@ struct SynchWaitParams { cvmu(cvmu_arg), thread(thread_arg), cv_word(cv_word_arg), - contention_start_cycles(base_internal::CycleClock::Now()) {} + contention_start_cycles(base_internal::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. @@ -504,6 +533,7 @@ struct SynchWaitParams { int64_t contention_start_cycles; // Time (in cycles) when this thread started // to contend for the mutex. + bool should_submit_contention_data; }; struct SynchLocksHeld { @@ -562,10 +592,15 @@ static SynchLocksHeld *Synch_GetAllLocks() { void Mutex::IncrementSynchSem(Mutex *mu, PerThreadSynch *w) { if (mu) { ABSL_TSAN_MUTEX_PRE_DIVERT(mu, 0); - } - PerThreadSem::Post(w->thread_identity()); - if (mu) { + // 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()); } } @@ -1120,7 +1155,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. -ABSL_XRAY_LOG_ARGS(1) 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 @@ -1273,15 +1308,17 @@ static char *StackString(void **pcs, int n, char *buf, int maxlen, char sym[kSymLen]; int len = 0; for (int i = 0; i != n; i++) { + if (len >= maxlen) + return buf; + size_t count = static_cast<size_t>(maxlen - len); if (symbolize) { if (!symbolizer(pcs[i], sym, kSymLen)) { sym[0] = '\0'; } - snprintf(buf + len, maxlen - len, "%s\t@ %p %s\n", - (i == 0 ? "\n" : ""), - pcs[i], sym); + snprintf(buf + len, count, "%s\t@ %p %s\n", (i == 0 ? "\n" : ""), pcs[i], + sym); } else { - snprintf(buf + len, maxlen - len, " %p", pcs[i]); + snprintf(buf + len, count, " %p", pcs[i]); } len += strlen(&buf[len]); } @@ -1366,12 +1403,12 @@ static GraphId DeadlockCheck(Mutex *mu) { bool symbolize = number_of_reported_deadlocks <= 2; ABSL_RAW_LOG(ERROR, "Potential Mutex deadlock: %s", CurrentStackString(b->buf, sizeof (b->buf), symbolize)); - int len = 0; + size_t len = 0; 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); - len += static_cast<int>(strlen(&b->buf[len])); + len += strlen(&b->buf[len]); } } ABSL_RAW_LOG(ERROR, @@ -1467,7 +1504,7 @@ static bool TryAcquireWithSpinning(std::atomic<intptr_t>* mu) { return false; } -ABSL_XRAY_LOG_ARGS(1) void Mutex::Lock() { +void Mutex::Lock() { ABSL_TSAN_MUTEX_PRE_LOCK(this, 0); GraphId id = DebugOnlyDeadlockCheck(this); intptr_t v = mu_.load(std::memory_order_relaxed); @@ -1485,7 +1522,7 @@ ABSL_XRAY_LOG_ARGS(1) void Mutex::Lock() { ABSL_TSAN_MUTEX_POST_LOCK(this, 0, 0); } -ABSL_XRAY_LOG_ARGS(1) void Mutex::ReaderLock() { +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); @@ -1598,7 +1635,7 @@ bool Mutex::AwaitCommon(const Condition &cond, KernelTimeout t) { return res; } -ABSL_XRAY_LOG_ARGS(1) bool Mutex::TryLock() { +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 @@ -1627,7 +1664,7 @@ ABSL_XRAY_LOG_ARGS(1) bool Mutex::TryLock() { return false; } -ABSL_XRAY_LOG_ARGS(1) bool Mutex::ReaderTryLock() { +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); @@ -1673,7 +1710,7 @@ ABSL_XRAY_LOG_ARGS(1) bool Mutex::ReaderTryLock() { return false; } -ABSL_XRAY_LOG_ARGS(1) void Mutex::Unlock() { +void Mutex::Unlock() { ABSL_TSAN_MUTEX_PRE_UNLOCK(this, 0); DebugOnlyLockLeave(this); intptr_t v = mu_.load(std::memory_order_relaxed); @@ -1725,7 +1762,7 @@ static bool ExactlyOneReader(intptr_t v) { return (v & kMuMultipleWaitersMask) == 0; } -ABSL_XRAY_LOG_ARGS(1) void Mutex::ReaderUnlock() { +void Mutex::ReaderUnlock() { ABSL_TSAN_MUTEX_PRE_UNLOCK(this, __tsan_mutex_read_lock); DebugOnlyLockLeave(this); intptr_t v = mu_.load(std::memory_order_relaxed); @@ -1755,7 +1792,7 @@ static intptr_t ClearDesignatedWakerMask(int flag) { case 1: // blocked; turn off the designated waker bit return ~static_cast<intptr_t>(kMuDesig); } - ABSL_INTERNAL_UNREACHABLE; + ABSL_UNREACHABLE(); } // Conditionally ignores the existence of waiting writers if a reader that has @@ -1769,7 +1806,7 @@ static intptr_t IgnoreWaitingWritersMask(int flag) { case 1: // blocked; pretend there are no waiting writers return ~static_cast<intptr_t>(kMuWrWait); } - ABSL_INTERNAL_UNREACHABLE; + ABSL_UNREACHABLE(); } // Internal version of LockWhen(). See LockSlowWithDeadline() @@ -1790,8 +1827,8 @@ static inline bool EvalConditionAnnotated(const Condition *cond, Mutex *mu, // operation tsan considers that we've already released the mutex. bool res = false; #ifdef ABSL_INTERNAL_HAVE_TSAN_INTERFACE - const int flags = read_lock ? __tsan_mutex_read_lock : 0; - const int tryflags = flags | (trylock ? __tsan_mutex_try_lock : 0); + const uint32_t flags = read_lock ? __tsan_mutex_read_lock : 0; + const uint32_t tryflags = flags | (trylock ? __tsan_mutex_try_lock : 0); #endif if (locking) { // For lock we pretend that we have finished the operation, @@ -1904,7 +1941,7 @@ static void CheckForMutexCorruption(intptr_t v, const char* label) { // Test for either of two situations that should not occur in v: // kMuWriter and kMuReader // kMuWrWait and !kMuWait - const uintptr_t w = v ^ kMuWait; + const uintptr_t w = static_cast<uintptr_t>(v ^ kMuWait); // By flipping that bit, we can now test for: // kMuWriter and kMuReader in w // kMuWrWait and kMuWait in w @@ -2331,21 +2368,26 @@ ABSL_ATTRIBUTE_NOINLINE void Mutex::UnlockSlow(SynchWaitParams *waitp) { } // end of for(;;)-loop if (wake_list != kPerThreadSynchNull) { - int64_t wait_cycles = 0; + int64_t total_wait_cycles = 0; + int64_t max_wait_cycles = 0; int64_t now = base_internal::CycleClock::Now(); do { - // Sample lock contention events only if the waiter was trying to acquire + // Profile lock contention events only if the waiter was trying to acquire // the lock, not waiting on a condition variable or Condition. if (!wake_list->cond_waiter) { - wait_cycles += (now - wake_list->waitp->contention_start_cycles); + int64_t cycles_waited = + (now - wake_list->waitp->contention_start_cycles); + total_wait_cycles += cycles_waited; + if (max_wait_cycles == 0) max_wait_cycles = cycles_waited; wake_list->waitp->contention_start_cycles = now; + wake_list->waitp->should_submit_contention_data = true; } wake_list = Wakeup(wake_list); // wake waiters } while (wake_list != kPerThreadSynchNull); - if (wait_cycles > 0) { - mutex_tracer("slow release", this, wait_cycles); + if (total_wait_cycles > 0) { + mutex_tracer("slow release", this, total_wait_cycles); ABSL_TSAN_MUTEX_PRE_DIVERT(this, 0); - submit_profile_data(wait_cycles); + submit_profile_data(total_wait_cycles); ABSL_TSAN_MUTEX_POST_DIVERT(this, 0); } } @@ -2746,25 +2788,31 @@ static bool Dereference(void *arg) { return *(static_cast<bool *>(arg)); } -Condition::Condition() {} // null constructor, used for kTrue only -const Condition Condition::kTrue; +ABSL_CONST_INIT const Condition Condition::kTrue; Condition::Condition(bool (*func)(void *), void *arg) : eval_(&CallVoidPtrFunction), - function_(func), - method_(nullptr), - arg_(arg) {} + arg_(arg) { + static_assert(sizeof(&func) <= sizeof(callback_), + "An overlarge function pointer passed to Condition."); + StoreCallback(func); +} bool Condition::CallVoidPtrFunction(const Condition *c) { - return (*c->function_)(c->arg_); + 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) : eval_(CallVoidPtrFunction), - function_(Dereference), - method_(nullptr), // const_cast is safe since Dereference does not modify arg - arg_(const_cast<bool *>(cond)) {} + arg_(const_cast<bool *>(cond)) { + using FunctionPointer = bool (*)(void *); + const FunctionPointer dereference = Dereference; + StoreCallback(dereference); +} bool Condition::Eval() const { // eval_ == null for kTrue @@ -2772,14 +2820,15 @@ bool Condition::Eval() const { } bool Condition::GuaranteedEqual(const Condition *a, const Condition *b) { - if (a == nullptr) { + // kTrue logic. + if (a == nullptr || a->eval_ == nullptr) { return b == nullptr || b->eval_ == nullptr; + } else if (b == nullptr || b->eval_ == nullptr) { + return false; } - if (b == nullptr || b->eval_ == nullptr) { - return a->eval_ == nullptr; - } - return a->eval_ == b->eval_ && a->function_ == b->function_ && - a->arg_ == b->arg_ && a->method_ == b->method_; + // Check equality of the representative fields. + return a->eval_ == b->eval_ && a->arg_ == b->arg_ && + !memcmp(a->callback_, b->callback_, sizeof(a->callback_)); } ABSL_NAMESPACE_END diff --git a/absl/synchronization/mutex.h b/absl/synchronization/mutex.h index b69b7089..f793cc0e 100644 --- a/absl/synchronization/mutex.h +++ b/absl/synchronization/mutex.h @@ -60,6 +60,8 @@ #include <atomic> #include <cstdint> +#include <cstring> +#include <iterator> #include <string> #include "absl/base/const_init.h" @@ -612,12 +614,12 @@ class ABSL_SCOPED_LOCKABLE WriterMutexLock { // Condition // ----------------------------------------------------------------------------- // -// As noted above, `Mutex` contains a number of member functions which take a -// `Condition` as an argument; clients can wait for conditions to become `true` -// before attempting to acquire the mutex. These sections are known as -// "condition critical" sections. To use a `Condition`, you simply need to -// construct it, and use within an appropriate `Mutex` member function; -// everything else in the `Condition` class is an implementation detail. +// `Mutex` contains a number of member functions which take a `Condition` as an +// argument; clients can wait for conditions to become `true` before attempting +// to acquire the mutex. These sections are known as "condition critical" +// sections. To use a `Condition`, you simply need to construct it, and use +// within an appropriate `Mutex` member function; everything else in the +// `Condition` class is an implementation detail. // // A `Condition` is specified as a function pointer which returns a boolean. // `Condition` functions should be pure functions -- their results should depend @@ -727,7 +729,7 @@ class Condition { : Condition(obj, static_cast<bool (T::*)() const>(&T::operator())) {} // A Condition that always returns `true`. - static const Condition kTrue; + ABSL_CONST_INIT static const Condition kTrue; // Evaluates the condition. bool Eval() const; @@ -742,22 +744,54 @@ class Condition { static bool GuaranteedEqual(const Condition *a, const Condition *b); private: - typedef bool (*InternalFunctionType)(void * arg); - typedef bool (Condition::*InternalMethodType)(); - typedef bool (*InternalMethodCallerType)(void * arg, - InternalMethodType internal_method); - - bool (*eval_)(const Condition*); // Actual evaluator - InternalFunctionType function_; // function taking pointer returning bool - InternalMethodType method_; // method returning bool - void *arg_; // arg of function_ or object of method_ - - Condition(); // null constructor used only to create kTrue + // Sizing an allocation for a method pointer can be subtle. In the Itanium + // specifications, a method pointer has a predictable, uniform size. On the + // other hand, MSVC ABI, method pointer sizes vary based on the + // inheritance of the class. Specifically, method pointers from classes with + // multiple inheritance are bigger than those of classes with single + // inheritance. Other variations also exist. + +#ifndef _MSC_VER + // Allocation for a function pointer or method pointer. + // The {0} initializer ensures that all unused bytes of this buffer are + // always zeroed out. This is necessary, because GuaranteedEqual() compares + // all of the bytes, unaware of which bytes are relevant to a given `eval_`. + using MethodPtr = bool (Condition::*)(); + char callback_[sizeof(MethodPtr)] = {0}; +#else + // It is well known that the larget MSVC pointer-to-member is 24 bytes. This + // may be the largest known pointer-to-member of any platform. For this + // reason we will allocate 24 bytes for MSVC platform toolchains. + char callback_[24] = {0}; +#endif + + // Function with which to evaluate callbacks and/or arguments. + bool (*eval_)(const Condition*) = nullptr; + + // Either an argument for a function call or an object for a method call. + 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); + + // Helper methods for storing, validating, and reading callback arguments. + template <typename T> + inline void StoreCallback(T callback) { + static_assert( + sizeof(callback) <= sizeof(callback_), + "An overlarge pointer was passed as a callback to Condition."); + std::memcpy(callback_, &callback, sizeof(callback)); + } + + template <typename T> + inline void ReadCallback(T *callback) const { + std::memcpy(callback, callback_, sizeof(*callback)); + } + + // Used only to create kTrue. + constexpr Condition() = default; }; // ----------------------------------------------------------------------------- @@ -949,56 +983,61 @@ inline CondVar::CondVar() : cv_(0) {} // static template <typename T> bool Condition::CastAndCallMethod(const Condition *c) { - typedef bool (T::*MemberType)(); - MemberType rm = reinterpret_cast<MemberType>(c->method_); - T *x = static_cast<T *>(c->arg_); - return (x->*rm)(); + T *object = static_cast<T *>(c->arg_); + bool (T::*method_pointer)(); + c->ReadCallback(&method_pointer); + return (object->*method_pointer)(); } // static template <typename T> bool Condition::CastAndCallFunction(const Condition *c) { - typedef bool (*FuncType)(T *); - FuncType fn = reinterpret_cast<FuncType>(c->function_); - T *x = static_cast<T *>(c->arg_); - return (*fn)(x); + bool (*function)(T *); + c->ReadCallback(&function); + T *argument = static_cast<T *>(c->arg_); + return (*function)(argument); } template <typename T> inline Condition::Condition(bool (*func)(T *), T *arg) : eval_(&CastAndCallFunction<T>), - function_(reinterpret_cast<InternalFunctionType>(func)), - method_(nullptr), - 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> inline Condition::Condition(T *object, bool (absl::internal::identity<T>::type::*method)()) : eval_(&CastAndCallMethod<T>), - function_(nullptr), - method_(reinterpret_cast<InternalMethodType>(method)), - arg_(object) {} + 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>), - function_(nullptr), - method_(reinterpret_cast<InternalMethodType>(method)), - arg_(reinterpret_cast<void *>(const_cast<T *>(object))) {} + arg_(reinterpret_cast<void *>(const_cast<T *>(object))) { + StoreCallback(method); +} -// Register a hook for profiling support. +// Register hooks for profiling support. // // The function pointer registered here will be called whenever a mutex is // contended. The callback is given the cycles for which waiting happened (as // measured by //absl/base/internal/cycleclock.h, and which may not // be real "cycle" counts.) // -// Calls to this function do not race or block, but there is no ordering -// guaranteed between calls to this function and call to the provided hook. -// In particular, the previously registered hook may still be called for some -// time after this function returns. +// There is no ordering guarantee between when the hook is registered and when +// callbacks will begin. Only a single profiler can be installed in a running +// binary; if this function is called a second time with a different function +// pointer, the value is ignored (and will cause an assertion failure in debug +// mode.) void RegisterMutexProfiler(void (*fn)(int64_t wait_cycles)); // Register a hook for Mutex tracing. @@ -1011,13 +1050,11 @@ void RegisterMutexProfiler(void (*fn)(int64_t wait_cycles)); // // The only event name currently sent is "slow release". // -// This has the same memory ordering concerns as RegisterMutexProfiler() above. +// This has the same ordering and single-use limitations as +// RegisterMutexProfiler() above. void RegisterMutexTracer(void (*fn)(const char *msg, const void *obj, int64_t wait_cycles)); -// TODO(gfalcon): Combine RegisterMutexProfiler() and RegisterMutexTracer() -// into a single interface, since they are only ever called in pairs. - // Register a hook for CondVar tracing. // // The function pointer registered here will be called here on various CondVar @@ -1028,7 +1065,8 @@ void RegisterMutexTracer(void (*fn)(const char *msg, const void *obj, // Events that can be sent are "Wait", "Unwait", "Signal wakeup", and // "SignalAll wakeup". // -// This has the same memory ordering concerns as RegisterMutexProfiler() above. +// 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. @@ -1038,7 +1076,8 @@ void RegisterCondVarTracer(void (*fn)(const char *msg, const void *cv)); // false if symbolizing failed, or true if a NUL-terminated symbol was written // to 'out.' // -// This has the same memory ordering concerns as RegisterMutexProfiler() above. +// 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 diff --git a/absl/synchronization/mutex_method_pointer_test.cc b/absl/synchronization/mutex_method_pointer_test.cc new file mode 100644 index 00000000..1ec801a0 --- /dev/null +++ b/absl/synchronization/mutex_method_pointer_test.cc @@ -0,0 +1,138 @@ +// 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/mutex.h" + +#include <cstdlib> +#include <string> + +#include "gtest/gtest.h" +#include "absl/base/config.h" + +namespace { + +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. + +// Declare classes of the various MSVC inheritance types. +class __single_inheritance SingleInheritance{}; +class __multiple_inheritance MultipleInheritance; +class __virtual_inheritance VirtualInheritance; + +TEST(MutexMethodPointerTest, MicrosoftMethodPointerSize) { + void (SingleInheritance::*single_inheritance_method_pointer)(); + void (MultipleInheritance::*multiple_inheritance_method_pointer)(); + void (VirtualInheritance::*virtual_inheritance_method_pointer)(); + +#if defined(_M_IX86) || defined(_M_ARM) + static_assert(sizeof(single_inheritance_method_pointer) == 4, + "Unexpected sizeof(single_inheritance_method_pointer)."); + static_assert(sizeof(multiple_inheritance_method_pointer) == 8, + "Unexpected sizeof(multiple_inheritance_method_pointer)."); + static_assert(sizeof(virtual_inheritance_method_pointer) == 12, + "Unexpected sizeof(virtual_inheritance_method_pointer)."); +#elif defined(_M_X64) || defined(__aarch64__) + static_assert(sizeof(single_inheritance_method_pointer) == 8, + "Unexpected sizeof(single_inheritance_method_pointer)."); + static_assert(sizeof(multiple_inheritance_method_pointer) == 16, + "Unexpected sizeof(multiple_inheritance_method_pointer)."); + static_assert(sizeof(virtual_inheritance_method_pointer) == 16, + "Unexpected sizeof(virtual_inheritance_method_pointer)."); +#endif + void (IncompleteClass::*incomplete_class_method_pointer)(); + static_assert(sizeof(incomplete_class_method_pointer) >= + sizeof(virtual_inheritance_method_pointer), + "Failed invariant: sizeof(incomplete_class_method_pointer) >= " + "sizeof(virtual_inheritance_method_pointer)!"); +} + +class Callback { + bool x = true; + + public: + Callback() {} + bool method() { + x = !x; + return x; + } +}; + +class M2 { + bool x = true; + + public: + M2() {} + bool method2() { + x = !x; + return x; + } +}; + +class MultipleInheritance : public Callback, public M2 {}; + +TEST(MutexMethodPointerTest, ConditionWithMultipleInheritanceMethod) { + // This test ensures that Condition can deal with method pointers from classes + // with multiple inheritance. + MultipleInheritance object = MultipleInheritance(); + absl::Condition condition(&object, &MultipleInheritance::method); + EXPECT_FALSE(condition.Eval()); + EXPECT_TRUE(condition.Eval()); +} + +class __virtual_inheritance VirtualInheritance : virtual public Callback { + bool x = false; + + public: + VirtualInheritance() {} + bool method() { + x = !x; + return x; + } +}; + +TEST(MutexMethodPointerTest, ConditionWithVirtualInheritanceMethod) { + // This test ensures that Condition can deal with method pointers from classes + // with virtual inheritance. + VirtualInheritance object = VirtualInheritance(); + absl::Condition condition(&object, &VirtualInheritance::method); + EXPECT_TRUE(condition.Eval()); + EXPECT_FALSE(condition.Eval()); +} +#endif // #ifdef _MSC_VER + +TEST(MutexMethodPointerTest, ConditionWithIncompleteClassMethod) { + using IncompleteClassMethodPointer = void (IncompleteClass::*)(); + + union CallbackSlot { + void (*anonymous_function_pointer)(); + IncompleteClassMethodPointer incomplete_class_method_pointer; + }; + + static_assert(sizeof(CallbackSlot) >= sizeof(IncompleteClassMethodPointer), + "The callback slot is not big enough for method pointers."); + static_assert( + sizeof(CallbackSlot) == sizeof(IncompleteClassMethodPointer), + "The callback slot is not big enough for anonymous function pointers."); + +#if defined(_MSC_VER) + static_assert(sizeof(IncompleteClassMethodPointer) <= 24, + "The pointer to a method of an incomplete class is too big."); +#endif +} + +} // namespace diff --git a/absl/synchronization/mutex_test.cc b/absl/synchronization/mutex_test.cc index 99bb0175..34751cb1 100644 --- a/absl/synchronization/mutex_test.cc +++ b/absl/synchronization/mutex_test.cc @@ -295,8 +295,9 @@ static void TestTime(TestContext *cxt, int c, bool use_cv) { "TestTime failed"); } elapsed = absl::Now() - start; - ABSL_RAW_CHECK(absl::Seconds(0.9) <= elapsed && - elapsed <= absl::Seconds(2.0), "TestTime failed"); + ABSL_RAW_CHECK( + absl::Seconds(0.9) <= elapsed && elapsed <= absl::Seconds(2.0), + "TestTime failed"); ABSL_RAW_CHECK(cxt->g0 == cxt->threads, "TestTime failed"); } else if (c == 1) { @@ -343,7 +344,7 @@ static void TestMuTime(TestContext *cxt, int c) { TestTime(cxt, c, false); } static void TestCVTime(TestContext *cxt, int c) { TestTime(cxt, c, true); } static void EndTest(int *c0, int *c1, absl::Mutex *mu, absl::CondVar *cv, - const std::function<void(int)>& cb) { + const std::function<void(int)> &cb) { mu->Lock(); int c = (*c0)++; mu->Unlock(); @@ -366,9 +367,9 @@ static int RunTestCommon(TestContext *cxt, void (*test)(TestContext *cxt, int), cxt->threads = threads; absl::synchronization_internal::ThreadPool tp(threads); for (int i = 0; i != threads; i++) { - tp.Schedule(std::bind(&EndTest, &c0, &c1, &mu2, &cv2, - std::function<void(int)>( - std::bind(test, cxt, std::placeholders::_1)))); + tp.Schedule(std::bind( + &EndTest, &c0, &c1, &mu2, &cv2, + std::function<void(int)>(std::bind(test, cxt, std::placeholders::_1)))); } mu2.Lock(); while (c1 != threads) { @@ -682,14 +683,14 @@ struct LockWhenTestStruct { bool waiting = false; }; -static bool LockWhenTestIsCond(LockWhenTestStruct* s) { +static bool LockWhenTestIsCond(LockWhenTestStruct *s) { s->mu2.Lock(); s->waiting = true; s->mu2.Unlock(); return s->cond; } -static void LockWhenTestWaitForIsCond(LockWhenTestStruct* s) { +static void LockWhenTestWaitForIsCond(LockWhenTestStruct *s) { s->mu1.LockWhen(absl::Condition(&LockWhenTestIsCond, s)); s->mu1.Unlock(); } @@ -1694,8 +1695,7 @@ TEST(Mutex, Timed) { TEST(Mutex, CVTime) { int threads = 10; // Use a fixed thread count of 10 int iterations = 1; - EXPECT_EQ(RunTest(&TestCVTime, threads, iterations, 1), - threads * iterations); + EXPECT_EQ(RunTest(&TestCVTime, threads, iterations, 1), threads * iterations); } TEST(Mutex, MuTime) { diff --git a/absl/synchronization/notification.cc b/absl/synchronization/notification.cc index e91b9038..165ba669 100644 --- a/absl/synchronization/notification.cc +++ b/absl/synchronization/notification.cc @@ -16,7 +16,6 @@ #include <atomic> -#include "absl/base/attributes.h" #include "absl/base/internal/raw_logging.h" #include "absl/synchronization/mutex.h" #include "absl/time/time.h" diff --git a/absl/synchronization/notification.h b/absl/synchronization/notification.h index 4bec2689..8986d9a4 100644 --- a/absl/synchronization/notification.h +++ b/absl/synchronization/notification.h @@ -53,7 +53,6 @@ #include <atomic> #include "absl/base/attributes.h" -#include "absl/base/macros.h" #include "absl/synchronization/mutex.h" #include "absl/time/time.h" diff --git a/absl/time/BUILD.bazel b/absl/time/BUILD.bazel index aa07df3d..c7b07c2f 100644 --- a/absl/time/BUILD.bazel +++ b/absl/time/BUILD.bazel @@ -45,22 +45,21 @@ cc_library( linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ "//absl/base", + "//absl/base:config", "//absl/base:core_headers", "//absl/base:raw_logging_internal", "//absl/numeric:int128", "//absl/strings", "//absl/time/internal/cctz:civil_time", "//absl/time/internal/cctz:time_zone", + "//absl/types:optional", ], ) cc_library( name = "test_util", testonly = 1, - srcs = [ - "internal/test_util.cc", - "internal/zoneinfo.inc", - ], + srcs = ["internal/test_util.cc"], hdrs = ["internal/test_util.h"], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, @@ -69,8 +68,6 @@ cc_library( ":time", "//absl/base:config", "//absl/base:raw_logging_internal", - "//absl/time/internal/cctz:time_zone", - "@com_google_googletest//:gtest", ], ) @@ -85,6 +82,8 @@ cc_test( "time_zone_test.cc", ], copts = ABSL_TEST_COPTS, + data = ["//absl/time/internal/cctz:zoneinfo"], + env = {"TZDIR": "absl/time/internal/cctz/testdata/zoneinfo"}, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":test_util", @@ -98,6 +97,30 @@ cc_test( ) cc_test( + name = "flag_test", + srcs = [ + "flag_test.cc", + ], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = [ + "no_test_android_arm", + "no_test_android_arm64", + "no_test_android_x86", + "no_test_ios_x86_64", + "no_test_lexan", + "no_test_loonix", + "no_test_wasm", + ], + deps = [ + ":time", + "//absl/flags:flag", + "//absl/flags:reflection", + "@com_google_googletest//:gtest_main", + ], +) + +cc_test( name = "time_benchmark", srcs = [ "civil_time_benchmark.cc", @@ -107,6 +130,8 @@ cc_test( "time_benchmark.cc", ], copts = ABSL_TEST_COPTS, + data = ["//absl/time/internal/cctz:zoneinfo"], + env = {"TZDIR": "absl/time/internal/cctz/testdata/zoneinfo"}, linkopts = ABSL_DEFAULT_LINKOPTS, tags = [ "benchmark", diff --git a/absl/time/CMakeLists.txt b/absl/time/CMakeLists.txt index debab3ba..7b720540 100644 --- a/absl/time/CMakeLists.txt +++ b/absl/time/CMakeLists.txt @@ -95,7 +95,6 @@ absl_cc_library( "internal/test_util.h" SRCS "internal/test_util.cc" - "internal/zoneinfo.inc" COPTS ${ABSL_DEFAULT_COPTS} DEPS @@ -103,7 +102,6 @@ absl_cc_library( absl::config absl::raw_logging_internal absl::time_zone - GTest::gmock TESTONLY ) @@ -127,3 +125,16 @@ absl_cc_test( absl::time_zone GTest::gmock_main ) + +absl_cc_test( + NAME + flag_test + SRCS + "flag_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::flags + absl::flags_reflection + GTest::gmock_main +) diff --git a/absl/time/civil_time.cc b/absl/time/civil_time.cc index 6a231edb..65df39d7 100644 --- a/absl/time/civil_time.cc +++ b/absl/time/civil_time.cc @@ -15,6 +15,7 @@ #include "absl/time/civil_time.h" #include <cstdlib> +#include <ostream> #include <string> #include "absl/strings/str_cat.h" @@ -167,6 +168,31 @@ std::ostream& operator<<(std::ostream& os, CivilSecond s) { return os << FormatCivilTime(s); } +bool AbslParseFlag(string_view s, CivilSecond* c, std::string*) { + return ParseLenientCivilTime(s, c); +} +bool AbslParseFlag(string_view s, CivilMinute* c, std::string*) { + return ParseLenientCivilTime(s, c); +} +bool AbslParseFlag(string_view s, CivilHour* c, std::string*) { + return ParseLenientCivilTime(s, c); +} +bool AbslParseFlag(string_view s, CivilDay* c, std::string*) { + return ParseLenientCivilTime(s, c); +} +bool AbslParseFlag(string_view s, CivilMonth* c, std::string*) { + return ParseLenientCivilTime(s, c); +} +bool AbslParseFlag(string_view s, CivilYear* c, std::string*) { + return ParseLenientCivilTime(s, c); +} +std::string AbslUnparseFlag(CivilSecond c) { return FormatCivilTime(c); } +std::string AbslUnparseFlag(CivilMinute c) { return FormatCivilTime(c); } +std::string AbslUnparseFlag(CivilHour c) { return FormatCivilTime(c); } +std::string AbslUnparseFlag(CivilDay c) { return FormatCivilTime(c); } +std::string AbslUnparseFlag(CivilMonth c) { return FormatCivilTime(c); } +std::string AbslUnparseFlag(CivilYear c) { return FormatCivilTime(c); } + } // namespace time_internal ABSL_NAMESPACE_END diff --git a/absl/time/civil_time.h b/absl/time/civil_time.h index bb460044..5855bc73 100644 --- a/absl/time/civil_time.h +++ b/absl/time/civil_time.h @@ -70,8 +70,10 @@ #ifndef ABSL_TIME_CIVIL_TIME_H_ #define ABSL_TIME_CIVIL_TIME_H_ +#include <iosfwd> #include <string> +#include "absl/base/config.h" #include "absl/strings/string_view.h" #include "absl/time/internal/cctz/include/cctz/civil_time.h" @@ -530,6 +532,29 @@ std::ostream& operator<<(std::ostream& os, CivilHour h); std::ostream& operator<<(std::ostream& os, CivilMinute m); std::ostream& operator<<(std::ostream& os, CivilSecond s); +// AbslParseFlag() +// +// Parses the command-line flag string representation `s` into a civil-time +// value. Flags must be specified in a format that is valid for +// `absl::ParseLenientCivilTime()`. +bool AbslParseFlag(absl::string_view s, CivilSecond* c, std::string* error); +bool AbslParseFlag(absl::string_view s, CivilMinute* c, std::string* error); +bool AbslParseFlag(absl::string_view s, CivilHour* c, std::string* error); +bool AbslParseFlag(absl::string_view s, CivilDay* c, std::string* error); +bool AbslParseFlag(absl::string_view s, CivilMonth* c, std::string* error); +bool AbslParseFlag(absl::string_view s, CivilYear* c, std::string* error); + +// AbslUnparseFlag() +// +// Unparses a civil-time value into a command-line string representation using +// the format specified by `absl::ParseCivilTime()`. +std::string AbslUnparseFlag(CivilSecond c); +std::string AbslUnparseFlag(CivilMinute c); +std::string AbslUnparseFlag(CivilHour c); +std::string AbslUnparseFlag(CivilDay c); +std::string AbslUnparseFlag(CivilMonth c); +std::string AbslUnparseFlag(CivilYear c); + } // namespace time_internal ABSL_NAMESPACE_END diff --git a/absl/time/clock.cc b/absl/time/clock.cc index a091efdc..2bf53d9c 100644 --- a/absl/time/clock.cc +++ b/absl/time/clock.cc @@ -217,9 +217,11 @@ static int64_t GetCurrentTimeNanosFromKernel(uint64_t last_cycleclock, uint64_t elapsed_cycles; int loops = 0; do { - before_cycles = GET_CURRENT_TIME_NANOS_CYCLECLOCK_NOW(); + before_cycles = + static_cast<uint64_t>(GET_CURRENT_TIME_NANOS_CYCLECLOCK_NOW()); current_time_nanos_from_system = GET_CURRENT_TIME_NANOS_FROM_SYSTEM(); - after_cycles = GET_CURRENT_TIME_NANOS_CYCLECLOCK_NOW(); + after_cycles = + static_cast<uint64_t>(GET_CURRENT_TIME_NANOS_CYCLECLOCK_NOW()); // elapsed_cycles is unsigned, so is large on overflow elapsed_cycles = after_cycles - before_cycles; if (elapsed_cycles >= local_approx_syscall_time_in_cycles && @@ -316,7 +318,8 @@ int64_t GetCurrentTimeNanos() { // contribute to register pressure - reading it early before initializing // the other pieces of the calculation minimizes spill/restore instructions, // minimizing icache cost. - uint64_t now_cycles = GET_CURRENT_TIME_NANOS_CYCLECLOCK_NOW(); + uint64_t now_cycles = + static_cast<uint64_t>(GET_CURRENT_TIME_NANOS_CYCLECLOCK_NOW()); // Acquire pairs with the barrier in SeqRelease - if this load sees that // store, the shared-data reads necessarily see that SeqRelease's updates @@ -356,7 +359,8 @@ int64_t GetCurrentTimeNanos() { uint64_t delta_cycles; if (seq_read0 == seq_read1 && (seq_read0 & 1) == 0 && (delta_cycles = now_cycles - base_cycles) < min_cycles_per_sample) { - return base_ns + ((delta_cycles * nsscaled_per_cycle) >> kScale); + return static_cast<int64_t>( + base_ns + ((delta_cycles * nsscaled_per_cycle) >> kScale)); } return GetCurrentTimeNanosSlowPath(); } @@ -404,8 +408,8 @@ static int64_t GetCurrentTimeNanosSlowPath() // Sample the kernel time base. This is the definition of // "now" if we take the slow path. uint64_t now_cycles; - uint64_t now_ns = - GetCurrentTimeNanosFromKernel(time_state.last_now_cycles, &now_cycles); + uint64_t now_ns = static_cast<uint64_t>( + GetCurrentTimeNanosFromKernel(time_state.last_now_cycles, &now_cycles)); time_state.last_now_cycles = now_cycles; uint64_t estimated_base_ns; @@ -432,7 +436,7 @@ static int64_t GetCurrentTimeNanosSlowPath() time_state.lock.Unlock(); - return estimated_base_ns; + return static_cast<int64_t>(estimated_base_ns); } // Main part of the algorithm. Locks out readers, updates the approximation @@ -489,7 +493,8 @@ static uint64_t UpdateLastSample(uint64_t now_cycles, uint64_t now_ns, uint64_t assumed_next_sample_delta_cycles = SafeDivideAndScale(kMinNSBetweenSamples, measured_nsscaled_per_cycle); - int64_t diff_ns = now_ns - estimated_base_ns; // estimate low by this much + // Estimate low by this much. + int64_t diff_ns = static_cast<int64_t>(now_ns - estimated_base_ns); // We want to set nsscaled_per_cycle so that our estimate of the ns time // at the assumed cycle time is the assumed ns time. @@ -500,7 +505,8 @@ static uint64_t UpdateLastSample(uint64_t now_cycles, uint64_t now_ns, // of our current error, by solving: // kMinNSBetweenSamples + diff_ns - (diff_ns / 16) == // (assumed_next_sample_delta_cycles * nsscaled_per_cycle) >> kScale - ns = kMinNSBetweenSamples + diff_ns - (diff_ns / 16); + ns = static_cast<uint64_t>(static_cast<int64_t>(kMinNSBetweenSamples) + + diff_ns - (diff_ns / 16)); uint64_t new_nsscaled_per_cycle = SafeDivideAndScale(ns, assumed_next_sample_delta_cycles); if (new_nsscaled_per_cycle != 0 && @@ -558,7 +564,7 @@ constexpr absl::Duration MaxSleep() { // REQUIRES: to_sleep <= MaxSleep(). void SleepOnce(absl::Duration to_sleep) { #ifdef _WIN32 - Sleep(to_sleep / absl::Milliseconds(1)); + Sleep(static_cast<DWORD>(to_sleep / absl::Milliseconds(1))); #else struct timespec sleep_time = absl::ToTimespec(to_sleep); while (nanosleep(&sleep_time, &sleep_time) != 0 && errno == EINTR) { diff --git a/absl/time/duration.cc b/absl/time/duration.cc index 2bba62da..911e80f8 100644 --- a/absl/time/duration.cc +++ b/absl/time/duration.cc @@ -617,7 +617,7 @@ timespec ToTimespec(Duration d) { rep_lo -= kTicksPerSecond; } } - ts.tv_sec = rep_hi; + ts.tv_sec = static_cast<decltype(ts.tv_sec)>(rep_hi); if (ts.tv_sec == rep_hi) { // no time_t narrowing ts.tv_nsec = rep_lo / kTicksPerNanosecond; return ts; @@ -645,7 +645,7 @@ timeval ToTimeval(Duration d) { ts.tv_nsec -= 1000 * 1000 * 1000; } } - tv.tv_sec = ts.tv_sec; + tv.tv_sec = static_cast<decltype(tv.tv_sec)>(ts.tv_sec); if (tv.tv_sec != ts.tv_sec) { // narrowing if (ts.tv_sec < 0) { tv.tv_sec = std::numeric_limits<decltype(tv.tv_sec)>::min(); @@ -691,7 +691,7 @@ namespace { char* Format64(char* ep, int width, int64_t v) { do { --width; - *--ep = '0' + (v % 10); // contiguous digits + *--ep = static_cast<char>('0' + (v % 10)); // contiguous digits } while (v /= 10); while (--width >= 0) *--ep = '0'; // zero pad return ep; @@ -728,7 +728,7 @@ void AppendNumberUnit(std::string* out, int64_t n, DisplayUnit unit) { char* const ep = buf + sizeof(buf); char* bp = Format64(ep, 0, n); if (*bp != '0' || bp + 1 != ep) { - out->append(bp, ep - bp); + out->append(bp, static_cast<size_t>(ep - bp)); out->append(unit.abbr.data(), unit.abbr.size()); } } @@ -745,12 +745,12 @@ void AppendNumberUnit(std::string* out, double n, DisplayUnit unit) { int64_t int_part = d; if (int_part != 0 || frac_part != 0) { char* bp = Format64(ep, 0, int_part); // always < 1000 - out->append(bp, ep - bp); + out->append(bp, static_cast<size_t>(ep - bp)); if (frac_part != 0) { out->push_back('.'); bp = Format64(ep, prec, frac_part); while (ep[-1] == '0') --ep; - out->append(bp, ep - bp); + out->append(bp, static_cast<size_t>(ep - bp)); } out->append(unit.abbr.data(), unit.abbr.size()); } @@ -841,7 +841,7 @@ bool ConsumeDurationNumber(const char** dpp, const char* ep, int64_t* int_part, // in "*unit". The given string pointer is modified to point to the first // unconsumed char. bool ConsumeDurationUnit(const char** start, const char* end, Duration* unit) { - size_t size = end - *start; + size_t size = static_cast<size_t>(end - *start); switch (size) { case 0: return false; diff --git a/absl/time/flag_test.cc b/absl/time/flag_test.cc new file mode 100644 index 00000000..8f8532b7 --- /dev/null +++ b/absl/time/flag_test.cc @@ -0,0 +1,147 @@ +// Copyright 2018 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/flags/flag.h" + +#include <string> + +#include "gtest/gtest.h" +#include "absl/flags/reflection.h" +#include "absl/time/civil_time.h" +#include "absl/time/time.h" + +ABSL_FLAG(absl::CivilSecond, test_flag_civil_second, + absl::CivilSecond(2015, 1, 2, 3, 4, 5), ""); +ABSL_FLAG(absl::CivilMinute, test_flag_civil_minute, + absl::CivilMinute(2015, 1, 2, 3, 4), ""); +ABSL_FLAG(absl::CivilHour, test_flag_civil_hour, absl::CivilHour(2015, 1, 2, 3), + ""); +ABSL_FLAG(absl::CivilDay, test_flag_civil_day, absl::CivilDay(2015, 1, 2), ""); +ABSL_FLAG(absl::CivilMonth, test_flag_civil_month, absl::CivilMonth(2015, 1), + ""); +ABSL_FLAG(absl::CivilYear, test_flag_civil_year, absl::CivilYear(2015), ""); + +ABSL_FLAG(absl::Duration, test_duration_flag, absl::Seconds(5), + "For testing support for Duration flags"); +ABSL_FLAG(absl::Time, test_time_flag, absl::InfinitePast(), + "For testing support for Time flags"); + +namespace { + +bool SetFlagValue(absl::string_view flag_name, absl::string_view value) { + auto* flag = absl::FindCommandLineFlag(flag_name); + if (!flag) return false; + std::string err; + return flag->ParseFrom(value, &err); +} + +bool GetFlagValue(absl::string_view flag_name, std::string& value) { + auto* flag = absl::FindCommandLineFlag(flag_name); + if (!flag) return false; + value = flag->CurrentValue(); + return true; +} + +TEST(CivilTime, FlagSupport) { + // Tests the default setting of the flags. + const absl::CivilSecond kDefaultSec(2015, 1, 2, 3, 4, 5); + EXPECT_EQ(absl::CivilSecond(kDefaultSec), + absl::GetFlag(FLAGS_test_flag_civil_second)); + EXPECT_EQ(absl::CivilMinute(kDefaultSec), + absl::GetFlag(FLAGS_test_flag_civil_minute)); + EXPECT_EQ(absl::CivilHour(kDefaultSec), + absl::GetFlag(FLAGS_test_flag_civil_hour)); + EXPECT_EQ(absl::CivilDay(kDefaultSec), + absl::GetFlag(FLAGS_test_flag_civil_day)); + EXPECT_EQ(absl::CivilMonth(kDefaultSec), + absl::GetFlag(FLAGS_test_flag_civil_month)); + EXPECT_EQ(absl::CivilYear(kDefaultSec), + absl::GetFlag(FLAGS_test_flag_civil_year)); + + // Sets flags to a new value. + const absl::CivilSecond kNewSec(2016, 6, 7, 8, 9, 10); + absl::SetFlag(&FLAGS_test_flag_civil_second, absl::CivilSecond(kNewSec)); + absl::SetFlag(&FLAGS_test_flag_civil_minute, absl::CivilMinute(kNewSec)); + absl::SetFlag(&FLAGS_test_flag_civil_hour, absl::CivilHour(kNewSec)); + absl::SetFlag(&FLAGS_test_flag_civil_day, absl::CivilDay(kNewSec)); + absl::SetFlag(&FLAGS_test_flag_civil_month, absl::CivilMonth(kNewSec)); + absl::SetFlag(&FLAGS_test_flag_civil_year, absl::CivilYear(kNewSec)); + + EXPECT_EQ(absl::CivilSecond(kNewSec), + absl::GetFlag(FLAGS_test_flag_civil_second)); + EXPECT_EQ(absl::CivilMinute(kNewSec), + absl::GetFlag(FLAGS_test_flag_civil_minute)); + EXPECT_EQ(absl::CivilHour(kNewSec), + absl::GetFlag(FLAGS_test_flag_civil_hour)); + EXPECT_EQ(absl::CivilDay(kNewSec), absl::GetFlag(FLAGS_test_flag_civil_day)); + EXPECT_EQ(absl::CivilMonth(kNewSec), + absl::GetFlag(FLAGS_test_flag_civil_month)); + EXPECT_EQ(absl::CivilYear(kNewSec), + absl::GetFlag(FLAGS_test_flag_civil_year)); +} + +TEST(Duration, FlagSupport) { + EXPECT_EQ(absl::Seconds(5), absl::GetFlag(FLAGS_test_duration_flag)); + + absl::SetFlag(&FLAGS_test_duration_flag, absl::Seconds(10)); + EXPECT_EQ(absl::Seconds(10), absl::GetFlag(FLAGS_test_duration_flag)); + + EXPECT_TRUE(SetFlagValue("test_duration_flag", "20s")); + EXPECT_EQ(absl::Seconds(20), absl::GetFlag(FLAGS_test_duration_flag)); + + std::string current_flag_value; + EXPECT_TRUE(GetFlagValue("test_duration_flag", current_flag_value)); + EXPECT_EQ("20s", current_flag_value); +} + +TEST(Time, FlagSupport) { + EXPECT_EQ(absl::InfinitePast(), absl::GetFlag(FLAGS_test_time_flag)); + + const absl::Time t = absl::FromCivil(absl::CivilSecond(2016, 1, 2, 3, 4, 5), + absl::UTCTimeZone()); + absl::SetFlag(&FLAGS_test_time_flag, t); + EXPECT_EQ(t, absl::GetFlag(FLAGS_test_time_flag)); + + // Successful parse + EXPECT_TRUE(SetFlagValue("test_time_flag", "2016-01-02T03:04:06Z")); + EXPECT_EQ(t + absl::Seconds(1), absl::GetFlag(FLAGS_test_time_flag)); + EXPECT_TRUE(SetFlagValue("test_time_flag", "2016-01-02T03:04:07.0Z")); + EXPECT_EQ(t + absl::Seconds(2), absl::GetFlag(FLAGS_test_time_flag)); + EXPECT_TRUE(SetFlagValue("test_time_flag", "2016-01-02T03:04:08.000Z")); + EXPECT_EQ(t + absl::Seconds(3), absl::GetFlag(FLAGS_test_time_flag)); + EXPECT_TRUE(SetFlagValue("test_time_flag", "2016-01-02T03:04:09+00:00")); + EXPECT_EQ(t + absl::Seconds(4), absl::GetFlag(FLAGS_test_time_flag)); + EXPECT_TRUE(SetFlagValue("test_time_flag", "2016-01-02T03:04:05.123+00:00")); + EXPECT_EQ(t + absl::Milliseconds(123), absl::GetFlag(FLAGS_test_time_flag)); + EXPECT_TRUE(SetFlagValue("test_time_flag", "2016-01-02T03:04:05.123+08:00")); + EXPECT_EQ(t + absl::Milliseconds(123) - absl::Hours(8), + absl::GetFlag(FLAGS_test_time_flag)); + EXPECT_TRUE(SetFlagValue("test_time_flag", "infinite-future")); + EXPECT_EQ(absl::InfiniteFuture(), absl::GetFlag(FLAGS_test_time_flag)); + EXPECT_TRUE(SetFlagValue("test_time_flag", "infinite-past")); + EXPECT_EQ(absl::InfinitePast(), absl::GetFlag(FLAGS_test_time_flag)); + + EXPECT_FALSE(SetFlagValue("test_time_flag", "2016-01-02T03:04:06")); + EXPECT_FALSE(SetFlagValue("test_time_flag", "2016-01-02")); + EXPECT_FALSE(SetFlagValue("test_time_flag", "2016-01-02Z")); + EXPECT_FALSE(SetFlagValue("test_time_flag", "2016-01-02+00:00")); + EXPECT_FALSE(SetFlagValue("test_time_flag", "2016-99-99T03:04:06Z")); + + EXPECT_TRUE(SetFlagValue("test_time_flag", "2016-01-02T03:04:05Z")); + std::string current_flag_value; + EXPECT_TRUE(GetFlagValue("test_time_flag", current_flag_value)); + EXPECT_EQ("2016-01-02T03:04:05+00:00", current_flag_value); +} + +} // namespace diff --git a/absl/time/format.cc b/absl/time/format.cc index 4005fb70..15a26b14 100644 --- a/absl/time/format.cc +++ b/absl/time/format.cc @@ -64,7 +64,8 @@ cctz_parts Split(absl::Time t) { // details about rep_hi and rep_lo. absl::Time Join(const cctz_parts& parts) { const int64_t rep_hi = (parts.sec - unix_epoch()).count(); - const uint32_t rep_lo = parts.fem.count() / (1000 * 1000 / 4); + const uint32_t rep_lo = + static_cast<uint32_t>(parts.fem.count() / (1000 * 1000 / 4)); const auto d = time_internal::MakeDuration(rep_hi, rep_lo); return time_internal::FromUnixDuration(d); } diff --git a/absl/time/internal/cctz/BUILD.bazel b/absl/time/internal/cctz/BUILD.bazel index 7304d40d..edeabd81 100644 --- a/absl/time/internal/cctz/BUILD.bazel +++ b/absl/time/internal/cctz/BUILD.bazel @@ -16,25 +16,6 @@ package(features = ["-parse_headers"]) licenses(["notice"]) -filegroup( - name = "zoneinfo", - srcs = glob(["testdata/zoneinfo/**"]), -) - -config_setting( - name = "osx", - constraint_values = [ - "@platforms//os:osx", - ], -) - -config_setting( - name = "ios", - constraint_values = [ - "@platforms//os:ios", - ], -) - ### libraries cc_library( @@ -72,15 +53,10 @@ cc_library( "include/cctz/time_zone.h", "include/cctz/zone_info_source.h", ], - linkopts = select({ - ":osx": [ - "-framework Foundation", - ], - ":ios": [ - "-framework Foundation", - ], - "//conditions:default": [], - }), + # 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. visibility = ["//visibility:public"], deps = [ ":civil_time", @@ -115,6 +91,7 @@ cc_test( size = "small", srcs = ["src/time_zone_format_test.cc"], data = [":zoneinfo"], + env = {"TZDIR": "absl/time/internal/cctz/testdata/zoneinfo"}, tags = [ "no_test_android_arm", "no_test_android_arm64", @@ -135,6 +112,7 @@ cc_test( timeout = "moderate", srcs = ["src/time_zone_lookup_test.cc"], data = [":zoneinfo"], + env = {"TZDIR": "absl/time/internal/cctz/testdata/zoneinfo"}, tags = [ "no_test_android_arm", "no_test_android_arm64", @@ -170,6 +148,12 @@ cc_test( ], ) +filegroup( + name = "zoneinfo", + srcs = glob(["testdata/zoneinfo/**"]), + visibility = ["//absl/time:__subpackages__"], +) + ### examples ### binaries diff --git a/absl/time/internal/cctz/src/cctz_benchmark.cc b/absl/time/internal/cctz/src/cctz_benchmark.cc index 6770ad6b..c64f3801 100644 --- a/absl/time/internal/cctz/src/cctz_benchmark.cc +++ b/absl/time/internal/cctz/src/cctz_benchmark.cc @@ -554,6 +554,7 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Europe/Kaliningrad", "Europe/Kiev", "Europe/Kirov", + "Europe/Kyiv", "Europe/Lisbon", "Europe/Ljubljana", "Europe/London", @@ -593,6 +594,7 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Europe/Zagreb", "Europe/Zaporozhye", "Europe/Zurich", + "Factory", "GB", "GB-Eire", "GMT", diff --git a/absl/time/internal/cctz/src/time_zone_format.cc b/absl/time/internal/cctz/src/time_zone_format.cc index d8cb0474..2e5f5329 100644 --- a/absl/time/internal/cctz/src/time_zone_format.cc +++ b/absl/time/internal/cctz/src/time_zone_format.cc @@ -19,7 +19,7 @@ #endif #if defined(HAS_STRPTIME) && HAS_STRPTIME -#if !defined(_XOPEN_SOURCE) +#if !defined(_XOPEN_SOURCE) && !defined(__OpenBSD__) #define _XOPEN_SOURCE // Definedness suffices for strptime. #endif #endif diff --git a/absl/time/internal/cctz/src/time_zone_info.cc b/absl/time/internal/cctz/src/time_zone_info.cc index 4f175d95..787426f7 100644 --- a/absl/time/internal/cctz/src/time_zone_info.cc +++ b/absl/time/internal/cctz/src/time_zone_info.cc @@ -134,6 +134,21 @@ std::int_fast64_t Decode64(const char* cp) { return static_cast<std::int_fast64_t>(v - s64maxU - 1) - s64max - 1; } +// 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) { + if (posix.dst_start.date.fmt != PosixTransition::N) return false; + if (posix.dst_start.date.n.day != 0) return false; + if (posix.dst_start.time.offset != 0) return false; + + if (posix.dst_end.date.fmt != PosixTransition::J) return false; + if (posix.dst_end.date.j.day != kDaysPerYear[0]) return false; + const auto offset = posix.std_offset - posix.dst_offset; + if (posix.dst_end.time.offset + offset != kSecsPerDay) return false; + + return true; +} + // Generate a year-relative offset for a PosixTransition. std::int_fast64_t TransOffset(bool leap_year, int jan1_weekday, const PosixTransition& pt) { @@ -351,6 +366,12 @@ bool TimeZoneInfo::ExtendTransitions() { if (!GetTransitionType(posix.dst_offset, true, posix.dst_abbr, &dst_ti)) return false; + if (AllYearDST(posix)) { // dst only + // The future specification should match the last transition, and + // that means that handling the future will fall out naturally. + 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. @@ -481,9 +502,9 @@ bool TimeZoneInfo::Load(ZoneInfoSource* zip) { // encoded zoneinfo. The ttisstd/ttisgmt indicators only apply when // interpreting a POSIX spec that does not include start/end rules, and // that isn't the case here (see "zic -p"). - bp += (8 + 4) * hdr.leapcnt; // leap-time + TAI-UTC - bp += 1 * hdr.ttisstdcnt; // UTC/local indicators - bp += 1 * hdr.ttisutcnt; // standard/wall indicators + bp += (time_len + 4) * hdr.leapcnt; // leap-time + TAI-UTC + bp += 1 * hdr.ttisstdcnt; // UTC/local indicators + bp += 1 * hdr.ttisutcnt; // standard/wall indicators assert(bp == tbuf.data() + tbuf.size()); future_spec_.clear(); @@ -512,8 +533,8 @@ bool TimeZoneInfo::Load(ZoneInfoSource* zip) { // Trim redundant transitions. zic may have added these to work around // differences between the glibc and reference implementations (see - // zic.c:dontmerge) and the Qt library (see zic.c:WORK_AROUND_QTBUG_53071). - // For us, they just get in the way when we do future_spec_ extension. + // zic.c:dontmerge) or to avoid bugs in old readers. For us, they just + // get in the way when we do future_spec_ extension. while (hdr.timecnt > 1) { if (!EquivTransitions(transitions_[hdr.timecnt - 1].type_index, transitions_[hdr.timecnt - 2].type_index)) { diff --git a/absl/time/internal/cctz/src/time_zone_lookup.cc b/absl/time/internal/cctz/src/time_zone_lookup.cc index 898d04c1..f6983aeb 100644 --- a/absl/time/internal/cctz/src/time_zone_lookup.cc +++ b/absl/time/internal/cctz/src/time_zone_lookup.cc @@ -31,7 +31,7 @@ #if defined(__Fuchsia__) #include <fuchsia/intl/cpp/fidl.h> #include <lib/async-loop/cpp/loop.h> -#include <lib/sys/cpp/component_context.h> +#include <lib/fdio/directory.h> #include <zircon/types.h> #endif @@ -140,8 +140,9 @@ time_zone local_time_zone() { if (CFStringRef tz_name = CFTimeZoneGetName(tz_default)) { CFStringEncoding encoding = kCFStringEncodingUTF8; CFIndex length = CFStringGetLength(tz_name); - buffer.resize(CFStringGetMaximumSizeForEncoding(length, encoding) + 1); - if (CFStringGetCString(tz_name, &buffer[0], buffer.size(), encoding)) { + CFIndex max_size = CFStringGetMaximumSizeForEncoding(length, encoding) + 1; + buffer.resize(static_cast<size_t>(max_size)); + if (CFStringGetCString(tz_name, &buffer[0], max_size, encoding)) { zone = &buffer[0]; } } @@ -160,11 +161,11 @@ time_zone local_time_zone() { // would be set to null when the loop is destroyed, causing any other FIDL // code running on the same thread to crash. async::Loop loop(&kAsyncLoopConfigNeverAttachToThread); - std::unique_ptr<sys::ComponentContext> context = - sys::ComponentContext::Create(); fuchsia::intl::PropertyProviderHandle handle; - zx_status_t status = context->svc()->Connect(handle.NewRequest()); + zx_status_t status = fdio_service_connect_by_name( + fuchsia::intl::PropertyProvider::Name_, + handle.NewRequest().TakeChannel().release()); if (status != ZX_OK) { return; } 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 27a53c5c..ab461f04 100644 --- a/absl/time/internal/cctz/src/time_zone_lookup_test.cc +++ b/absl/time/internal/cctz/src/time_zone_lookup_test.cc @@ -489,6 +489,7 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Europe/Kaliningrad", "Europe/Kiev", "Europe/Kirov", + "Europe/Kyiv", "Europe/Lisbon", "Europe/Ljubljana", "Europe/London", @@ -528,6 +529,7 @@ const char* const kTimeZoneNames[] = {"Africa/Abidjan", "Europe/Zagreb", "Europe/Zaporozhye", "Europe/Zurich", + "Factory", "GB", "GB-Eire", "GMT", @@ -1188,11 +1190,12 @@ TEST(PrevTransition, AmericaNewYork) { TEST(NextTransition, Scan) { for (const char* const* np = kTimeZoneNames; *np != nullptr; ++np) { + SCOPED_TRACE(testing::Message() << "In " << *np); time_zone tz; + // EXPECT_TRUE(load_time_zone(*np, &tz)); if (!load_time_zone(*np, &tz)) { continue; // tolerate kTimeZoneNames/zoneinfo skew } - SCOPED_TRACE(testing::Message() << "In " << *np); auto tp = time_point<absl::time_internal::cctz::seconds>::min(); time_zone::civil_transition trans; diff --git a/absl/time/internal/cctz/src/zone_info_source.cc b/absl/time/internal/cctz/src/zone_info_source.cc index 5ab5a59e..1b162337 100644 --- a/absl/time/internal/cctz/src/zone_info_source.cc +++ b/absl/time/internal/cctz/src/zone_info_source.cc @@ -58,7 +58,8 @@ std::unique_ptr<absl::time_internal::cctz::ZoneInfoSource> DefaultFactory( // MinGW is GCC on Windows, so while it asserts __has_attribute(weak), the // Windows linker cannot handle that. Nor does the MinGW compiler know how to // pass "#pragma comment(linker, ...)" to the Windows linker. -#if (__has_attribute(weak) || defined(__GNUC__)) && !defined(__MINGW32__) +#if (__has_attribute(weak) || defined(__GNUC__)) && !defined(__MINGW32__) && \ + !defined(__CYGWIN__) ZoneInfoSourceFactory zone_info_source_factory __attribute__((weak)) = DefaultFactory; #elif defined(_MSC_VER) && !defined(__MINGW32__) && !defined(_LIBCPP_VERSION) @@ -66,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 a41c7b88..67e9c404 100644 --- a/absl/time/internal/cctz/testdata/README.zoneinfo +++ b/absl/time/internal/cctz/testdata/README.zoneinfo @@ -13,12 +13,7 @@ New versions can be generated using the following shell script. trap "rm -fr ${DESTDIR}" 0 2 15 ( cd ${DESTDIR} - if [ -n "${USE_GLOBAL_TZ}" ] - then - git clone -b global-tz https://github.com/JodaOrg/global-tz.git tz - else - git clone https://github.com/eggert/tz.git - fi + git clone https://github.com/eggert/tz.git make --directory=tz \ install DESTDIR=${DESTDIR} \ DATAFORM=vanguard \ @@ -26,6 +21,7 @@ New versions can be generated using the following shell script. REDO=posix_only \ LOCALTIME=Factory \ TZDATA_TEXT= \ + PACKRATDATA=backzone PACKRATLIST=zone.tab \ ZONETABLES=zone1970.tab tar --create --dereference --hard-dereference --file tzfile.tar \ --directory=tz tzfile.h diff --git a/absl/time/internal/cctz/testdata/version b/absl/time/internal/cctz/testdata/version index ca002de2..b74fa117 100644 --- a/absl/time/internal/cctz/testdata/version +++ b/absl/time/internal/cctz/testdata/version @@ -1 +1 @@ -2022a +2022g diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Accra b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Accra Binary files differindex 8906e88c..c39ae382 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Accra +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Accra diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Addis_Ababa b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Addis_Ababa Binary files differindex 5f4ebcb7..4e8951f5 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Addis_Ababa +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Addis_Ababa diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Asmara b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Asmara Binary files differindex 5f4ebcb7..194e9869 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Asmara +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Asmara diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Asmera b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Asmera Binary files differindex 5f4ebcb7..194e9869 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Asmera +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Asmera diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bamako b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bamako Binary files differindex 8906e88c..3cb875fa 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bamako +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bamako diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bangui b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bangui Binary files differindex 3d7a71ba..0021d2d7 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bangui +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bangui diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Banjul b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Banjul Binary files differindex 8906e88c..b2357443 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Banjul +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Banjul diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Blantyre b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Blantyre Binary files differindex 651e5cf6..d7bca1e6 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Blantyre +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Blantyre diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Brazzaville b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Brazzaville Binary files differindex 3d7a71ba..57a723b2 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Brazzaville +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Brazzaville diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bujumbura b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bujumbura Binary files differindex 651e5cf6..90b8679b 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bujumbura +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Bujumbura diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Conakry b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Conakry Binary files differindex 8906e88c..c22c328b 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Conakry +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Conakry diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Dakar b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Dakar Binary files differindex 8906e88c..1f04c586 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Dakar +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Dakar diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Dar_es_Salaam b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Dar_es_Salaam Binary files differindex 5f4ebcb7..b37c2b44 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Dar_es_Salaam +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Dar_es_Salaam diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Djibouti b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Djibouti Binary files differindex 5f4ebcb7..e9bbc7ad 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Djibouti +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Djibouti diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Douala b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Douala Binary files differindex 3d7a71ba..65001f60 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Douala +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Douala diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Freetown b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Freetown Binary files differindex 8906e88c..8431ed65 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Freetown +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Freetown diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Gaborone b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Gaborone Binary files differindex 651e5cf6..e4420989 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Gaborone +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Gaborone diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Harare b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Harare Binary files differindex 651e5cf6..c4a502cf 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Harare +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Harare diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kampala b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kampala Binary files differindex 5f4ebcb7..3021d844 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kampala +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kampala diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kigali b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kigali Binary files differindex 651e5cf6..b2eff570 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kigali +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kigali diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kinshasa b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kinshasa Binary files differindex 3d7a71ba..8d6f2a8c 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kinshasa +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Kinshasa diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Libreville b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Libreville Binary files differindex 3d7a71ba..1544cf5b 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Libreville +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Libreville diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lome b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lome Binary files differindex 8906e88c..8e2b7001 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lome +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lome diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Luanda b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Luanda Binary files differindex 3d7a71ba..226d87f0 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Luanda +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Luanda diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lubumbashi b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lubumbashi Binary files differindex 651e5cf6..14e1ee16 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lubumbashi +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lubumbashi diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lusaka b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lusaka Binary files differindex 651e5cf6..18fcb168 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lusaka +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Lusaka diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Malabo b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Malabo Binary files differindex 3d7a71ba..8a3f4e9a 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Malabo +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Malabo diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Maseru b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Maseru Binary files differindex bada0638..820d8521 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Maseru +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Maseru diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Mbabane b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Mbabane Binary files differindex bada0638..d57a53c4 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Mbabane +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Mbabane diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Mogadishu b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Mogadishu Binary files differindex 5f4ebcb7..25a59739 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Mogadishu +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Mogadishu diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Niamey b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Niamey Binary files differindex 3d7a71ba..bdf222af 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Niamey +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Niamey diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Nouakchott b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Nouakchott Binary files differindex 8906e88c..faa6f324 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Nouakchott +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Nouakchott diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Ouagadougou b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Ouagadougou Binary files differindex 8906e88c..f4e55aeb 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Ouagadougou +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Ouagadougou diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Porto-Novo b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Porto-Novo Binary files differindex 3d7a71ba..a869ec3f 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Porto-Novo +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Porto-Novo diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Timbuktu b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Timbuktu Binary files differindex 8906e88c..3cb875fa 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Africa/Timbuktu +++ b/absl/time/internal/cctz/testdata/zoneinfo/Africa/Timbuktu diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Anguilla b/absl/time/internal/cctz/testdata/zoneinfo/America/Anguilla Binary files differindex 47b4dc34..d0577350 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Anguilla +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Anguilla diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Antigua b/absl/time/internal/cctz/testdata/zoneinfo/America/Antigua Binary files differindex 47b4dc34..7ef2cc99 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Antigua +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Antigua diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Aruba b/absl/time/internal/cctz/testdata/zoneinfo/America/Aruba Binary files differindex 47b4dc34..6158ca50 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Aruba +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Aruba diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Atikokan b/absl/time/internal/cctz/testdata/zoneinfo/America/Atikokan Binary files differindex 9154643f..c8287152 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Atikokan +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Atikokan diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Bahia_Banderas b/absl/time/internal/cctz/testdata/zoneinfo/America/Bahia_Banderas Binary files differindex cbe22a76..48faea2e 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Bahia_Banderas +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Bahia_Banderas diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Blanc-Sablon b/absl/time/internal/cctz/testdata/zoneinfo/America/Blanc-Sablon Binary files differindex 47b4dc34..7096b69a 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Blanc-Sablon +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Blanc-Sablon diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Bogota b/absl/time/internal/cctz/testdata/zoneinfo/America/Bogota Binary files differindex 6cb53d4e..85b90333 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Bogota +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Bogota diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Cambridge_Bay b/absl/time/internal/cctz/testdata/zoneinfo/America/Cambridge_Bay Binary files differindex 0a222524..1092f4b6 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Cambridge_Bay +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Cambridge_Bay diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Cayman b/absl/time/internal/cctz/testdata/zoneinfo/America/Cayman Binary files differindex 9154643f..8be55156 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Cayman +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Cayman diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Chihuahua b/absl/time/internal/cctz/testdata/zoneinfo/America/Chihuahua Binary files differindex e1780a57..5e0a54f0 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Chihuahua +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Chihuahua diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Ciudad_Juarez b/absl/time/internal/cctz/testdata/zoneinfo/America/Ciudad_Juarez Binary files differnew file mode 100644 index 00000000..f636ee64 --- /dev/null +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Ciudad_Juarez diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Coral_Harbour b/absl/time/internal/cctz/testdata/zoneinfo/America/Coral_Harbour Binary files differindex 9154643f..c8287152 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Coral_Harbour +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Coral_Harbour diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Creston b/absl/time/internal/cctz/testdata/zoneinfo/America/Creston Binary files differindex c2bd2f94..9d69a0ab 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Creston +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Creston diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Curacao b/absl/time/internal/cctz/testdata/zoneinfo/America/Curacao Binary files differindex 47b4dc34..d6ddf7d8 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Curacao +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Curacao diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Dominica b/absl/time/internal/cctz/testdata/zoneinfo/America/Dominica Binary files differindex 47b4dc34..7c7cebfa 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Dominica +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Dominica diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Ensenada b/absl/time/internal/cctz/testdata/zoneinfo/America/Ensenada Binary files differindex 19ccd357..e8be26b1 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Ensenada +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Ensenada diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Godthab b/absl/time/internal/cctz/testdata/zoneinfo/America/Godthab Binary files differindex 4ddc99d8..79d7a454 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/Grenada b/absl/time/internal/cctz/testdata/zoneinfo/America/Grenada Binary files differindex 47b4dc34..a58e63a4 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Grenada +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Grenada diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Guadeloupe b/absl/time/internal/cctz/testdata/zoneinfo/America/Guadeloupe Binary files differindex 47b4dc34..71747383 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Guadeloupe +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Guadeloupe diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Hermosillo b/absl/time/internal/cctz/testdata/zoneinfo/America/Hermosillo Binary files differindex 8283239e..5c92e296 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Hermosillo +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Hermosillo diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Inuvik b/absl/time/internal/cctz/testdata/zoneinfo/America/Inuvik Binary files differindex af3107db..86639f6e 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Inuvik +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Inuvik diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Iqaluit b/absl/time/internal/cctz/testdata/zoneinfo/America/Iqaluit Binary files differindex eb2c99cc..95e055cb 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Iqaluit +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Iqaluit diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Kralendijk b/absl/time/internal/cctz/testdata/zoneinfo/America/Kralendijk Binary files differindex 47b4dc34..d6ddf7d8 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Kralendijk +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Kralendijk diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Lower_Princes b/absl/time/internal/cctz/testdata/zoneinfo/America/Lower_Princes Binary files differindex 47b4dc34..d6ddf7d8 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Lower_Princes +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Lower_Princes diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Marigot b/absl/time/internal/cctz/testdata/zoneinfo/America/Marigot Binary files differindex 47b4dc34..f4fe5903 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Marigot +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Marigot diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Matamoros b/absl/time/internal/cctz/testdata/zoneinfo/America/Matamoros Binary files differindex 722751b2..88cabcd1 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Matamoros +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Matamoros diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Mazatlan b/absl/time/internal/cctz/testdata/zoneinfo/America/Mazatlan Binary files differindex 4c819fab..97d4d36c 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Mazatlan +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Mazatlan diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Merida b/absl/time/internal/cctz/testdata/zoneinfo/America/Merida Binary files differindex d3b0ca12..e5de1131 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Merida +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Merida diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Mexico_City b/absl/time/internal/cctz/testdata/zoneinfo/America/Mexico_City Binary files differindex ffcf8bee..80a415c7 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Mexico_City +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Mexico_City diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Monterrey b/absl/time/internal/cctz/testdata/zoneinfo/America/Monterrey Binary files differindex dea9e3f5..a5822e2c 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Monterrey +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Monterrey diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Montserrat b/absl/time/internal/cctz/testdata/zoneinfo/America/Montserrat Binary files differindex 47b4dc34..41bf898b 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Montserrat +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Montserrat diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Nassau b/absl/time/internal/cctz/testdata/zoneinfo/America/Nassau Binary files differindex fe6be8ea..2ef2aa89 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Nassau +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Nassau diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Nipigon b/absl/time/internal/cctz/testdata/zoneinfo/America/Nipigon Binary files differindex b9f67a9f..fe6be8ea 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Nipigon +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Nipigon diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Nuuk b/absl/time/internal/cctz/testdata/zoneinfo/America/Nuuk Binary files differindex 4ddc99d8..79d7a454 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 da0909cb..2fc74e94 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/Pangnirtung b/absl/time/internal/cctz/testdata/zoneinfo/America/Pangnirtung Binary files differindex 5be6f9b0..95e055cb 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Pangnirtung +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Pangnirtung diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Port_of_Spain b/absl/time/internal/cctz/testdata/zoneinfo/America/Port_of_Spain Binary files differindex 47b4dc34..f4fe5903 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Port_of_Spain +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Port_of_Spain diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Punta_Arenas b/absl/time/internal/cctz/testdata/zoneinfo/America/Punta_Arenas Binary files differindex c0421040..aa839ea7 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Punta_Arenas +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Punta_Arenas diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Rainy_River b/absl/time/internal/cctz/testdata/zoneinfo/America/Rainy_River Binary files differindex d6ddda48..7e646d18 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Rainy_River +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Rainy_River diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Rankin_Inlet b/absl/time/internal/cctz/testdata/zoneinfo/America/Rankin_Inlet Binary files differindex 92e2ed2d..6d1d90de 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Rankin_Inlet +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Rankin_Inlet diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Resolute b/absl/time/internal/cctz/testdata/zoneinfo/America/Resolute Binary files differindex a84d1dfd..97eb8a9c 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Resolute +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Resolute diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Santa_Isabel b/absl/time/internal/cctz/testdata/zoneinfo/America/Santa_Isabel Binary files differindex 19ccd357..e8be26b1 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Santa_Isabel +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Santa_Isabel diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Santiago b/absl/time/internal/cctz/testdata/zoneinfo/America/Santiago Binary files differindex cde8dbbf..d3fc9b83 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Santiago +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Santiago diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/St_Barthelemy b/absl/time/internal/cctz/testdata/zoneinfo/America/St_Barthelemy Binary files differindex 47b4dc34..f4fe5903 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/St_Barthelemy +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/St_Barthelemy diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/St_Kitts b/absl/time/internal/cctz/testdata/zoneinfo/America/St_Kitts Binary files differindex 47b4dc34..6170b6c0 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/St_Kitts +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/St_Kitts diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/St_Lucia b/absl/time/internal/cctz/testdata/zoneinfo/America/St_Lucia Binary files differindex 47b4dc34..e265baff 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/St_Lucia +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/St_Lucia diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/St_Thomas b/absl/time/internal/cctz/testdata/zoneinfo/America/St_Thomas Binary files differindex 47b4dc34..0e62d30b 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/St_Thomas +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/St_Thomas diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/St_Vincent b/absl/time/internal/cctz/testdata/zoneinfo/America/St_Vincent Binary files differindex 47b4dc34..64cbf902 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/St_Vincent +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/St_Vincent diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Thunder_Bay b/absl/time/internal/cctz/testdata/zoneinfo/America/Thunder_Bay Binary files differindex fcb03280..fe6be8ea 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Thunder_Bay +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Thunder_Bay diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Tijuana b/absl/time/internal/cctz/testdata/zoneinfo/America/Tijuana Binary files differindex 19ccd357..e8be26b1 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Tijuana +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Tijuana diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Tortola b/absl/time/internal/cctz/testdata/zoneinfo/America/Tortola Binary files differindex 47b4dc34..a0a5d602 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Tortola +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Tortola diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Virgin b/absl/time/internal/cctz/testdata/zoneinfo/America/Virgin Binary files differindex 47b4dc34..0e62d30b 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Virgin +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Virgin diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Whitehorse b/absl/time/internal/cctz/testdata/zoneinfo/America/Whitehorse Binary files differindex 878b6a92..40baa9ab 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Whitehorse +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Whitehorse diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Yellowknife b/absl/time/internal/cctz/testdata/zoneinfo/America/Yellowknife Binary files differindex c779cef9..ff3eb878 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/DumontDUrville b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/DumontDUrville Binary files differindex 5d8fc3a1..c0cfc85a 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/DumontDUrville +++ b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/DumontDUrville diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/McMurdo b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/McMurdo Binary files differindex afb39293..ea1f8f8a 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/McMurdo +++ b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/McMurdo diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/South_Pole b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/South_Pole Binary files differindex afb39293..ea1f8f8a 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/South_Pole +++ b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/South_Pole diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Syowa b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Syowa Binary files differindex 01c47ccb..97d80d75 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Syowa +++ b/absl/time/internal/cctz/testdata/zoneinfo/Antarctica/Syowa diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Aden b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Aden Binary files differindex 01c47ccb..ac571479 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Aden +++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Aden diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Amman b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Amman Binary files differindex d97d308d..a3f9dff5 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Amman +++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Amman diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Bahrain b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Bahrain Binary files differindex 7409d749..33f7a207 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Bahrain +++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Bahrain diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Damascus b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Damascus Binary files differindex 168ef9ba..bd1624de 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Damascus +++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Damascus diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Gaza b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Gaza Binary files differindex effc4df5..bed968e7 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 aa52bd26..3ce1bac6 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/Ho_Chi_Minh b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Ho_Chi_Minh Binary files differindex 7ca99725..de53596d 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Ho_Chi_Minh +++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Ho_Chi_Minh diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Kuala_Lumpur b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Kuala_Lumpur Binary files differindex e93dd514..b396deca 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Kuala_Lumpur +++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Kuala_Lumpur diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Kuwait b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Kuwait Binary files differindex 01c47ccb..5c7f106a 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Kuwait +++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Kuwait diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Muscat b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Muscat Binary files differindex 58d75bc2..cce5e193 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Muscat +++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Muscat diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Phnom_Penh b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Phnom_Penh Binary files differindex ed687d29..c49800e7 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Phnom_Penh +++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Phnom_Penh diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Saigon b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Saigon Binary files differindex 7ca99725..de53596d 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Saigon +++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Saigon diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Singapore b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Singapore Binary files differindex 350d77e2..dbbdea3c 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Singapore +++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Singapore diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Tehran b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Tehran Binary files differindex f1555f00..824acb04 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Tehran +++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Tehran diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Vientiane b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Vientiane Binary files differindex ed687d29..659e511d 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Asia/Vientiane +++ b/absl/time/internal/cctz/testdata/zoneinfo/Asia/Vientiane diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Atlantic/St_Helena b/absl/time/internal/cctz/testdata/zoneinfo/Atlantic/St_Helena Binary files differindex 8906e88c..6f750680 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Atlantic/St_Helena +++ b/absl/time/internal/cctz/testdata/zoneinfo/Atlantic/St_Helena diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Canada/Yukon b/absl/time/internal/cctz/testdata/zoneinfo/Canada/Yukon Binary files differindex 878b6a92..40baa9ab 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Canada/Yukon +++ b/absl/time/internal/cctz/testdata/zoneinfo/Canada/Yukon diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Chile/Continental b/absl/time/internal/cctz/testdata/zoneinfo/Chile/Continental Binary files differindex cde8dbbf..d3fc9b83 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Chile/Continental +++ b/absl/time/internal/cctz/testdata/zoneinfo/Chile/Continental diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Chile/EasterIsland b/absl/time/internal/cctz/testdata/zoneinfo/Chile/EasterIsland Binary files differindex d29bcd68..54dff005 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Chile/EasterIsland +++ b/absl/time/internal/cctz/testdata/zoneinfo/Chile/EasterIsland diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Eire b/absl/time/internal/cctz/testdata/zoneinfo/Eire Binary files differindex 4a45ea8f..17d2b158 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Eire +++ b/absl/time/internal/cctz/testdata/zoneinfo/Eire diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Dublin b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Dublin Binary files differindex 4a45ea8f..17d2b158 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Dublin +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Dublin diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Guernsey b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Guernsey Binary files differindex 323cd381..d40bcaa3 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 323cd381..b0a37e7e 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 323cd381..9a10a2ec 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/Kyiv b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kyiv Binary files differnew file mode 100644 index 00000000..4e026859 --- /dev/null +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Kyiv diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Ljubljana b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Ljubljana Binary files differindex a1bf9281..fdb9e86d 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Ljubljana +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Ljubljana diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Sarajevo b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Sarajevo Binary files differindex a1bf9281..53db0568 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Sarajevo +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Sarajevo diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Simferopol b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Simferopol Binary files differindex 40d23c02..298b8326 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Simferopol +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Simferopol diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Skopje b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Skopje Binary files differindex a1bf9281..036361cf 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Skopje +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Skopje diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Uzhgorod b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Uzhgorod Binary files differindex d4c35914..4e026859 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/Vaduz b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Vaduz Binary files differindex 388df296..28465d83 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Vaduz +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Vaduz diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Zagreb b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Zagreb Binary files differindex a1bf9281..8e13ede8 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Zagreb +++ b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Zagreb diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Europe/Zaporozhye b/absl/time/internal/cctz/testdata/zoneinfo/Europe/Zaporozhye Binary files differindex 71819a5a..4e026859 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/Indian/Antananarivo b/absl/time/internal/cctz/testdata/zoneinfo/Indian/Antananarivo Binary files differindex 5f4ebcb7..0bf86f02 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Indian/Antananarivo +++ b/absl/time/internal/cctz/testdata/zoneinfo/Indian/Antananarivo diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Indian/Comoro b/absl/time/internal/cctz/testdata/zoneinfo/Indian/Comoro Binary files differindex 5f4ebcb7..640b3e88 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Indian/Comoro +++ b/absl/time/internal/cctz/testdata/zoneinfo/Indian/Comoro diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Indian/Mayotte b/absl/time/internal/cctz/testdata/zoneinfo/Indian/Mayotte Binary files differindex 5f4ebcb7..7a009c31 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Indian/Mayotte +++ b/absl/time/internal/cctz/testdata/zoneinfo/Indian/Mayotte diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Iran b/absl/time/internal/cctz/testdata/zoneinfo/Iran Binary files differindex f1555f00..824acb04 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Iran +++ b/absl/time/internal/cctz/testdata/zoneinfo/Iran diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaNorte b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaNorte Binary files differindex 19ccd357..e8be26b1 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaNorte +++ b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaNorte diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaSur b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaSur Binary files differindex 4c819fab..97d4d36c 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaSur +++ b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaSur diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/General b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/General Binary files differindex ffcf8bee..80a415c7 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/General +++ b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/General diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Easter b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Easter Binary files differindex d29bcd68..54dff005 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Easter +++ b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Easter diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Fiji b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Fiji Binary files differindex 8b2dd52b..610b850b 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Fiji +++ b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Fiji diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Midway b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Midway Binary files differindex 001289ce..b25364c5 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Midway +++ b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Midway diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Saipan b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Saipan Binary files differindex bf9a2d95..9539353b 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Saipan +++ b/absl/time/internal/cctz/testdata/zoneinfo/Pacific/Saipan diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Singapore b/absl/time/internal/cctz/testdata/zoneinfo/Singapore Binary files differindex 350d77e2..dbbdea3c 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Singapore +++ b/absl/time/internal/cctz/testdata/zoneinfo/Singapore diff --git a/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab b/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab index a4ff61a4..911af5e8 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab +++ b/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab @@ -3,13 +3,13 @@ # This file is in the public domain, so clarified as of # 2009-05-17 by Arthur David Olson. # -# From Paul Eggert (2015-05-02): +# From Paul Eggert (2022-11-18): # 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 N976 (2018-11-06). See: Updates on ISO 3166-1 +# 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. @@ -238,7 +238,7 @@ SY Syria SZ Eswatini (Swaziland) TC Turks & Caicos Is TD Chad -TF French Southern & Antarctic Lands +TF French Southern Territories TG Togo TH Thailand TJ Tajikistan diff --git a/absl/time/internal/cctz/testdata/zoneinfo/localtime b/absl/time/internal/cctz/testdata/zoneinfo/localtime Binary files differdeleted file mode 100644 index afeeb88d..00000000 --- a/absl/time/internal/cctz/testdata/zoneinfo/localtime +++ /dev/null diff --git a/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab b/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab index c614be81..a9b36d36 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab +++ b/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab @@ -34,7 +34,7 @@ #country- #codes coordinates TZ comments AD +4230+00131 Europe/Andorra -AE,OM +2518+05518 Asia/Dubai +AE,OM,RE,SC,TF +2518+05518 Asia/Dubai UAE, Oman, Réunion, Seychelles, Crozet, Scattered Is AF +3431+06912 Asia/Kabul AL +4120+01950 Europe/Tirane AM +4011+04430 Asia/Yerevan @@ -44,7 +44,6 @@ 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 -2447-06525 America/Argentina/Salta Salta (SA, LP, NQ, RN) @@ -74,10 +73,9 @@ AU -3143+12852 Australia/Eucla Western Australia (Eucla) AZ +4023+04951 Asia/Baku BB +1306-05937 America/Barbados BD +2343+09025 Asia/Dhaka -BE +5050+00420 Europe/Brussels +BE,LU,NL +5050+00420 Europe/Brussels BG +4241+02319 Europe/Sofia BM +3217-06446 Atlantic/Bermuda -BN +0456+11455 Asia/Brunei BO -1630-06809 America/La_Paz BR -0351-03225 America/Noronha Atlantic islands BR -0127-04829 America/Belem Pará (east); Amapá @@ -104,12 +102,8 @@ CA +4612-05957 America/Glace_Bay Atlantic - NS (Cape Breton) CA +4606-06447 America/Moncton Atlantic - New Brunswick CA +5320-06025 America/Goose_Bay Atlantic - Labrador (most areas) CA,BS +4339-07923 America/Toronto Eastern - ON, QC (most areas), Bahamas -CA +4901-08816 America/Nipigon Eastern - ON, QC (no DST 1967-73) -CA +4823-08915 America/Thunder_Bay Eastern - ON (Thunder Bay) -CA +6344-06828 America/Iqaluit Eastern - NU (most east areas) -CA +6608-06544 America/Pangnirtung Eastern - NU (Pangnirtung) +CA +6344-06828 America/Iqaluit Eastern - NU (most areas) CA +4953-09709 America/Winnipeg Central - ON (west); Manitoba -CA +4843-09434 America/Rainy_River Central - ON (Rainy R, Ft Frances) CA +744144-0944945 America/Resolute Central - NU (Resolute) CA +624900-0920459 America/Rankin_Inlet Central - NU (central) CA +5024-10439 America/Regina CST - SK (most areas) @@ -118,30 +112,27 @@ CA +5333-11328 America/Edmonton Mountain - AB; BC (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 +5946-12014 America/Dawson_Creek MST - BC (Dawson Cr, Ft St John) +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) -CC -1210+09655 Indian/Cocos CH,DE,LI +4723+00832 Europe/Zurich Swiss time -CI,BF,GH,GM,GN,ML,MR,SH,SL,SN,TG +0519-00402 Africa/Abidjan +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 -5309-07055 America/Punta_Arenas Region of Magallanes CL -2709-10926 Pacific/Easter Easter Island CN +3114+12128 Asia/Shanghai Beijing Time -CN +4348+08735 Asia/Urumqi Xinjiang Time +CN,AQ +4348+08735 Asia/Urumqi Xinjiang Time, Vostok CO +0436-07405 America/Bogota CR +0956-08405 America/Costa_Rica CU +2308-08222 America/Havana CV +1455-02331 Atlantic/Cape_Verde -CX -1025+10543 Indian/Christmas CY +3510+03322 Asia/Nicosia Cyprus (most areas) CY +3507+03357 Asia/Famagusta Northern Cyprus CZ,SK +5005+01426 Europe/Prague -DE +5230+01322 Europe/Berlin Germany (most areas) -DK +5540+01235 Europe/Copenhagen +DE,DK,NO,SE,SJ +5230+01322 Europe/Berlin Germany (most areas), Scandinavia DO +1828-06954 America/Santo_Domingo DZ +3647+00303 Africa/Algiers EC -0210-07950 America/Guayaquil Ecuador (mainland) @@ -155,11 +146,9 @@ ES +2806-01524 Atlantic/Canary Canary Islands FI,AX +6010+02458 Europe/Helsinki FJ -1808+17825 Pacific/Fiji FK -5142-05751 Atlantic/Stanley -FM +0725+15147 Pacific/Chuuk Chuuk/Truk, Yap -FM +0658+15813 Pacific/Pohnpei Pohnpei/Ponape FM +0519+16259 Pacific/Kosrae Kosrae FO +6201-00646 Atlantic/Faroe -FR +4852+00220 Europe/Paris +FR,MC +4852+00220 Europe/Paris GB,GG,IM,JE +513030-0000731 Europe/London GE +4143+04449 Asia/Tbilisi GF +0456-05220 America/Cayenne @@ -188,14 +177,13 @@ IN +2232+08822 Asia/Kolkata IO -0720+07225 Indian/Chagos IQ +3321+04425 Asia/Baghdad IR +3540+05126 Asia/Tehran -IS +6409-02151 Atlantic/Reykjavik IT,SM,VA +4154+01229 Europe/Rome JM +175805-0764736 America/Jamaica 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 +0125+17300 Pacific/Tarawa Gilbert Islands +KI,MH,TV,UM,WF +0125+17300 Pacific/Tarawa Gilberts, Marshalls, Tuvalu, Wallis & Futuna, Wake KI -0247-17143 Pacific/Kanton Phoenix Islands KI +0152-15720 Pacific/Kiritimati Line Islands KP +3901+12545 Asia/Pyongyang @@ -211,15 +199,12 @@ LB +3353+03530 Asia/Beirut LK +0656+07951 Asia/Colombo LR +0618-01047 Africa/Monrovia LT +5441+02519 Europe/Vilnius -LU +4936+00609 Europe/Luxembourg LV +5657+02406 Europe/Riga LY +3254+01311 Africa/Tripoli MA +3339-00735 Africa/Casablanca -MC +4342+00723 Europe/Monaco MD +4700+02850 Europe/Chisinau -MH +0709+17112 Pacific/Majuro Marshall Islands (most areas) MH +0905+16720 Pacific/Kwajalein Kwajalein -MM +1647+09610 Asia/Yangon +MM,CC +1647+09610 Asia/Yangon MN +4755+10653 Asia/Ulaanbaatar Mongolia (most areas) MN +4801+09139 Asia/Hovd Bayan-Ölgii, Govi-Altai, Hovd, Uvs, Zavkhan MN +4804+11430 Asia/Choibalsan Dornod, Sükhbaatar @@ -227,28 +212,26 @@ MO +221150+1133230 Asia/Macau MQ +1436-06105 America/Martinique MT +3554+01431 Europe/Malta MU -2010+05730 Indian/Mauritius -MV +0410+07330 Indian/Maldives -MX +1924-09909 America/Mexico_City Central Time -MX +2105-08646 America/Cancun Eastern Standard Time - Quintana Roo -MX +2058-08937 America/Merida Central Time - Campeche, Yucatán -MX +2540-10019 America/Monterrey Central Time - Durango; Coahuila, Nuevo León, Tamaulipas (most areas) -MX +2550-09730 America/Matamoros Central Time US - Coahuila, Nuevo León, Tamaulipas (US border) -MX +2313-10625 America/Mazatlan Mountain Time - Baja California Sur, Nayarit, Sinaloa -MX +2838-10605 America/Chihuahua Mountain Time - Chihuahua (most areas) -MX +2934-10425 America/Ojinaga Mountain Time US - Chihuahua (US border) -MX +2904-11058 America/Hermosillo Mountain Standard Time - Sonora -MX +3232-11701 America/Tijuana Pacific Time US - Baja California -MX +2048-10515 America/Bahia_Banderas Central Time - BahÃa de Banderas -MY +0310+10142 Asia/Kuala_Lumpur Malaysia (peninsula) -MY +0133+11020 Asia/Kuching Sabah, Sarawak +MV,TF +0410+07330 Indian/Maldives 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 +MX +2540-10019 America/Monterrey Durango; Coahuila, Nuevo León, Tamaulipas (most areas) +MX +2550-09730 America/Matamoros Coahuila, Nuevo León, Tamaulipas (US border) +MX +2838-10605 America/Chihuahua Chihuahua (most areas) +MX +3144-10629 America/Ciudad_Juarez Chihuahua (US border - west) +MX +2934-10425 America/Ojinaga Chihuahua (US border - east) +MX +2313-10625 America/Mazatlan Baja California Sur, Nayarit (most areas), Sinaloa +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 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 NF -2903+16758 Pacific/Norfolk NG,AO,BJ,CD,CF,CG,CM,GA,GQ,NE +0627+00324 Africa/Lagos West Africa Time NI +1209-08617 America/Managua -NL +5222+00454 Europe/Amsterdam -NO,SJ +5955+01045 Europe/Oslo NP +2743+08519 Asia/Kathmandu NR -0031+16655 Pacific/Nauru NU -1901-16955 Pacific/Niue @@ -259,7 +242,7 @@ PE -1203-07703 America/Lima PF -1732-14934 Pacific/Tahiti Society Islands PF -0900-13930 Pacific/Marquesas Marquesas Islands PF -2308-13457 Pacific/Gambier Gambier Islands -PG,AQ -0930+14710 Pacific/Port_Moresby Papua New Guinea (most areas), Dumont d'Urville +PG,AQ,FM -0930+14710 Pacific/Port_Moresby Papua New Guinea (most areas), Chuuk, Yap, Dumont d'Urville PG -0613+15534 Pacific/Bougainville Bougainville PH +1435+12100 Asia/Manila PK +2452+06703 Asia/Karachi @@ -275,7 +258,6 @@ PT +3744-02540 Atlantic/Azores Azores PW +0720+13429 Pacific/Palau PY -2516-05740 America/Asuncion QA,BH +2517+05132 Asia/Qatar -RE,TF -2052+05528 Indian/Reunion Réunion, Crozet, Scattered Islands RO +4426+02606 Europe/Bucharest RS,BA,HR,ME,MK,SI +4450+02030 Europe/Belgrade RU +5443+02030 Europe/Kaliningrad MSK-01 - Kaliningrad @@ -307,10 +289,8 @@ RU +6728+15343 Asia/Srednekolymsk MSK+08 - Sakha (E); North 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 -0932+16012 Pacific/Guadalcanal -SC -0440+05528 Indian/Mahe +SB,FM -0932+16012 Pacific/Guadalcanal Solomons, Pohnpei SD +1536+03232 Africa/Khartoum -SE +5920+01803 Europe/Stockholm SG,MY +0117+10351 Asia/Singapore Singapore, peninsular Malaysia SR +0550-05510 America/Paramaribo SS +0451+03137 Africa/Juba @@ -319,8 +299,7 @@ SV +1342-08912 America/El_Salvador SY +3330+03618 Asia/Damascus TC +2128-07108 America/Grand_Turk TD +1207+01503 Africa/Ndjamena -TF -492110+0701303 Indian/Kerguelen Kerguelen, St Paul Island, Amsterdam Island -TH,KH,LA,VN +1345+10031 Asia/Bangkok Indochina (most areas) +TH,CX,KH,LA,VN +1345+10031 Asia/Bangkok Indochina (most areas) TJ +3835+06848 Asia/Dushanbe TK -0922-17114 Pacific/Fakaofo TL -0833+12535 Asia/Dili @@ -328,12 +307,8 @@ TM +3757+05823 Asia/Ashgabat TN +3648+01011 Africa/Tunis TO -210800-1751200 Pacific/Tongatapu TR +4101+02858 Europe/Istanbul -TV -0831+17913 Pacific/Funafuti TW +2503+12130 Asia/Taipei -UA +5026+03031 Europe/Kiev Ukraine (most areas) -UA +4837+02218 Europe/Uzhgorod Transcarpathia -UA +4750+03510 Europe/Zaporozhye Zaporozhye and east Lugansk -UM +1917+16637 Pacific/Wake Wake Island +UA +5026+03031 Europe/Kyiv Ukraine (most areas) 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) @@ -369,6 +344,29 @@ UZ +4120+06918 Asia/Tashkent Uzbekistan (east) VE +1030-06656 America/Caracas VN +1045+10640 Asia/Ho_Chi_Minh Vietnam (south) VU -1740+16825 Pacific/Efate -WF -1318-17610 Pacific/Wallis WS -1350-17144 Pacific/Apia ZA,LS,SZ -2615+02800 Africa/Johannesburg +# +# The next section contains experimental tab-separated comments for +# use by user agents like tzselect that identify continents and oceans. +# +# For example, the comment "#@AQ<tab>Antarctica/" means the country code +# AQ is in the continent Antarctica regardless of the Zone name, +# so Pacific/Auckland should be listed under Antarctica as well as +# under the Pacific because its line's country codes include AQ. +# +# If more than one country code is affected each is listed separated +# by commas, e.g., #@IS,SH<tab>Atlantic/". If a country code is in +# more than one continent or ocean, each is listed separated by +# commas, e.g., the second column of "#@CY,TR<tab>Asia/,Europe/". +# +# These experimental comments are present only for country codes where +# the continent or ocean is not already obvious from the Zone name. +# For example, there is no such comment for RU since it already +# corresponds to Zone names starting with both "Europe/" and "Asia/". +# +#@AQ Antarctica/ +#@IS,SH Atlantic/ +#@CY,TR Asia/,Europe/ +#@SJ Arctic/ +#@CC,CX,KM,MG,YT Indian/ diff --git a/absl/time/internal/test_util.cc b/absl/time/internal/test_util.cc index 454b33a1..3e2452e9 100644 --- a/absl/time/internal/test_util.cc +++ b/absl/time/internal/test_util.cc @@ -14,16 +14,8 @@ #include "absl/time/internal/test_util.h" -#include <algorithm> -#include <cstddef> -#include <cstring> -#include <memory> - #include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" -#include "absl/time/internal/cctz/include/cctz/zone_info_source.h" - -namespace cctz = absl::time_internal::cctz; namespace absl { ABSL_NAMESPACE_BEGIN @@ -38,94 +30,3 @@ TimeZone LoadTimeZone(const std::string& name) { } // namespace time_internal ABSL_NAMESPACE_END } // namespace absl - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace time_internal { -namespace cctz_extension { -namespace { - -// Embed the zoneinfo data for time zones used during tests and benchmarks. -// The data was generated using "xxd -i zoneinfo-file". There is no need -// to update the data as long as the tests do not depend on recent changes -// (and the past rules remain the same). -#include "absl/time/internal/zoneinfo.inc" - -const struct ZoneInfo { - const char* name; - const char* data; - std::size_t length; -} kZoneInfo[] = { - // The three real time zones used by :time_test and :time_benchmark. - {"America/Los_Angeles", // - reinterpret_cast<char*>(America_Los_Angeles), America_Los_Angeles_len}, - {"America/New_York", // - reinterpret_cast<char*>(America_New_York), America_New_York_len}, - {"Australia/Sydney", // - reinterpret_cast<char*>(Australia_Sydney), Australia_Sydney_len}, - - // Other zones named in tests but which should fail to load. - {"Invalid/TimeZone", nullptr, 0}, - {"", nullptr, 0}, - - // Allows use of the local time zone from a system-specific location. -#ifdef _MSC_VER - {"localtime", // - reinterpret_cast<char*>(America_Los_Angeles), America_Los_Angeles_len}, -#else - {"/etc/localtime", // - reinterpret_cast<char*>(America_Los_Angeles), America_Los_Angeles_len}, -#endif -}; - -class TestZoneInfoSource : public cctz::ZoneInfoSource { - public: - TestZoneInfoSource(const char* data, std::size_t size) - : data_(data), end_(data + size) {} - - std::size_t Read(void* ptr, std::size_t size) override { - const std::size_t len = std::min<std::size_t>(size, end_ - data_); - memcpy(ptr, data_, len); - data_ += len; - return len; - } - - int Skip(std::size_t offset) override { - data_ += std::min<std::size_t>(offset, end_ - data_); - return 0; - } - - private: - const char* data_; - const char* const end_; -}; - -std::unique_ptr<cctz::ZoneInfoSource> TestFactory( - const std::string& name, - const std::function<std::unique_ptr<cctz::ZoneInfoSource>( - const std::string& name)>& /*fallback_factory*/) { - for (const ZoneInfo& zoneinfo : kZoneInfo) { - if (name == zoneinfo.name) { - if (zoneinfo.data == nullptr) return nullptr; - return std::unique_ptr<cctz::ZoneInfoSource>( - new TestZoneInfoSource(zoneinfo.data, zoneinfo.length)); - } - } - - // The embedded zoneinfo data does not include the zone, so fallback to - // built-in UTC. The tests have been crafted so that this should only - // happen when testing absl::LocalTimeZone() with an unconstrained ${TZ}. - return nullptr; -} - -} // namespace - -#if !defined(__MINGW32__) -// MinGW does not support the weak symbol extension mechanism. -ZoneInfoSourceFactory zone_info_source_factory = TestFactory; -#endif - -} // namespace cctz_extension -} // namespace time_internal -ABSL_NAMESPACE_END -} // namespace absl diff --git a/absl/time/internal/zoneinfo.inc b/absl/time/internal/zoneinfo.inc deleted file mode 100644 index 7d8b3ff2..00000000 --- a/absl/time/internal/zoneinfo.inc +++ /dev/null @@ -1,724 +0,0 @@ -unsigned char America_Los_Angeles[] = { - 0x54, 0x5a, 0x69, 0x66, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, - 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xba, - 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x14, 0x80, 0x00, 0x00, 0x00, - 0x9e, 0xa6, 0x48, 0xa0, 0x9f, 0xbb, 0x15, 0x90, 0xa0, 0x86, 0x2a, 0xa0, - 0xa1, 0x9a, 0xf7, 0x90, 0xcb, 0x89, 0x1a, 0xa0, 0xd2, 0x23, 0xf4, 0x70, - 0xd2, 0x61, 0x26, 0x10, 0xd6, 0xfe, 0x74, 0x5c, 0xd8, 0x80, 0xad, 0x90, - 0xda, 0xfe, 0xc3, 0x90, 0xdb, 0xc0, 0x90, 0x10, 0xdc, 0xde, 0xa5, 0x90, - 0xdd, 0xa9, 0xac, 0x90, 0xde, 0xbe, 0x87, 0x90, 0xdf, 0x89, 0x8e, 0x90, - 0xe0, 0x9e, 0x69, 0x90, 0xe1, 0x69, 0x70, 0x90, 0xe2, 0x7e, 0x4b, 0x90, - 0xe3, 0x49, 0x52, 0x90, 0xe4, 0x5e, 0x2d, 0x90, 0xe5, 0x29, 0x34, 0x90, - 0xe6, 0x47, 0x4a, 0x10, 0xe7, 0x12, 0x51, 0x10, 0xe8, 0x27, 0x2c, 0x10, - 0xe8, 0xf2, 0x33, 0x10, 0xea, 0x07, 0x0e, 0x10, 0xea, 0xd2, 0x15, 0x10, - 0xeb, 0xe6, 0xf0, 0x10, 0xec, 0xb1, 0xf7, 0x10, 0xed, 0xc6, 0xd2, 0x10, - 0xee, 0x91, 0xd9, 0x10, 0xef, 0xaf, 0xee, 0x90, 0xf0, 0x71, 0xbb, 0x10, - 0xf1, 0x8f, 0xd0, 0x90, 0xf2, 0x7f, 0xc1, 0x90, 0xf3, 0x6f, 0xb2, 0x90, - 0xf4, 0x5f, 0xa3, 0x90, 0xf5, 0x4f, 0x94, 0x90, 0xf6, 0x3f, 0x85, 0x90, - 0xf7, 0x2f, 0x76, 0x90, 0xf8, 0x28, 0xa2, 0x10, 0xf9, 0x0f, 0x58, 0x90, - 0xfa, 0x08, 0x84, 0x10, 0xfa, 0xf8, 0x83, 0x20, 0xfb, 0xe8, 0x66, 0x10, - 0xfc, 0xd8, 0x65, 0x20, 0xfd, 0xc8, 0x48, 0x10, 0xfe, 0xb8, 0x47, 0x20, - 0xff, 0xa8, 0x2a, 0x10, 0x00, 0x98, 0x29, 0x20, 0x01, 0x88, 0x0c, 0x10, - 0x02, 0x78, 0x0b, 0x20, 0x03, 0x71, 0x28, 0x90, 0x04, 0x61, 0x27, 0xa0, - 0x05, 0x51, 0x0a, 0x90, 0x06, 0x41, 0x09, 0xa0, 0x07, 0x30, 0xec, 0x90, - 0x07, 0x8d, 0x43, 0xa0, 0x09, 0x10, 0xce, 0x90, 0x09, 0xad, 0xbf, 0x20, - 0x0a, 0xf0, 0xb0, 0x90, 0x0b, 0xe0, 0xaf, 0xa0, 0x0c, 0xd9, 0xcd, 0x10, - 0x0d, 0xc0, 0x91, 0xa0, 0x0e, 0xb9, 0xaf, 0x10, 0x0f, 0xa9, 0xae, 0x20, - 0x10, 0x99, 0x91, 0x10, 0x11, 0x89, 0x90, 0x20, 0x12, 0x79, 0x73, 0x10, - 0x13, 0x69, 0x72, 0x20, 0x14, 0x59, 0x55, 0x10, 0x15, 0x49, 0x54, 0x20, - 0x16, 0x39, 0x37, 0x10, 0x17, 0x29, 0x36, 0x20, 0x18, 0x22, 0x53, 0x90, - 0x19, 0x09, 0x18, 0x20, 0x1a, 0x02, 0x35, 0x90, 0x1a, 0xf2, 0x34, 0xa0, - 0x1b, 0xe2, 0x17, 0x90, 0x1c, 0xd2, 0x16, 0xa0, 0x1d, 0xc1, 0xf9, 0x90, - 0x1e, 0xb1, 0xf8, 0xa0, 0x1f, 0xa1, 0xdb, 0x90, 0x20, 0x76, 0x2b, 0x20, - 0x21, 0x81, 0xbd, 0x90, 0x22, 0x56, 0x0d, 0x20, 0x23, 0x6a, 0xda, 0x10, - 0x24, 0x35, 0xef, 0x20, 0x25, 0x4a, 0xbc, 0x10, 0x26, 0x15, 0xd1, 0x20, - 0x27, 0x2a, 0x9e, 0x10, 0x27, 0xfe, 0xed, 0xa0, 0x29, 0x0a, 0x80, 0x10, - 0x29, 0xde, 0xcf, 0xa0, 0x2a, 0xea, 0x62, 0x10, 0x2b, 0xbe, 0xb1, 0xa0, - 0x2c, 0xd3, 0x7e, 0x90, 0x2d, 0x9e, 0x93, 0xa0, 0x2e, 0xb3, 0x60, 0x90, - 0x2f, 0x7e, 0x75, 0xa0, 0x30, 0x93, 0x42, 0x90, 0x31, 0x67, 0x92, 0x20, - 0x32, 0x73, 0x24, 0x90, 0x33, 0x47, 0x74, 0x20, 0x34, 0x53, 0x06, 0x90, - 0x35, 0x27, 0x56, 0x20, 0x36, 0x32, 0xe8, 0x90, 0x37, 0x07, 0x38, 0x20, - 0x38, 0x1c, 0x05, 0x10, 0x38, 0xe7, 0x1a, 0x20, 0x39, 0xfb, 0xe7, 0x10, - 0x3a, 0xc6, 0xfc, 0x20, 0x3b, 0xdb, 0xc9, 0x10, 0x3c, 0xb0, 0x18, 0xa0, - 0x3d, 0xbb, 0xab, 0x10, 0x3e, 0x8f, 0xfa, 0xa0, 0x3f, 0x9b, 0x8d, 0x10, - 0x40, 0x6f, 0xdc, 0xa0, 0x41, 0x84, 0xa9, 0x90, 0x42, 0x4f, 0xbe, 0xa0, - 0x43, 0x64, 0x8b, 0x90, 0x44, 0x2f, 0xa0, 0xa0, 0x45, 0x44, 0x6d, 0x90, - 0x45, 0xf3, 0xd3, 0x20, 0x47, 0x2d, 0x8a, 0x10, 0x47, 0xd3, 0xb5, 0x20, - 0x49, 0x0d, 0x6c, 0x10, 0x49, 0xb3, 0x97, 0x20, 0x4a, 0xed, 0x4e, 0x10, - 0x4b, 0x9c, 0xb3, 0xa0, 0x4c, 0xd6, 0x6a, 0x90, 0x4d, 0x7c, 0x95, 0xa0, - 0x4e, 0xb6, 0x4c, 0x90, 0x4f, 0x5c, 0x77, 0xa0, 0x50, 0x96, 0x2e, 0x90, - 0x51, 0x3c, 0x59, 0xa0, 0x52, 0x76, 0x10, 0x90, 0x53, 0x1c, 0x3b, 0xa0, - 0x54, 0x55, 0xf2, 0x90, 0x54, 0xfc, 0x1d, 0xa0, 0x56, 0x35, 0xd4, 0x90, - 0x56, 0xe5, 0x3a, 0x20, 0x58, 0x1e, 0xf1, 0x10, 0x58, 0xc5, 0x1c, 0x20, - 0x59, 0xfe, 0xd3, 0x10, 0x5a, 0xa4, 0xfe, 0x20, 0x5b, 0xde, 0xb5, 0x10, - 0x5c, 0x84, 0xe0, 0x20, 0x5d, 0xbe, 0x97, 0x10, 0x5e, 0x64, 0xc2, 0x20, - 0x5f, 0x9e, 0x79, 0x10, 0x60, 0x4d, 0xde, 0xa0, 0x61, 0x87, 0x95, 0x90, - 0x62, 0x2d, 0xc0, 0xa0, 0x63, 0x67, 0x77, 0x90, 0x64, 0x0d, 0xa2, 0xa0, - 0x65, 0x47, 0x59, 0x90, 0x65, 0xed, 0x84, 0xa0, 0x67, 0x27, 0x3b, 0x90, - 0x67, 0xcd, 0x66, 0xa0, 0x69, 0x07, 0x1d, 0x90, 0x69, 0xad, 0x48, 0xa0, - 0x6a, 0xe6, 0xff, 0x90, 0x6b, 0x96, 0x65, 0x20, 0x6c, 0xd0, 0x1c, 0x10, - 0x6d, 0x76, 0x47, 0x20, 0x6e, 0xaf, 0xfe, 0x10, 0x6f, 0x56, 0x29, 0x20, - 0x70, 0x8f, 0xe0, 0x10, 0x71, 0x36, 0x0b, 0x20, 0x72, 0x6f, 0xc2, 0x10, - 0x73, 0x15, 0xed, 0x20, 0x74, 0x4f, 0xa4, 0x10, 0x74, 0xff, 0x09, 0xa0, - 0x76, 0x38, 0xc0, 0x90, 0x76, 0xde, 0xeb, 0xa0, 0x78, 0x18, 0xa2, 0x90, - 0x78, 0xbe, 0xcd, 0xa0, 0x79, 0xf8, 0x84, 0x90, 0x7a, 0x9e, 0xaf, 0xa0, - 0x7b, 0xd8, 0x66, 0x90, 0x7c, 0x7e, 0x91, 0xa0, 0x7d, 0xb8, 0x48, 0x90, - 0x7e, 0x5e, 0x73, 0xa0, 0x7f, 0x98, 0x2a, 0x90, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x03, 0x04, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0xff, 0xff, 0x91, 0x26, 0x00, 0x00, 0xff, 0xff, 0x9d, 0x90, - 0x01, 0x04, 0xff, 0xff, 0x8f, 0x80, 0x00, 0x08, 0xff, 0xff, 0x9d, 0x90, - 0x01, 0x0c, 0xff, 0xff, 0x9d, 0x90, 0x01, 0x10, 0x4c, 0x4d, 0x54, 0x00, - 0x50, 0x44, 0x54, 0x00, 0x50, 0x53, 0x54, 0x00, 0x50, 0x57, 0x54, 0x00, - 0x50, 0x50, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, - 0x00, 0x01, 0x54, 0x5a, 0x69, 0x66, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x05, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0xba, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x14, 0xff, 0xff, - 0xff, 0xff, 0x5e, 0x04, 0x1a, 0xc0, 0xff, 0xff, 0xff, 0xff, 0x9e, 0xa6, - 0x48, 0xa0, 0xff, 0xff, 0xff, 0xff, 0x9f, 0xbb, 0x15, 0x90, 0xff, 0xff, - 0xff, 0xff, 0xa0, 0x86, 0x2a, 0xa0, 0xff, 0xff, 0xff, 0xff, 0xa1, 0x9a, - 0xf7, 0x90, 0xff, 0xff, 0xff, 0xff, 0xcb, 0x89, 0x1a, 0xa0, 0xff, 0xff, - 0xff, 0xff, 0xd2, 0x23, 0xf4, 0x70, 0xff, 0xff, 0xff, 0xff, 0xd2, 0x61, - 0x26, 0x10, 0xff, 0xff, 0xff, 0xff, 0xd6, 0xfe, 0x74, 0x5c, 0xff, 0xff, - 0xff, 0xff, 0xd8, 0x80, 0xad, 0x90, 0xff, 0xff, 0xff, 0xff, 0xda, 0xfe, - 0xc3, 0x90, 0xff, 0xff, 0xff, 0xff, 0xdb, 0xc0, 0x90, 0x10, 0xff, 0xff, - 0xff, 0xff, 0xdc, 0xde, 0xa5, 0x90, 0xff, 0xff, 0xff, 0xff, 0xdd, 0xa9, - 0xac, 0x90, 0xff, 0xff, 0xff, 0xff, 0xde, 0xbe, 0x87, 0x90, 0xff, 0xff, - 0xff, 0xff, 0xdf, 0x89, 0x8e, 0x90, 0xff, 0xff, 0xff, 0xff, 0xe0, 0x9e, - 0x69, 0x90, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x69, 0x70, 0x90, 0xff, 0xff, - 0xff, 0xff, 0xe2, 0x7e, 0x4b, 0x90, 0xff, 0xff, 0xff, 0xff, 0xe3, 0x49, - 0x52, 0x90, 0xff, 0xff, 0xff, 0xff, 0xe4, 0x5e, 0x2d, 0x90, 0xff, 0xff, - 0xff, 0xff, 0xe5, 0x29, 0x34, 0x90, 0xff, 0xff, 0xff, 0xff, 0xe6, 0x47, - 0x4a, 0x10, 0xff, 0xff, 0xff, 0xff, 0xe7, 0x12, 0x51, 0x10, 0xff, 0xff, - 0xff, 0xff, 0xe8, 0x27, 0x2c, 0x10, 0xff, 0xff, 0xff, 0xff, 0xe8, 0xf2, - 0x33, 0x10, 0xff, 0xff, 0xff, 0xff, 0xea, 0x07, 0x0e, 0x10, 0xff, 0xff, - 0xff, 0xff, 0xea, 0xd2, 0x15, 0x10, 0xff, 0xff, 0xff, 0xff, 0xeb, 0xe6, - 0xf0, 0x10, 0xff, 0xff, 0xff, 0xff, 0xec, 0xb1, 0xf7, 0x10, 0xff, 0xff, - 0xff, 0xff, 0xed, 0xc6, 0xd2, 0x10, 0xff, 0xff, 0xff, 0xff, 0xee, 0x91, - 0xd9, 0x10, 0xff, 0xff, 0xff, 0xff, 0xef, 0xaf, 0xee, 0x90, 0xff, 0xff, - 0xff, 0xff, 0xf0, 0x71, 0xbb, 0x10, 0xff, 0xff, 0xff, 0xff, 0xf1, 0x8f, - 0xd0, 0x90, 0xff, 0xff, 0xff, 0xff, 0xf2, 0x7f, 0xc1, 0x90, 0xff, 0xff, - 0xff, 0xff, 0xf3, 0x6f, 0xb2, 0x90, 0xff, 0xff, 0xff, 0xff, 0xf4, 0x5f, - 0xa3, 0x90, 0xff, 0xff, 0xff, 0xff, 0xf5, 0x4f, 0x94, 0x90, 0xff, 0xff, - 0xff, 0xff, 0xf6, 0x3f, 0x85, 0x90, 0xff, 0xff, 0xff, 0xff, 0xf7, 0x2f, - 0x76, 0x90, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x28, 0xa2, 0x10, 0xff, 0xff, - 0xff, 0xff, 0xf9, 0x0f, 0x58, 0x90, 0xff, 0xff, 0xff, 0xff, 0xfa, 0x08, - 0x84, 0x10, 0xff, 0xff, 0xff, 0xff, 0xfa, 0xf8, 0x83, 0x20, 0xff, 0xff, - 0xff, 0xff, 0xfb, 0xe8, 0x66, 0x10, 0xff, 0xff, 0xff, 0xff, 0xfc, 0xd8, - 0x65, 0x20, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xc8, 0x48, 0x10, 0xff, 0xff, - 0xff, 0xff, 0xfe, 0xb8, 0x47, 0x20, 0xff, 0xff, 0xff, 0xff, 0xff, 0xa8, - 0x2a, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x98, 0x29, 0x20, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x88, 0x0c, 0x10, 0x00, 0x00, 0x00, 0x00, 0x02, 0x78, - 0x0b, 0x20, 0x00, 0x00, 0x00, 0x00, 0x03, 0x71, 0x28, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x04, 0x61, 0x27, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x05, 0x51, - 0x0a, 0x90, 0x00, 0x00, 0x00, 0x00, 0x06, 0x41, 0x09, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x07, 0x30, 0xec, 0x90, 0x00, 0x00, 0x00, 0x00, 0x07, 0x8d, - 0x43, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x09, 0x10, 0xce, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x09, 0xad, 0xbf, 0x20, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xf0, - 0xb0, 0x90, 0x00, 0x00, 0x00, 0x00, 0x0b, 0xe0, 0xaf, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x0c, 0xd9, 0xcd, 0x10, 0x00, 0x00, 0x00, 0x00, 0x0d, 0xc0, - 0x91, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xb9, 0xaf, 0x10, 0x00, 0x00, - 0x00, 0x00, 0x0f, 0xa9, 0xae, 0x20, 0x00, 0x00, 0x00, 0x00, 0x10, 0x99, - 0x91, 0x10, 0x00, 0x00, 0x00, 0x00, 0x11, 0x89, 0x90, 0x20, 0x00, 0x00, - 0x00, 0x00, 0x12, 0x79, 0x73, 0x10, 0x00, 0x00, 0x00, 0x00, 0x13, 0x69, - 0x72, 0x20, 0x00, 0x00, 0x00, 0x00, 0x14, 0x59, 0x55, 0x10, 0x00, 0x00, - 0x00, 0x00, 0x15, 0x49, 0x54, 0x20, 0x00, 0x00, 0x00, 0x00, 0x16, 0x39, - 0x37, 0x10, 0x00, 0x00, 0x00, 0x00, 0x17, 0x29, 0x36, 0x20, 0x00, 0x00, - 0x00, 0x00, 0x18, 0x22, 0x53, 0x90, 0x00, 0x00, 0x00, 0x00, 0x19, 0x09, - 0x18, 0x20, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x02, 0x35, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x1a, 0xf2, 0x34, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x1b, 0xe2, - 0x17, 0x90, 0x00, 0x00, 0x00, 0x00, 0x1c, 0xd2, 0x16, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x1d, 0xc1, 0xf9, 0x90, 0x00, 0x00, 0x00, 0x00, 0x1e, 0xb1, - 0xf8, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xa1, 0xdb, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x20, 0x76, 0x2b, 0x20, 0x00, 0x00, 0x00, 0x00, 0x21, 0x81, - 0xbd, 0x90, 0x00, 0x00, 0x00, 0x00, 0x22, 0x56, 0x0d, 0x20, 0x00, 0x00, - 0x00, 0x00, 0x23, 0x6a, 0xda, 0x10, 0x00, 0x00, 0x00, 0x00, 0x24, 0x35, - 0xef, 0x20, 0x00, 0x00, 0x00, 0x00, 0x25, 0x4a, 0xbc, 0x10, 0x00, 0x00, - 0x00, 0x00, 0x26, 0x15, 0xd1, 0x20, 0x00, 0x00, 0x00, 0x00, 0x27, 0x2a, - 0x9e, 0x10, 0x00, 0x00, 0x00, 0x00, 0x27, 0xfe, 0xed, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x29, 0x0a, 0x80, 0x10, 0x00, 0x00, 0x00, 0x00, 0x29, 0xde, - 0xcf, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x2a, 0xea, 0x62, 0x10, 0x00, 0x00, - 0x00, 0x00, 0x2b, 0xbe, 0xb1, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x2c, 0xd3, - 0x7e, 0x90, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x9e, 0x93, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x2e, 0xb3, 0x60, 0x90, 0x00, 0x00, 0x00, 0x00, 0x2f, 0x7e, - 0x75, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x30, 0x93, 0x42, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x31, 0x67, 0x92, 0x20, 0x00, 0x00, 0x00, 0x00, 0x32, 0x73, - 0x24, 0x90, 0x00, 0x00, 0x00, 0x00, 0x33, 0x47, 0x74, 0x20, 0x00, 0x00, - 0x00, 0x00, 0x34, 0x53, 0x06, 0x90, 0x00, 0x00, 0x00, 0x00, 0x35, 0x27, - 0x56, 0x20, 0x00, 0x00, 0x00, 0x00, 0x36, 0x32, 0xe8, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x37, 0x07, 0x38, 0x20, 0x00, 0x00, 0x00, 0x00, 0x38, 0x1c, - 0x05, 0x10, 0x00, 0x00, 0x00, 0x00, 0x38, 0xe7, 0x1a, 0x20, 0x00, 0x00, - 0x00, 0x00, 0x39, 0xfb, 0xe7, 0x10, 0x00, 0x00, 0x00, 0x00, 0x3a, 0xc6, - 0xfc, 0x20, 0x00, 0x00, 0x00, 0x00, 0x3b, 0xdb, 0xc9, 0x10, 0x00, 0x00, - 0x00, 0x00, 0x3c, 0xb0, 0x18, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x3d, 0xbb, - 0xab, 0x10, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x8f, 0xfa, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x3f, 0x9b, 0x8d, 0x10, 0x00, 0x00, 0x00, 0x00, 0x40, 0x6f, - 0xdc, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x41, 0x84, 0xa9, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x42, 0x4f, 0xbe, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x43, 0x64, - 0x8b, 0x90, 0x00, 0x00, 0x00, 0x00, 0x44, 0x2f, 0xa0, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x45, 0x44, 0x6d, 0x90, 0x00, 0x00, 0x00, 0x00, 0x45, 0xf3, - 0xd3, 0x20, 0x00, 0x00, 0x00, 0x00, 0x47, 0x2d, 0x8a, 0x10, 0x00, 0x00, - 0x00, 0x00, 0x47, 0xd3, 0xb5, 0x20, 0x00, 0x00, 0x00, 0x00, 0x49, 0x0d, - 0x6c, 0x10, 0x00, 0x00, 0x00, 0x00, 0x49, 0xb3, 0x97, 0x20, 0x00, 0x00, - 0x00, 0x00, 0x4a, 0xed, 0x4e, 0x10, 0x00, 0x00, 0x00, 0x00, 0x4b, 0x9c, - 0xb3, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x4c, 0xd6, 0x6a, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x4d, 0x7c, 0x95, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x4e, 0xb6, - 0x4c, 0x90, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x5c, 0x77, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x50, 0x96, 0x2e, 0x90, 0x00, 0x00, 0x00, 0x00, 0x51, 0x3c, - 0x59, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x52, 0x76, 0x10, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x53, 0x1c, 0x3b, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x54, 0x55, - 0xf2, 0x90, 0x00, 0x00, 0x00, 0x00, 0x54, 0xfc, 0x1d, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x56, 0x35, 0xd4, 0x90, 0x00, 0x00, 0x00, 0x00, 0x56, 0xe5, - 0x3a, 0x20, 0x00, 0x00, 0x00, 0x00, 0x58, 0x1e, 0xf1, 0x10, 0x00, 0x00, - 0x00, 0x00, 0x58, 0xc5, 0x1c, 0x20, 0x00, 0x00, 0x00, 0x00, 0x59, 0xfe, - 0xd3, 0x10, 0x00, 0x00, 0x00, 0x00, 0x5a, 0xa4, 0xfe, 0x20, 0x00, 0x00, - 0x00, 0x00, 0x5b, 0xde, 0xb5, 0x10, 0x00, 0x00, 0x00, 0x00, 0x5c, 0x84, - 0xe0, 0x20, 0x00, 0x00, 0x00, 0x00, 0x5d, 0xbe, 0x97, 0x10, 0x00, 0x00, - 0x00, 0x00, 0x5e, 0x64, 0xc2, 0x20, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x9e, - 0x79, 0x10, 0x00, 0x00, 0x00, 0x00, 0x60, 0x4d, 0xde, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x61, 0x87, 0x95, 0x90, 0x00, 0x00, 0x00, 0x00, 0x62, 0x2d, - 0xc0, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x63, 0x67, 0x77, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x64, 0x0d, 0xa2, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x65, 0x47, - 0x59, 0x90, 0x00, 0x00, 0x00, 0x00, 0x65, 0xed, 0x84, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x67, 0x27, 0x3b, 0x90, 0x00, 0x00, 0x00, 0x00, 0x67, 0xcd, - 0x66, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x69, 0x07, 0x1d, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x69, 0xad, 0x48, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x6a, 0xe6, - 0xff, 0x90, 0x00, 0x00, 0x00, 0x00, 0x6b, 0x96, 0x65, 0x20, 0x00, 0x00, - 0x00, 0x00, 0x6c, 0xd0, 0x1c, 0x10, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x76, - 0x47, 0x20, 0x00, 0x00, 0x00, 0x00, 0x6e, 0xaf, 0xfe, 0x10, 0x00, 0x00, - 0x00, 0x00, 0x6f, 0x56, 0x29, 0x20, 0x00, 0x00, 0x00, 0x00, 0x70, 0x8f, - 0xe0, 0x10, 0x00, 0x00, 0x00, 0x00, 0x71, 0x36, 0x0b, 0x20, 0x00, 0x00, - 0x00, 0x00, 0x72, 0x6f, 0xc2, 0x10, 0x00, 0x00, 0x00, 0x00, 0x73, 0x15, - 0xed, 0x20, 0x00, 0x00, 0x00, 0x00, 0x74, 0x4f, 0xa4, 0x10, 0x00, 0x00, - 0x00, 0x00, 0x74, 0xff, 0x09, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x76, 0x38, - 0xc0, 0x90, 0x00, 0x00, 0x00, 0x00, 0x76, 0xde, 0xeb, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x78, 0x18, 0xa2, 0x90, 0x00, 0x00, 0x00, 0x00, 0x78, 0xbe, - 0xcd, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x79, 0xf8, 0x84, 0x90, 0x00, 0x00, - 0x00, 0x00, 0x7a, 0x9e, 0xaf, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x7b, 0xd8, - 0x66, 0x90, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x7e, 0x91, 0xa0, 0x00, 0x00, - 0x00, 0x00, 0x7d, 0xb8, 0x48, 0x90, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x5e, - 0x73, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x98, 0x2a, 0x90, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x03, 0x04, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0xff, 0xff, 0x91, 0x26, 0x00, 0x00, 0xff, 0xff, - 0x9d, 0x90, 0x01, 0x04, 0xff, 0xff, 0x8f, 0x80, 0x00, 0x08, 0xff, 0xff, - 0x9d, 0x90, 0x01, 0x0c, 0xff, 0xff, 0x9d, 0x90, 0x01, 0x10, 0x4c, 0x4d, - 0x54, 0x00, 0x50, 0x44, 0x54, 0x00, 0x50, 0x53, 0x54, 0x00, 0x50, 0x57, - 0x54, 0x00, 0x50, 0x50, 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, - 0x00, 0x00, 0x00, 0x01, 0x0a, 0x50, 0x53, 0x54, 0x38, 0x50, 0x44, 0x54, - 0x2c, 0x4d, 0x33, 0x2e, 0x32, 0x2e, 0x30, 0x2c, 0x4d, 0x31, 0x31, 0x2e, - 0x31, 0x2e, 0x30, 0x0a -}; -unsigned int America_Los_Angeles_len = 2836; -unsigned char America_New_York[] = { - 0x54, 0x5a, 0x69, 0x66, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, - 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xec, - 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x14, 0x80, 0x00, 0x00, 0x00, - 0x9e, 0xa6, 0x1e, 0x70, 0x9f, 0xba, 0xeb, 0x60, 0xa0, 0x86, 0x00, 0x70, - 0xa1, 0x9a, 0xcd, 0x60, 0xa2, 0x65, 0xe2, 0x70, 0xa3, 0x83, 0xe9, 0xe0, - 0xa4, 0x6a, 0xae, 0x70, 0xa5, 0x35, 0xa7, 0x60, 0xa6, 0x53, 0xca, 0xf0, - 0xa7, 0x15, 0x89, 0x60, 0xa8, 0x33, 0xac, 0xf0, 0xa8, 0xfe, 0xa5, 0xe0, - 0xaa, 0x13, 0x8e, 0xf0, 0xaa, 0xde, 0x87, 0xe0, 0xab, 0xf3, 0x70, 0xf0, - 0xac, 0xbe, 0x69, 0xe0, 0xad, 0xd3, 0x52, 0xf0, 0xae, 0x9e, 0x4b, 0xe0, - 0xaf, 0xb3, 0x34, 0xf0, 0xb0, 0x7e, 0x2d, 0xe0, 0xb1, 0x9c, 0x51, 0x70, - 0xb2, 0x67, 0x4a, 0x60, 0xb3, 0x7c, 0x33, 0x70, 0xb4, 0x47, 0x2c, 0x60, - 0xb5, 0x5c, 0x15, 0x70, 0xb6, 0x27, 0x0e, 0x60, 0xb7, 0x3b, 0xf7, 0x70, - 0xb8, 0x06, 0xf0, 0x60, 0xb9, 0x1b, 0xd9, 0x70, 0xb9, 0xe6, 0xd2, 0x60, - 0xbb, 0x04, 0xf5, 0xf0, 0xbb, 0xc6, 0xb4, 0x60, 0xbc, 0xe4, 0xd7, 0xf0, - 0xbd, 0xaf, 0xd0, 0xe0, 0xbe, 0xc4, 0xb9, 0xf0, 0xbf, 0x8f, 0xb2, 0xe0, - 0xc0, 0xa4, 0x9b, 0xf0, 0xc1, 0x6f, 0x94, 0xe0, 0xc2, 0x84, 0x7d, 0xf0, - 0xc3, 0x4f, 0x76, 0xe0, 0xc4, 0x64, 0x5f, 0xf0, 0xc5, 0x2f, 0x58, 0xe0, - 0xc6, 0x4d, 0x7c, 0x70, 0xc7, 0x0f, 0x3a, 0xe0, 0xc8, 0x2d, 0x5e, 0x70, - 0xc8, 0xf8, 0x57, 0x60, 0xca, 0x0d, 0x40, 0x70, 0xca, 0xd8, 0x39, 0x60, - 0xcb, 0x88, 0xf0, 0x70, 0xd2, 0x23, 0xf4, 0x70, 0xd2, 0x60, 0xfb, 0xe0, - 0xd3, 0x75, 0xe4, 0xf0, 0xd4, 0x40, 0xdd, 0xe0, 0xd5, 0x55, 0xc6, 0xf0, - 0xd6, 0x20, 0xbf, 0xe0, 0xd7, 0x35, 0xa8, 0xf0, 0xd8, 0x00, 0xa1, 0xe0, - 0xd9, 0x15, 0x8a, 0xf0, 0xd9, 0xe0, 0x83, 0xe0, 0xda, 0xfe, 0xa7, 0x70, - 0xdb, 0xc0, 0x65, 0xe0, 0xdc, 0xde, 0x89, 0x70, 0xdd, 0xa9, 0x82, 0x60, - 0xde, 0xbe, 0x6b, 0x70, 0xdf, 0x89, 0x64, 0x60, 0xe0, 0x9e, 0x4d, 0x70, - 0xe1, 0x69, 0x46, 0x60, 0xe2, 0x7e, 0x2f, 0x70, 0xe3, 0x49, 0x28, 0x60, - 0xe4, 0x5e, 0x11, 0x70, 0xe5, 0x57, 0x2e, 0xe0, 0xe6, 0x47, 0x2d, 0xf0, - 0xe7, 0x37, 0x10, 0xe0, 0xe8, 0x27, 0x0f, 0xf0, 0xe9, 0x16, 0xf2, 0xe0, - 0xea, 0x06, 0xf1, 0xf0, 0xea, 0xf6, 0xd4, 0xe0, 0xeb, 0xe6, 0xd3, 0xf0, - 0xec, 0xd6, 0xb6, 0xe0, 0xed, 0xc6, 0xb5, 0xf0, 0xee, 0xbf, 0xd3, 0x60, - 0xef, 0xaf, 0xd2, 0x70, 0xf0, 0x9f, 0xb5, 0x60, 0xf1, 0x8f, 0xb4, 0x70, - 0xf2, 0x7f, 0x97, 0x60, 0xf3, 0x6f, 0x96, 0x70, 0xf4, 0x5f, 0x79, 0x60, - 0xf5, 0x4f, 0x78, 0x70, 0xf6, 0x3f, 0x5b, 0x60, 0xf7, 0x2f, 0x5a, 0x70, - 0xf8, 0x28, 0x77, 0xe0, 0xf9, 0x0f, 0x3c, 0x70, 0xfa, 0x08, 0x59, 0xe0, - 0xfa, 0xf8, 0x58, 0xf0, 0xfb, 0xe8, 0x3b, 0xe0, 0xfc, 0xd8, 0x3a, 0xf0, - 0xfd, 0xc8, 0x1d, 0xe0, 0xfe, 0xb8, 0x1c, 0xf0, 0xff, 0xa7, 0xff, 0xe0, - 0x00, 0x97, 0xfe, 0xf0, 0x01, 0x87, 0xe1, 0xe0, 0x02, 0x77, 0xe0, 0xf0, - 0x03, 0x70, 0xfe, 0x60, 0x04, 0x60, 0xfd, 0x70, 0x05, 0x50, 0xe0, 0x60, - 0x06, 0x40, 0xdf, 0x70, 0x07, 0x30, 0xc2, 0x60, 0x07, 0x8d, 0x19, 0x70, - 0x09, 0x10, 0xa4, 0x60, 0x09, 0xad, 0x94, 0xf0, 0x0a, 0xf0, 0x86, 0x60, - 0x0b, 0xe0, 0x85, 0x70, 0x0c, 0xd9, 0xa2, 0xe0, 0x0d, 0xc0, 0x67, 0x70, - 0x0e, 0xb9, 0x84, 0xe0, 0x0f, 0xa9, 0x83, 0xf0, 0x10, 0x99, 0x66, 0xe0, - 0x11, 0x89, 0x65, 0xf0, 0x12, 0x79, 0x48, 0xe0, 0x13, 0x69, 0x47, 0xf0, - 0x14, 0x59, 0x2a, 0xe0, 0x15, 0x49, 0x29, 0xf0, 0x16, 0x39, 0x0c, 0xe0, - 0x17, 0x29, 0x0b, 0xf0, 0x18, 0x22, 0x29, 0x60, 0x19, 0x08, 0xed, 0xf0, - 0x1a, 0x02, 0x0b, 0x60, 0x1a, 0xf2, 0x0a, 0x70, 0x1b, 0xe1, 0xed, 0x60, - 0x1c, 0xd1, 0xec, 0x70, 0x1d, 0xc1, 0xcf, 0x60, 0x1e, 0xb1, 0xce, 0x70, - 0x1f, 0xa1, 0xb1, 0x60, 0x20, 0x76, 0x00, 0xf0, 0x21, 0x81, 0x93, 0x60, - 0x22, 0x55, 0xe2, 0xf0, 0x23, 0x6a, 0xaf, 0xe0, 0x24, 0x35, 0xc4, 0xf0, - 0x25, 0x4a, 0x91, 0xe0, 0x26, 0x15, 0xa6, 0xf0, 0x27, 0x2a, 0x73, 0xe0, - 0x27, 0xfe, 0xc3, 0x70, 0x29, 0x0a, 0x55, 0xe0, 0x29, 0xde, 0xa5, 0x70, - 0x2a, 0xea, 0x37, 0xe0, 0x2b, 0xbe, 0x87, 0x70, 0x2c, 0xd3, 0x54, 0x60, - 0x2d, 0x9e, 0x69, 0x70, 0x2e, 0xb3, 0x36, 0x60, 0x2f, 0x7e, 0x4b, 0x70, - 0x30, 0x93, 0x18, 0x60, 0x31, 0x67, 0x67, 0xf0, 0x32, 0x72, 0xfa, 0x60, - 0x33, 0x47, 0x49, 0xf0, 0x34, 0x52, 0xdc, 0x60, 0x35, 0x27, 0x2b, 0xf0, - 0x36, 0x32, 0xbe, 0x60, 0x37, 0x07, 0x0d, 0xf0, 0x38, 0x1b, 0xda, 0xe0, - 0x38, 0xe6, 0xef, 0xf0, 0x39, 0xfb, 0xbc, 0xe0, 0x3a, 0xc6, 0xd1, 0xf0, - 0x3b, 0xdb, 0x9e, 0xe0, 0x3c, 0xaf, 0xee, 0x70, 0x3d, 0xbb, 0x80, 0xe0, - 0x3e, 0x8f, 0xd0, 0x70, 0x3f, 0x9b, 0x62, 0xe0, 0x40, 0x6f, 0xb2, 0x70, - 0x41, 0x84, 0x7f, 0x60, 0x42, 0x4f, 0x94, 0x70, 0x43, 0x64, 0x61, 0x60, - 0x44, 0x2f, 0x76, 0x70, 0x45, 0x44, 0x43, 0x60, 0x45, 0xf3, 0xa8, 0xf0, - 0x47, 0x2d, 0x5f, 0xe0, 0x47, 0xd3, 0x8a, 0xf0, 0x49, 0x0d, 0x41, 0xe0, - 0x49, 0xb3, 0x6c, 0xf0, 0x4a, 0xed, 0x23, 0xe0, 0x4b, 0x9c, 0x89, 0x70, - 0x4c, 0xd6, 0x40, 0x60, 0x4d, 0x7c, 0x6b, 0x70, 0x4e, 0xb6, 0x22, 0x60, - 0x4f, 0x5c, 0x4d, 0x70, 0x50, 0x96, 0x04, 0x60, 0x51, 0x3c, 0x2f, 0x70, - 0x52, 0x75, 0xe6, 0x60, 0x53, 0x1c, 0x11, 0x70, 0x54, 0x55, 0xc8, 0x60, - 0x54, 0xfb, 0xf3, 0x70, 0x56, 0x35, 0xaa, 0x60, 0x56, 0xe5, 0x0f, 0xf0, - 0x58, 0x1e, 0xc6, 0xe0, 0x58, 0xc4, 0xf1, 0xf0, 0x59, 0xfe, 0xa8, 0xe0, - 0x5a, 0xa4, 0xd3, 0xf0, 0x5b, 0xde, 0x8a, 0xe0, 0x5c, 0x84, 0xb5, 0xf0, - 0x5d, 0xbe, 0x6c, 0xe0, 0x5e, 0x64, 0x97, 0xf0, 0x5f, 0x9e, 0x4e, 0xe0, - 0x60, 0x4d, 0xb4, 0x70, 0x61, 0x87, 0x6b, 0x60, 0x62, 0x2d, 0x96, 0x70, - 0x63, 0x67, 0x4d, 0x60, 0x64, 0x0d, 0x78, 0x70, 0x65, 0x47, 0x2f, 0x60, - 0x65, 0xed, 0x5a, 0x70, 0x67, 0x27, 0x11, 0x60, 0x67, 0xcd, 0x3c, 0x70, - 0x69, 0x06, 0xf3, 0x60, 0x69, 0xad, 0x1e, 0x70, 0x6a, 0xe6, 0xd5, 0x60, - 0x6b, 0x96, 0x3a, 0xf0, 0x6c, 0xcf, 0xf1, 0xe0, 0x6d, 0x76, 0x1c, 0xf0, - 0x6e, 0xaf, 0xd3, 0xe0, 0x6f, 0x55, 0xfe, 0xf0, 0x70, 0x8f, 0xb5, 0xe0, - 0x71, 0x35, 0xe0, 0xf0, 0x72, 0x6f, 0x97, 0xe0, 0x73, 0x15, 0xc2, 0xf0, - 0x74, 0x4f, 0x79, 0xe0, 0x74, 0xfe, 0xdf, 0x70, 0x76, 0x38, 0x96, 0x60, - 0x76, 0xde, 0xc1, 0x70, 0x78, 0x18, 0x78, 0x60, 0x78, 0xbe, 0xa3, 0x70, - 0x79, 0xf8, 0x5a, 0x60, 0x7a, 0x9e, 0x85, 0x70, 0x7b, 0xd8, 0x3c, 0x60, - 0x7c, 0x7e, 0x67, 0x70, 0x7d, 0xb8, 0x1e, 0x60, 0x7e, 0x5e, 0x49, 0x70, - 0x7f, 0x98, 0x00, 0x60, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x03, 0x04, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0xff, 0xff, 0xba, 0x9e, 0x00, 0x00, 0xff, 0xff, 0xc7, 0xc0, 0x01, 0x04, - 0xff, 0xff, 0xb9, 0xb0, 0x00, 0x08, 0xff, 0xff, 0xc7, 0xc0, 0x01, 0x0c, - 0xff, 0xff, 0xc7, 0xc0, 0x01, 0x10, 0x4c, 0x4d, 0x54, 0x00, 0x45, 0x44, - 0x54, 0x00, 0x45, 0x53, 0x54, 0x00, 0x45, 0x57, 0x54, 0x00, 0x45, 0x50, - 0x54, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, - 0x54, 0x5a, 0x69, 0x66, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, - 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xec, - 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00, 0x14, 0xff, 0xff, 0xff, 0xff, - 0x5e, 0x03, 0xf0, 0x90, 0xff, 0xff, 0xff, 0xff, 0x9e, 0xa6, 0x1e, 0x70, - 0xff, 0xff, 0xff, 0xff, 0x9f, 0xba, 0xeb, 0x60, 0xff, 0xff, 0xff, 0xff, - 0xa0, 0x86, 0x00, 0x70, 0xff, 0xff, 0xff, 0xff, 0xa1, 0x9a, 0xcd, 0x60, - 0xff, 0xff, 0xff, 0xff, 0xa2, 0x65, 0xe2, 0x70, 0xff, 0xff, 0xff, 0xff, - 0xa3, 0x83, 0xe9, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xa4, 0x6a, 0xae, 0x70, - 0xff, 0xff, 0xff, 0xff, 0xa5, 0x35, 0xa7, 0x60, 0xff, 0xff, 0xff, 0xff, - 0xa6, 0x53, 0xca, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xa7, 0x15, 0x89, 0x60, - 0xff, 0xff, 0xff, 0xff, 0xa8, 0x33, 0xac, 0xf0, 0xff, 0xff, 0xff, 0xff, - 0xa8, 0xfe, 0xa5, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xaa, 0x13, 0x8e, 0xf0, - 0xff, 0xff, 0xff, 0xff, 0xaa, 0xde, 0x87, 0xe0, 0xff, 0xff, 0xff, 0xff, - 0xab, 0xf3, 0x70, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xac, 0xbe, 0x69, 0xe0, - 0xff, 0xff, 0xff, 0xff, 0xad, 0xd3, 0x52, 0xf0, 0xff, 0xff, 0xff, 0xff, - 0xae, 0x9e, 0x4b, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xaf, 0xb3, 0x34, 0xf0, - 0xff, 0xff, 0xff, 0xff, 0xb0, 0x7e, 0x2d, 0xe0, 0xff, 0xff, 0xff, 0xff, - 0xb1, 0x9c, 0x51, 0x70, 0xff, 0xff, 0xff, 0xff, 0xb2, 0x67, 0x4a, 0x60, - 0xff, 0xff, 0xff, 0xff, 0xb3, 0x7c, 0x33, 0x70, 0xff, 0xff, 0xff, 0xff, - 0xb4, 0x47, 0x2c, 0x60, 0xff, 0xff, 0xff, 0xff, 0xb5, 0x5c, 0x15, 0x70, - 0xff, 0xff, 0xff, 0xff, 0xb6, 0x27, 0x0e, 0x60, 0xff, 0xff, 0xff, 0xff, - 0xb7, 0x3b, 0xf7, 0x70, 0xff, 0xff, 0xff, 0xff, 0xb8, 0x06, 0xf0, 0x60, - 0xff, 0xff, 0xff, 0xff, 0xb9, 0x1b, 0xd9, 0x70, 0xff, 0xff, 0xff, 0xff, - 0xb9, 0xe6, 0xd2, 0x60, 0xff, 0xff, 0xff, 0xff, 0xbb, 0x04, 0xf5, 0xf0, - 0xff, 0xff, 0xff, 0xff, 0xbb, 0xc6, 0xb4, 0x60, 0xff, 0xff, 0xff, 0xff, - 0xbc, 0xe4, 0xd7, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xbd, 0xaf, 0xd0, 0xe0, - 0xff, 0xff, 0xff, 0xff, 0xbe, 0xc4, 0xb9, 0xf0, 0xff, 0xff, 0xff, 0xff, - 0xbf, 0x8f, 0xb2, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xc0, 0xa4, 0x9b, 0xf0, - 0xff, 0xff, 0xff, 0xff, 0xc1, 0x6f, 0x94, 0xe0, 0xff, 0xff, 0xff, 0xff, - 0xc2, 0x84, 0x7d, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xc3, 0x4f, 0x76, 0xe0, - 0xff, 0xff, 0xff, 0xff, 0xc4, 0x64, 0x5f, 0xf0, 0xff, 0xff, 0xff, 0xff, - 0xc5, 0x2f, 0x58, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xc6, 0x4d, 0x7c, 0x70, - 0xff, 0xff, 0xff, 0xff, 0xc7, 0x0f, 0x3a, 0xe0, 0xff, 0xff, 0xff, 0xff, - 0xc8, 0x2d, 0x5e, 0x70, 0xff, 0xff, 0xff, 0xff, 0xc8, 0xf8, 0x57, 0x60, - 0xff, 0xff, 0xff, 0xff, 0xca, 0x0d, 0x40, 0x70, 0xff, 0xff, 0xff, 0xff, - 0xca, 0xd8, 0x39, 0x60, 0xff, 0xff, 0xff, 0xff, 0xcb, 0x88, 0xf0, 0x70, - 0xff, 0xff, 0xff, 0xff, 0xd2, 0x23, 0xf4, 0x70, 0xff, 0xff, 0xff, 0xff, - 0xd2, 0x60, 0xfb, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xd3, 0x75, 0xe4, 0xf0, - 0xff, 0xff, 0xff, 0xff, 0xd4, 0x40, 0xdd, 0xe0, 0xff, 0xff, 0xff, 0xff, - 0xd5, 0x55, 0xc6, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xd6, 0x20, 0xbf, 0xe0, - 0xff, 0xff, 0xff, 0xff, 0xd7, 0x35, 0xa8, 0xf0, 0xff, 0xff, 0xff, 0xff, - 0xd8, 0x00, 0xa1, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xd9, 0x15, 0x8a, 0xf0, - 0xff, 0xff, 0xff, 0xff, 0xd9, 0xe0, 0x83, 0xe0, 0xff, 0xff, 0xff, 0xff, - 0xda, 0xfe, 0xa7, 0x70, 0xff, 0xff, 0xff, 0xff, 0xdb, 0xc0, 0x65, 0xe0, - 0xff, 0xff, 0xff, 0xff, 0xdc, 0xde, 0x89, 0x70, 0xff, 0xff, 0xff, 0xff, - 0xdd, 0xa9, 0x82, 0x60, 0xff, 0xff, 0xff, 0xff, 0xde, 0xbe, 0x6b, 0x70, - 0xff, 0xff, 0xff, 0xff, 0xdf, 0x89, 0x64, 0x60, 0xff, 0xff, 0xff, 0xff, - 0xe0, 0x9e, 0x4d, 0x70, 0xff, 0xff, 0xff, 0xff, 0xe1, 0x69, 0x46, 0x60, - 0xff, 0xff, 0xff, 0xff, 0xe2, 0x7e, 0x2f, 0x70, 0xff, 0xff, 0xff, 0xff, - 0xe3, 0x49, 0x28, 0x60, 0xff, 0xff, 0xff, 0xff, 0xe4, 0x5e, 0x11, 0x70, - 0xff, 0xff, 0xff, 0xff, 0xe5, 0x57, 0x2e, 0xe0, 0xff, 0xff, 0xff, 0xff, - 0xe6, 0x47, 0x2d, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xe7, 0x37, 0x10, 0xe0, - 0xff, 0xff, 0xff, 0xff, 0xe8, 0x27, 0x0f, 0xf0, 0xff, 0xff, 0xff, 0xff, - 0xe9, 0x16, 0xf2, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xea, 0x06, 0xf1, 0xf0, - 0xff, 0xff, 0xff, 0xff, 0xea, 0xf6, 0xd4, 0xe0, 0xff, 0xff, 0xff, 0xff, - 0xeb, 0xe6, 0xd3, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xec, 0xd6, 0xb6, 0xe0, - 0xff, 0xff, 0xff, 0xff, 0xed, 0xc6, 0xb5, 0xf0, 0xff, 0xff, 0xff, 0xff, - 0xee, 0xbf, 0xd3, 0x60, 0xff, 0xff, 0xff, 0xff, 0xef, 0xaf, 0xd2, 0x70, - 0xff, 0xff, 0xff, 0xff, 0xf0, 0x9f, 0xb5, 0x60, 0xff, 0xff, 0xff, 0xff, - 0xf1, 0x8f, 0xb4, 0x70, 0xff, 0xff, 0xff, 0xff, 0xf2, 0x7f, 0x97, 0x60, - 0xff, 0xff, 0xff, 0xff, 0xf3, 0x6f, 0x96, 0x70, 0xff, 0xff, 0xff, 0xff, - 0xf4, 0x5f, 0x79, 0x60, 0xff, 0xff, 0xff, 0xff, 0xf5, 0x4f, 0x78, 0x70, - 0xff, 0xff, 0xff, 0xff, 0xf6, 0x3f, 0x5b, 0x60, 0xff, 0xff, 0xff, 0xff, - 0xf7, 0x2f, 0x5a, 0x70, 0xff, 0xff, 0xff, 0xff, 0xf8, 0x28, 0x77, 0xe0, - 0xff, 0xff, 0xff, 0xff, 0xf9, 0x0f, 0x3c, 0x70, 0xff, 0xff, 0xff, 0xff, - 0xfa, 0x08, 0x59, 0xe0, 0xff, 0xff, 0xff, 0xff, 0xfa, 0xf8, 0x58, 0xf0, - 0xff, 0xff, 0xff, 0xff, 0xfb, 0xe8, 0x3b, 0xe0, 0xff, 0xff, 0xff, 0xff, - 0xfc, 0xd8, 0x3a, 0xf0, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xc8, 0x1d, 0xe0, - 0xff, 0xff, 0xff, 0xff, 0xfe, 0xb8, 0x1c, 0xf0, 0xff, 0xff, 0xff, 0xff, - 0xff, 0xa7, 0xff, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x97, 0xfe, 0xf0, - 0x00, 0x00, 0x00, 0x00, 0x01, 0x87, 0xe1, 0xe0, 0x00, 0x00, 0x00, 0x00, - 0x02, 0x77, 0xe0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x03, 0x70, 0xfe, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x04, 0x60, 0xfd, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x05, 0x50, 0xe0, 0x60, 0x00, 0x00, 0x00, 0x00, 0x06, 0x40, 0xdf, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x07, 0x30, 0xc2, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x07, 0x8d, 0x19, 0x70, 0x00, 0x00, 0x00, 0x00, 0x09, 0x10, 0xa4, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x09, 0xad, 0x94, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x0a, 0xf0, 0x86, 0x60, 0x00, 0x00, 0x00, 0x00, 0x0b, 0xe0, 0x85, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x0c, 0xd9, 0xa2, 0xe0, 0x00, 0x00, 0x00, 0x00, - 0x0d, 0xc0, 0x67, 0x70, 0x00, 0x00, 0x00, 0x00, 0x0e, 0xb9, 0x84, 0xe0, - 0x00, 0x00, 0x00, 0x00, 0x0f, 0xa9, 0x83, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x10, 0x99, 0x66, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x11, 0x89, 0x65, 0xf0, - 0x00, 0x00, 0x00, 0x00, 0x12, 0x79, 0x48, 0xe0, 0x00, 0x00, 0x00, 0x00, - 0x13, 0x69, 0x47, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x14, 0x59, 0x2a, 0xe0, - 0x00, 0x00, 0x00, 0x00, 0x15, 0x49, 0x29, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x16, 0x39, 0x0c, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x17, 0x29, 0x0b, 0xf0, - 0x00, 0x00, 0x00, 0x00, 0x18, 0x22, 0x29, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x19, 0x08, 0xed, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x1a, 0x02, 0x0b, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x1a, 0xf2, 0x0a, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x1b, 0xe1, 0xed, 0x60, 0x00, 0x00, 0x00, 0x00, 0x1c, 0xd1, 0xec, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x1d, 0xc1, 0xcf, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x1e, 0xb1, 0xce, 0x70, 0x00, 0x00, 0x00, 0x00, 0x1f, 0xa1, 0xb1, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x20, 0x76, 0x00, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x21, 0x81, 0x93, 0x60, 0x00, 0x00, 0x00, 0x00, 0x22, 0x55, 0xe2, 0xf0, - 0x00, 0x00, 0x00, 0x00, 0x23, 0x6a, 0xaf, 0xe0, 0x00, 0x00, 0x00, 0x00, - 0x24, 0x35, 0xc4, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x25, 0x4a, 0x91, 0xe0, - 0x00, 0x00, 0x00, 0x00, 0x26, 0x15, 0xa6, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x27, 0x2a, 0x73, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x27, 0xfe, 0xc3, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x29, 0x0a, 0x55, 0xe0, 0x00, 0x00, 0x00, 0x00, - 0x29, 0xde, 0xa5, 0x70, 0x00, 0x00, 0x00, 0x00, 0x2a, 0xea, 0x37, 0xe0, - 0x00, 0x00, 0x00, 0x00, 0x2b, 0xbe, 0x87, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x2c, 0xd3, 0x54, 0x60, 0x00, 0x00, 0x00, 0x00, 0x2d, 0x9e, 0x69, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x2e, 0xb3, 0x36, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x2f, 0x7e, 0x4b, 0x70, 0x00, 0x00, 0x00, 0x00, 0x30, 0x93, 0x18, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x31, 0x67, 0x67, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x32, 0x72, 0xfa, 0x60, 0x00, 0x00, 0x00, 0x00, 0x33, 0x47, 0x49, 0xf0, - 0x00, 0x00, 0x00, 0x00, 0x34, 0x52, 0xdc, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x35, 0x27, 0x2b, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x36, 0x32, 0xbe, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x37, 0x07, 0x0d, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x38, 0x1b, 0xda, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x38, 0xe6, 0xef, 0xf0, - 0x00, 0x00, 0x00, 0x00, 0x39, 0xfb, 0xbc, 0xe0, 0x00, 0x00, 0x00, 0x00, - 0x3a, 0xc6, 0xd1, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x3b, 0xdb, 0x9e, 0xe0, - 0x00, 0x00, 0x00, 0x00, 0x3c, 0xaf, 0xee, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x3d, 0xbb, 0x80, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x3e, 0x8f, 0xd0, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x3f, 0x9b, 0x62, 0xe0, 0x00, 0x00, 0x00, 0x00, - 0x40, 0x6f, 0xb2, 0x70, 0x00, 0x00, 0x00, 0x00, 0x41, 0x84, 0x7f, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x42, 0x4f, 0x94, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x43, 0x64, 0x61, 0x60, 0x00, 0x00, 0x00, 0x00, 0x44, 0x2f, 0x76, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x45, 0x44, 0x43, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x45, 0xf3, 0xa8, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x47, 0x2d, 0x5f, 0xe0, - 0x00, 0x00, 0x00, 0x00, 0x47, 0xd3, 0x8a, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x49, 0x0d, 0x41, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x49, 0xb3, 0x6c, 0xf0, - 0x00, 0x00, 0x00, 0x00, 0x4a, 0xed, 0x23, 0xe0, 0x00, 0x00, 0x00, 0x00, - 0x4b, 0x9c, 0x89, 0x70, 0x00, 0x00, 0x00, 0x00, 0x4c, 0xd6, 0x40, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x4d, 0x7c, 0x6b, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x4e, 0xb6, 0x22, 0x60, 0x00, 0x00, 0x00, 0x00, 0x4f, 0x5c, 0x4d, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x50, 0x96, 0x04, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x51, 0x3c, 0x2f, 0x70, 0x00, 0x00, 0x00, 0x00, 0x52, 0x75, 0xe6, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x53, 0x1c, 0x11, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x54, 0x55, 0xc8, 0x60, 0x00, 0x00, 0x00, 0x00, 0x54, 0xfb, 0xf3, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x56, 0x35, 0xaa, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x56, 0xe5, 0x0f, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x58, 0x1e, 0xc6, 0xe0, - 0x00, 0x00, 0x00, 0x00, 0x58, 0xc4, 0xf1, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x59, 0xfe, 0xa8, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x5a, 0xa4, 0xd3, 0xf0, - 0x00, 0x00, 0x00, 0x00, 0x5b, 0xde, 0x8a, 0xe0, 0x00, 0x00, 0x00, 0x00, - 0x5c, 0x84, 0xb5, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x5d, 0xbe, 0x6c, 0xe0, - 0x00, 0x00, 0x00, 0x00, 0x5e, 0x64, 0x97, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x5f, 0x9e, 0x4e, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x60, 0x4d, 0xb4, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x61, 0x87, 0x6b, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x62, 0x2d, 0x96, 0x70, 0x00, 0x00, 0x00, 0x00, 0x63, 0x67, 0x4d, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x64, 0x0d, 0x78, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x65, 0x47, 0x2f, 0x60, 0x00, 0x00, 0x00, 0x00, 0x65, 0xed, 0x5a, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x67, 0x27, 0x11, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x67, 0xcd, 0x3c, 0x70, 0x00, 0x00, 0x00, 0x00, 0x69, 0x06, 0xf3, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x69, 0xad, 0x1e, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x6a, 0xe6, 0xd5, 0x60, 0x00, 0x00, 0x00, 0x00, 0x6b, 0x96, 0x3a, 0xf0, - 0x00, 0x00, 0x00, 0x00, 0x6c, 0xcf, 0xf1, 0xe0, 0x00, 0x00, 0x00, 0x00, - 0x6d, 0x76, 0x1c, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x6e, 0xaf, 0xd3, 0xe0, - 0x00, 0x00, 0x00, 0x00, 0x6f, 0x55, 0xfe, 0xf0, 0x00, 0x00, 0x00, 0x00, - 0x70, 0x8f, 0xb5, 0xe0, 0x00, 0x00, 0x00, 0x00, 0x71, 0x35, 0xe0, 0xf0, - 0x00, 0x00, 0x00, 0x00, 0x72, 0x6f, 0x97, 0xe0, 0x00, 0x00, 0x00, 0x00, - 0x73, 0x15, 0xc2, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x74, 0x4f, 0x79, 0xe0, - 0x00, 0x00, 0x00, 0x00, 0x74, 0xfe, 0xdf, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x76, 0x38, 0x96, 0x60, 0x00, 0x00, 0x00, 0x00, 0x76, 0xde, 0xc1, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x78, 0x18, 0x78, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x78, 0xbe, 0xa3, 0x70, 0x00, 0x00, 0x00, 0x00, 0x79, 0xf8, 0x5a, 0x60, - 0x00, 0x00, 0x00, 0x00, 0x7a, 0x9e, 0x85, 0x70, 0x00, 0x00, 0x00, 0x00, - 0x7b, 0xd8, 0x3c, 0x60, 0x00, 0x00, 0x00, 0x00, 0x7c, 0x7e, 0x67, 0x70, - 0x00, 0x00, 0x00, 0x00, 0x7d, 0xb8, 0x1e, 0x60, 0x00, 0x00, 0x00, 0x00, - 0x7e, 0x5e, 0x49, 0x70, 0x00, 0x00, 0x00, 0x00, 0x7f, 0x98, 0x00, 0x60, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x03, 0x04, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, - 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0xff, 0xff, 0xba, 0x9e, - 0x00, 0x00, 0xff, 0xff, 0xc7, 0xc0, 0x01, 0x04, 0xff, 0xff, 0xb9, 0xb0, - 0x00, 0x08, 0xff, 0xff, 0xc7, 0xc0, 0x01, 0x0c, 0xff, 0xff, 0xc7, 0xc0, - 0x01, 0x10, 0x4c, 0x4d, 0x54, 0x00, 0x45, 0x44, 0x54, 0x00, 0x45, 0x53, - 0x54, 0x00, 0x45, 0x57, 0x54, 0x00, 0x45, 0x50, 0x54, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01, 0x0a, 0x45, 0x53, 0x54, - 0x35, 0x45, 0x44, 0x54, 0x2c, 0x4d, 0x33, 0x2e, 0x32, 0x2e, 0x30, 0x2c, - 0x4d, 0x31, 0x31, 0x2e, 0x31, 0x2e, 0x30, 0x0a -}; -unsigned int America_New_York_len = 3536; -unsigned char Australia_Sydney[] = { - 0x54, 0x5a, 0x69, 0x66, 0x32, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8e, - 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0e, 0x80, 0x00, 0x00, 0x00, - 0x9c, 0x4e, 0xc2, 0x80, 0x9c, 0xbc, 0x2f, 0x00, 0xcb, 0x54, 0xb3, 0x00, - 0xcb, 0xc7, 0x65, 0x80, 0xcc, 0xb7, 0x56, 0x80, 0xcd, 0xa7, 0x47, 0x80, - 0xce, 0xa0, 0x73, 0x00, 0xcf, 0x87, 0x29, 0x80, 0x03, 0x70, 0x39, 0x80, - 0x04, 0x0d, 0x1c, 0x00, 0x05, 0x50, 0x1b, 0x80, 0x05, 0xf6, 0x38, 0x80, - 0x07, 0x2f, 0xfd, 0x80, 0x07, 0xd6, 0x1a, 0x80, 0x09, 0x0f, 0xdf, 0x80, - 0x09, 0xb5, 0xfc, 0x80, 0x0a, 0xef, 0xc1, 0x80, 0x0b, 0x9f, 0x19, 0x00, - 0x0c, 0xd8, 0xde, 0x00, 0x0d, 0x7e, 0xfb, 0x00, 0x0e, 0xb8, 0xc0, 0x00, - 0x0f, 0x5e, 0xdd, 0x00, 0x10, 0x98, 0xa2, 0x00, 0x11, 0x3e, 0xbf, 0x00, - 0x12, 0x78, 0x84, 0x00, 0x13, 0x1e, 0xa1, 0x00, 0x14, 0x58, 0x66, 0x00, - 0x14, 0xfe, 0x83, 0x00, 0x16, 0x38, 0x48, 0x00, 0x17, 0x0c, 0x89, 0x80, - 0x18, 0x21, 0x64, 0x80, 0x18, 0xc7, 0x81, 0x80, 0x1a, 0x01, 0x46, 0x80, - 0x1a, 0xa7, 0x63, 0x80, 0x1b, 0xe1, 0x28, 0x80, 0x1c, 0x87, 0x45, 0x80, - 0x1d, 0xc1, 0x0a, 0x80, 0x1e, 0x79, 0x9c, 0x80, 0x1f, 0x97, 0xb2, 0x00, - 0x20, 0x59, 0x7e, 0x80, 0x21, 0x80, 0xce, 0x80, 0x22, 0x42, 0x9b, 0x00, - 0x23, 0x69, 0xeb, 0x00, 0x24, 0x22, 0x7d, 0x00, 0x25, 0x49, 0xcd, 0x00, - 0x25, 0xef, 0xea, 0x00, 0x27, 0x29, 0xaf, 0x00, 0x27, 0xcf, 0xcc, 0x00, - 0x29, 0x09, 0x91, 0x00, 0x29, 0xaf, 0xae, 0x00, 0x2a, 0xe9, 0x73, 0x00, - 0x2b, 0x98, 0xca, 0x80, 0x2c, 0xd2, 0x8f, 0x80, 0x2d, 0x78, 0xac, 0x80, - 0x2e, 0xb2, 0x71, 0x80, 0x2f, 0x58, 0x8e, 0x80, 0x30, 0x92, 0x53, 0x80, - 0x31, 0x5d, 0x5a, 0x80, 0x32, 0x72, 0x35, 0x80, 0x33, 0x3d, 0x3c, 0x80, - 0x34, 0x52, 0x17, 0x80, 0x35, 0x1d, 0x1e, 0x80, 0x36, 0x31, 0xf9, 0x80, - 0x36, 0xfd, 0x00, 0x80, 0x38, 0x1b, 0x16, 0x00, 0x38, 0xdc, 0xe2, 0x80, - 0x39, 0xa7, 0xe9, 0x80, 0x3a, 0xbc, 0xc4, 0x80, 0x3b, 0xda, 0xda, 0x00, - 0x3c, 0xa5, 0xe1, 0x00, 0x3d, 0xba, 0xbc, 0x00, 0x3e, 0x85, 0xc3, 0x00, - 0x3f, 0x9a, 0x9e, 0x00, 0x40, 0x65, 0xa5, 0x00, 0x41, 0x83, 0xba, 0x80, - 0x42, 0x45, 0x87, 0x00, 0x43, 0x63, 0x9c, 0x80, 0x44, 0x2e, 0xa3, 0x80, - 0x45, 0x43, 0x7e, 0x80, 0x46, 0x05, 0x4b, 0x00, 0x47, 0x23, 0x60, 0x80, - 0x47, 0xf7, 0xa2, 0x00, 0x48, 0xe7, 0x93, 0x00, 0x49, 0xd7, 0x84, 0x00, - 0x4a, 0xc7, 0x75, 0x00, 0x4b, 0xb7, 0x66, 0x00, 0x4c, 0xa7, 0x57, 0x00, - 0x4d, 0x97, 0x48, 0x00, 0x4e, 0x87, 0x39, 0x00, 0x4f, 0x77, 0x2a, 0x00, - 0x50, 0x70, 0x55, 0x80, 0x51, 0x60, 0x46, 0x80, 0x52, 0x50, 0x37, 0x80, - 0x53, 0x40, 0x28, 0x80, 0x54, 0x30, 0x19, 0x80, 0x55, 0x20, 0x0a, 0x80, - 0x56, 0x0f, 0xfb, 0x80, 0x56, 0xff, 0xec, 0x80, 0x57, 0xef, 0xdd, 0x80, - 0x58, 0xdf, 0xce, 0x80, 0x59, 0xcf, 0xbf, 0x80, 0x5a, 0xbf, 0xb0, 0x80, - 0x5b, 0xb8, 0xdc, 0x00, 0x5c, 0xa8, 0xcd, 0x00, 0x5d, 0x98, 0xbe, 0x00, - 0x5e, 0x88, 0xaf, 0x00, 0x5f, 0x78, 0xa0, 0x00, 0x60, 0x68, 0x91, 0x00, - 0x61, 0x58, 0x82, 0x00, 0x62, 0x48, 0x73, 0x00, 0x63, 0x38, 0x64, 0x00, - 0x64, 0x28, 0x55, 0x00, 0x65, 0x18, 0x46, 0x00, 0x66, 0x11, 0x71, 0x80, - 0x67, 0x01, 0x62, 0x80, 0x67, 0xf1, 0x53, 0x80, 0x68, 0xe1, 0x44, 0x80, - 0x69, 0xd1, 0x35, 0x80, 0x6a, 0xc1, 0x26, 0x80, 0x6b, 0xb1, 0x17, 0x80, - 0x6c, 0xa1, 0x08, 0x80, 0x6d, 0x90, 0xf9, 0x80, 0x6e, 0x80, 0xea, 0x80, - 0x6f, 0x70, 0xdb, 0x80, 0x70, 0x6a, 0x07, 0x00, 0x71, 0x59, 0xf8, 0x00, - 0x72, 0x49, 0xe9, 0x00, 0x73, 0x39, 0xda, 0x00, 0x74, 0x29, 0xcb, 0x00, - 0x75, 0x19, 0xbc, 0x00, 0x76, 0x09, 0xad, 0x00, 0x76, 0xf9, 0x9e, 0x00, - 0x77, 0xe9, 0x8f, 0x00, 0x78, 0xd9, 0x80, 0x00, 0x79, 0xc9, 0x71, 0x00, - 0x7a, 0xb9, 0x62, 0x00, 0x7b, 0xb2, 0x8d, 0x80, 0x7c, 0xa2, 0x7e, 0x80, - 0x7d, 0x92, 0x6f, 0x80, 0x7e, 0x82, 0x60, 0x80, 0x7f, 0x72, 0x51, 0x80, - 0x03, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x00, 0x00, - 0x8d, 0xc4, 0x00, 0x00, 0x00, 0x00, 0x9a, 0xb0, 0x01, 0x04, 0x00, 0x00, - 0x8c, 0xa0, 0x00, 0x09, 0x00, 0x00, 0x8c, 0xa0, 0x00, 0x09, 0x4c, 0x4d, - 0x54, 0x00, 0x41, 0x45, 0x44, 0x54, 0x00, 0x41, 0x45, 0x53, 0x54, 0x00, - 0x00, 0x01, 0x01, 0x00, 0x54, 0x5a, 0x69, 0x66, 0x32, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x8e, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x0e, - 0xff, 0xff, 0xff, 0xff, 0x73, 0x16, 0x7f, 0x3c, 0xff, 0xff, 0xff, 0xff, - 0x9c, 0x4e, 0xc2, 0x80, 0xff, 0xff, 0xff, 0xff, 0x9c, 0xbc, 0x2f, 0x00, - 0xff, 0xff, 0xff, 0xff, 0xcb, 0x54, 0xb3, 0x00, 0xff, 0xff, 0xff, 0xff, - 0xcb, 0xc7, 0x65, 0x80, 0xff, 0xff, 0xff, 0xff, 0xcc, 0xb7, 0x56, 0x80, - 0xff, 0xff, 0xff, 0xff, 0xcd, 0xa7, 0x47, 0x80, 0xff, 0xff, 0xff, 0xff, - 0xce, 0xa0, 0x73, 0x00, 0xff, 0xff, 0xff, 0xff, 0xcf, 0x87, 0x29, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x03, 0x70, 0x39, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x04, 0x0d, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x50, 0x1b, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x05, 0xf6, 0x38, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x07, 0x2f, 0xfd, 0x80, 0x00, 0x00, 0x00, 0x00, 0x07, 0xd6, 0x1a, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x09, 0x0f, 0xdf, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x09, 0xb5, 0xfc, 0x80, 0x00, 0x00, 0x00, 0x00, 0x0a, 0xef, 0xc1, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x0b, 0x9f, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0c, 0xd8, 0xde, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0d, 0x7e, 0xfb, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x0e, 0xb8, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x0f, 0x5e, 0xdd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x98, 0xa2, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x11, 0x3e, 0xbf, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x12, 0x78, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x1e, 0xa1, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x14, 0x58, 0x66, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x14, 0xfe, 0x83, 0x00, 0x00, 0x00, 0x00, 0x00, 0x16, 0x38, 0x48, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x17, 0x0c, 0x89, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x18, 0x21, 0x64, 0x80, 0x00, 0x00, 0x00, 0x00, 0x18, 0xc7, 0x81, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x1a, 0x01, 0x46, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x1a, 0xa7, 0x63, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1b, 0xe1, 0x28, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x1c, 0x87, 0x45, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x1d, 0xc1, 0x0a, 0x80, 0x00, 0x00, 0x00, 0x00, 0x1e, 0x79, 0x9c, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x1f, 0x97, 0xb2, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x20, 0x59, 0x7e, 0x80, 0x00, 0x00, 0x00, 0x00, 0x21, 0x80, 0xce, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x22, 0x42, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x23, 0x69, 0xeb, 0x00, 0x00, 0x00, 0x00, 0x00, 0x24, 0x22, 0x7d, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x25, 0x49, 0xcd, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x25, 0xef, 0xea, 0x00, 0x00, 0x00, 0x00, 0x00, 0x27, 0x29, 0xaf, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x27, 0xcf, 0xcc, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x29, 0x09, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, 0x29, 0xaf, 0xae, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x2a, 0xe9, 0x73, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x2b, 0x98, 0xca, 0x80, 0x00, 0x00, 0x00, 0x00, 0x2c, 0xd2, 0x8f, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x2d, 0x78, 0xac, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x2e, 0xb2, 0x71, 0x80, 0x00, 0x00, 0x00, 0x00, 0x2f, 0x58, 0x8e, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x30, 0x92, 0x53, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x31, 0x5d, 0x5a, 0x80, 0x00, 0x00, 0x00, 0x00, 0x32, 0x72, 0x35, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x33, 0x3d, 0x3c, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x34, 0x52, 0x17, 0x80, 0x00, 0x00, 0x00, 0x00, 0x35, 0x1d, 0x1e, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x36, 0x31, 0xf9, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x36, 0xfd, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x38, 0x1b, 0x16, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x38, 0xdc, 0xe2, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x39, 0xa7, 0xe9, 0x80, 0x00, 0x00, 0x00, 0x00, 0x3a, 0xbc, 0xc4, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x3b, 0xda, 0xda, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3c, 0xa5, 0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3d, 0xba, 0xbc, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x3e, 0x85, 0xc3, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x3f, 0x9a, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x65, 0xa5, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x41, 0x83, 0xba, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x42, 0x45, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x43, 0x63, 0x9c, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x44, 0x2e, 0xa3, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x45, 0x43, 0x7e, 0x80, 0x00, 0x00, 0x00, 0x00, 0x46, 0x05, 0x4b, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x47, 0x23, 0x60, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x47, 0xf7, 0xa2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0xe7, 0x93, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x49, 0xd7, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x4a, 0xc7, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4b, 0xb7, 0x66, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x4c, 0xa7, 0x57, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x4d, 0x97, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, 0x4e, 0x87, 0x39, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x4f, 0x77, 0x2a, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x50, 0x70, 0x55, 0x80, 0x00, 0x00, 0x00, 0x00, 0x51, 0x60, 0x46, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x52, 0x50, 0x37, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x53, 0x40, 0x28, 0x80, 0x00, 0x00, 0x00, 0x00, 0x54, 0x30, 0x19, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x55, 0x20, 0x0a, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x56, 0x0f, 0xfb, 0x80, 0x00, 0x00, 0x00, 0x00, 0x56, 0xff, 0xec, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x57, 0xef, 0xdd, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x58, 0xdf, 0xce, 0x80, 0x00, 0x00, 0x00, 0x00, 0x59, 0xcf, 0xbf, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x5a, 0xbf, 0xb0, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x5b, 0xb8, 0xdc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5c, 0xa8, 0xcd, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x5d, 0x98, 0xbe, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x5e, 0x88, 0xaf, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5f, 0x78, 0xa0, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x60, 0x68, 0x91, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x61, 0x58, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x62, 0x48, 0x73, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x63, 0x38, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x64, 0x28, 0x55, 0x00, 0x00, 0x00, 0x00, 0x00, 0x65, 0x18, 0x46, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x66, 0x11, 0x71, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x67, 0x01, 0x62, 0x80, 0x00, 0x00, 0x00, 0x00, 0x67, 0xf1, 0x53, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x68, 0xe1, 0x44, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x69, 0xd1, 0x35, 0x80, 0x00, 0x00, 0x00, 0x00, 0x6a, 0xc1, 0x26, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x6b, 0xb1, 0x17, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x6c, 0xa1, 0x08, 0x80, 0x00, 0x00, 0x00, 0x00, 0x6d, 0x90, 0xf9, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x6e, 0x80, 0xea, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x6f, 0x70, 0xdb, 0x80, 0x00, 0x00, 0x00, 0x00, 0x70, 0x6a, 0x07, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x71, 0x59, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x72, 0x49, 0xe9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x73, 0x39, 0xda, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x74, 0x29, 0xcb, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x75, 0x19, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x76, 0x09, 0xad, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x76, 0xf9, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x77, 0xe9, 0x8f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x78, 0xd9, 0x80, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x79, 0xc9, 0x71, 0x00, 0x00, 0x00, 0x00, 0x00, - 0x7a, 0xb9, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7b, 0xb2, 0x8d, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x7c, 0xa2, 0x7e, 0x80, 0x00, 0x00, 0x00, 0x00, - 0x7d, 0x92, 0x6f, 0x80, 0x00, 0x00, 0x00, 0x00, 0x7e, 0x82, 0x60, 0x80, - 0x00, 0x00, 0x00, 0x00, 0x7f, 0x72, 0x51, 0x80, 0x03, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, - 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x00, 0x00, 0x8d, 0xc4, 0x00, 0x00, - 0x00, 0x00, 0x9a, 0xb0, 0x01, 0x04, 0x00, 0x00, 0x8c, 0xa0, 0x00, 0x09, - 0x00, 0x00, 0x8c, 0xa0, 0x00, 0x09, 0x4c, 0x4d, 0x54, 0x00, 0x41, 0x45, - 0x44, 0x54, 0x00, 0x41, 0x45, 0x53, 0x54, 0x00, 0x00, 0x01, 0x01, 0x00, - 0x0a, 0x41, 0x45, 0x53, 0x54, 0x2d, 0x31, 0x30, 0x41, 0x45, 0x44, 0x54, - 0x2c, 0x4d, 0x31, 0x30, 0x2e, 0x31, 0x2e, 0x30, 0x2c, 0x4d, 0x34, 0x2e, - 0x31, 0x2e, 0x30, 0x2f, 0x33, 0x0a -}; -unsigned int Australia_Sydney_len = 2190; diff --git a/absl/time/time.cc b/absl/time/time.cc index 1ec2026e..7256a699 100644 --- a/absl/time/time.cc +++ b/absl/time/time.cc @@ -297,7 +297,7 @@ timespec ToTimespec(Time t) { timespec ts; absl::Duration d = time_internal::ToUnixDuration(t); if (!time_internal::IsInfiniteDuration(d)) { - ts.tv_sec = time_internal::GetRepHi(d); + ts.tv_sec = static_cast<decltype(ts.tv_sec)>(time_internal::GetRepHi(d)); if (ts.tv_sec == time_internal::GetRepHi(d)) { // no time_t narrowing ts.tv_nsec = time_internal::GetRepLo(d) / 4; // floor return ts; @@ -316,7 +316,7 @@ timespec ToTimespec(Time t) { timeval ToTimeval(Time t) { timeval tv; timespec ts = absl::ToTimespec(t); - tv.tv_sec = ts.tv_sec; + tv.tv_sec = static_cast<decltype(tv.tv_sec)>(ts.tv_sec); if (tv.tv_sec != ts.tv_sec) { // narrowing if (ts.tv_sec < 0) { tv.tv_sec = std::numeric_limits<decltype(tv.tv_sec)>::min(); diff --git a/absl/time/time.h b/absl/time/time.h index bd01867e..cc390082 100644 --- a/absl/time/time.h +++ b/absl/time/time.h @@ -78,6 +78,7 @@ struct timeval; #include <cmath> #include <cstdint> #include <ctime> +#include <limits> #include <ostream> #include <string> #include <type_traits> @@ -97,19 +98,24 @@ class TimeZone; // Defined below namespace time_internal { int64_t IDivDuration(bool satq, Duration num, Duration den, Duration* rem); -constexpr Time FromUnixDuration(Duration d); -constexpr Duration ToUnixDuration(Time t); -constexpr int64_t GetRepHi(Duration d); -constexpr uint32_t GetRepLo(Duration d); -constexpr Duration MakeDuration(int64_t hi, uint32_t lo); -constexpr Duration MakeDuration(int64_t hi, int64_t lo); -inline Duration MakePosDoubleDuration(double n); +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromUnixDuration(Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration ToUnixDuration(Time t); +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr int64_t GetRepHi(Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr uint32_t GetRepLo(Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration MakeDuration(int64_t hi, + uint32_t lo); +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration MakeDuration(int64_t hi, + int64_t lo); +ABSL_ATTRIBUTE_CONST_FUNCTION inline Duration MakePosDoubleDuration(double n); constexpr int64_t kTicksPerNanosecond = 4; constexpr int64_t kTicksPerSecond = 1000 * 1000 * 1000 * kTicksPerNanosecond; template <std::intmax_t N> -constexpr Duration FromInt64(int64_t v, std::ratio<1, N>); -constexpr Duration FromInt64(int64_t v, std::ratio<60>); -constexpr Duration FromInt64(int64_t v, std::ratio<3600>); +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration FromInt64(int64_t v, + std::ratio<1, N>); +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration FromInt64(int64_t v, + std::ratio<60>); +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration FromInt64(int64_t v, + std::ratio<3600>); template <typename T> using EnableIfIntegral = typename std::enable_if< std::is_integral<T>::value || std::is_enum<T>::value, int>::type; @@ -222,37 +228,61 @@ class Duration { }; // Relational Operators -constexpr bool operator<(Duration lhs, Duration rhs); -constexpr bool operator>(Duration lhs, Duration rhs) { return rhs < lhs; } -constexpr bool operator>=(Duration lhs, Duration rhs) { return !(lhs < rhs); } -constexpr bool operator<=(Duration lhs, Duration rhs) { return !(rhs < lhs); } -constexpr bool operator==(Duration lhs, Duration rhs); -constexpr bool operator!=(Duration lhs, Duration rhs) { return !(lhs == rhs); } +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator<(Duration lhs, + Duration rhs); +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator>(Duration lhs, + Duration rhs) { + return rhs < lhs; +} +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator>=(Duration lhs, + Duration rhs) { + return !(lhs < rhs); +} +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator<=(Duration lhs, + Duration rhs) { + return !(rhs < lhs); +} +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator==(Duration lhs, + Duration rhs); +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator!=(Duration lhs, + Duration rhs) { + return !(lhs == rhs); +} // Additive Operators -constexpr Duration operator-(Duration d); -inline Duration operator+(Duration lhs, Duration rhs) { return lhs += rhs; } -inline Duration operator-(Duration lhs, Duration rhs) { return lhs -= rhs; } +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration operator-(Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION inline Duration operator+(Duration lhs, + Duration rhs) { + return lhs += rhs; +} +ABSL_ATTRIBUTE_CONST_FUNCTION inline Duration operator-(Duration lhs, + Duration rhs) { + return lhs -= rhs; +} // Multiplicative Operators // Integer operands must be representable as int64_t. template <typename T> -Duration operator*(Duration lhs, T rhs) { +ABSL_ATTRIBUTE_CONST_FUNCTION Duration operator*(Duration lhs, T rhs) { return lhs *= rhs; } template <typename T> -Duration operator*(T lhs, Duration rhs) { +ABSL_ATTRIBUTE_CONST_FUNCTION Duration operator*(T lhs, Duration rhs) { return rhs *= lhs; } template <typename T> -Duration operator/(Duration lhs, T rhs) { +ABSL_ATTRIBUTE_CONST_FUNCTION Duration operator/(Duration lhs, T rhs) { return lhs /= rhs; } -inline int64_t operator/(Duration lhs, Duration rhs) { +ABSL_ATTRIBUTE_CONST_FUNCTION inline int64_t operator/(Duration lhs, + Duration rhs) { return time_internal::IDivDuration(true, lhs, rhs, &lhs); // trunc towards zero } -inline Duration operator%(Duration lhs, Duration rhs) { return lhs %= rhs; } +ABSL_ATTRIBUTE_CONST_FUNCTION inline Duration operator%(Duration lhs, + Duration rhs) { + return lhs %= rhs; +} // IDivDuration() // @@ -299,18 +329,20 @@ inline int64_t IDivDuration(Duration num, Duration den, Duration* rem) { // // double d = absl::FDivDuration(absl::Milliseconds(1500), absl::Seconds(1)); // // d == 1.5 -double FDivDuration(Duration num, Duration den); +ABSL_ATTRIBUTE_CONST_FUNCTION double FDivDuration(Duration num, Duration den); // ZeroDuration() // // Returns a zero-length duration. This function behaves just like the default // constructor, but the name helps make the semantics clear at call sites. -constexpr Duration ZeroDuration() { return Duration(); } +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration ZeroDuration() { + return Duration(); +} // AbsDuration() // // Returns the absolute value of a duration. -inline Duration AbsDuration(Duration d) { +ABSL_ATTRIBUTE_CONST_FUNCTION inline Duration AbsDuration(Duration d) { return (d < ZeroDuration()) ? -d : d; } @@ -322,7 +354,7 @@ inline Duration AbsDuration(Duration d) { // // absl::Duration d = absl::Nanoseconds(123456789); // absl::Duration a = absl::Trunc(d, absl::Microseconds(1)); // 123456us -Duration Trunc(Duration d, Duration unit); +ABSL_ATTRIBUTE_CONST_FUNCTION Duration Trunc(Duration d, Duration unit); // Floor() // @@ -333,7 +365,7 @@ Duration Trunc(Duration d, Duration unit); // // absl::Duration d = absl::Nanoseconds(123456789); // absl::Duration b = absl::Floor(d, absl::Microseconds(1)); // 123456us -Duration Floor(Duration d, Duration unit); +ABSL_ATTRIBUTE_CONST_FUNCTION Duration Floor(Duration d, Duration unit); // Ceil() // @@ -344,7 +376,7 @@ Duration Floor(Duration d, Duration unit); // // absl::Duration d = absl::Nanoseconds(123456789); // absl::Duration c = absl::Ceil(d, absl::Microseconds(1)); // 123457us -Duration Ceil(Duration d, Duration unit); +ABSL_ATTRIBUTE_CONST_FUNCTION Duration Ceil(Duration d, Duration unit); // InfiniteDuration() // @@ -380,7 +412,7 @@ Duration Ceil(Duration d, Duration unit); // // The examples involving the `/` operator above also apply to `IDivDuration()` // and `FDivDuration()`. -constexpr Duration InfiniteDuration(); +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration InfiniteDuration(); // Nanoseconds() // Microseconds() @@ -404,27 +436,27 @@ constexpr Duration InfiniteDuration(); // absl::Duration a = absl::Seconds(60); // absl::Duration b = absl::Minutes(1); // b == a template <typename T, time_internal::EnableIfIntegral<T> = 0> -constexpr Duration Nanoseconds(T n) { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration Nanoseconds(T n) { return time_internal::FromInt64(n, std::nano{}); } template <typename T, time_internal::EnableIfIntegral<T> = 0> -constexpr Duration Microseconds(T n) { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration Microseconds(T n) { return time_internal::FromInt64(n, std::micro{}); } template <typename T, time_internal::EnableIfIntegral<T> = 0> -constexpr Duration Milliseconds(T n) { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration Milliseconds(T n) { return time_internal::FromInt64(n, std::milli{}); } template <typename T, time_internal::EnableIfIntegral<T> = 0> -constexpr Duration Seconds(T n) { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration Seconds(T n) { return time_internal::FromInt64(n, std::ratio<1>{}); } template <typename T, time_internal::EnableIfIntegral<T> = 0> -constexpr Duration Minutes(T n) { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration Minutes(T n) { return time_internal::FromInt64(n, std::ratio<60>{}); } template <typename T, time_internal::EnableIfIntegral<T> = 0> -constexpr Duration Hours(T n) { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration Hours(T n) { return time_internal::FromInt64(n, std::ratio<3600>{}); } @@ -438,19 +470,19 @@ constexpr Duration Hours(T n) { // auto a = absl::Seconds(1.5); // OK // auto b = absl::Milliseconds(1500); // BETTER template <typename T, time_internal::EnableIfFloat<T> = 0> -Duration Nanoseconds(T n) { +ABSL_ATTRIBUTE_CONST_FUNCTION Duration Nanoseconds(T n) { return n * Nanoseconds(1); } template <typename T, time_internal::EnableIfFloat<T> = 0> -Duration Microseconds(T n) { +ABSL_ATTRIBUTE_CONST_FUNCTION Duration Microseconds(T n) { return n * Microseconds(1); } template <typename T, time_internal::EnableIfFloat<T> = 0> -Duration Milliseconds(T n) { +ABSL_ATTRIBUTE_CONST_FUNCTION Duration Milliseconds(T n) { return n * Milliseconds(1); } template <typename T, time_internal::EnableIfFloat<T> = 0> -Duration Seconds(T n) { +ABSL_ATTRIBUTE_CONST_FUNCTION Duration Seconds(T n) { if (n >= 0) { // Note: `NaN >= 0` is false. if (n >= static_cast<T>((std::numeric_limits<int64_t>::max)())) { return InfiniteDuration(); @@ -464,11 +496,11 @@ Duration Seconds(T n) { } } template <typename T, time_internal::EnableIfFloat<T> = 0> -Duration Minutes(T n) { +ABSL_ATTRIBUTE_CONST_FUNCTION Duration Minutes(T n) { return n * Minutes(1); } template <typename T, time_internal::EnableIfFloat<T> = 0> -Duration Hours(T n) { +ABSL_ATTRIBUTE_CONST_FUNCTION Duration Hours(T n) { return n * Hours(1); } @@ -488,14 +520,14 @@ Duration Hours(T n) { // // absl::Duration d = absl::Milliseconds(1500); // int64_t isec = absl::ToInt64Seconds(d); // isec == 1 -ABSL_ATTRIBUTE_PURE_FUNCTION int64_t ToInt64Nanoseconds(Duration d); -ABSL_ATTRIBUTE_PURE_FUNCTION int64_t ToInt64Microseconds(Duration d); -ABSL_ATTRIBUTE_PURE_FUNCTION int64_t ToInt64Milliseconds(Duration d); -ABSL_ATTRIBUTE_PURE_FUNCTION int64_t ToInt64Seconds(Duration d); -ABSL_ATTRIBUTE_PURE_FUNCTION int64_t ToInt64Minutes(Duration d); -ABSL_ATTRIBUTE_PURE_FUNCTION int64_t ToInt64Hours(Duration d); - -// ToDoubleNanoSeconds() +ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToInt64Nanoseconds(Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToInt64Microseconds(Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToInt64Milliseconds(Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToInt64Seconds(Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToInt64Minutes(Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToInt64Hours(Duration d); + +// ToDoubleNanoseconds() // ToDoubleMicroseconds() // ToDoubleMilliseconds() // ToDoubleSeconds() @@ -510,12 +542,12 @@ ABSL_ATTRIBUTE_PURE_FUNCTION int64_t ToInt64Hours(Duration d); // // absl::Duration d = absl::Milliseconds(1500); // double dsec = absl::ToDoubleSeconds(d); // dsec == 1.5 -ABSL_ATTRIBUTE_PURE_FUNCTION double ToDoubleNanoseconds(Duration d); -ABSL_ATTRIBUTE_PURE_FUNCTION double ToDoubleMicroseconds(Duration d); -ABSL_ATTRIBUTE_PURE_FUNCTION double ToDoubleMilliseconds(Duration d); -ABSL_ATTRIBUTE_PURE_FUNCTION double ToDoubleSeconds(Duration d); -ABSL_ATTRIBUTE_PURE_FUNCTION double ToDoubleMinutes(Duration d); -ABSL_ATTRIBUTE_PURE_FUNCTION double ToDoubleHours(Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION double ToDoubleNanoseconds(Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION double ToDoubleMicroseconds(Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION double ToDoubleMilliseconds(Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION double ToDoubleSeconds(Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION double ToDoubleMinutes(Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION double ToDoubleHours(Duration d); // FromChrono() // @@ -525,12 +557,18 @@ ABSL_ATTRIBUTE_PURE_FUNCTION double ToDoubleHours(Duration d); // // std::chrono::milliseconds ms(123); // absl::Duration d = absl::FromChrono(ms); -constexpr Duration FromChrono(const std::chrono::nanoseconds& d); -constexpr Duration FromChrono(const std::chrono::microseconds& d); -constexpr Duration FromChrono(const std::chrono::milliseconds& d); -constexpr Duration FromChrono(const std::chrono::seconds& d); -constexpr Duration FromChrono(const std::chrono::minutes& d); -constexpr Duration FromChrono(const std::chrono::hours& d); +ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono( + const std::chrono::nanoseconds& d); +ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono( + const std::chrono::microseconds& d); +ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono( + const std::chrono::milliseconds& d); +ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono( + const std::chrono::seconds& d); +ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono( + const std::chrono::minutes& d); +ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono( + const std::chrono::hours& d); // ToChronoNanoseconds() // ToChronoMicroseconds() @@ -550,18 +588,21 @@ constexpr Duration FromChrono(const std::chrono::hours& d); // auto y = absl::ToChronoNanoseconds(d); // x == y // auto z = absl::ToChronoSeconds(absl::InfiniteDuration()); // // z == std::chrono::seconds::max() -std::chrono::nanoseconds ToChronoNanoseconds(Duration d); -std::chrono::microseconds ToChronoMicroseconds(Duration d); -std::chrono::milliseconds ToChronoMilliseconds(Duration d); -std::chrono::seconds ToChronoSeconds(Duration d); -std::chrono::minutes ToChronoMinutes(Duration d); -std::chrono::hours ToChronoHours(Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION std::chrono::nanoseconds ToChronoNanoseconds( + Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION std::chrono::microseconds ToChronoMicroseconds( + Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION std::chrono::milliseconds ToChronoMilliseconds( + Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION std::chrono::seconds ToChronoSeconds(Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION std::chrono::minutes ToChronoMinutes(Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION std::chrono::hours ToChronoHours(Duration d); // FormatDuration() // // Returns a string representing the duration in the form "72h3m0.5s". // Returns "inf" or "-inf" for +/- `InfiniteDuration()`. -std::string FormatDuration(Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION std::string FormatDuration(Duration d); // Output stream operator. inline std::ostream& operator<<(std::ostream& os, Duration d) { @@ -725,29 +766,49 @@ class Time { }; // Relational Operators -constexpr bool operator<(Time lhs, Time rhs) { return lhs.rep_ < rhs.rep_; } -constexpr bool operator>(Time lhs, Time rhs) { return rhs < lhs; } -constexpr bool operator>=(Time lhs, Time rhs) { return !(lhs < rhs); } -constexpr bool operator<=(Time lhs, Time rhs) { return !(rhs < lhs); } -constexpr bool operator==(Time lhs, Time rhs) { return lhs.rep_ == rhs.rep_; } -constexpr bool operator!=(Time lhs, Time rhs) { return !(lhs == rhs); } +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator<(Time lhs, Time rhs) { + return lhs.rep_ < rhs.rep_; +} +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator>(Time lhs, Time rhs) { + return rhs < lhs; +} +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator>=(Time lhs, Time rhs) { + return !(lhs < rhs); +} +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator<=(Time lhs, Time rhs) { + return !(rhs < lhs); +} +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator==(Time lhs, Time rhs) { + return lhs.rep_ == rhs.rep_; +} +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator!=(Time lhs, Time rhs) { + return !(lhs == rhs); +} // Additive Operators -inline Time operator+(Time lhs, Duration rhs) { return lhs += rhs; } -inline Time operator+(Duration lhs, Time rhs) { return rhs += lhs; } -inline Time operator-(Time lhs, Duration rhs) { return lhs -= rhs; } -inline Duration operator-(Time lhs, Time rhs) { return lhs.rep_ - rhs.rep_; } +ABSL_ATTRIBUTE_CONST_FUNCTION inline Time operator+(Time lhs, Duration rhs) { + return lhs += rhs; +} +ABSL_ATTRIBUTE_CONST_FUNCTION inline Time operator+(Duration lhs, Time rhs) { + return rhs += lhs; +} +ABSL_ATTRIBUTE_CONST_FUNCTION inline Time operator-(Time lhs, Duration rhs) { + return lhs -= rhs; +} +ABSL_ATTRIBUTE_CONST_FUNCTION inline Duration operator-(Time lhs, Time rhs) { + return lhs.rep_ - rhs.rep_; +} // UnixEpoch() // // Returns the `absl::Time` representing "1970-01-01 00:00:00.0 +0000". -constexpr Time UnixEpoch() { return Time(); } +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time UnixEpoch() { return Time(); } // UniversalEpoch() // // Returns the `absl::Time` representing "0001-01-01 00:00:00.0 +0000", the // epoch of the ICU Universal Time Scale. -constexpr Time UniversalEpoch() { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time UniversalEpoch() { // 719162 is the number of days from 0001-01-01 to 1970-01-01, // assuming the Gregorian calendar. return Time( @@ -757,7 +818,7 @@ constexpr Time UniversalEpoch() { // InfiniteFuture() // // Returns an `absl::Time` that is infinitely far in the future. -constexpr Time InfiniteFuture() { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time InfiniteFuture() { return Time(time_internal::MakeDuration((std::numeric_limits<int64_t>::max)(), ~uint32_t{0})); } @@ -765,7 +826,7 @@ constexpr Time InfiniteFuture() { // InfinitePast() // // Returns an `absl::Time` that is infinitely far in the past. -constexpr Time InfinitePast() { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time InfinitePast() { return Time(time_internal::MakeDuration((std::numeric_limits<int64_t>::min)(), ~uint32_t{0})); } @@ -779,13 +840,13 @@ constexpr Time InfinitePast() { // FromUniversal() // // Creates an `absl::Time` from a variety of other representations. -constexpr Time FromUnixNanos(int64_t ns); -constexpr Time FromUnixMicros(int64_t us); -constexpr Time FromUnixMillis(int64_t ms); -constexpr Time FromUnixSeconds(int64_t s); -constexpr Time FromTimeT(time_t t); -Time FromUDate(double udate); -Time FromUniversal(int64_t universal); +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); +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromUnixSeconds(int64_t s); +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromTimeT(time_t t); +ABSL_ATTRIBUTE_CONST_FUNCTION Time FromUDate(double udate); +ABSL_ATTRIBUTE_CONST_FUNCTION Time FromUniversal(int64_t universal); // ToUnixNanos() // ToUnixMicros() @@ -799,13 +860,13 @@ Time FromUniversal(int64_t universal); // 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. -int64_t ToUnixNanos(Time t); -int64_t ToUnixMicros(Time t); -int64_t ToUnixMillis(Time t); -int64_t ToUnixSeconds(Time t); -time_t ToTimeT(Time t); -double ToUDate(Time t); -int64_t ToUniversal(Time t); +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); +ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToUnixSeconds(Time t); +ABSL_ATTRIBUTE_CONST_FUNCTION time_t ToTimeT(Time t); +ABSL_ATTRIBUTE_CONST_FUNCTION double ToUDate(Time t); +ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToUniversal(Time t); // DurationFromTimespec() // DurationFromTimeval() @@ -821,14 +882,14 @@ int64_t ToUniversal(Time t); // and gettimeofday(2)), so conversion functions are provided for both cases. // The "to timespec/val" direction is easily handled via overloading, but // for "from timespec/val" the desired type is part of the function name. -Duration DurationFromTimespec(timespec ts); -Duration DurationFromTimeval(timeval tv); -timespec ToTimespec(Duration d); -timeval ToTimeval(Duration d); -Time TimeFromTimespec(timespec ts); -Time TimeFromTimeval(timeval tv); -timespec ToTimespec(Time t); -timeval ToTimeval(Time t); +ABSL_ATTRIBUTE_CONST_FUNCTION Duration DurationFromTimespec(timespec ts); +ABSL_ATTRIBUTE_CONST_FUNCTION Duration DurationFromTimeval(timeval tv); +ABSL_ATTRIBUTE_CONST_FUNCTION timespec ToTimespec(Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION timeval ToTimeval(Duration d); +ABSL_ATTRIBUTE_CONST_FUNCTION Time TimeFromTimespec(timespec ts); +ABSL_ATTRIBUTE_CONST_FUNCTION Time TimeFromTimeval(timeval tv); +ABSL_ATTRIBUTE_CONST_FUNCTION timespec ToTimespec(Time t); +ABSL_ATTRIBUTE_CONST_FUNCTION timeval ToTimeval(Time t); // FromChrono() // @@ -839,7 +900,8 @@ timeval ToTimeval(Time t); // auto tp = std::chrono::system_clock::from_time_t(123); // absl::Time t = absl::FromChrono(tp); // // t == absl::FromTimeT(123) -Time FromChrono(const std::chrono::system_clock::time_point& tp); +ABSL_ATTRIBUTE_PURE_FUNCTION Time +FromChrono(const std::chrono::system_clock::time_point& tp); // ToChronoTime() // @@ -852,7 +914,8 @@ Time FromChrono(const std::chrono::system_clock::time_point& tp); // absl::Time t = absl::FromTimeT(123); // auto tp = absl::ToChronoTime(t); // // tp == std::chrono::system_clock::from_time_t(123); -std::chrono::system_clock::time_point ToChronoTime(Time); +ABSL_ATTRIBUTE_CONST_FUNCTION std::chrono::system_clock::time_point + ToChronoTime(Time); // AbslParseFlag() // @@ -1124,22 +1187,25 @@ inline TimeZone LocalTimeZone() { // absl::Time t = ...; // absl::TimeZone tz = ...; // const auto cd = absl::ToCivilDay(t, tz); -inline CivilSecond ToCivilSecond(Time t, TimeZone tz) { +ABSL_ATTRIBUTE_PURE_FUNCTION inline CivilSecond ToCivilSecond(Time t, + TimeZone tz) { return tz.At(t).cs; // already a CivilSecond } -inline CivilMinute ToCivilMinute(Time t, TimeZone tz) { +ABSL_ATTRIBUTE_PURE_FUNCTION inline CivilMinute ToCivilMinute(Time t, + TimeZone tz) { return CivilMinute(tz.At(t).cs); } -inline CivilHour ToCivilHour(Time t, TimeZone tz) { +ABSL_ATTRIBUTE_PURE_FUNCTION inline CivilHour ToCivilHour(Time t, TimeZone tz) { return CivilHour(tz.At(t).cs); } -inline CivilDay ToCivilDay(Time t, TimeZone tz) { +ABSL_ATTRIBUTE_PURE_FUNCTION inline CivilDay ToCivilDay(Time t, TimeZone tz) { return CivilDay(tz.At(t).cs); } -inline CivilMonth ToCivilMonth(Time t, TimeZone tz) { +ABSL_ATTRIBUTE_PURE_FUNCTION inline CivilMonth ToCivilMonth(Time t, + TimeZone tz) { return CivilMonth(tz.At(t).cs); } -inline CivilYear ToCivilYear(Time t, TimeZone tz) { +ABSL_ATTRIBUTE_PURE_FUNCTION inline CivilYear ToCivilYear(Time t, TimeZone tz) { return CivilYear(tz.At(t).cs); } @@ -1155,7 +1221,8 @@ inline CivilYear ToCivilYear(Time t, TimeZone tz) { // being when two non-existent civil times map to the same transition time. // // Note: Accepts civil times of any alignment. -inline Time FromCivil(CivilSecond ct, TimeZone tz) { +ABSL_ATTRIBUTE_PURE_FUNCTION inline Time FromCivil(CivilSecond ct, + TimeZone tz) { const auto ti = tz.At(ct); if (ti.kind == TimeZone::TimeInfo::SKIPPED) return ti.trans; return ti.pre; @@ -1240,13 +1307,13 @@ inline Time FromDateTime(int64_t year, int mon, int day, int hour, // instant, so `tm_isdst != 0` returns the DST instant, and `tm_isdst == 0` // returns the non-DST instant, that would have matched if the transition never // happened. -Time FromTM(const struct tm& tm, TimeZone tz); +ABSL_ATTRIBUTE_PURE_FUNCTION Time FromTM(const struct tm& tm, TimeZone tz); // ToTM() // // Converts the given `absl::Time` to a struct tm using the given time zone. // See ctime(3) for a description of the values of the tm fields. -struct tm ToTM(Time t, TimeZone tz); +ABSL_ATTRIBUTE_PURE_FUNCTION struct tm ToTM(Time t, TimeZone tz); // RFC3339_full // RFC3339_sec @@ -1305,13 +1372,14 @@ ABSL_DLL extern const char RFC1123_no_wday[]; // %d %b %E4Y %H:%M:%S %z // `absl::InfinitePast()`, the returned string will be exactly "infinite-past". // In both cases the given format string and `absl::TimeZone` are ignored. // -std::string FormatTime(absl::string_view format, Time t, TimeZone tz); +ABSL_ATTRIBUTE_PURE_FUNCTION std::string FormatTime(absl::string_view format, + Time t, TimeZone tz); // Convenience functions that format the given time using the RFC3339_full // format. The first overload uses the provided TimeZone, while the second // uses LocalTimeZone(). -std::string FormatTime(Time t, TimeZone tz); -std::string FormatTime(Time t); +ABSL_ATTRIBUTE_PURE_FUNCTION std::string FormatTime(Time t, TimeZone tz); +ABSL_ATTRIBUTE_PURE_FUNCTION std::string FormatTime(Time t); // Output stream operator. inline std::ostream& operator<<(std::ostream& os, Time t) { @@ -1389,18 +1457,20 @@ namespace time_internal { // Creates a Duration with a given representation. // REQUIRES: hi,lo is a valid representation of a Duration as specified // in time/duration.cc. -constexpr Duration MakeDuration(int64_t hi, uint32_t lo = 0) { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration MakeDuration(int64_t hi, + uint32_t lo = 0) { return Duration(hi, lo); } -constexpr Duration MakeDuration(int64_t hi, int64_t lo) { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration MakeDuration(int64_t hi, + int64_t lo) { return MakeDuration(hi, static_cast<uint32_t>(lo)); } // Make a Duration value from a floating-point number, as long as that number // is in the range [ 0 .. numeric_limits<int64_t>::max ), that is, as long as // it's positive and can be converted to int64_t without risk of UB. -inline Duration MakePosDoubleDuration(double n) { +ABSL_ATTRIBUTE_CONST_FUNCTION inline Duration MakePosDoubleDuration(double n) { const int64_t int_secs = static_cast<int64_t>(n); const uint32_t ticks = static_cast<uint32_t>( std::round((n - static_cast<double>(int_secs)) * kTicksPerSecond)); @@ -1413,23 +1483,28 @@ inline Duration MakePosDoubleDuration(double n) { // pair. 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 in the resulting Duration. -constexpr Duration MakeNormalizedDuration(int64_t sec, int64_t ticks) { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration MakeNormalizedDuration( + int64_t sec, int64_t ticks) { return (ticks < 0) ? MakeDuration(sec - 1, ticks + kTicksPerSecond) : MakeDuration(sec, ticks); } // Provide access to the Duration representation. -constexpr int64_t GetRepHi(Duration d) { return d.rep_hi_; } -constexpr uint32_t GetRepLo(Duration d) { return d.rep_lo_; } +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr int64_t GetRepHi(Duration d) { + return d.rep_hi_; +} +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr uint32_t GetRepLo(Duration d) { + return d.rep_lo_; +} // Returns true iff d is positive or negative infinity. -constexpr bool IsInfiniteDuration(Duration d) { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool IsInfiniteDuration(Duration d) { return GetRepLo(d) == ~uint32_t{0}; } // Returns an infinite Duration with the opposite sign. // REQUIRES: IsInfiniteDuration(d) -constexpr Duration OppositeInfinity(Duration d) { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration OppositeInfinity(Duration d) { return GetRepHi(d) < 0 ? MakeDuration((std::numeric_limits<int64_t>::max)(), ~uint32_t{0}) : MakeDuration((std::numeric_limits<int64_t>::min)(), @@ -1437,7 +1512,8 @@ constexpr Duration OppositeInfinity(Duration d) { } // Returns (-n)-1 (equivalently -(n+1)) without avoidable overflow. -constexpr int64_t NegateAndSubtractOne(int64_t n) { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr int64_t NegateAndSubtractOne( + int64_t n) { // Note: Good compilers will optimize this expression to ~n when using // a two's-complement representation (which is required for int64_t). return (n < 0) ? -(n + 1) : (-n) - 1; @@ -1447,23 +1523,30 @@ constexpr int64_t NegateAndSubtractOne(int64_t n) { // functions depend on the above mentioned choice of the Unix epoch for the // Time representation (and both need to be Time friends). Without this // knowledge, we would need to add-in/subtract-out UnixEpoch() respectively. -constexpr Time FromUnixDuration(Duration d) { return Time(d); } -constexpr Duration ToUnixDuration(Time t) { return t.rep_; } +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromUnixDuration(Duration d) { + return Time(d); +} +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration ToUnixDuration(Time t) { + return t.rep_; +} template <std::intmax_t N> -constexpr Duration FromInt64(int64_t v, std::ratio<1, N>) { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration FromInt64(int64_t v, + std::ratio<1, N>) { static_assert(0 < N && N <= 1000 * 1000 * 1000, "Unsupported ratio"); // Subsecond ratios cannot overflow. return MakeNormalizedDuration( v / N, v % N * kTicksPerNanosecond * 1000 * 1000 * 1000 / N); } -constexpr Duration FromInt64(int64_t v, std::ratio<60>) { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration FromInt64(int64_t v, + std::ratio<60>) { return (v <= (std::numeric_limits<int64_t>::max)() / 60 && v >= (std::numeric_limits<int64_t>::min)() / 60) ? MakeDuration(v * 60) : v > 0 ? InfiniteDuration() : -InfiniteDuration(); } -constexpr Duration FromInt64(int64_t v, std::ratio<3600>) { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration FromInt64(int64_t v, + std::ratio<3600>) { return (v <= (std::numeric_limits<int64_t>::max)() / 3600 && v >= (std::numeric_limits<int64_t>::min)() / 3600) ? MakeDuration(v * 3600) @@ -1483,40 +1566,44 @@ constexpr auto IsValidRep64(char) -> bool { // Converts a std::chrono::duration to an absl::Duration. template <typename Rep, typename Period> -constexpr Duration FromChrono(const std::chrono::duration<Rep, Period>& d) { +ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono( + const std::chrono::duration<Rep, Period>& d) { static_assert(IsValidRep64<Rep>(0), "duration::rep is invalid"); return FromInt64(int64_t{d.count()}, Period{}); } template <typename Ratio> -int64_t ToInt64(Duration d, Ratio) { +ABSL_ATTRIBUTE_CONST_FUNCTION int64_t ToInt64(Duration d, Ratio) { // Note: This may be used on MSVC, which may have a system_clock period of // std::ratio<1, 10 * 1000 * 1000> return ToInt64Seconds(d * Ratio::den / Ratio::num); } // Fastpath implementations for the 6 common duration units. -inline int64_t ToInt64(Duration d, std::nano) { +ABSL_ATTRIBUTE_CONST_FUNCTION inline int64_t ToInt64(Duration d, std::nano) { return ToInt64Nanoseconds(d); } -inline int64_t ToInt64(Duration d, std::micro) { +ABSL_ATTRIBUTE_CONST_FUNCTION inline int64_t ToInt64(Duration d, std::micro) { return ToInt64Microseconds(d); } -inline int64_t ToInt64(Duration d, std::milli) { +ABSL_ATTRIBUTE_CONST_FUNCTION inline int64_t ToInt64(Duration d, std::milli) { return ToInt64Milliseconds(d); } -inline int64_t ToInt64(Duration d, std::ratio<1>) { +ABSL_ATTRIBUTE_CONST_FUNCTION inline int64_t ToInt64(Duration d, + std::ratio<1>) { return ToInt64Seconds(d); } -inline int64_t ToInt64(Duration d, std::ratio<60>) { +ABSL_ATTRIBUTE_CONST_FUNCTION inline int64_t ToInt64(Duration d, + std::ratio<60>) { return ToInt64Minutes(d); } -inline int64_t ToInt64(Duration d, std::ratio<3600>) { +ABSL_ATTRIBUTE_CONST_FUNCTION inline int64_t ToInt64(Duration d, + std::ratio<3600>) { return ToInt64Hours(d); } // Converts an absl::Duration to a chrono duration of type T. template <typename T> -T ToChronoDuration(Duration d) { +ABSL_ATTRIBUTE_CONST_FUNCTION T ToChronoDuration(Duration d) { using Rep = typename T::rep; using Period = typename T::period; static_assert(IsValidRep64<Rep>(0), "duration::rep is invalid"); @@ -1530,7 +1617,8 @@ T ToChronoDuration(Duration d) { } // namespace time_internal -constexpr bool operator<(Duration lhs, Duration rhs) { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator<(Duration lhs, + Duration rhs) { return time_internal::GetRepHi(lhs) != time_internal::GetRepHi(rhs) ? time_internal::GetRepHi(lhs) < time_internal::GetRepHi(rhs) : time_internal::GetRepHi(lhs) == (std::numeric_limits<int64_t>::min)() @@ -1539,12 +1627,13 @@ constexpr bool operator<(Duration lhs, Duration rhs) { : time_internal::GetRepLo(lhs) < time_internal::GetRepLo(rhs); } -constexpr bool operator==(Duration lhs, Duration rhs) { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr bool operator==(Duration lhs, + Duration rhs) { return time_internal::GetRepHi(lhs) == time_internal::GetRepHi(rhs) && time_internal::GetRepLo(lhs) == time_internal::GetRepLo(rhs); } -constexpr Duration operator-(Duration d) { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration operator-(Duration d) { // This is a little interesting because of the special cases. // // If rep_lo_ is zero, we have it easy; it's safe to negate rep_hi_, we're @@ -1570,47 +1659,53 @@ constexpr Duration operator-(Duration d) { time_internal::GetRepLo(d)); } -constexpr Duration InfiniteDuration() { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration InfiniteDuration() { return time_internal::MakeDuration((std::numeric_limits<int64_t>::max)(), ~uint32_t{0}); } -constexpr Duration FromChrono(const std::chrono::nanoseconds& d) { +ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono( + const std::chrono::nanoseconds& d) { return time_internal::FromChrono(d); } -constexpr Duration FromChrono(const std::chrono::microseconds& d) { +ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono( + const std::chrono::microseconds& d) { return time_internal::FromChrono(d); } -constexpr Duration FromChrono(const std::chrono::milliseconds& d) { +ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono( + const std::chrono::milliseconds& d) { return time_internal::FromChrono(d); } -constexpr Duration FromChrono(const std::chrono::seconds& d) { +ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono( + const std::chrono::seconds& d) { return time_internal::FromChrono(d); } -constexpr Duration FromChrono(const std::chrono::minutes& d) { +ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono( + const std::chrono::minutes& d) { return time_internal::FromChrono(d); } -constexpr Duration FromChrono(const std::chrono::hours& d) { +ABSL_ATTRIBUTE_PURE_FUNCTION constexpr Duration FromChrono( + const std::chrono::hours& d) { return time_internal::FromChrono(d); } -constexpr Time FromUnixNanos(int64_t ns) { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromUnixNanos(int64_t ns) { return time_internal::FromUnixDuration(Nanoseconds(ns)); } -constexpr Time FromUnixMicros(int64_t us) { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromUnixMicros(int64_t us) { return time_internal::FromUnixDuration(Microseconds(us)); } -constexpr Time FromUnixMillis(int64_t ms) { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromUnixMillis(int64_t ms) { return time_internal::FromUnixDuration(Milliseconds(ms)); } -constexpr Time FromUnixSeconds(int64_t s) { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromUnixSeconds(int64_t s) { return time_internal::FromUnixDuration(Seconds(s)); } -constexpr Time FromTimeT(time_t t) { +ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Time FromTimeT(time_t t) { return time_internal::FromUnixDuration(Seconds(t)); } diff --git a/absl/time/time_benchmark.cc b/absl/time/time_benchmark.cc index 99e62799..93a7c41b 100644 --- a/absl/time/time_benchmark.cc +++ b/absl/time/time_benchmark.cc @@ -185,9 +185,11 @@ void BM_Time_FromCivil_Absl(benchmark::State& state) { int i = 0; while (state.KeepRunning()) { if ((i & 1) == 0) { - absl::FromCivil(absl::CivilSecond(2014, 12, 18, 20, 16, 18), tz); + benchmark::DoNotOptimize( + absl::FromCivil(absl::CivilSecond(2014, 12, 18, 20, 16, 18), tz)); } else { - absl::FromCivil(absl::CivilSecond(2013, 11, 15, 18, 30, 27), tz); + benchmark::DoNotOptimize( + absl::FromCivil(absl::CivilSecond(2013, 11, 15, 18, 30, 27), tz)); } ++i; } @@ -224,7 +226,8 @@ BENCHMARK(BM_Time_FromCivil_Libc); void BM_Time_FromCivilUTC_Absl(benchmark::State& state) { const absl::TimeZone tz = absl::UTCTimeZone(); while (state.KeepRunning()) { - absl::FromCivil(absl::CivilSecond(2014, 12, 18, 20, 16, 18), tz); + benchmark::DoNotOptimize( + absl::FromCivil(absl::CivilSecond(2014, 12, 18, 20, 16, 18), tz)); } } BENCHMARK(BM_Time_FromCivilUTC_Absl); @@ -235,9 +238,11 @@ void BM_Time_FromCivilDay0_Absl(benchmark::State& state) { int i = 0; while (state.KeepRunning()) { if ((i & 1) == 0) { - absl::FromCivil(absl::CivilSecond(2014, 12, 0, 20, 16, 18), tz); + benchmark::DoNotOptimize( + absl::FromCivil(absl::CivilSecond(2014, 12, 0, 20, 16, 18), tz)); } else { - absl::FromCivil(absl::CivilSecond(2013, 11, 0, 18, 30, 27), tz); + benchmark::DoNotOptimize( + absl::FromCivil(absl::CivilSecond(2013, 11, 0, 18, 30, 27), tz)); } ++i; } diff --git a/absl/types/compare.h b/absl/types/compare.h index 19b076e7..1a965e97 100644 --- a/absl/types/compare.h +++ b/absl/types/compare.h @@ -44,29 +44,28 @@ namespace compare_internal { using value_type = int8_t; -template <typename T> -struct Fail { - static_assert(sizeof(T) < 0, "Only literal `0` is allowed."); -}; +class OnlyLiteralZero { + // A private type which cannot be named to explicitly cast to it. + struct MatchLiteralZero; -// We need the NullPtrT template to avoid triggering the modernize-use-nullptr -// ClangTidy warning in user code. -template <typename NullPtrT = std::nullptr_t> -struct OnlyLiteralZero { - constexpr OnlyLiteralZero(NullPtrT) noexcept {} // NOLINT + public: + // 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 // Fails compilation when `nullptr` or integral type arguments other than // `int` are passed. This constructor doesn't accept `int` because literal `0` // has type `int`. Literal `0` arguments will be implicitly converted to // `std::nullptr_t` and accepted by the above constructor, while other `int` // arguments will fail to be converted and cause compilation failure. - template < - typename T, - typename = typename std::enable_if< - std::is_same<T, std::nullptr_t>::value || - (std::is_integral<T>::value && !std::is_same<T, int>::value)>::type, - typename = typename Fail<T>::type> - OnlyLiteralZero(T); // NOLINT + template <typename T, typename = typename std::enable_if< + std::is_same<T, std::nullptr_t>::value || + (std::is_integral<T>::value && + !std::is_same<T, int>::value)>::type> + OnlyLiteralZero(T) { // NOLINT + static_assert(sizeof(T) < 0, "Only literal `0` is allowed."); + } }; enum class eq : value_type { @@ -163,18 +162,18 @@ class weak_equality // Comparisons friend constexpr bool operator==( - weak_equality v, compare_internal::OnlyLiteralZero<>) noexcept { + weak_equality v, compare_internal::OnlyLiteralZero) noexcept { return v.value_ == 0; } friend constexpr bool operator!=( - weak_equality v, compare_internal::OnlyLiteralZero<>) noexcept { + weak_equality v, compare_internal::OnlyLiteralZero) noexcept { return v.value_ != 0; } - friend constexpr bool operator==(compare_internal::OnlyLiteralZero<>, + friend constexpr bool operator==(compare_internal::OnlyLiteralZero, weak_equality v) noexcept { return 0 == v.value_; } - friend constexpr bool operator!=(compare_internal::OnlyLiteralZero<>, + friend constexpr bool operator!=(compare_internal::OnlyLiteralZero, weak_equality v) noexcept { return 0 != v.value_; } @@ -214,18 +213,18 @@ class strong_equality } // Comparisons friend constexpr bool operator==( - strong_equality v, compare_internal::OnlyLiteralZero<>) noexcept { + strong_equality v, compare_internal::OnlyLiteralZero) noexcept { return v.value_ == 0; } friend constexpr bool operator!=( - strong_equality v, compare_internal::OnlyLiteralZero<>) noexcept { + strong_equality v, compare_internal::OnlyLiteralZero) noexcept { return v.value_ != 0; } - friend constexpr bool operator==(compare_internal::OnlyLiteralZero<>, + friend constexpr bool operator==(compare_internal::OnlyLiteralZero, strong_equality v) noexcept { return 0 == v.value_; } - friend constexpr bool operator!=(compare_internal::OnlyLiteralZero<>, + friend constexpr bool operator!=(compare_internal::OnlyLiteralZero, strong_equality v) noexcept { return 0 != v.value_; } @@ -277,50 +276,50 @@ class partial_ordering } // Comparisons friend constexpr bool operator==( - partial_ordering v, compare_internal::OnlyLiteralZero<>) noexcept { + partial_ordering v, compare_internal::OnlyLiteralZero) noexcept { return v.is_ordered() && v.value_ == 0; } friend constexpr bool operator!=( - partial_ordering v, compare_internal::OnlyLiteralZero<>) noexcept { + partial_ordering v, compare_internal::OnlyLiteralZero) noexcept { return !v.is_ordered() || v.value_ != 0; } friend constexpr bool operator<( - partial_ordering v, compare_internal::OnlyLiteralZero<>) noexcept { + partial_ordering v, compare_internal::OnlyLiteralZero) noexcept { return v.is_ordered() && v.value_ < 0; } friend constexpr bool operator<=( - partial_ordering v, compare_internal::OnlyLiteralZero<>) noexcept { + partial_ordering v, compare_internal::OnlyLiteralZero) noexcept { return v.is_ordered() && v.value_ <= 0; } friend constexpr bool operator>( - partial_ordering v, compare_internal::OnlyLiteralZero<>) noexcept { + partial_ordering v, compare_internal::OnlyLiteralZero) noexcept { return v.is_ordered() && v.value_ > 0; } friend constexpr bool operator>=( - partial_ordering v, compare_internal::OnlyLiteralZero<>) noexcept { + partial_ordering v, compare_internal::OnlyLiteralZero) noexcept { return v.is_ordered() && v.value_ >= 0; } - friend constexpr bool operator==(compare_internal::OnlyLiteralZero<>, + friend constexpr bool operator==(compare_internal::OnlyLiteralZero, partial_ordering v) noexcept { return v.is_ordered() && 0 == v.value_; } - friend constexpr bool operator!=(compare_internal::OnlyLiteralZero<>, + friend constexpr bool operator!=(compare_internal::OnlyLiteralZero, partial_ordering v) noexcept { return !v.is_ordered() || 0 != v.value_; } - friend constexpr bool operator<(compare_internal::OnlyLiteralZero<>, + friend constexpr bool operator<(compare_internal::OnlyLiteralZero, partial_ordering v) noexcept { return v.is_ordered() && 0 < v.value_; } - friend constexpr bool operator<=(compare_internal::OnlyLiteralZero<>, + friend constexpr bool operator<=(compare_internal::OnlyLiteralZero, partial_ordering v) noexcept { return v.is_ordered() && 0 <= v.value_; } - friend constexpr bool operator>(compare_internal::OnlyLiteralZero<>, + friend constexpr bool operator>(compare_internal::OnlyLiteralZero, partial_ordering v) noexcept { return v.is_ordered() && 0 > v.value_; } - friend constexpr bool operator>=(compare_internal::OnlyLiteralZero<>, + friend constexpr bool operator>=(compare_internal::OnlyLiteralZero, partial_ordering v) noexcept { return v.is_ordered() && 0 >= v.value_; } @@ -369,50 +368,50 @@ class weak_ordering } // Comparisons friend constexpr bool operator==( - weak_ordering v, compare_internal::OnlyLiteralZero<>) noexcept { + weak_ordering v, compare_internal::OnlyLiteralZero) noexcept { return v.value_ == 0; } friend constexpr bool operator!=( - weak_ordering v, compare_internal::OnlyLiteralZero<>) noexcept { + weak_ordering v, compare_internal::OnlyLiteralZero) noexcept { return v.value_ != 0; } friend constexpr bool operator<( - weak_ordering v, compare_internal::OnlyLiteralZero<>) noexcept { + weak_ordering v, compare_internal::OnlyLiteralZero) noexcept { return v.value_ < 0; } friend constexpr bool operator<=( - weak_ordering v, compare_internal::OnlyLiteralZero<>) noexcept { + weak_ordering v, compare_internal::OnlyLiteralZero) noexcept { return v.value_ <= 0; } friend constexpr bool operator>( - weak_ordering v, compare_internal::OnlyLiteralZero<>) noexcept { + weak_ordering v, compare_internal::OnlyLiteralZero) noexcept { return v.value_ > 0; } friend constexpr bool operator>=( - weak_ordering v, compare_internal::OnlyLiteralZero<>) noexcept { + weak_ordering v, compare_internal::OnlyLiteralZero) noexcept { return v.value_ >= 0; } - friend constexpr bool operator==(compare_internal::OnlyLiteralZero<>, + friend constexpr bool operator==(compare_internal::OnlyLiteralZero, weak_ordering v) noexcept { return 0 == v.value_; } - friend constexpr bool operator!=(compare_internal::OnlyLiteralZero<>, + friend constexpr bool operator!=(compare_internal::OnlyLiteralZero, weak_ordering v) noexcept { return 0 != v.value_; } - friend constexpr bool operator<(compare_internal::OnlyLiteralZero<>, + friend constexpr bool operator<(compare_internal::OnlyLiteralZero, weak_ordering v) noexcept { return 0 < v.value_; } - friend constexpr bool operator<=(compare_internal::OnlyLiteralZero<>, + friend constexpr bool operator<=(compare_internal::OnlyLiteralZero, weak_ordering v) noexcept { return 0 <= v.value_; } - friend constexpr bool operator>(compare_internal::OnlyLiteralZero<>, + friend constexpr bool operator>(compare_internal::OnlyLiteralZero, weak_ordering v) noexcept { return 0 > v.value_; } - friend constexpr bool operator>=(compare_internal::OnlyLiteralZero<>, + friend constexpr bool operator>=(compare_internal::OnlyLiteralZero, weak_ordering v) noexcept { return 0 >= v.value_; } @@ -468,50 +467,50 @@ class strong_ordering } // Comparisons friend constexpr bool operator==( - strong_ordering v, compare_internal::OnlyLiteralZero<>) noexcept { + strong_ordering v, compare_internal::OnlyLiteralZero) noexcept { return v.value_ == 0; } friend constexpr bool operator!=( - strong_ordering v, compare_internal::OnlyLiteralZero<>) noexcept { + strong_ordering v, compare_internal::OnlyLiteralZero) noexcept { return v.value_ != 0; } friend constexpr bool operator<( - strong_ordering v, compare_internal::OnlyLiteralZero<>) noexcept { + strong_ordering v, compare_internal::OnlyLiteralZero) noexcept { return v.value_ < 0; } friend constexpr bool operator<=( - strong_ordering v, compare_internal::OnlyLiteralZero<>) noexcept { + strong_ordering v, compare_internal::OnlyLiteralZero) noexcept { return v.value_ <= 0; } friend constexpr bool operator>( - strong_ordering v, compare_internal::OnlyLiteralZero<>) noexcept { + strong_ordering v, compare_internal::OnlyLiteralZero) noexcept { return v.value_ > 0; } friend constexpr bool operator>=( - strong_ordering v, compare_internal::OnlyLiteralZero<>) noexcept { + strong_ordering v, compare_internal::OnlyLiteralZero) noexcept { return v.value_ >= 0; } - friend constexpr bool operator==(compare_internal::OnlyLiteralZero<>, + friend constexpr bool operator==(compare_internal::OnlyLiteralZero, strong_ordering v) noexcept { return 0 == v.value_; } - friend constexpr bool operator!=(compare_internal::OnlyLiteralZero<>, + friend constexpr bool operator!=(compare_internal::OnlyLiteralZero, strong_ordering v) noexcept { return 0 != v.value_; } - friend constexpr bool operator<(compare_internal::OnlyLiteralZero<>, + friend constexpr bool operator<(compare_internal::OnlyLiteralZero, strong_ordering v) noexcept { return 0 < v.value_; } - friend constexpr bool operator<=(compare_internal::OnlyLiteralZero<>, + friend constexpr bool operator<=(compare_internal::OnlyLiteralZero, strong_ordering v) noexcept { return 0 <= v.value_; } - friend constexpr bool operator>(compare_internal::OnlyLiteralZero<>, + friend constexpr bool operator>(compare_internal::OnlyLiteralZero, strong_ordering v) noexcept { return 0 > v.value_; } - friend constexpr bool operator>=(compare_internal::OnlyLiteralZero<>, + friend constexpr bool operator>=(compare_internal::OnlyLiteralZero, strong_ordering v) noexcept { return 0 >= v.value_; } @@ -544,9 +543,9 @@ namespace compare_internal { // Helper functions to do a boolean comparison of two keys given a boolean // or three-way comparator. // SFINAE prevents implicit conversions to bool (such as from int). -template <typename Bool, - absl::enable_if_t<std::is_same<bool, Bool>::value, int> = 0> -constexpr bool compare_result_as_less_than(const Bool r) { return r; } +template <typename BoolT, + absl::enable_if_t<std::is_same<bool, BoolT>::value, int> = 0> +constexpr bool compare_result_as_less_than(const BoolT r) { return r; } constexpr bool compare_result_as_less_than(const absl::weak_ordering r) { return r < 0; } diff --git a/absl/types/internal/span.h b/absl/types/internal/span.h index 112612f4..344ad4db 100644 --- a/absl/types/internal/span.h +++ b/absl/types/internal/span.h @@ -28,10 +28,10 @@ namespace absl { ABSL_NAMESPACE_BEGIN -namespace span_internal { -// A constexpr min function -constexpr size_t Min(size_t a, size_t b) noexcept { return a < b ? a : b; } +template <typename T> +class Span; +namespace span_internal { // Wrappers for access to container data pointers. template <typename C> constexpr auto GetDataImpl(C& c, char) noexcept // NOLINT(runtime/references) @@ -99,28 +99,39 @@ bool LessThanImpl(SpanT<T> a, SpanT<T> b) { return std::lexicographical_compare(a.begin(), a.end(), b.begin(), b.end()); } -// The `IsConvertible` classes here are needed because of the -// `std::is_convertible` bug in libcxx when compiled with GCC. This build -// configuration is used by Android NDK toolchain. Reference link: -// https://bugs.llvm.org/show_bug.cgi?id=27538. template <typename From, typename To> -struct IsConvertibleHelper { - private: - static std::true_type testval(To); - static std::false_type testval(...); +using EnableIfConvertibleTo = + typename std::enable_if<std::is_convertible<From, To>::value>::type; + +// IsView is true for types where the return type of .data() is the same for +// mutable and const instances. This isn't foolproof, but it's only used to +// enable a compiler warning. +template <typename T, typename = void, typename = void> +struct IsView { + static constexpr bool value = false; +}; +template <typename T> +struct IsView< + T, absl::void_t<decltype(span_internal::GetData(std::declval<const T&>()))>, + absl::void_t<decltype(span_internal::GetData(std::declval<T&>()))>> { + private: + using Container = std::remove_const_t<T>; + using ConstData = + decltype(span_internal::GetData(std::declval<const Container&>())); + using MutData = decltype(span_internal::GetData(std::declval<Container&>())); public: - using type = decltype(testval(std::declval<From>())); + static constexpr bool value = std::is_same<ConstData, MutData>::value; }; -template <typename From, typename To> -struct IsConvertible : IsConvertibleHelper<From, To>::type {}; +// These enablers result in 'int' so they can be used as typenames or defaults +// in template paramters lists. +template <typename T> +using EnableIfIsView = std::enable_if_t<IsView<T>::value, int>; + +template <typename T> +using EnableIfNotIsView = std::enable_if_t<!IsView<T>::value, int>; -// TODO(zhangxy): replace `IsConvertible` with `std::is_convertible` once the -// older version of libcxx is not supported. -template <typename From, typename To> -using EnableIfConvertibleTo = - typename std::enable_if<IsConvertible<From, To>::value>::type; } // namespace span_internal ABSL_NAMESPACE_END } // namespace absl diff --git a/absl/types/internal/variant.h b/absl/types/internal/variant.h index 7c402ece..c82ded44 100644 --- a/absl/types/internal/variant.h +++ b/absl/types/internal/variant.h @@ -16,8 +16,8 @@ // separate file to avoid cluttering the top of the API header with // implementation details. -#ifndef ABSL_TYPES_VARIANT_INTERNAL_H_ -#define ABSL_TYPES_VARIANT_INTERNAL_H_ +#ifndef ABSL_TYPES_INTERNAL_VARIANT_H_ +#define ABSL_TYPES_INTERNAL_VARIANT_H_ #include <cassert> #include <cstddef> @@ -449,7 +449,7 @@ struct FlattenIndices; template <std::size_t HeadSize, std::size_t... TailSize> struct FlattenIndices<HeadSize, TailSize...> { - template<class... SizeType> + template <class... SizeType> static constexpr std::size_t Run(std::size_t head, SizeType... tail) { return head + HeadSize * FlattenIndices<TailSize...>::Run(tail...); } @@ -498,8 +498,8 @@ struct VisitIndicesVariadicImpl<absl::index_sequence<N...>, EndIndices...> { }; template <class Op, class... SizeType> - static VisitIndicesResultT<Op, decltype(EndIndices)...> Run( - Op&& op, SizeType... i) { + static VisitIndicesResultT<Op, decltype(EndIndices)...> Run(Op&& op, + SizeType... i) { return VisitIndicesSwitch<NumCasesOfSwitch<EndIndices...>::value>::Run( FlattenedOp<Op>{absl::forward<Op>(op)}, FlattenIndices<(EndIndices + std::size_t{1})...>::Run( @@ -683,13 +683,13 @@ struct VariantCoreAccess { variant_internal::IndexOfConstructedType<Left, QualifiedNew>; void operator()(SizeT<NewIndex::value> /*old_i*/ - ) const { + ) const { Access<NewIndex::value>(*left) = absl::forward<QualifiedNew>(other); } template <std::size_t OldIndex> void operator()(SizeT<OldIndex> /*old_i*/ - ) const { + ) const { using New = typename absl::variant_alternative<NewIndex::value, Left>::type; if (std::is_nothrow_constructible<New, QualifiedNew>::value || @@ -868,18 +868,6 @@ struct IsNeitherSelfNorInPlace<Self, in_place_type_t<T>> : std::false_type {}; template <class Self, std::size_t I> struct IsNeitherSelfNorInPlace<Self, in_place_index_t<I>> : std::false_type {}; -template <class Variant, class T, class = void> -struct ConversionIsPossibleImpl : std::false_type {}; - -template <class Variant, class T> -struct ConversionIsPossibleImpl< - Variant, T, - void_t<decltype(ImaginaryFun<Variant>::Run(std::declval<T>(), {}))>> - : std::true_type {}; - -template <class Variant, class T> -struct ConversionIsPossible : ConversionIsPossibleImpl<Variant, T>::type {}; - template <class Variant, class T> struct IndexOfConstructedType< Variant, T, @@ -1151,16 +1139,16 @@ struct VariantHelper<variant<Ts...>> { // Type metafunction which returns the element type selected if // OverloadSet::Overload() is well-formed when called with argument type U. template <typename U> - using BestMatch = decltype( - variant_internal::OverloadSet<Ts...>::Overload(std::declval<U>())); + using BestMatch = decltype(variant_internal::OverloadSet<Ts...>::Overload( + std::declval<U>())); // Type metafunction which returns true if OverloadSet::Overload() is // well-formed when called with argument type U. // CanAccept can't be just an alias because there is a MSVC bug on parameter // pack expansion involving decltype. template <typename U> - struct CanAccept : - std::integral_constant<bool, !std::is_void<BestMatch<U>>::value> {}; + struct CanAccept + : std::integral_constant<bool, !std::is_void<BestMatch<U>>::value> {}; // Type metafunction which returns true if Other is an instantiation of // variant, and variants's converting constructor from Other will be @@ -1183,8 +1171,8 @@ struct TrivialMoveOnly { // A union's defaulted copy/move constructor is deleted if any variant member's // copy/move constructor is nontrivial. template <typename T> -struct IsTriviallyMoveConstructible: - std::is_move_constructible<Union<T, TrivialMoveOnly>> {}; +struct IsTriviallyMoveConstructible + : std::is_move_constructible<Union<T, TrivialMoveOnly>> {}; // To guarantee triviality of all special-member functions that can be trivial, // we use a chain of conditional bases for each one. @@ -1419,14 +1407,14 @@ class VariantMoveAssignBaseNontrivial : protected VariantCopyBase<T...> { VariantMoveAssignBaseNontrivial& operator=( VariantMoveAssignBaseNontrivial const&) = default; - VariantMoveAssignBaseNontrivial& - operator=(VariantMoveAssignBaseNontrivial&& other) noexcept( - absl::conjunction<std::is_nothrow_move_constructible<T>..., - std::is_nothrow_move_assignable<T>...>::value) { - VisitIndices<sizeof...(T)>::Run( - VariantCoreAccess::MakeMoveAssignVisitor(this, &other), other.index_); - return *this; - } + VariantMoveAssignBaseNontrivial& + operator=(VariantMoveAssignBaseNontrivial&& other) noexcept( + absl::conjunction<std::is_nothrow_move_constructible<T>..., + std::is_nothrow_move_assignable<T>...>::value) { + VisitIndices<sizeof...(T)>::Run( + VariantCoreAccess::MakeMoveAssignVisitor(this, &other), other.index_); + return *this; + } protected: using Base::index_; @@ -1450,12 +1438,12 @@ class VariantCopyAssignBaseNontrivial : protected VariantMoveAssignBase<T...> { VariantCopyAssignBaseNontrivial& operator=( VariantCopyAssignBaseNontrivial&&) = default; - VariantCopyAssignBaseNontrivial& operator=( - const VariantCopyAssignBaseNontrivial& other) { - VisitIndices<sizeof...(T)>::Run( - VariantCoreAccess::MakeCopyAssignVisitor(this, other), other.index_); - return *this; - } + VariantCopyAssignBaseNontrivial& operator=( + const VariantCopyAssignBaseNontrivial& other) { + VisitIndices<sizeof...(T)>::Run( + VariantCoreAccess::MakeCopyAssignVisitor(this, other), other.index_); + return *this; + } protected: using Base::index_; @@ -1643,4 +1631,4 @@ ABSL_NAMESPACE_END } // namespace absl #endif // !defined(ABSL_USES_STD_VARIANT) -#endif // ABSL_TYPES_VARIANT_INTERNAL_H_ +#endif // ABSL_TYPES_INTERNAL_VARIANT_H_ diff --git a/absl/types/optional_test.cc b/absl/types/optional_test.cc index 9477c150..21653a90 100644 --- a/absl/types/optional_test.cc +++ b/absl/types/optional_test.cc @@ -988,8 +988,8 @@ TEST(optionalTest, PointerStuff) { EXPECT_EQ("foo", *opt); const auto& opt_const = opt; EXPECT_EQ("foo", *opt_const); - EXPECT_EQ(opt->size(), 3); - EXPECT_EQ(opt_const->size(), 3); + EXPECT_EQ(opt->size(), 3u); + EXPECT_EQ(opt_const->size(), 3u); constexpr absl::optional<ConstexprType> opt1(1); static_assert((*opt1).x == ConstexprType::kCtorInt, ""); @@ -1523,7 +1523,7 @@ TEST(optionalTest, Hash) { for (int i = 0; i < 100; ++i) { hashcodes.insert(hash(i)); } - EXPECT_GT(hashcodes.size(), 90); + EXPECT_GT(hashcodes.size(), 90u); static_assert(is_hash_enabled_for<absl::optional<int>>::value, ""); static_assert(is_hash_enabled_for<absl::optional<Hashable>>::value, ""); diff --git a/absl/types/span.h b/absl/types/span.h index fdfbd77c..d7bdbb1f 100644 --- a/absl/types/span.h +++ b/absl/types/span.h @@ -60,6 +60,7 @@ #include <type_traits> #include <utility> +#include "absl/base/attributes.h" #include "absl/base/internal/throw_delegate.h" #include "absl/base/macros.h" #include "absl/base/optimization.h" @@ -160,12 +161,12 @@ class Span { // Used to SFINAE-enable a function when the slice elements are const. template <typename U> - using EnableIfConstView = + using EnableIfValueIsConst = typename std::enable_if<std::is_const<T>::value, U>::type; // Used to SFINAE-enable a function when the slice elements are mutable. template <typename U> - using EnableIfMutableView = + using EnableIfValueIsMutable = typename std::enable_if<!std::is_const<T>::value, U>::type; public: @@ -196,13 +197,34 @@ class Span { // Explicit reference constructor for a mutable `Span<T>` type. Can be // replaced with MakeSpan() to infer the type parameter. template <typename V, typename = EnableIfConvertibleFrom<V>, - typename = EnableIfMutableView<V>> - explicit Span(V& v) noexcept // NOLINT(runtime/references) + typename = EnableIfValueIsMutable<V>, + typename = span_internal::EnableIfNotIsView<V>> + explicit Span( + V& v + ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept // NOLINT(runtime/references) : Span(span_internal::GetData(v), v.size()) {} // Implicit reference constructor for a read-only `Span<const T>` type template <typename V, typename = EnableIfConvertibleFrom<V>, - typename = EnableIfConstView<V>> + typename = EnableIfValueIsConst<V>, + typename = span_internal::EnableIfNotIsView<V>> + constexpr Span( + const V& v + ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept // NOLINT(runtime/explicit) + : Span(span_internal::GetData(v), v.size()) {} + + // Overloads of the above two functions that are only enabled for view types. + // This is so we can drop the ABSL_ATTRIBUTE_LIFETIME_BOUND annotation. These + // overloads must be made unique by using a different template parameter list + // (hence the = 0 for the IsView enabler). + template <typename V, typename = EnableIfConvertibleFrom<V>, + typename = EnableIfValueIsMutable<V>, + span_internal::EnableIfIsView<V> = 0> + explicit Span(V& v) noexcept // NOLINT(runtime/references) + : Span(span_internal::GetData(v), v.size()) {} + template <typename V, typename = EnableIfConvertibleFrom<V>, + typename = EnableIfValueIsConst<V>, + span_internal::EnableIfIsView<V> = 0> constexpr Span(const V& v) noexcept // NOLINT(runtime/explicit) : Span(span_internal::GetData(v), v.size()) {} @@ -242,7 +264,7 @@ class Span { // Process(ints); // template <typename LazyT = T, - typename = EnableIfConstView<LazyT>> + typename = EnableIfValueIsConst<LazyT>> Span(std::initializer_list<value_type> v ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept // NOLINT(runtime/explicit) : Span(v.begin(), v.size()) {} @@ -398,7 +420,7 @@ class Span { // absl::MakeSpan(vec).subspan(5); // throws std::out_of_range constexpr Span subspan(size_type pos = 0, size_type len = npos) const { return (pos <= size()) - ? Span(data() + pos, span_internal::Min(size() - pos, len)) + ? Span(data() + pos, (std::min)(size() - pos, len)) : (base_internal::ThrowStdOutOfRange("pos > size()"), Span()); } diff --git a/absl/types/variant_test.cc b/absl/types/variant_test.cc index cf237334..4cd5b7a3 100644 --- a/absl/types/variant_test.cc +++ b/absl/types/variant_test.cc @@ -281,7 +281,7 @@ TEST(VariantTest, TestDefaultConstructor) { using X = variant<int>; constexpr variant<int> x{}; ASSERT_FALSE(x.valueless_by_exception()); - ASSERT_EQ(0, x.index()); + ASSERT_EQ(0u, x.index()); EXPECT_EQ(0, absl::get<0>(x)); EXPECT_TRUE(std::is_nothrow_default_constructible<X>::value); } @@ -290,7 +290,7 @@ TEST(VariantTest, TestDefaultConstructor) { using X = variant<NonNoexceptDefaultConstructible>; X x{}; ASSERT_FALSE(x.valueless_by_exception()); - ASSERT_EQ(0, x.index()); + ASSERT_EQ(0u, x.index()); EXPECT_EQ(5, absl::get<0>(x).value); EXPECT_FALSE(std::is_nothrow_default_constructible<X>::value); } @@ -299,7 +299,7 @@ TEST(VariantTest, TestDefaultConstructor) { using X = variant<int, NonNoexceptDefaultConstructible>; X x{}; ASSERT_FALSE(x.valueless_by_exception()); - ASSERT_EQ(0, x.index()); + ASSERT_EQ(0u, x.index()); EXPECT_EQ(0, absl::get<0>(x)); EXPECT_TRUE(std::is_nothrow_default_constructible<X>::value); } @@ -308,7 +308,7 @@ TEST(VariantTest, TestDefaultConstructor) { using X = variant<NonNoexceptDefaultConstructible, int>; X x{}; ASSERT_FALSE(x.valueless_by_exception()); - ASSERT_EQ(0, x.index()); + ASSERT_EQ(0u, x.index()); EXPECT_EQ(5, absl::get<0>(x).value); EXPECT_FALSE(std::is_nothrow_default_constructible<X>::value); } @@ -480,7 +480,7 @@ TEST(VariantTest, InPlaceType) { ASSERT_TRUE(absl::holds_alternative<std::string>(v2)); EXPECT_EQ("ABC", absl::get<std::string>(v2)); - Var v3(in_place_type_t<std::string>(), "ABC", 2); + Var v3(in_place_type_t<std::string>(), "ABC", 2u); ASSERT_TRUE(absl::holds_alternative<std::string>(v3)); EXPECT_EQ("AB", absl::get<std::string>(v3)); @@ -503,7 +503,7 @@ TEST(VariantTest, InPlaceTypeVariableTemplate) { ASSERT_TRUE(absl::holds_alternative<std::string>(v2)); EXPECT_EQ("ABC", absl::get<std::string>(v2)); - Var v3(in_place_type<std::string>, "ABC", 2); + Var v3(in_place_type<std::string>, "ABC", 2u); ASSERT_TRUE(absl::holds_alternative<std::string>(v3)); EXPECT_EQ("AB", absl::get<std::string>(v3)); @@ -544,7 +544,7 @@ TEST(VariantTest, InPlaceIndex) { ASSERT_TRUE(absl::holds_alternative<std::string>(v2)); EXPECT_EQ("ABC", absl::get<std::string>(v2)); - Var v3(in_place_index_t<1>(), "ABC", 2); + Var v3(in_place_index_t<1>(), "ABC", 2u); ASSERT_TRUE(absl::holds_alternative<std::string>(v3)); EXPECT_EQ("AB", absl::get<std::string>(v3)); @@ -571,7 +571,7 @@ TEST(VariantTest, InPlaceIndexVariableTemplate) { ASSERT_TRUE(absl::holds_alternative<std::string>(v2)); EXPECT_EQ("ABC", absl::get<std::string>(v2)); - Var v3(in_place_index<1>, "ABC", 2); + Var v3(in_place_index<1>, "ABC", 2u); ASSERT_TRUE(absl::holds_alternative<std::string>(v3)); EXPECT_EQ("AB", absl::get<std::string>(v3)); @@ -688,11 +688,11 @@ TEST(VariantTest, TestSelfAssignment) { EXPECT_EQ(long_str, foo); variant<int, std::string> so = long_str; - ASSERT_EQ(1, so.index()); + ASSERT_EQ(1u, so.index()); EXPECT_EQ(long_str, absl::get<1>(so)); so = *&so; - ASSERT_EQ(1, so.index()); + ASSERT_EQ(1u, so.index()); EXPECT_EQ(long_str, absl::get<1>(so)); } @@ -968,16 +968,16 @@ TEST(VariantTest, Index) { using Var = variant<int, std::string, double>; Var v = 1; - EXPECT_EQ(0, v.index()); + EXPECT_EQ(0u, v.index()); v = "str"; - EXPECT_EQ(1, v.index()); + EXPECT_EQ(1u, v.index()); v = 0.; - EXPECT_EQ(2, v.index()); + EXPECT_EQ(2u, v.index()); Var v2 = v; - EXPECT_EQ(2, v2.index()); + EXPECT_EQ(2u, v2.index()); v2.emplace<int>(3); - EXPECT_EQ(0, v2.index()); + EXPECT_EQ(0u, v2.index()); } TEST(VariantTest, NotValuelessByException) { @@ -1002,11 +1002,11 @@ TEST(VariantTest, IndexValuelessByException) { using Var = variant<MoveCanThrow, std::string, double>; Var v(absl::in_place_index<0>); - EXPECT_EQ(0, v.index()); + EXPECT_EQ(0u, v.index()); ToValuelessByException(v); EXPECT_EQ(absl::variant_npos, v.index()); v = "str"; - EXPECT_EQ(1, v.index()); + EXPECT_EQ(1u, v.index()); } TEST(VariantTest, ValuelessByException) { @@ -1084,18 +1084,18 @@ TEST(VariantTest, MemberSwap) { TEST(VariantTest, VariantSize) { { using Size1Variant = absl::variant<int>; - EXPECT_EQ(1, absl::variant_size<Size1Variant>::value); - EXPECT_EQ(1, absl::variant_size<const Size1Variant>::value); - EXPECT_EQ(1, absl::variant_size<volatile Size1Variant>::value); - EXPECT_EQ(1, absl::variant_size<const volatile Size1Variant>::value); + EXPECT_EQ(1u, absl::variant_size<Size1Variant>::value); + EXPECT_EQ(1u, absl::variant_size<const Size1Variant>::value); + EXPECT_EQ(1u, absl::variant_size<volatile Size1Variant>::value); + EXPECT_EQ(1u, absl::variant_size<const volatile Size1Variant>::value); } { using Size3Variant = absl::variant<int, float, int>; - EXPECT_EQ(3, absl::variant_size<Size3Variant>::value); - EXPECT_EQ(3, absl::variant_size<const Size3Variant>::value); - EXPECT_EQ(3, absl::variant_size<volatile Size3Variant>::value); - EXPECT_EQ(3, absl::variant_size<const volatile Size3Variant>::value); + EXPECT_EQ(3u, absl::variant_size<Size3Variant>::value); + EXPECT_EQ(3u, absl::variant_size<const Size3Variant>::value); + EXPECT_EQ(3u, absl::variant_size<volatile Size3Variant>::value); + EXPECT_EQ(3u, absl::variant_size<const volatile Size3Variant>::value); } } @@ -1799,14 +1799,14 @@ TEST(VariantTest, VisitSimple) { EXPECT_EQ("B", piece); struct StrLen { - int operator()(const char* s) const { return strlen(s); } - int operator()(const std::string& s) const { return s.size(); } + size_t operator()(const char* s) const { return strlen(s); } + size_t operator()(const std::string& s) const { return s.size(); } }; v = "SomeStr"; - EXPECT_EQ(7, absl::visit(StrLen{}, v)); + EXPECT_EQ(7u, absl::visit(StrLen{}, v)); v = std::string("VeryLargeThisTime"); - EXPECT_EQ(17, absl::visit(StrLen{}, v)); + EXPECT_EQ(17u, absl::visit(StrLen{}, v)); } TEST(VariantTest, VisitRValue) { @@ -1979,7 +1979,7 @@ TEST(VariantTest, MonostateBasic) { TEST(VariantTest, VariantMonostateDefaultConstruction) { absl::variant<absl::monostate, NonDefaultConstructible> var; - EXPECT_EQ(var.index(), 0); + EXPECT_EQ(var.index(), 0u); } //////////////////////////////// @@ -2100,7 +2100,7 @@ TEST(VariantTest, Hash) { for (int i = 0; i < 100; ++i) { hashcodes.insert(hash(i)); } - EXPECT_GT(hashcodes.size(), 90); + EXPECT_GT(hashcodes.size(), 90u); // test const-qualified static_assert(type_traits_internal::IsHashable<variant<const int>>::value, @@ -2312,9 +2312,9 @@ TEST(VariantTest, TestRvalueConversion) { EXPECT_EQ(42, absl::get<int32_t>(variant2)); variant2 = - ConvertVariantTo<variant<int32_t, uint32_t>>(variant<uint32_t>(42)); + ConvertVariantTo<variant<int32_t, uint32_t>>(variant<uint32_t>(42u)); ASSERT_TRUE(absl::holds_alternative<uint32_t>(variant2)); - EXPECT_EQ(42, absl::get<uint32_t>(variant2)); + EXPECT_EQ(42u, absl::get<uint32_t>(variant2)); #endif // !ABSL_USES_STD_VARIANT variant<Convertible1, Convertible2> variant3( @@ -2361,10 +2361,10 @@ TEST(VariantTest, TestLvalueConversion) { ASSERT_TRUE(absl::holds_alternative<int32_t>(variant2)); EXPECT_EQ(42, absl::get<int32_t>(variant2)); - variant<uint32_t> source6(42); + variant<uint32_t> source6(42u); variant2 = ConvertVariantTo<variant<int32_t, uint32_t>>(source6); ASSERT_TRUE(absl::holds_alternative<uint32_t>(variant2)); - EXPECT_EQ(42, absl::get<uint32_t>(variant2)); + EXPECT_EQ(42u, absl::get<uint32_t>(variant2)); #endif variant<Convertible2, Convertible1> source7((Convertible1())); @@ -2455,8 +2455,8 @@ TEST(VariantTest, TestRvalueConversionViaConvertVariantTo) { EXPECT_THAT(absl::get_if<int32_t>(&variant2), Pointee(42)); variant2 = - ConvertVariantTo<variant<int32_t, uint32_t>>(variant<uint32_t>(42)); - EXPECT_THAT(absl::get_if<uint32_t>(&variant2), Pointee(42)); + ConvertVariantTo<variant<int32_t, uint32_t>>(variant<uint32_t>(42u)); + EXPECT_THAT(absl::get_if<uint32_t>(&variant2), Pointee(42u)); #endif variant<Convertible1, Convertible2> variant3( @@ -2499,9 +2499,9 @@ TEST(VariantTest, TestLvalueConversionViaConvertVariantTo) { ConvertVariantTo<variant<int32_t, uint32_t>>(source5)); EXPECT_THAT(absl::get_if<int32_t>(&variant2), Pointee(42)); - variant<uint32_t> source6(42); + variant<uint32_t> source6(42u); variant2 = ConvertVariantTo<variant<int32_t, uint32_t>>(source6); - EXPECT_THAT(absl::get_if<uint32_t>(&variant2), Pointee(42)); + EXPECT_THAT(absl::get_if<uint32_t>(&variant2), Pointee(42u)); #endif // !ABSL_USES_STD_VARIANT variant<Convertible2, Convertible1> source7((Convertible1())); @@ -2570,7 +2570,7 @@ TEST(VariantTest, TestVectorOfMoveonlyVariant) { vec.reserve(3); auto another_vec = absl::move(vec); // As a sanity check, verify vector contents. - ASSERT_EQ(2, another_vec.size()); + ASSERT_EQ(2u, another_vec.size()); EXPECT_EQ(42, *absl::get<std::unique_ptr<int>>(another_vec[0])); EXPECT_EQ("Hello", absl::get<std::string>(another_vec[1])); } diff --git a/absl/utility/utility_test.cc b/absl/utility/utility_test.cc index f044ad64..2f0509aa 100644 --- a/absl/utility/utility_test.cc +++ b/absl/utility/utility_test.cc @@ -1,4 +1,4 @@ -// Copyright 2017 The Abseil Authors. +// 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. @@ -14,10 +14,12 @@ #include "absl/utility/utility.h" +#include <memory> #include <sstream> #include <string> #include <tuple> #include <type_traits> +#include <utility> #include <vector> #include "gmock/gmock.h" @@ -35,10 +37,10 @@ namespace { // 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 +#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; @@ -227,8 +229,7 @@ TEST(ApplyTest, NonCopyableArgument) { } TEST(ApplyTest, NonCopyableResult) { - EXPECT_THAT(absl::apply(Factory, std::make_tuple(42)), - ::testing::Pointee(42)); + EXPECT_THAT(absl::apply(Factory, std::make_tuple(42)), Pointee(42)); } TEST(ApplyTest, VoidResult) { absl::apply(NoOp, std::tuple<>()); } @@ -373,4 +374,3 @@ TEST(MakeFromTupleTest, Pair) { } } // namespace - diff --git a/ci/absl_alternate_options.h b/ci/absl_alternate_options.h index 29b020d9..82d2ecf8 100644 --- a/ci/absl_alternate_options.h +++ b/ci/absl_alternate_options.h @@ -15,8 +15,8 @@ // Alternate options.h file, used in continuous integration testing to exercise // option settings not used by default. -#ifndef ABSL_BASE_OPTIONS_H_ -#define ABSL_BASE_OPTIONS_H_ +#ifndef ABSL_CI_ABSL_ALTERNATE_OPTIONS_H_ +#define ABSL_CI_ABSL_ALTERNATE_OPTIONS_H_ #define ABSL_OPTION_USE_STD_ANY 0 #define ABSL_OPTION_USE_STD_OPTIONAL 0 @@ -26,4 +26,4 @@ #define ABSL_OPTION_INLINE_NAMESPACE_NAME ns #define ABSL_OPTION_HARDENED 1 -#endif // ABSL_BASE_OPTIONS_H_ +#endif // ABSL_CI_ABSL_ALTERNATE_OPTIONS_H_ diff --git a/ci/cmake_common.sh b/ci/cmake_common.sh index 3c08255b..373aaa9c 100644 --- a/ci/cmake_common.sh +++ b/ci/cmake_common.sh @@ -14,7 +14,7 @@ # 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="15460959cbbfa20e66ef0b5ab497367e47fc0a04" # release-1.12.0 +readonly ABSL_GOOGLETEST_COMMIT="b796f7d44681514f58a683a3a71ff17c94edb0c1" # v1.13.0 # 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/cmake_install_test.sh b/ci/cmake_install_test.sh index 97ed8478..ab3b86f0 100755 --- a/ci/cmake_install_test.sh +++ b/ci/cmake_install_test.sh @@ -29,6 +29,18 @@ source "${ABSEIL_ROOT}/ci/cmake_common.sh" source "${ABSEIL_ROOT}/ci/linux_docker_containers.sh" readonly DOCKER_CONTAINER=${LINUX_GCC_LATEST_CONTAINER} +# Verify that everything works with the standard "cmake && make && make install" +# without building tests or requiring GoogleTest. +time docker run \ + --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp-ro,readonly \ + --tmpfs=/buildfs:exec \ + --workdir=/buildfs \ + --rm \ + ${DOCKER_EXTRA_ARGS:-} \ + ${DOCKER_CONTAINER} \ + /bin/bash -c "cmake /abseil-cpp-ro && make -j$(nproc) && make install" + +# Verify that a more complicated project works. for link_type in ${LINK_TYPE}; do time docker run \ --mount type=bind,source="${ABSEIL_ROOT}",target=/abseil-cpp-ro,readonly \ diff --git a/ci/linux_clang-latest_libcxx_asan_bazel.sh b/ci/linux_clang-latest_libcxx_asan_bazel.sh index 196e5c1d..f9c146b0 100755 --- a/ci/linux_clang-latest_libcxx_asan_bazel.sh +++ b/ci/linux_clang-latest_libcxx_asan_bazel.sh @@ -25,7 +25,7 @@ if [[ -z ${ABSEIL_ROOT:-} ]]; then fi if [[ -z ${STD:-} ]]; then - STD="c++11 c++14 c++17 c++20" + STD="c++14 c++17 c++20" fi if [[ -z ${COMPILATION_MODE:-} ]]; then @@ -85,6 +85,7 @@ for std in ${STD}; do --copt="-fno-sanitize-blacklist" \ --copt=-Werror \ --distdir="/bazel-distdir" \ + --features=external_include_paths \ --keep_going \ --linkopt="-fsanitize=address" \ --linkopt="-fsanitize-link-c++-runtime" \ diff --git a/ci/linux_clang-latest_libcxx_bazel.sh b/ci/linux_clang-latest_libcxx_bazel.sh index 39aecf58..38b2d744 100755 --- a/ci/linux_clang-latest_libcxx_bazel.sh +++ b/ci/linux_clang-latest_libcxx_bazel.sh @@ -25,7 +25,7 @@ if [[ -z ${ABSEIL_ROOT:-} ]]; then fi if [[ -z ${STD:-} ]]; then - STD="c++11 c++14 c++17 c++20" + STD="c++14 c++17 c++20" fi if [[ -z ${COMPILATION_MODE:-} ]]; then @@ -87,6 +87,7 @@ for std in ${STD}; do --copt=-Werror \ --define=\"absl=1\" \ --distdir=\"/bazel-distdir\" \ + --features=external_include_paths \ --keep_going \ --show_timestamps \ --test_env=\"GTEST_INSTALL_FAILURE_SIGNAL_HANDLER=1\" \ diff --git a/ci/linux_clang-latest_libcxx_tsan_bazel.sh b/ci/linux_clang-latest_libcxx_tsan_bazel.sh index b0a1abff..34d7940e 100755 --- a/ci/linux_clang-latest_libcxx_tsan_bazel.sh +++ b/ci/linux_clang-latest_libcxx_tsan_bazel.sh @@ -25,7 +25,7 @@ if [[ -z ${ABSEIL_ROOT:-} ]]; then fi if [[ -z ${STD:-} ]]; then - STD="c++11 c++14 c++17 c++20" + STD="c++14 c++17 c++20" fi if [[ -z ${COMPILATION_MODE:-} ]]; then @@ -83,10 +83,10 @@ for std in ${STD}; do --copt="-fno-sanitize-blacklist" \ --copt=-Werror \ --distdir="/bazel-distdir" \ + --features=external_include_paths \ --keep_going \ --linkopt="-fsanitize=thread" \ --show_timestamps \ - --test_env="TSAN_OPTIONS=report_atomic_races=0" \ --test_env="TSAN_SYMBOLIZER_PATH=/opt/llvm/clang/bin/llvm-symbolizer" \ --test_env="TZDIR=/abseil-cpp/absl/time/internal/cctz/testdata/zoneinfo" \ --test_output=errors \ diff --git a/ci/linux_clang-latest_libstdcxx_bazel.sh b/ci/linux_clang-latest_libstdcxx_bazel.sh index 8550481b..13d56fc9 100755 --- a/ci/linux_clang-latest_libstdcxx_bazel.sh +++ b/ci/linux_clang-latest_libstdcxx_bazel.sh @@ -25,7 +25,7 @@ if [[ -z ${ABSEIL_ROOT:-} ]]; then fi if [[ -z ${STD:-} ]]; then - STD="c++11 c++14 c++17" + STD="c++14 c++17" fi if [[ -z ${COMPILATION_MODE:-} ]]; then @@ -77,9 +77,11 @@ for std in ${STD}; do --copt="--gcc-toolchain=/usr/local" \ --copt="-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1" \ --copt="${exceptions_mode}" \ + --copt="-march=haswell" \ --copt=-Werror \ --define="absl=1" \ --distdir="/bazel-distdir" \ + --features=external_include_paths \ --keep_going \ --linkopt="--gcc-toolchain=/usr/local" \ --show_timestamps \ diff --git a/ci/linux_docker_containers.sh b/ci/linux_docker_containers.sh index f55e153b..dcaf18b9 100644 --- a/ci/linux_docker_containers.sh +++ b/ci/linux_docker_containers.sh @@ -16,6 +16,6 @@ # 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:20220217" -readonly LINUX_GCC_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20220217" -readonly LINUX_GCC_FLOOR_CONTAINER="gcr.io/google.com/absl-177019/linux_gcc-floor:20220621" +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_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 8b41b5d7..68b39994 100755 --- a/ci/linux_gcc-floor_libstdcxx_bazel.sh +++ b/ci/linux_gcc-floor_libstdcxx_bazel.sh @@ -25,7 +25,7 @@ if [[ -z ${ABSEIL_ROOT:-} ]]; then fi if [[ -z ${STD:-} ]]; then - STD="c++11 c++14" + STD="c++14" fi if [[ -z ${COMPILATION_MODE:-} ]]; then @@ -79,6 +79,7 @@ for std in ${STD}; do --copt=-Werror \ --define="absl=1" \ --distdir="/bazel-distdir" \ + --features=external_include_paths \ --keep_going \ --show_timestamps \ --test_env="GTEST_INSTALL_FAILURE_SIGNAL_HANDLER=1" \ diff --git a/ci/linux_gcc-latest_libstdcxx_bazel.sh b/ci/linux_gcc-latest_libstdcxx_bazel.sh index 3f7c71af..091acb33 100755 --- a/ci/linux_gcc-latest_libstdcxx_bazel.sh +++ b/ci/linux_gcc-latest_libstdcxx_bazel.sh @@ -25,7 +25,7 @@ if [[ -z ${ABSEIL_ROOT:-} ]]; then fi if [[ -z ${STD:-} ]]; then - STD="c++11 c++14 c++17 c++20" + STD="c++14 c++17 c++20" fi if [[ -z ${COMPILATION_MODE:-} ]]; then @@ -85,6 +85,7 @@ for std in ${STD}; do --copt=-Werror \ --define=\"absl=1\" \ --distdir=\"/bazel-distdir\" \ + --features=external_include_paths \ --keep_going \ --show_timestamps \ --test_env=\"GTEST_INSTALL_FAILURE_SIGNAL_HANDLER=1\" \ diff --git a/ci/linux_gcc-latest_libstdcxx_cmake.sh b/ci/linux_gcc-latest_libstdcxx_cmake.sh index eccb3818..1f721236 100755 --- a/ci/linux_gcc-latest_libstdcxx_cmake.sh +++ b/ci/linux_gcc-latest_libstdcxx_cmake.sh @@ -23,7 +23,7 @@ fi source "${ABSEIL_ROOT}/ci/cmake_common.sh" if [[ -z ${ABSL_CMAKE_CXX_STANDARDS:-} ]]; then - ABSL_CMAKE_CXX_STANDARDS="11 14 17 20" + ABSL_CMAKE_CXX_STANDARDS="14 17 20" fi if [[ -z ${ABSL_CMAKE_BUILD_TYPES:-} ]]; then @@ -59,6 +59,7 @@ for std in ${ABSL_CMAKE_CXX_STANDARDS}; do -DCMAKE_CXX_STANDARD=${std} \ -DCMAKE_MODULE_LINKER_FLAGS=\"-Wl,--no-undefined\" && \ make -j$(nproc) && \ + TZDIR=/abseil-cpp/absl/time/internal/cctz/testdata/zoneinfo \ ctest -j$(nproc) --output-on-failure" done done diff --git a/ci/linux_gcc_alpine_cmake.sh b/ci/linux_gcc_alpine_cmake.sh index bf2e1239..b784456f 100755 --- a/ci/linux_gcc_alpine_cmake.sh +++ b/ci/linux_gcc_alpine_cmake.sh @@ -23,7 +23,7 @@ fi source "${ABSEIL_ROOT}/ci/cmake_common.sh" if [[ -z ${ABSL_CMAKE_CXX_STANDARDS:-} ]]; then - ABSL_CMAKE_CXX_STANDARDS="11 14 17" + ABSL_CMAKE_CXX_STANDARDS="14 17" fi if [[ -z ${ABSL_CMAKE_BUILD_TYPES:-} ]]; then @@ -58,6 +58,7 @@ for std in ${ABSL_CMAKE_CXX_STANDARDS}; do -DCMAKE_CXX_STANDARD=${std} \ -DCMAKE_MODULE_LINKER_FLAGS=\"-Wl,--no-undefined\" && \ make -j$(nproc) && \ + TZDIR=/abseil-cpp/absl/time/internal/cctz/testdata/zoneinfo \ ctest -j$(nproc) --output-on-failure" done done diff --git a/ci/macos_xcode_bazel.sh b/ci/macos_xcode_bazel.sh index 8d69f1de..04c9a1a2 100755 --- a/ci/macos_xcode_bazel.sh +++ b/ci/macos_xcode_bazel.sh @@ -54,7 +54,9 @@ fi ${BAZEL_BIN} test ... \ --copt="-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1" \ - --copt=-Werror \ + --copt="-Werror" \ + --cxxopt="-std=c++14" \ + --features=external_include_paths \ --keep_going \ --show_timestamps \ --test_env="TZDIR=${ABSEIL_ROOT}/absl/time/internal/cctz/testdata/zoneinfo" \ diff --git a/ci/macos_xcode_cmake.sh b/ci/macos_xcode_cmake.sh index 71ea2534..690f86b8 100755 --- a/ci/macos_xcode_cmake.sh +++ b/ci/macos_xcode_cmake.sh @@ -47,10 +47,11 @@ for compilation_mode in ${ABSL_CMAKE_BUILD_TYPES}; do -DBUILD_SHARED_LIBS=${build_shared} \ -DABSL_BUILD_TESTING=ON \ -DCMAKE_BUILD_TYPE=${compilation_mode} \ - -DCMAKE_CXX_STANDARD=11 \ + -DCMAKE_CXX_STANDARD=14 \ -DCMAKE_MODULE_LINKER_FLAGS="-Wl,--no-undefined" \ -DABSL_GOOGLETEST_DOWNLOAD_URL="${ABSL_GOOGLETEST_DOWNLOAD_URL}" time cmake --build . - time ctest -C ${compilation_mode} --output-on-failure + time TZDIR=${ABSEIL_ROOT}/absl/time/internal/cctz/testdata/zoneinfo \ + ctest -C ${compilation_mode} --output-on-failure done done diff --git a/conanfile.py b/conanfile.py index 926ec5cc..4bbc62ee 100755 --- a/conanfile.py +++ b/conanfile.py @@ -30,7 +30,7 @@ class AbseilConan(ConanFile): raise ConanInvalidConfiguration("Abseil does not support MSVC < 14") def build(self): - tools.replace_in_file("CMakeLists.txt", "project(absl CXX)", "project(absl CXX)\ninclude(conanbuildinfo.cmake)\nconan_basic_setup()") + tools.replace_in_file("CMakeLists.txt", "project(absl LANGUAGES CXX)", "project(absl LANGUAGES CXX)\ninclude(conanbuildinfo.cmake)\nconan_basic_setup()") cmake = CMake(self) cmake.definitions["BUILD_TESTING"] = False cmake.configure() |