Snap for 15614157 from eb736d608fd999817a2beb08a547a5822a51b320 to androidx-sqlite-release Change-Id: I520a50f9e1489975af090b1824b6801b3515dd55
diff --git a/Android.bp b/Android.bp index 2a6bf5c..5be4fd8 100644 --- a/Android.bp +++ b/Android.bp
@@ -58,13 +58,6 @@ stl: "libc++", } -cc_defaults { - name: "absl_notls_test_defaults", - host_supported: true, - stl: "libc++", - cflags: ["-DANDROID_DISABLE_TLS_FOR_LINKER=1"], -} - genrule { name: "absl_synchronization_hdrs", srcs: [ @@ -144,6 +137,7 @@ "absl_base_tracing_internal", "absl_debugging_stacktrace", "absl_debugging_symbolize", + "absl_meta_type_traits", "absl_time", ], export_static_lib_headers: [ @@ -161,6 +155,7 @@ "absl_base_tracing_internal", "absl_debugging_stacktrace", "absl_debugging_symbolize", + "absl_meta_type_traits", "absl_time", ], @@ -202,6 +197,7 @@ "absl_base_tracing_internal_notls", "absl_debugging_stacktrace_notls", "absl_debugging_symbolize_notls", + "absl_meta_type_traits_notls", "absl_time_notls", ], export_static_lib_headers: [ @@ -219,6 +215,7 @@ "absl_base_tracing_internal_notls", "absl_debugging_stacktrace_notls", "absl_debugging_symbolize_notls", + "absl_meta_type_traits_notls", "absl_time_notls", ], @@ -644,6 +641,7 @@ "absl/time/internal/cctz/src/time_zone_libc.h", "absl/time/internal/cctz/src/time_zone_posix.h", "absl/time/internal/cctz/src/tzfile.h", + "absl/time/internal/cctz/src/time_zone_name_win.h", ], out: [ "my_include_dir/absl/time/internal/cctz/include/cctz/time_zone.h", @@ -655,6 +653,7 @@ "my_include_dir/absl/time/internal/cctz/src/time_zone_libc.h", "my_include_dir/absl/time/internal/cctz/src/time_zone_posix.h", "my_include_dir/absl/time/internal/cctz/src/tzfile.h", + "my_include_dir/absl/time/internal/cctz/src/time_zone_name_win.h", ], export_include_dirs: ["my_include_dir"], cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + @@ -679,6 +678,7 @@ "absl/time/internal/cctz/src/time_zone_lookup.cc", "absl/time/internal/cctz/src/time_zone_posix.cc", "absl/time/internal/cctz/src/zone_info_source.cc", + "absl/time/internal/cctz/src/time_zone_name_win.cc", ], generated_headers: ["absl_time_internal_cctz_time_zone_hdrs"], export_generated_headers: ["absl_time_internal_cctz_time_zone_hdrs"], @@ -708,6 +708,7 @@ "absl/time/internal/cctz/src/time_zone_lookup.cc", "absl/time/internal/cctz/src/time_zone_posix.cc", "absl/time/internal/cctz/src/zone_info_source.cc", + "absl/time/internal/cctz/src/time_zone_name_win.cc", ], generated_headers: ["absl_time_internal_cctz_time_zone_hdrs"], export_generated_headers: ["absl_time_internal_cctz_time_zone_hdrs"], @@ -802,24 +803,20 @@ defaults: ["absl_defaults"], visibility: ["//visibility:public"], srcs: [ - "absl/strings/string_view.cc", + ], generated_headers: ["absl_strings_string_view_hdrs"], export_generated_headers: ["absl_strings_string_view_hdrs"], whole_static_libs: [ - "absl_base", "absl_base_config", "absl_base_core_headers", "absl_base_nullability", - "absl_base_throw_delegate", ], export_static_lib_headers: [ - "absl_base", "absl_base_config", "absl_base_core_headers", "absl_base_nullability", - "absl_base_throw_delegate", ], } @@ -829,327 +826,20 @@ defaults: ["absl_notls_defaults"], visibility: ["//external/protobuf"], srcs: [ - "absl/strings/string_view.cc", + ], generated_headers: ["absl_strings_string_view_hdrs"], export_generated_headers: ["absl_strings_string_view_hdrs"], whole_static_libs: [ - "absl_base_notls", "absl_base_config_notls", "absl_base_core_headers_notls", "absl_base_nullability_notls", - "absl_base_throw_delegate_notls", ], export_static_lib_headers: [ - "absl_base_notls", "absl_base_config_notls", "absl_base_core_headers_notls", "absl_base_nullability_notls", - "absl_base_throw_delegate_notls", - ], - -} - -genrule { - name: "absl_base_throw_delegate_hdrs", - srcs: [ - "absl/base/internal/throw_delegate.h", - ], - out: [ - "my_include_dir/absl/base/internal/throw_delegate.h", - ], - export_include_dirs: ["my_include_dir"], - cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + - "cp --parents $(in) $(genDir)/temp && " + - // delete empty folders automatically created by soong - "rm -rf $(genDir)/my_include_dir/* && " + - "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + - "rm -rf $(genDir)/temp", -} - -cc_library_static { - name: "absl_base_throw_delegate", - defaults: ["absl_defaults"], - - srcs: [ - "absl/base/internal/throw_delegate.cc", - ], - generated_headers: ["absl_base_throw_delegate_hdrs"], - export_generated_headers: ["absl_base_throw_delegate_hdrs"], - - whole_static_libs: [ - "absl_base_config", - "absl_base_raw_logging_internal", - ], - export_static_lib_headers: [ - "absl_base_config", - "absl_base_raw_logging_internal", - ], - -} - -cc_library_static { - name: "absl_base_throw_delegate_notls", - defaults: ["absl_notls_defaults"], - - srcs: [ - "absl/base/internal/throw_delegate.cc", - ], - generated_headers: ["absl_base_throw_delegate_hdrs"], - export_generated_headers: ["absl_base_throw_delegate_hdrs"], - - whole_static_libs: [ - "absl_base_config_notls", - "absl_base_raw_logging_internal_notls", - ], - export_static_lib_headers: [ - "absl_base_config_notls", - "absl_base_raw_logging_internal_notls", - ], - -} - -genrule { - name: "absl_base_raw_logging_internal_hdrs", - srcs: [ - "absl/base/internal/raw_logging.h", - ], - out: [ - "my_include_dir/absl/base/internal/raw_logging.h", - ], - export_include_dirs: ["my_include_dir"], - cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + - "cp --parents $(in) $(genDir)/temp && " + - // delete empty folders automatically created by soong - "rm -rf $(genDir)/my_include_dir/* && " + - "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + - "rm -rf $(genDir)/temp", -} - -cc_library_static { - name: "absl_base_raw_logging_internal", - defaults: ["absl_defaults"], - - srcs: [ - "absl/base/internal/raw_logging.cc", - ], - generated_headers: ["absl_base_raw_logging_internal_hdrs"], - export_generated_headers: ["absl_base_raw_logging_internal_hdrs"], - - whole_static_libs: [ - "absl_base_atomic_hook", - "absl_base_config", - "absl_base_core_headers", - "absl_base_errno_saver", - "absl_base_log_severity", - ], - export_static_lib_headers: [ - "absl_base_atomic_hook", - "absl_base_config", - "absl_base_core_headers", - "absl_base_errno_saver", - "absl_base_log_severity", - ], - -} - -cc_library_static { - name: "absl_base_raw_logging_internal_notls", - defaults: ["absl_notls_defaults"], - - srcs: [ - "absl/base/internal/raw_logging.cc", - ], - generated_headers: ["absl_base_raw_logging_internal_hdrs"], - export_generated_headers: ["absl_base_raw_logging_internal_hdrs"], - - whole_static_libs: [ - "absl_base_atomic_hook_notls", - "absl_base_config_notls", - "absl_base_core_headers_notls", - "absl_base_errno_saver_notls", - "absl_base_log_severity_notls", - ], - export_static_lib_headers: [ - "absl_base_atomic_hook_notls", - "absl_base_config_notls", - "absl_base_core_headers_notls", - "absl_base_errno_saver_notls", - "absl_base_log_severity_notls", - ], - -} - -genrule { - name: "absl_base_log_severity_hdrs", - srcs: [ - "absl/base/log_severity.h", - ], - out: [ - "my_include_dir/absl/base/log_severity.h", - ], - export_include_dirs: ["my_include_dir"], - cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + - "cp --parents $(in) $(genDir)/temp && " + - // delete empty folders automatically created by soong - "rm -rf $(genDir)/my_include_dir/* && " + - "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + - "rm -rf $(genDir)/temp", -} - -cc_library_static { - name: "absl_base_log_severity", - defaults: ["absl_defaults"], - visibility: ["//visibility:public"], - srcs: [ - "absl/base/log_severity.cc", - ], - generated_headers: ["absl_base_log_severity_hdrs"], - export_generated_headers: ["absl_base_log_severity_hdrs"], - - whole_static_libs: [ - "absl_base_config", - "absl_base_core_headers", - ], - export_static_lib_headers: [ - "absl_base_config", - "absl_base_core_headers", - ], - -} - -cc_library_static { - name: "absl_base_log_severity_notls", - defaults: ["absl_notls_defaults"], - visibility: ["//external/protobuf"], - srcs: [ - "absl/base/log_severity.cc", - ], - generated_headers: ["absl_base_log_severity_hdrs"], - export_generated_headers: ["absl_base_log_severity_hdrs"], - - whole_static_libs: [ - "absl_base_config_notls", - "absl_base_core_headers_notls", - ], - export_static_lib_headers: [ - "absl_base_config_notls", - "absl_base_core_headers_notls", - ], - -} - -genrule { - name: "absl_base_errno_saver_hdrs", - srcs: [ - "absl/base/internal/errno_saver.h", - ], - out: [ - "my_include_dir/absl/base/internal/errno_saver.h", - ], - export_include_dirs: ["my_include_dir"], - cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + - "cp --parents $(in) $(genDir)/temp && " + - // delete empty folders automatically created by soong - "rm -rf $(genDir)/my_include_dir/* && " + - "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + - "rm -rf $(genDir)/temp", -} - -cc_library_static { - name: "absl_base_errno_saver", - defaults: ["absl_defaults"], - - srcs: [ - - ], - generated_headers: ["absl_base_errno_saver_hdrs"], - export_generated_headers: ["absl_base_errno_saver_hdrs"], - - whole_static_libs: [ - "absl_base_config", - ], - export_static_lib_headers: [ - "absl_base_config", - ], - -} - -cc_library_static { - name: "absl_base_errno_saver_notls", - defaults: ["absl_notls_defaults"], - - srcs: [ - - ], - generated_headers: ["absl_base_errno_saver_hdrs"], - export_generated_headers: ["absl_base_errno_saver_hdrs"], - - whole_static_libs: [ - "absl_base_config_notls", - ], - export_static_lib_headers: [ - "absl_base_config_notls", - ], - -} - -genrule { - name: "absl_base_atomic_hook_hdrs", - srcs: [ - "absl/base/internal/atomic_hook.h", - ], - out: [ - "my_include_dir/absl/base/internal/atomic_hook.h", - ], - export_include_dirs: ["my_include_dir"], - cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + - "cp --parents $(in) $(genDir)/temp && " + - // delete empty folders automatically created by soong - "rm -rf $(genDir)/my_include_dir/* && " + - "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + - "rm -rf $(genDir)/temp", -} - -cc_library_static { - name: "absl_base_atomic_hook", - defaults: ["absl_defaults"], - - srcs: [ - - ], - generated_headers: ["absl_base_atomic_hook_hdrs"], - export_generated_headers: ["absl_base_atomic_hook_hdrs"], - - whole_static_libs: [ - "absl_base_config", - "absl_base_core_headers", - ], - export_static_lib_headers: [ - "absl_base_config", - "absl_base_core_headers", - ], - -} - -cc_library_static { - name: "absl_base_atomic_hook_notls", - defaults: ["absl_notls_defaults"], - - srcs: [ - - ], - generated_headers: ["absl_base_atomic_hook_hdrs"], - export_generated_headers: ["absl_base_atomic_hook_hdrs"], - - whole_static_libs: [ - "absl_base_config_notls", - "absl_base_core_headers_notls", - ], - export_static_lib_headers: [ - "absl_base_config_notls", - "absl_base_core_headers_notls", ], } @@ -1158,11 +848,9 @@ name: "absl_base_nullability_hdrs", srcs: [ "absl/base/nullability.h", - "absl/base/internal/nullability_deprecated.h", ], out: [ "my_include_dir/absl/base/nullability.h", - "my_include_dir/absl/base/internal/nullability_deprecated.h", ], export_include_dirs: ["my_include_dir"], cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + @@ -1185,11 +873,9 @@ whole_static_libs: [ "absl_base_config", - "absl_base_core_headers", ], export_static_lib_headers: [ "absl_base_config", - "absl_base_core_headers", ], } @@ -1206,388 +892,9 @@ whole_static_libs: [ "absl_base_config_notls", - "absl_base_core_headers_notls", ], export_static_lib_headers: [ "absl_base_config_notls", - "absl_base_core_headers_notls", - ], - -} - -genrule { - name: "absl_base_hdrs", - srcs: [ - "absl/base/call_once.h", - "absl/base/casts.h", - "absl/base/internal/cycleclock.h", - "absl/base/internal/low_level_scheduling.h", - "absl/base/internal/per_thread_tls.h", - "absl/base/internal/spinlock.h", - "absl/base/internal/sysinfo.h", - "absl/base/internal/thread_identity.h", - "absl/base/internal/tsan_mutex_interface.h", - "absl/base/internal/unscaledcycleclock.h", - ], - out: [ - "my_include_dir/absl/base/call_once.h", - "my_include_dir/absl/base/casts.h", - "my_include_dir/absl/base/internal/cycleclock.h", - "my_include_dir/absl/base/internal/low_level_scheduling.h", - "my_include_dir/absl/base/internal/per_thread_tls.h", - "my_include_dir/absl/base/internal/spinlock.h", - "my_include_dir/absl/base/internal/sysinfo.h", - "my_include_dir/absl/base/internal/thread_identity.h", - "my_include_dir/absl/base/internal/tsan_mutex_interface.h", - "my_include_dir/absl/base/internal/unscaledcycleclock.h", - ], - export_include_dirs: ["my_include_dir"], - cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + - "cp --parents $(in) $(genDir)/temp && " + - // delete empty folders automatically created by soong - "rm -rf $(genDir)/my_include_dir/* && " + - "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + - "rm -rf $(genDir)/temp", -} - -cc_library_static { - name: "absl_base", - defaults: ["absl_defaults"], - visibility: ["//visibility:public"], - srcs: [ - "absl/base/internal/cycleclock.cc", - "absl/base/internal/spinlock.cc", - "absl/base/internal/sysinfo.cc", - "absl/base/internal/thread_identity.cc", - "absl/base/internal/unscaledcycleclock.cc", - ], - generated_headers: ["absl_base_hdrs"], - export_generated_headers: ["absl_base_hdrs"], - - whole_static_libs: [ - "absl_base_atomic_hook", - "absl_base_base_internal", - "absl_base_config", - "absl_base_core_headers", - "absl_base_cycleclock_internal", - "absl_base_dynamic_annotations", - "absl_base_log_severity", - "absl_base_nullability", - "absl_base_raw_logging_internal", - "absl_base_spinlock_wait", - "absl_meta_type_traits", - ], - export_static_lib_headers: [ - "absl_base_atomic_hook", - "absl_base_base_internal", - "absl_base_config", - "absl_base_core_headers", - "absl_base_cycleclock_internal", - "absl_base_dynamic_annotations", - "absl_base_log_severity", - "absl_base_nullability", - "absl_base_raw_logging_internal", - "absl_base_spinlock_wait", - "absl_meta_type_traits", - ], - -} - -cc_library_static { - name: "absl_base_notls", - defaults: ["absl_notls_defaults"], - visibility: ["//external/protobuf"], - srcs: [ - "absl/base/internal/cycleclock.cc", - "absl/base/internal/spinlock.cc", - "absl/base/internal/sysinfo.cc", - "absl/base/internal/thread_identity.cc", - "absl/base/internal/unscaledcycleclock.cc", - ], - generated_headers: ["absl_base_hdrs"], - export_generated_headers: ["absl_base_hdrs"], - - whole_static_libs: [ - "absl_base_atomic_hook_notls", - "absl_base_base_internal_notls", - "absl_base_config_notls", - "absl_base_core_headers_notls", - "absl_base_cycleclock_internal_notls", - "absl_base_dynamic_annotations_notls", - "absl_base_log_severity_notls", - "absl_base_nullability_notls", - "absl_base_raw_logging_internal_notls", - "absl_base_spinlock_wait_notls", - "absl_meta_type_traits_notls", - ], - export_static_lib_headers: [ - "absl_base_atomic_hook_notls", - "absl_base_base_internal_notls", - "absl_base_config_notls", - "absl_base_core_headers_notls", - "absl_base_cycleclock_internal_notls", - "absl_base_dynamic_annotations_notls", - "absl_base_log_severity_notls", - "absl_base_nullability_notls", - "absl_base_raw_logging_internal_notls", - "absl_base_spinlock_wait_notls", - "absl_meta_type_traits_notls", - ], - -} - -genrule { - name: "absl_base_spinlock_wait_hdrs", - srcs: [ - "absl/base/internal/spinlock_wait.h", - "absl/base/internal/spinlock_akaros.inc", - "absl/base/internal/spinlock_linux.inc", - "absl/base/internal/spinlock_posix.inc", - "absl/base/internal/spinlock_win32.inc", - ], - out: [ - "my_include_dir/absl/base/internal/spinlock_wait.h", - "my_include_dir/absl/base/internal/spinlock_akaros.inc", - "my_include_dir/absl/base/internal/spinlock_linux.inc", - "my_include_dir/absl/base/internal/spinlock_posix.inc", - "my_include_dir/absl/base/internal/spinlock_win32.inc", - ], - export_include_dirs: ["my_include_dir"], - cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + - "cp --parents $(in) $(genDir)/temp && " + - // delete empty folders automatically created by soong - "rm -rf $(genDir)/my_include_dir/* && " + - "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + - "rm -rf $(genDir)/temp", -} - -cc_library_static { - name: "absl_base_spinlock_wait", - defaults: ["absl_defaults"], - - srcs: [ - "absl/base/internal/spinlock_wait.cc", - ], - generated_headers: ["absl_base_spinlock_wait_hdrs"], - export_generated_headers: ["absl_base_spinlock_wait_hdrs"], - - whole_static_libs: [ - "absl_base_base_internal", - "absl_base_core_headers", - "absl_base_errno_saver", - ], - export_static_lib_headers: [ - "absl_base_base_internal", - "absl_base_core_headers", - "absl_base_errno_saver", - ], - -} - -cc_library_static { - name: "absl_base_spinlock_wait_notls", - defaults: ["absl_notls_defaults"], - - srcs: [ - "absl/base/internal/spinlock_wait.cc", - ], - generated_headers: ["absl_base_spinlock_wait_hdrs"], - export_generated_headers: ["absl_base_spinlock_wait_hdrs"], - - whole_static_libs: [ - "absl_base_base_internal_notls", - "absl_base_core_headers_notls", - "absl_base_errno_saver_notls", - ], - export_static_lib_headers: [ - "absl_base_base_internal_notls", - "absl_base_core_headers_notls", - "absl_base_errno_saver_notls", - ], - -} - -genrule { - name: "absl_base_base_internal_hdrs", - srcs: [ - "absl/base/internal/hide_ptr.h", - "absl/base/internal/identity.h", - "absl/base/internal/scheduling_mode.h", - ], - out: [ - "my_include_dir/absl/base/internal/hide_ptr.h", - "my_include_dir/absl/base/internal/identity.h", - "my_include_dir/absl/base/internal/scheduling_mode.h", - ], - export_include_dirs: ["my_include_dir"], - cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + - "cp --parents $(in) $(genDir)/temp && " + - // delete empty folders automatically created by soong - "rm -rf $(genDir)/my_include_dir/* && " + - "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + - "rm -rf $(genDir)/temp", -} - -cc_library_static { - name: "absl_base_base_internal", - defaults: ["absl_defaults"], - - srcs: [ - - ], - generated_headers: ["absl_base_base_internal_hdrs"], - export_generated_headers: ["absl_base_base_internal_hdrs"], - - whole_static_libs: [ - "absl_base_config", - "absl_meta_type_traits", - ], - export_static_lib_headers: [ - "absl_base_config", - "absl_meta_type_traits", - ], - -} - -cc_library_static { - name: "absl_base_base_internal_notls", - defaults: ["absl_notls_defaults"], - - srcs: [ - - ], - generated_headers: ["absl_base_base_internal_hdrs"], - export_generated_headers: ["absl_base_base_internal_hdrs"], - - whole_static_libs: [ - "absl_base_config_notls", - "absl_meta_type_traits_notls", - ], - export_static_lib_headers: [ - "absl_base_config_notls", - "absl_meta_type_traits_notls", - ], - -} - -genrule { - name: "absl_base_dynamic_annotations_hdrs", - srcs: [ - "absl/base/dynamic_annotations.h", - "absl/base/internal/dynamic_annotations.h", - ], - out: [ - "my_include_dir/absl/base/dynamic_annotations.h", - "my_include_dir/absl/base/internal/dynamic_annotations.h", - ], - export_include_dirs: ["my_include_dir"], - cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + - "cp --parents $(in) $(genDir)/temp && " + - // delete empty folders automatically created by soong - "rm -rf $(genDir)/my_include_dir/* && " + - "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + - "rm -rf $(genDir)/temp", -} - -cc_library_static { - name: "absl_base_dynamic_annotations", - defaults: ["absl_defaults"], - visibility: ["//visibility:public"], - srcs: [ - - ], - generated_headers: ["absl_base_dynamic_annotations_hdrs"], - export_generated_headers: ["absl_base_dynamic_annotations_hdrs"], - - whole_static_libs: [ - "absl_base_config", - "absl_base_core_headers", - ], - export_static_lib_headers: [ - "absl_base_config", - "absl_base_core_headers", - ], - -} - -cc_library_static { - name: "absl_base_dynamic_annotations_notls", - defaults: ["absl_notls_defaults"], - visibility: ["//external/protobuf"], - srcs: [ - - ], - generated_headers: ["absl_base_dynamic_annotations_hdrs"], - export_generated_headers: ["absl_base_dynamic_annotations_hdrs"], - - whole_static_libs: [ - "absl_base_config_notls", - "absl_base_core_headers_notls", - ], - export_static_lib_headers: [ - "absl_base_config_notls", - "absl_base_core_headers_notls", - ], - -} - -genrule { - name: "absl_base_cycleclock_internal_hdrs", - srcs: [ - "absl/base/internal/cycleclock_config.h", - "absl/base/internal/unscaledcycleclock_config.h", - ], - out: [ - "my_include_dir/absl/base/internal/cycleclock_config.h", - "my_include_dir/absl/base/internal/unscaledcycleclock_config.h", - ], - export_include_dirs: ["my_include_dir"], - cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + - "cp --parents $(in) $(genDir)/temp && " + - // delete empty folders automatically created by soong - "rm -rf $(genDir)/my_include_dir/* && " + - "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + - "rm -rf $(genDir)/temp", -} - -cc_library_static { - name: "absl_base_cycleclock_internal", - defaults: ["absl_defaults"], - - srcs: [ - - ], - generated_headers: ["absl_base_cycleclock_internal_hdrs"], - export_generated_headers: ["absl_base_cycleclock_internal_hdrs"], - - whole_static_libs: [ - "absl_base_base_internal", - "absl_base_config", - ], - export_static_lib_headers: [ - "absl_base_base_internal", - "absl_base_config", - ], - -} - -cc_library_static { - name: "absl_base_cycleclock_internal_notls", - defaults: ["absl_notls_defaults"], - - srcs: [ - - ], - generated_headers: ["absl_base_cycleclock_internal_hdrs"], - export_generated_headers: ["absl_base_cycleclock_internal_hdrs"], - - whole_static_libs: [ - "absl_base_base_internal_notls", - "absl_base_config_notls", - ], - export_static_lib_headers: [ - "absl_base_base_internal_notls", - "absl_base_config_notls", ], } @@ -1675,8 +982,10 @@ export_generated_headers: ["absl_strings_hdrs"], whole_static_libs: [ + "absl_strings_append_and_overwrite", "absl_strings_charset", "absl_strings_internal", + "absl_strings_resize_and_overwrite", "absl_strings_string_view", "absl_base", "absl_base_config", @@ -1692,8 +1001,10 @@ "absl_numeric_int128", ], export_static_lib_headers: [ + "absl_strings_append_and_overwrite", "absl_strings_charset", "absl_strings_internal", + "absl_strings_resize_and_overwrite", "absl_strings_string_view", "absl_base", "absl_base_config", @@ -1735,8 +1046,10 @@ export_generated_headers: ["absl_strings_hdrs"], whole_static_libs: [ + "absl_strings_append_and_overwrite_notls", "absl_strings_charset_notls", "absl_strings_internal_notls", + "absl_strings_resize_and_overwrite_notls", "absl_strings_string_view_notls", "absl_base_notls", "absl_base_config_notls", @@ -1752,8 +1065,10 @@ "absl_numeric_int128_notls", ], export_static_lib_headers: [ + "absl_strings_append_and_overwrite_notls", "absl_strings_charset_notls", "absl_strings_internal_notls", + "absl_strings_resize_and_overwrite_notls", "absl_strings_string_view_notls", "absl_base_notls", "absl_base_config_notls", @@ -2040,6 +1355,558 @@ } genrule { + name: "absl_base_hdrs", + srcs: [ + "absl/base/call_once.h", + "absl/base/casts.h", + "absl/base/internal/cycleclock.h", + "absl/base/internal/low_level_scheduling.h", + "absl/base/internal/per_thread_tls.h", + "absl/base/internal/spinlock.h", + "absl/base/internal/sysinfo.h", + "absl/base/internal/thread_identity.h", + "absl/base/internal/tsan_mutex_interface.h", + "absl/base/internal/unscaledcycleclock.h", + ], + out: [ + "my_include_dir/absl/base/call_once.h", + "my_include_dir/absl/base/casts.h", + "my_include_dir/absl/base/internal/cycleclock.h", + "my_include_dir/absl/base/internal/low_level_scheduling.h", + "my_include_dir/absl/base/internal/per_thread_tls.h", + "my_include_dir/absl/base/internal/spinlock.h", + "my_include_dir/absl/base/internal/sysinfo.h", + "my_include_dir/absl/base/internal/thread_identity.h", + "my_include_dir/absl/base/internal/tsan_mutex_interface.h", + "my_include_dir/absl/base/internal/unscaledcycleclock.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_base", + defaults: ["absl_defaults"], + visibility: ["//visibility:public"], + srcs: [ + "absl/base/casts.cc", + "absl/base/internal/cycleclock.cc", + "absl/base/internal/spinlock.cc", + "absl/base/internal/sysinfo.cc", + "absl/base/internal/thread_identity.cc", + "absl/base/internal/unscaledcycleclock.cc", + ], + generated_headers: ["absl_base_hdrs"], + export_generated_headers: ["absl_base_hdrs"], + + whole_static_libs: [ + "absl_base_atomic_hook", + "absl_base_base_internal", + "absl_base_config", + "absl_base_core_headers", + "absl_base_cycleclock_internal", + "absl_base_nullability", + "absl_base_raw_logging_internal", + "absl_base_spinlock_wait", + "absl_meta_type_traits", + ], + export_static_lib_headers: [ + "absl_base_atomic_hook", + "absl_base_base_internal", + "absl_base_config", + "absl_base_core_headers", + "absl_base_cycleclock_internal", + "absl_base_nullability", + "absl_base_raw_logging_internal", + "absl_base_spinlock_wait", + "absl_meta_type_traits", + ], + +} + +cc_library_static { + name: "absl_base_notls", + defaults: ["absl_notls_defaults"], + visibility: ["//external/protobuf"], + srcs: [ + "absl/base/casts.cc", + "absl/base/internal/cycleclock.cc", + "absl/base/internal/spinlock.cc", + "absl/base/internal/sysinfo.cc", + "absl/base/internal/thread_identity.cc", + "absl/base/internal/unscaledcycleclock.cc", + ], + generated_headers: ["absl_base_hdrs"], + export_generated_headers: ["absl_base_hdrs"], + + whole_static_libs: [ + "absl_base_atomic_hook_notls", + "absl_base_base_internal_notls", + "absl_base_config_notls", + "absl_base_core_headers_notls", + "absl_base_cycleclock_internal_notls", + "absl_base_nullability_notls", + "absl_base_raw_logging_internal_notls", + "absl_base_spinlock_wait_notls", + "absl_meta_type_traits_notls", + ], + export_static_lib_headers: [ + "absl_base_atomic_hook_notls", + "absl_base_base_internal_notls", + "absl_base_config_notls", + "absl_base_core_headers_notls", + "absl_base_cycleclock_internal_notls", + "absl_base_nullability_notls", + "absl_base_raw_logging_internal_notls", + "absl_base_spinlock_wait_notls", + "absl_meta_type_traits_notls", + ], + +} + +genrule { + name: "absl_base_spinlock_wait_hdrs", + srcs: [ + "absl/base/internal/spinlock_wait.h", + "absl/base/internal/spinlock_akaros.inc", + "absl/base/internal/spinlock_linux.inc", + "absl/base/internal/spinlock_posix.inc", + "absl/base/internal/spinlock_win32.inc", + ], + out: [ + "my_include_dir/absl/base/internal/spinlock_wait.h", + "my_include_dir/absl/base/internal/spinlock_akaros.inc", + "my_include_dir/absl/base/internal/spinlock_linux.inc", + "my_include_dir/absl/base/internal/spinlock_posix.inc", + "my_include_dir/absl/base/internal/spinlock_win32.inc", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_base_spinlock_wait", + defaults: ["absl_defaults"], + + srcs: [ + "absl/base/internal/spinlock_wait.cc", + ], + generated_headers: ["absl_base_spinlock_wait_hdrs"], + export_generated_headers: ["absl_base_spinlock_wait_hdrs"], + + whole_static_libs: [ + "absl_base_base_internal", + "absl_base_core_headers", + "absl_base_errno_saver", + ], + export_static_lib_headers: [ + "absl_base_base_internal", + "absl_base_core_headers", + "absl_base_errno_saver", + ], + +} + +cc_library_static { + name: "absl_base_spinlock_wait_notls", + defaults: ["absl_notls_defaults"], + + srcs: [ + "absl/base/internal/spinlock_wait.cc", + ], + generated_headers: ["absl_base_spinlock_wait_hdrs"], + export_generated_headers: ["absl_base_spinlock_wait_hdrs"], + + whole_static_libs: [ + "absl_base_base_internal_notls", + "absl_base_core_headers_notls", + "absl_base_errno_saver_notls", + ], + export_static_lib_headers: [ + "absl_base_base_internal_notls", + "absl_base_core_headers_notls", + "absl_base_errno_saver_notls", + ], + +} + +genrule { + name: "absl_base_errno_saver_hdrs", + srcs: [ + "absl/base/internal/errno_saver.h", + ], + out: [ + "my_include_dir/absl/base/internal/errno_saver.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_base_errno_saver", + defaults: ["absl_defaults"], + + srcs: [ + + ], + generated_headers: ["absl_base_errno_saver_hdrs"], + export_generated_headers: ["absl_base_errno_saver_hdrs"], + + whole_static_libs: [ + "absl_base_config", + ], + export_static_lib_headers: [ + "absl_base_config", + ], + +} + +cc_library_static { + name: "absl_base_errno_saver_notls", + defaults: ["absl_notls_defaults"], + + srcs: [ + + ], + generated_headers: ["absl_base_errno_saver_hdrs"], + export_generated_headers: ["absl_base_errno_saver_hdrs"], + + whole_static_libs: [ + "absl_base_config_notls", + ], + export_static_lib_headers: [ + "absl_base_config_notls", + ], + +} + +genrule { + name: "absl_base_base_internal_hdrs", + srcs: [ + "absl/base/internal/hide_ptr.h", + "absl/base/internal/scheduling_mode.h", + ], + out: [ + "my_include_dir/absl/base/internal/hide_ptr.h", + "my_include_dir/absl/base/internal/scheduling_mode.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_base_base_internal", + defaults: ["absl_defaults"], + + srcs: [ + + ], + generated_headers: ["absl_base_base_internal_hdrs"], + export_generated_headers: ["absl_base_base_internal_hdrs"], + + whole_static_libs: [ + "absl_base_config", + "absl_meta_type_traits", + ], + export_static_lib_headers: [ + "absl_base_config", + "absl_meta_type_traits", + ], + +} + +cc_library_static { + name: "absl_base_base_internal_notls", + defaults: ["absl_notls_defaults"], + + srcs: [ + + ], + generated_headers: ["absl_base_base_internal_hdrs"], + export_generated_headers: ["absl_base_base_internal_hdrs"], + + whole_static_libs: [ + "absl_base_config_notls", + "absl_meta_type_traits_notls", + ], + export_static_lib_headers: [ + "absl_base_config_notls", + "absl_meta_type_traits_notls", + ], + +} + +genrule { + name: "absl_base_raw_logging_internal_hdrs", + srcs: [ + "absl/base/internal/raw_logging.h", + ], + out: [ + "my_include_dir/absl/base/internal/raw_logging.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_base_raw_logging_internal", + defaults: ["absl_defaults"], + + srcs: [ + "absl/base/internal/raw_logging.cc", + ], + generated_headers: ["absl_base_raw_logging_internal_hdrs"], + export_generated_headers: ["absl_base_raw_logging_internal_hdrs"], + + whole_static_libs: [ + "absl_base_atomic_hook", + "absl_base_config", + "absl_base_core_headers", + "absl_base_errno_saver", + "absl_base_log_severity", + ], + export_static_lib_headers: [ + "absl_base_atomic_hook", + "absl_base_config", + "absl_base_core_headers", + "absl_base_errno_saver", + "absl_base_log_severity", + ], + +} + +cc_library_static { + name: "absl_base_raw_logging_internal_notls", + defaults: ["absl_notls_defaults"], + + srcs: [ + "absl/base/internal/raw_logging.cc", + ], + generated_headers: ["absl_base_raw_logging_internal_hdrs"], + export_generated_headers: ["absl_base_raw_logging_internal_hdrs"], + + whole_static_libs: [ + "absl_base_atomic_hook_notls", + "absl_base_config_notls", + "absl_base_core_headers_notls", + "absl_base_errno_saver_notls", + "absl_base_log_severity_notls", + ], + export_static_lib_headers: [ + "absl_base_atomic_hook_notls", + "absl_base_config_notls", + "absl_base_core_headers_notls", + "absl_base_errno_saver_notls", + "absl_base_log_severity_notls", + ], + +} + +genrule { + name: "absl_base_log_severity_hdrs", + srcs: [ + "absl/base/log_severity.h", + ], + out: [ + "my_include_dir/absl/base/log_severity.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_base_log_severity", + defaults: ["absl_defaults"], + visibility: ["//visibility:public"], + srcs: [ + "absl/base/log_severity.cc", + ], + generated_headers: ["absl_base_log_severity_hdrs"], + export_generated_headers: ["absl_base_log_severity_hdrs"], + + whole_static_libs: [ + "absl_base_config", + "absl_base_core_headers", + ], + export_static_lib_headers: [ + "absl_base_config", + "absl_base_core_headers", + ], + +} + +cc_library_static { + name: "absl_base_log_severity_notls", + defaults: ["absl_notls_defaults"], + visibility: ["//external/protobuf"], + srcs: [ + "absl/base/log_severity.cc", + ], + generated_headers: ["absl_base_log_severity_hdrs"], + export_generated_headers: ["absl_base_log_severity_hdrs"], + + whole_static_libs: [ + "absl_base_config_notls", + "absl_base_core_headers_notls", + ], + export_static_lib_headers: [ + "absl_base_config_notls", + "absl_base_core_headers_notls", + ], + +} + +genrule { + name: "absl_base_atomic_hook_hdrs", + srcs: [ + "absl/base/internal/atomic_hook.h", + ], + out: [ + "my_include_dir/absl/base/internal/atomic_hook.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_base_atomic_hook", + defaults: ["absl_defaults"], + + srcs: [ + + ], + generated_headers: ["absl_base_atomic_hook_hdrs"], + export_generated_headers: ["absl_base_atomic_hook_hdrs"], + + whole_static_libs: [ + "absl_base_config", + "absl_base_core_headers", + ], + export_static_lib_headers: [ + "absl_base_config", + "absl_base_core_headers", + ], + +} + +cc_library_static { + name: "absl_base_atomic_hook_notls", + defaults: ["absl_notls_defaults"], + + srcs: [ + + ], + generated_headers: ["absl_base_atomic_hook_hdrs"], + export_generated_headers: ["absl_base_atomic_hook_hdrs"], + + whole_static_libs: [ + "absl_base_config_notls", + "absl_base_core_headers_notls", + ], + export_static_lib_headers: [ + "absl_base_config_notls", + "absl_base_core_headers_notls", + ], + +} + +genrule { + name: "absl_base_cycleclock_internal_hdrs", + srcs: [ + "absl/base/internal/cycleclock_config.h", + "absl/base/internal/unscaledcycleclock_config.h", + ], + out: [ + "my_include_dir/absl/base/internal/cycleclock_config.h", + "my_include_dir/absl/base/internal/unscaledcycleclock_config.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_base_cycleclock_internal", + defaults: ["absl_defaults"], + + srcs: [ + + ], + generated_headers: ["absl_base_cycleclock_internal_hdrs"], + export_generated_headers: ["absl_base_cycleclock_internal_hdrs"], + + whole_static_libs: [ + "absl_base_base_internal", + "absl_base_config", + ], + export_static_lib_headers: [ + "absl_base_base_internal", + "absl_base_config", + ], + +} + +cc_library_static { + name: "absl_base_cycleclock_internal_notls", + defaults: ["absl_notls_defaults"], + + srcs: [ + + ], + generated_headers: ["absl_base_cycleclock_internal_hdrs"], + export_generated_headers: ["absl_base_cycleclock_internal_hdrs"], + + whole_static_libs: [ + "absl_base_base_internal_notls", + "absl_base_config_notls", + ], + export_static_lib_headers: [ + "absl_base_base_internal_notls", + "absl_base_config_notls", + ], + +} + +genrule { name: "absl_memory_hdrs", srcs: [ "absl/memory/memory.h", @@ -2099,6 +1966,65 @@ } genrule { + name: "absl_base_throw_delegate_hdrs", + srcs: [ + "absl/base/internal/throw_delegate.h", + ], + out: [ + "my_include_dir/absl/base/internal/throw_delegate.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_base_throw_delegate", + defaults: ["absl_defaults"], + + srcs: [ + "absl/base/internal/throw_delegate.cc", + ], + generated_headers: ["absl_base_throw_delegate_hdrs"], + export_generated_headers: ["absl_base_throw_delegate_hdrs"], + + whole_static_libs: [ + "absl_base_config", + "absl_base_raw_logging_internal", + ], + export_static_lib_headers: [ + "absl_base_config", + "absl_base_raw_logging_internal", + ], + +} + +cc_library_static { + name: "absl_base_throw_delegate_notls", + defaults: ["absl_notls_defaults"], + + srcs: [ + "absl/base/internal/throw_delegate.cc", + ], + generated_headers: ["absl_base_throw_delegate_hdrs"], + export_generated_headers: ["absl_base_throw_delegate_hdrs"], + + whole_static_libs: [ + "absl_base_config_notls", + "absl_base_raw_logging_internal_notls", + ], + export_static_lib_headers: [ + "absl_base_config_notls", + "absl_base_raw_logging_internal_notls", + ], + +} + +genrule { name: "absl_base_iterator_traits_internal_hdrs", srcs: [ "absl/base/internal/iterator_traits.h", @@ -2118,7 +2044,7 @@ cc_library_static { name: "absl_base_iterator_traits_internal", defaults: ["absl_defaults"], - visibility: ["//visibility:public"], + srcs: [ ], @@ -2139,7 +2065,7 @@ cc_library_static { name: "absl_base_iterator_traits_internal_notls", defaults: ["absl_notls_defaults"], - visibility: ["//external/protobuf"], + srcs: [ ], @@ -2158,6 +2084,134 @@ } genrule { + name: "absl_strings_resize_and_overwrite_hdrs", + srcs: [ + "absl/strings/resize_and_overwrite.h", + ], + out: [ + "my_include_dir/absl/strings/resize_and_overwrite.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_strings_resize_and_overwrite", + defaults: ["absl_defaults"], + visibility: ["//visibility:public"], + srcs: [ + + ], + generated_headers: ["absl_strings_resize_and_overwrite_hdrs"], + export_generated_headers: ["absl_strings_resize_and_overwrite_hdrs"], + + whole_static_libs: [ + "absl_base_config", + "absl_base_core_headers", + "absl_base_dynamic_annotations", + "absl_base_throw_delegate", + ], + export_static_lib_headers: [ + "absl_base_config", + "absl_base_core_headers", + "absl_base_dynamic_annotations", + "absl_base_throw_delegate", + ], + +} + +cc_library_static { + name: "absl_strings_resize_and_overwrite_notls", + defaults: ["absl_notls_defaults"], + visibility: ["//external/protobuf"], + srcs: [ + + ], + generated_headers: ["absl_strings_resize_and_overwrite_hdrs"], + export_generated_headers: ["absl_strings_resize_and_overwrite_hdrs"], + + whole_static_libs: [ + "absl_base_config_notls", + "absl_base_core_headers_notls", + "absl_base_dynamic_annotations_notls", + "absl_base_throw_delegate_notls", + ], + export_static_lib_headers: [ + "absl_base_config_notls", + "absl_base_core_headers_notls", + "absl_base_dynamic_annotations_notls", + "absl_base_throw_delegate_notls", + ], + +} + +genrule { + name: "absl_base_dynamic_annotations_hdrs", + srcs: [ + "absl/base/dynamic_annotations.h", + "absl/base/internal/dynamic_annotations.h", + ], + out: [ + "my_include_dir/absl/base/dynamic_annotations.h", + "my_include_dir/absl/base/internal/dynamic_annotations.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_base_dynamic_annotations", + defaults: ["absl_defaults"], + visibility: ["//visibility:public"], + srcs: [ + + ], + generated_headers: ["absl_base_dynamic_annotations_hdrs"], + export_generated_headers: ["absl_base_dynamic_annotations_hdrs"], + + whole_static_libs: [ + "absl_base_config", + "absl_base_core_headers", + ], + export_static_lib_headers: [ + "absl_base_config", + "absl_base_core_headers", + ], + +} + +cc_library_static { + name: "absl_base_dynamic_annotations_notls", + defaults: ["absl_notls_defaults"], + visibility: ["//external/protobuf"], + srcs: [ + + ], + generated_headers: ["absl_base_dynamic_annotations_hdrs"], + export_generated_headers: ["absl_base_dynamic_annotations_hdrs"], + + whole_static_libs: [ + "absl_base_config_notls", + "absl_base_core_headers_notls", + ], + export_static_lib_headers: [ + "absl_base_config_notls", + "absl_base_core_headers_notls", + ], + +} + +genrule { name: "absl_strings_internal_hdrs", srcs: [ "absl/strings/internal/escaping.h", @@ -2193,6 +2247,7 @@ export_generated_headers: ["absl_strings_internal_hdrs"], whole_static_libs: [ + "absl_strings_resize_and_overwrite", "absl_base_config", "absl_base_core_headers", "absl_base_endian", @@ -2200,6 +2255,7 @@ "absl_meta_type_traits", ], export_static_lib_headers: [ + "absl_strings_resize_and_overwrite", "absl_base_config", "absl_base_core_headers", "absl_base_endian", @@ -2222,6 +2278,7 @@ export_generated_headers: ["absl_strings_internal_hdrs"], whole_static_libs: [ + "absl_strings_resize_and_overwrite_notls", "absl_base_config_notls", "absl_base_core_headers_notls", "absl_base_endian_notls", @@ -2229,6 +2286,7 @@ "absl_meta_type_traits_notls", ], export_static_lib_headers: [ + "absl_strings_resize_and_overwrite_notls", "absl_base_config_notls", "absl_base_core_headers_notls", "absl_base_endian_notls", @@ -2298,6 +2356,73 @@ } genrule { + name: "absl_strings_append_and_overwrite_hdrs", + srcs: [ + "absl/strings/internal/append_and_overwrite.h", + ], + out: [ + "my_include_dir/absl/strings/internal/append_and_overwrite.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_strings_append_and_overwrite", + defaults: ["absl_defaults"], + + srcs: [ + + ], + generated_headers: ["absl_strings_append_and_overwrite_hdrs"], + export_generated_headers: ["absl_strings_append_and_overwrite_hdrs"], + + whole_static_libs: [ + "absl_strings_resize_and_overwrite", + "absl_base_config", + "absl_base_core_headers", + "absl_base_throw_delegate", + ], + export_static_lib_headers: [ + "absl_strings_resize_and_overwrite", + "absl_base_config", + "absl_base_core_headers", + "absl_base_throw_delegate", + ], + +} + +cc_library_static { + name: "absl_strings_append_and_overwrite_notls", + defaults: ["absl_notls_defaults"], + + srcs: [ + + ], + generated_headers: ["absl_strings_append_and_overwrite_hdrs"], + export_generated_headers: ["absl_strings_append_and_overwrite_hdrs"], + + whole_static_libs: [ + "absl_strings_resize_and_overwrite_notls", + "absl_base_config_notls", + "absl_base_core_headers_notls", + "absl_base_throw_delegate_notls", + ], + export_static_lib_headers: [ + "absl_strings_resize_and_overwrite_notls", + "absl_base_config_notls", + "absl_base_core_headers_notls", + "absl_base_throw_delegate_notls", + ], + +} + +genrule { name: "absl_debugging_symbolize_hdrs", srcs: [ "absl/debugging/internal/symbolize.h", @@ -2921,17 +3046,21 @@ export_generated_headers: ["absl_debugging_stacktrace_hdrs"], whole_static_libs: [ + "absl_debugging_borrowed_fixup_buffer", "absl_debugging_debugging_internal", "absl_base_config", "absl_base_core_headers", "absl_base_dynamic_annotations", + "absl_base_malloc_internal", "absl_base_raw_logging_internal", ], export_static_lib_headers: [ + "absl_debugging_borrowed_fixup_buffer", "absl_debugging_debugging_internal", "absl_base_config", "absl_base_core_headers", "absl_base_dynamic_annotations", + "absl_base_malloc_internal", "absl_base_raw_logging_internal", ], @@ -2948,23 +3077,786 @@ export_generated_headers: ["absl_debugging_stacktrace_hdrs"], whole_static_libs: [ + "absl_debugging_borrowed_fixup_buffer_notls", "absl_debugging_debugging_internal_notls", "absl_base_config_notls", "absl_base_core_headers_notls", "absl_base_dynamic_annotations_notls", + "absl_base_malloc_internal_notls", "absl_base_raw_logging_internal_notls", ], export_static_lib_headers: [ + "absl_debugging_borrowed_fixup_buffer_notls", "absl_debugging_debugging_internal_notls", "absl_base_config_notls", "absl_base_core_headers_notls", "absl_base_dynamic_annotations_notls", + "absl_base_malloc_internal_notls", "absl_base_raw_logging_internal_notls", ], } genrule { + name: "absl_debugging_borrowed_fixup_buffer_hdrs", + srcs: [ + "absl/debugging/internal/borrowed_fixup_buffer.h", + ], + out: [ + "my_include_dir/absl/debugging/internal/borrowed_fixup_buffer.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_debugging_borrowed_fixup_buffer", + defaults: ["absl_defaults"], + visibility: ["//visibility:public"], + srcs: [ + "absl/debugging/internal/borrowed_fixup_buffer.cc", + ], + generated_headers: ["absl_debugging_borrowed_fixup_buffer_hdrs"], + export_generated_headers: ["absl_debugging_borrowed_fixup_buffer_hdrs"], + + whole_static_libs: [ + "absl_base_config", + "absl_base_core_headers", + "absl_base_malloc_internal", + "absl_hash", + ], + export_static_lib_headers: [ + "absl_base_config", + "absl_base_core_headers", + "absl_base_malloc_internal", + "absl_hash", + ], + +} + +cc_library_static { + name: "absl_debugging_borrowed_fixup_buffer_notls", + defaults: ["absl_notls_defaults"], + visibility: ["//external/protobuf"], + srcs: [ + "absl/debugging/internal/borrowed_fixup_buffer.cc", + ], + generated_headers: ["absl_debugging_borrowed_fixup_buffer_hdrs"], + export_generated_headers: ["absl_debugging_borrowed_fixup_buffer_hdrs"], + + whole_static_libs: [ + "absl_base_config_notls", + "absl_base_core_headers_notls", + "absl_base_malloc_internal_notls", + "absl_hash_notls", + ], + export_static_lib_headers: [ + "absl_base_config_notls", + "absl_base_core_headers_notls", + "absl_base_malloc_internal_notls", + "absl_hash_notls", + ], + +} + +genrule { + name: "absl_hash_hdrs", + srcs: [ + "absl/hash/hash.h", + "absl/hash/internal/hash.h", + ], + out: [ + "my_include_dir/absl/hash/hash.h", + "my_include_dir/absl/hash/internal/hash.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_hash", + defaults: ["absl_defaults"], + visibility: ["//visibility:public"], + srcs: [ + "absl/hash/internal/hash.cc", + ], + generated_headers: ["absl_hash_hdrs"], + export_generated_headers: ["absl_hash_hdrs"], + + whole_static_libs: [ + "absl_hash_city", + "absl_hash_weakly_mixed_integer", + "absl_base_config", + "absl_base_core_headers", + "absl_base_endian", + "absl_base_prefetch", + "absl_container_fixed_array", + "absl_functional_function_ref", + "absl_meta_type_traits", + "absl_numeric_bits", + "absl_numeric_int128", + "absl_strings", + "absl_types_optional", + "absl_types_variant", + "absl_utility", + ], + export_static_lib_headers: [ + "absl_hash_city", + "absl_hash_weakly_mixed_integer", + "absl_base_config", + "absl_base_core_headers", + "absl_base_endian", + "absl_base_prefetch", + "absl_container_fixed_array", + "absl_functional_function_ref", + "absl_meta_type_traits", + "absl_numeric_bits", + "absl_numeric_int128", + "absl_strings", + "absl_types_optional", + "absl_types_variant", + "absl_utility", + ], + +} + +cc_library_static { + name: "absl_hash_notls", + defaults: ["absl_notls_defaults"], + visibility: ["//external/protobuf"], + srcs: [ + "absl/hash/internal/hash.cc", + ], + generated_headers: ["absl_hash_hdrs"], + export_generated_headers: ["absl_hash_hdrs"], + + whole_static_libs: [ + "absl_hash_city_notls", + "absl_hash_weakly_mixed_integer_notls", + "absl_base_config_notls", + "absl_base_core_headers_notls", + "absl_base_endian_notls", + "absl_base_prefetch_notls", + "absl_container_fixed_array_notls", + "absl_functional_function_ref_notls", + "absl_meta_type_traits_notls", + "absl_numeric_bits_notls", + "absl_numeric_int128_notls", + "absl_strings_notls", + "absl_types_optional_notls", + "absl_types_variant_notls", + "absl_utility_notls", + ], + export_static_lib_headers: [ + "absl_hash_city_notls", + "absl_hash_weakly_mixed_integer_notls", + "absl_base_config_notls", + "absl_base_core_headers_notls", + "absl_base_endian_notls", + "absl_base_prefetch_notls", + "absl_container_fixed_array_notls", + "absl_functional_function_ref_notls", + "absl_meta_type_traits_notls", + "absl_numeric_bits_notls", + "absl_numeric_int128_notls", + "absl_strings_notls", + "absl_types_optional_notls", + "absl_types_variant_notls", + "absl_utility_notls", + ], + +} + +genrule { + name: "absl_types_variant_hdrs", + srcs: [ + "absl/types/variant.h", + ], + out: [ + "my_include_dir/absl/types/variant.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_types_variant", + defaults: ["absl_defaults"], + visibility: ["//visibility:public"], + srcs: [ + + ], + generated_headers: ["absl_types_variant_hdrs"], + export_generated_headers: ["absl_types_variant_hdrs"], + + whole_static_libs: [ + "absl_base_config", + "absl_utility", + ], + export_static_lib_headers: [ + "absl_base_config", + "absl_utility", + ], + +} + +cc_library_static { + name: "absl_types_variant_notls", + defaults: ["absl_notls_defaults"], + visibility: ["//external/protobuf"], + srcs: [ + + ], + generated_headers: ["absl_types_variant_hdrs"], + export_generated_headers: ["absl_types_variant_hdrs"], + + whole_static_libs: [ + "absl_base_config_notls", + "absl_utility_notls", + ], + export_static_lib_headers: [ + "absl_base_config_notls", + "absl_utility_notls", + ], + +} + +genrule { + name: "absl_functional_function_ref_hdrs", + srcs: [ + "absl/functional/function_ref.h", + "absl/functional/internal/function_ref.h", + ], + out: [ + "my_include_dir/absl/functional/function_ref.h", + "my_include_dir/absl/functional/internal/function_ref.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_functional_function_ref", + defaults: ["absl_defaults"], + visibility: ["//visibility:public"], + srcs: [ + + ], + generated_headers: ["absl_functional_function_ref_hdrs"], + export_generated_headers: ["absl_functional_function_ref_hdrs"], + + whole_static_libs: [ + "absl_functional_any_invocable", + "absl_base_config", + "absl_base_core_headers", + "absl_meta_type_traits", + "absl_utility", + ], + export_static_lib_headers: [ + "absl_functional_any_invocable", + "absl_base_config", + "absl_base_core_headers", + "absl_meta_type_traits", + "absl_utility", + ], + +} + +cc_library_static { + name: "absl_functional_function_ref_notls", + defaults: ["absl_notls_defaults"], + visibility: ["//external/protobuf"], + srcs: [ + + ], + generated_headers: ["absl_functional_function_ref_hdrs"], + export_generated_headers: ["absl_functional_function_ref_hdrs"], + + whole_static_libs: [ + "absl_functional_any_invocable_notls", + "absl_base_config_notls", + "absl_base_core_headers_notls", + "absl_meta_type_traits_notls", + "absl_utility_notls", + ], + export_static_lib_headers: [ + "absl_functional_any_invocable_notls", + "absl_base_config_notls", + "absl_base_core_headers_notls", + "absl_meta_type_traits_notls", + "absl_utility_notls", + ], + +} + +genrule { + name: "absl_functional_any_invocable_hdrs", + srcs: [ + "absl/functional/any_invocable.h", + "absl/functional/internal/any_invocable.h", + ], + out: [ + "my_include_dir/absl/functional/any_invocable.h", + "my_include_dir/absl/functional/internal/any_invocable.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_functional_any_invocable", + defaults: ["absl_defaults"], + visibility: ["//visibility:public"], + srcs: [ + + ], + generated_headers: ["absl_functional_any_invocable_hdrs"], + export_generated_headers: ["absl_functional_any_invocable_hdrs"], + + whole_static_libs: [ + "absl_base_config", + "absl_base_core_headers", + "absl_base_nullability", + "absl_meta_type_traits", + "absl_utility", + ], + export_static_lib_headers: [ + "absl_base_config", + "absl_base_core_headers", + "absl_base_nullability", + "absl_meta_type_traits", + "absl_utility", + ], + +} + +cc_library_static { + name: "absl_functional_any_invocable_notls", + defaults: ["absl_notls_defaults"], + visibility: ["//external/protobuf"], + srcs: [ + + ], + generated_headers: ["absl_functional_any_invocable_hdrs"], + export_generated_headers: ["absl_functional_any_invocable_hdrs"], + + whole_static_libs: [ + "absl_base_config_notls", + "absl_base_core_headers_notls", + "absl_base_nullability_notls", + "absl_meta_type_traits_notls", + "absl_utility_notls", + ], + export_static_lib_headers: [ + "absl_base_config_notls", + "absl_base_core_headers_notls", + "absl_base_nullability_notls", + "absl_meta_type_traits_notls", + "absl_utility_notls", + ], + +} + +genrule { + name: "absl_container_fixed_array_hdrs", + srcs: [ + "absl/container/fixed_array.h", + ], + out: [ + "my_include_dir/absl/container/fixed_array.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_container_fixed_array", + defaults: ["absl_defaults"], + visibility: ["//visibility:public"], + srcs: [ + + ], + generated_headers: ["absl_container_fixed_array_hdrs"], + export_generated_headers: ["absl_container_fixed_array_hdrs"], + + whole_static_libs: [ + "absl_container_compressed_tuple", + "absl_algorithm", + "absl_base_config", + "absl_base_core_headers", + "absl_base_dynamic_annotations", + "absl_base_iterator_traits_internal", + "absl_base_throw_delegate", + "absl_hash_weakly_mixed_integer", + "absl_memory", + ], + export_static_lib_headers: [ + "absl_container_compressed_tuple", + "absl_algorithm", + "absl_base_config", + "absl_base_core_headers", + "absl_base_dynamic_annotations", + "absl_base_iterator_traits_internal", + "absl_base_throw_delegate", + "absl_hash_weakly_mixed_integer", + "absl_memory", + ], + +} + +cc_library_static { + name: "absl_container_fixed_array_notls", + defaults: ["absl_notls_defaults"], + visibility: ["//external/protobuf"], + srcs: [ + + ], + generated_headers: ["absl_container_fixed_array_hdrs"], + export_generated_headers: ["absl_container_fixed_array_hdrs"], + + whole_static_libs: [ + "absl_container_compressed_tuple_notls", + "absl_algorithm_notls", + "absl_base_config_notls", + "absl_base_core_headers_notls", + "absl_base_dynamic_annotations_notls", + "absl_base_iterator_traits_internal_notls", + "absl_base_throw_delegate_notls", + "absl_hash_weakly_mixed_integer_notls", + "absl_memory_notls", + ], + export_static_lib_headers: [ + "absl_container_compressed_tuple_notls", + "absl_algorithm_notls", + "absl_base_config_notls", + "absl_base_core_headers_notls", + "absl_base_dynamic_annotations_notls", + "absl_base_iterator_traits_internal_notls", + "absl_base_throw_delegate_notls", + "absl_hash_weakly_mixed_integer_notls", + "absl_memory_notls", + ], + +} + +genrule { + name: "absl_hash_weakly_mixed_integer_hdrs", + srcs: [ + "absl/hash/internal/weakly_mixed_integer.h", + ], + out: [ + "my_include_dir/absl/hash/internal/weakly_mixed_integer.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_hash_weakly_mixed_integer", + defaults: ["absl_defaults"], + + srcs: [ + + ], + generated_headers: ["absl_hash_weakly_mixed_integer_hdrs"], + export_generated_headers: ["absl_hash_weakly_mixed_integer_hdrs"], + + whole_static_libs: [ + "absl_base_config", + ], + export_static_lib_headers: [ + "absl_base_config", + ], + +} + +cc_library_static { + name: "absl_hash_weakly_mixed_integer_notls", + defaults: ["absl_notls_defaults"], + + srcs: [ + + ], + generated_headers: ["absl_hash_weakly_mixed_integer_hdrs"], + export_generated_headers: ["absl_hash_weakly_mixed_integer_hdrs"], + + whole_static_libs: [ + "absl_base_config_notls", + ], + export_static_lib_headers: [ + "absl_base_config_notls", + ], + +} + +genrule { + name: "absl_algorithm_hdrs", + srcs: [ + "absl/algorithm/algorithm.h", + ], + out: [ + "my_include_dir/absl/algorithm/algorithm.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_algorithm", + defaults: ["absl_defaults"], + visibility: ["//visibility:public"], + srcs: [ + + ], + generated_headers: ["absl_algorithm_hdrs"], + export_generated_headers: ["absl_algorithm_hdrs"], + + whole_static_libs: [ + "absl_base_config", + ], + export_static_lib_headers: [ + "absl_base_config", + ], + +} + +cc_library_static { + name: "absl_algorithm_notls", + defaults: ["absl_notls_defaults"], + visibility: ["//external/protobuf"], + srcs: [ + + ], + generated_headers: ["absl_algorithm_hdrs"], + export_generated_headers: ["absl_algorithm_hdrs"], + + whole_static_libs: [ + "absl_base_config_notls", + ], + export_static_lib_headers: [ + "absl_base_config_notls", + ], + +} + +genrule { + name: "absl_container_compressed_tuple_hdrs", + srcs: [ + "absl/container/internal/compressed_tuple.h", + ], + out: [ + "my_include_dir/absl/container/internal/compressed_tuple.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_container_compressed_tuple", + defaults: ["absl_defaults"], + visibility: ["//visibility:public"], + srcs: [ + + ], + generated_headers: ["absl_container_compressed_tuple_hdrs"], + export_generated_headers: ["absl_container_compressed_tuple_hdrs"], + + whole_static_libs: [ + "absl_utility", + ], + export_static_lib_headers: [ + "absl_utility", + ], + +} + +cc_library_static { + name: "absl_container_compressed_tuple_notls", + defaults: ["absl_notls_defaults"], + visibility: ["//external/protobuf"], + srcs: [ + + ], + generated_headers: ["absl_container_compressed_tuple_hdrs"], + export_generated_headers: ["absl_container_compressed_tuple_hdrs"], + + whole_static_libs: [ + "absl_utility_notls", + ], + export_static_lib_headers: [ + "absl_utility_notls", + ], + +} + +genrule { + name: "absl_base_prefetch_hdrs", + srcs: [ + "absl/base/prefetch.h", + ], + out: [ + "my_include_dir/absl/base/prefetch.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_base_prefetch", + defaults: ["absl_defaults"], + visibility: ["//visibility:public"], + srcs: [ + + ], + generated_headers: ["absl_base_prefetch_hdrs"], + export_generated_headers: ["absl_base_prefetch_hdrs"], + + whole_static_libs: [ + "absl_base_config", + "absl_base_core_headers", + ], + export_static_lib_headers: [ + "absl_base_config", + "absl_base_core_headers", + ], + +} + +cc_library_static { + name: "absl_base_prefetch_notls", + defaults: ["absl_notls_defaults"], + visibility: ["//external/protobuf"], + srcs: [ + + ], + generated_headers: ["absl_base_prefetch_hdrs"], + export_generated_headers: ["absl_base_prefetch_hdrs"], + + whole_static_libs: [ + "absl_base_config_notls", + "absl_base_core_headers_notls", + ], + export_static_lib_headers: [ + "absl_base_config_notls", + "absl_base_core_headers_notls", + ], + +} + +genrule { + name: "absl_hash_city_hdrs", + srcs: [ + "absl/hash/internal/city.h", + ], + out: [ + "my_include_dir/absl/hash/internal/city.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_hash_city", + defaults: ["absl_defaults"], + visibility: ["//visibility:public"], + srcs: [ + "absl/hash/internal/city.cc", + ], + generated_headers: ["absl_hash_city_hdrs"], + export_generated_headers: ["absl_hash_city_hdrs"], + + whole_static_libs: [ + "absl_base_config", + "absl_base_core_headers", + "absl_base_endian", + ], + export_static_lib_headers: [ + "absl_base_config", + "absl_base_core_headers", + "absl_base_endian", + ], + +} + +cc_library_static { + name: "absl_hash_city_notls", + defaults: ["absl_notls_defaults"], + visibility: ["//external/protobuf"], + srcs: [ + "absl/hash/internal/city.cc", + ], + generated_headers: ["absl_hash_city_hdrs"], + export_generated_headers: ["absl_hash_city_hdrs"], + + whole_static_libs: [ + "absl_base_config_notls", + "absl_base_core_headers_notls", + "absl_base_endian_notls", + ], + export_static_lib_headers: [ + "absl_base_config_notls", + "absl_base_core_headers_notls", + "absl_base_endian_notls", + ], + +} + +genrule { name: "absl_base_tracing_internal_hdrs", srcs: [ "absl/base/internal/tracing.h", @@ -3170,6 +4062,58 @@ } genrule { + name: "absl_status_status_matchers_hdrs", + srcs: [ + "absl/status/status_matchers.h", + "absl/status/internal/status_matchers.h", + ], + out: [ + "my_include_dir/absl/status/status_matchers.h", + "my_include_dir/absl/status/internal/status_matchers.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_test_library { + name: "absl_status_status_matchers", + defaults: ["absl_test_defaults"], + visibility: ["//visibility:public"], + srcs: [ + "absl/status/internal/status_matchers.cc", + ], + generated_headers: ["absl_status_status_matchers_hdrs"], + export_generated_headers: ["absl_status_status_matchers_hdrs"], + + whole_static_libs: [ + "absl_status", + "absl_status_statusor", + "absl_base_config", + "absl_strings_string_view", + ], + export_static_lib_headers: [ + "absl_status", + "absl_status_statusor", + "absl_base_config", + "absl_strings_string_view", + ], + + static_libs: [ + "libgmock", + "libgtest", + ], + shared: { + enabled: false, + }, + +} + +genrule { name: "absl_status_statusor_hdrs", srcs: [ "absl/status/statusor.h", @@ -3271,65 +4215,6 @@ } genrule { - name: "absl_types_variant_hdrs", - srcs: [ - "absl/types/variant.h", - ], - out: [ - "my_include_dir/absl/types/variant.h", - ], - export_include_dirs: ["my_include_dir"], - cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + - "cp --parents $(in) $(genDir)/temp && " + - // delete empty folders automatically created by soong - "rm -rf $(genDir)/my_include_dir/* && " + - "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + - "rm -rf $(genDir)/temp", -} - -cc_library_static { - name: "absl_types_variant", - defaults: ["absl_defaults"], - visibility: ["//visibility:public"], - srcs: [ - - ], - generated_headers: ["absl_types_variant_hdrs"], - export_generated_headers: ["absl_types_variant_hdrs"], - - whole_static_libs: [ - "absl_base_config", - "absl_utility", - ], - export_static_lib_headers: [ - "absl_base_config", - "absl_utility", - ], - -} - -cc_library_static { - name: "absl_types_variant_notls", - defaults: ["absl_notls_defaults"], - visibility: ["//external/protobuf"], - srcs: [ - - ], - generated_headers: ["absl_types_variant_hdrs"], - export_generated_headers: ["absl_types_variant_hdrs"], - - whole_static_libs: [ - "absl_base_config_notls", - "absl_utility_notls", - ], - export_static_lib_headers: [ - "absl_base_config_notls", - "absl_utility_notls", - ], - -} - -genrule { name: "absl_strings_str_format_hdrs", srcs: [ "absl/strings/str_format.h", @@ -3486,116 +4371,6 @@ } genrule { - name: "absl_hash_weakly_mixed_integer_hdrs", - srcs: [ - "absl/hash/internal/weakly_mixed_integer.h", - ], - out: [ - "my_include_dir/absl/hash/internal/weakly_mixed_integer.h", - ], - export_include_dirs: ["my_include_dir"], - cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + - "cp --parents $(in) $(genDir)/temp && " + - // delete empty folders automatically created by soong - "rm -rf $(genDir)/my_include_dir/* && " + - "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + - "rm -rf $(genDir)/temp", -} - -cc_library_static { - name: "absl_hash_weakly_mixed_integer", - defaults: ["absl_defaults"], - - srcs: [ - - ], - generated_headers: ["absl_hash_weakly_mixed_integer_hdrs"], - export_generated_headers: ["absl_hash_weakly_mixed_integer_hdrs"], - - whole_static_libs: [ - "absl_base_config", - ], - export_static_lib_headers: [ - "absl_base_config", - ], - -} - -cc_library_static { - name: "absl_hash_weakly_mixed_integer_notls", - defaults: ["absl_notls_defaults"], - - srcs: [ - - ], - generated_headers: ["absl_hash_weakly_mixed_integer_hdrs"], - export_generated_headers: ["absl_hash_weakly_mixed_integer_hdrs"], - - whole_static_libs: [ - "absl_base_config_notls", - ], - export_static_lib_headers: [ - "absl_base_config_notls", - ], - -} - -genrule { - name: "absl_algorithm_hdrs", - srcs: [ - "absl/algorithm/algorithm.h", - ], - out: [ - "my_include_dir/absl/algorithm/algorithm.h", - ], - export_include_dirs: ["my_include_dir"], - cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + - "cp --parents $(in) $(genDir)/temp && " + - // delete empty folders automatically created by soong - "rm -rf $(genDir)/my_include_dir/* && " + - "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + - "rm -rf $(genDir)/temp", -} - -cc_library_static { - name: "absl_algorithm", - defaults: ["absl_defaults"], - visibility: ["//visibility:public"], - srcs: [ - - ], - generated_headers: ["absl_algorithm_hdrs"], - export_generated_headers: ["absl_algorithm_hdrs"], - - whole_static_libs: [ - "absl_base_config", - ], - export_static_lib_headers: [ - "absl_base_config", - ], - -} - -cc_library_static { - name: "absl_algorithm_notls", - defaults: ["absl_notls_defaults"], - visibility: ["//external/protobuf"], - srcs: [ - - ], - generated_headers: ["absl_algorithm_hdrs"], - export_generated_headers: ["absl_algorithm_hdrs"], - - whole_static_libs: [ - "absl_base_config_notls", - ], - export_static_lib_headers: [ - "absl_base_config_notls", - ], - -} - -genrule { name: "absl_strings_str_format_internal_hdrs", srcs: [ "absl/strings/internal/str_format/arg.h", @@ -3782,140 +4557,6 @@ } genrule { - name: "absl_functional_function_ref_hdrs", - srcs: [ - "absl/functional/function_ref.h", - "absl/functional/internal/function_ref.h", - ], - out: [ - "my_include_dir/absl/functional/function_ref.h", - "my_include_dir/absl/functional/internal/function_ref.h", - ], - export_include_dirs: ["my_include_dir"], - cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + - "cp --parents $(in) $(genDir)/temp && " + - // delete empty folders automatically created by soong - "rm -rf $(genDir)/my_include_dir/* && " + - "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + - "rm -rf $(genDir)/temp", -} - -cc_library_static { - name: "absl_functional_function_ref", - defaults: ["absl_defaults"], - visibility: ["//visibility:public"], - srcs: [ - - ], - generated_headers: ["absl_functional_function_ref_hdrs"], - export_generated_headers: ["absl_functional_function_ref_hdrs"], - - whole_static_libs: [ - "absl_functional_any_invocable", - "absl_base_core_headers", - "absl_meta_type_traits", - ], - export_static_lib_headers: [ - "absl_functional_any_invocable", - "absl_base_core_headers", - "absl_meta_type_traits", - ], - -} - -cc_library_static { - name: "absl_functional_function_ref_notls", - defaults: ["absl_notls_defaults"], - visibility: ["//external/protobuf"], - srcs: [ - - ], - generated_headers: ["absl_functional_function_ref_hdrs"], - export_generated_headers: ["absl_functional_function_ref_hdrs"], - - whole_static_libs: [ - "absl_functional_any_invocable_notls", - "absl_base_core_headers_notls", - "absl_meta_type_traits_notls", - ], - export_static_lib_headers: [ - "absl_functional_any_invocable_notls", - "absl_base_core_headers_notls", - "absl_meta_type_traits_notls", - ], - -} - -genrule { - name: "absl_functional_any_invocable_hdrs", - srcs: [ - "absl/functional/any_invocable.h", - "absl/functional/internal/any_invocable.h", - ], - out: [ - "my_include_dir/absl/functional/any_invocable.h", - "my_include_dir/absl/functional/internal/any_invocable.h", - ], - export_include_dirs: ["my_include_dir"], - cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + - "cp --parents $(in) $(genDir)/temp && " + - // delete empty folders automatically created by soong - "rm -rf $(genDir)/my_include_dir/* && " + - "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + - "rm -rf $(genDir)/temp", -} - -cc_library_static { - name: "absl_functional_any_invocable", - defaults: ["absl_defaults"], - visibility: ["//visibility:public"], - srcs: [ - - ], - generated_headers: ["absl_functional_any_invocable_hdrs"], - export_generated_headers: ["absl_functional_any_invocable_hdrs"], - - whole_static_libs: [ - "absl_base_config", - "absl_base_core_headers", - "absl_meta_type_traits", - "absl_utility", - ], - export_static_lib_headers: [ - "absl_base_config", - "absl_base_core_headers", - "absl_meta_type_traits", - "absl_utility", - ], - -} - -cc_library_static { - name: "absl_functional_any_invocable_notls", - defaults: ["absl_notls_defaults"], - visibility: ["//external/protobuf"], - srcs: [ - - ], - generated_headers: ["absl_functional_any_invocable_hdrs"], - export_generated_headers: ["absl_functional_any_invocable_hdrs"], - - whole_static_libs: [ - "absl_base_config_notls", - "absl_base_core_headers_notls", - "absl_meta_type_traits_notls", - "absl_utility_notls", - ], - export_static_lib_headers: [ - "absl_base_config_notls", - "absl_base_core_headers_notls", - "absl_meta_type_traits_notls", - "absl_utility_notls", - ], - -} - -genrule { name: "absl_container_inlined_vector_hdrs", srcs: [ "absl/container/inlined_vector.h", @@ -4078,148 +4719,6 @@ } genrule { - name: "absl_container_compressed_tuple_hdrs", - srcs: [ - "absl/container/internal/compressed_tuple.h", - ], - out: [ - "my_include_dir/absl/container/internal/compressed_tuple.h", - ], - export_include_dirs: ["my_include_dir"], - cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + - "cp --parents $(in) $(genDir)/temp && " + - // delete empty folders automatically created by soong - "rm -rf $(genDir)/my_include_dir/* && " + - "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + - "rm -rf $(genDir)/temp", -} - -cc_library_static { - name: "absl_container_compressed_tuple", - defaults: ["absl_defaults"], - visibility: ["//visibility:public"], - srcs: [ - - ], - generated_headers: ["absl_container_compressed_tuple_hdrs"], - export_generated_headers: ["absl_container_compressed_tuple_hdrs"], - - whole_static_libs: [ - "absl_utility", - ], - export_static_lib_headers: [ - "absl_utility", - ], - -} - -cc_library_static { - name: "absl_container_compressed_tuple_notls", - defaults: ["absl_notls_defaults"], - visibility: ["//external/protobuf"], - srcs: [ - - ], - generated_headers: ["absl_container_compressed_tuple_hdrs"], - export_generated_headers: ["absl_container_compressed_tuple_hdrs"], - - whole_static_libs: [ - "absl_utility_notls", - ], - export_static_lib_headers: [ - "absl_utility_notls", - ], - -} - -genrule { - name: "absl_container_fixed_array_hdrs", - srcs: [ - "absl/container/fixed_array.h", - ], - out: [ - "my_include_dir/absl/container/fixed_array.h", - ], - export_include_dirs: ["my_include_dir"], - cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + - "cp --parents $(in) $(genDir)/temp && " + - // delete empty folders automatically created by soong - "rm -rf $(genDir)/my_include_dir/* && " + - "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + - "rm -rf $(genDir)/temp", -} - -cc_library_static { - name: "absl_container_fixed_array", - defaults: ["absl_defaults"], - visibility: ["//visibility:public"], - srcs: [ - - ], - generated_headers: ["absl_container_fixed_array_hdrs"], - export_generated_headers: ["absl_container_fixed_array_hdrs"], - - whole_static_libs: [ - "absl_container_compressed_tuple", - "absl_algorithm", - "absl_base_config", - "absl_base_core_headers", - "absl_base_dynamic_annotations", - "absl_base_iterator_traits_internal", - "absl_base_throw_delegate", - "absl_hash_weakly_mixed_integer", - "absl_memory", - ], - export_static_lib_headers: [ - "absl_container_compressed_tuple", - "absl_algorithm", - "absl_base_config", - "absl_base_core_headers", - "absl_base_dynamic_annotations", - "absl_base_iterator_traits_internal", - "absl_base_throw_delegate", - "absl_hash_weakly_mixed_integer", - "absl_memory", - ], - -} - -cc_library_static { - name: "absl_container_fixed_array_notls", - defaults: ["absl_notls_defaults"], - visibility: ["//external/protobuf"], - srcs: [ - - ], - generated_headers: ["absl_container_fixed_array_hdrs"], - export_generated_headers: ["absl_container_fixed_array_hdrs"], - - whole_static_libs: [ - "absl_container_compressed_tuple_notls", - "absl_algorithm_notls", - "absl_base_config_notls", - "absl_base_core_headers_notls", - "absl_base_dynamic_annotations_notls", - "absl_base_iterator_traits_internal_notls", - "absl_base_throw_delegate_notls", - "absl_hash_weakly_mixed_integer_notls", - "absl_memory_notls", - ], - export_static_lib_headers: [ - "absl_container_compressed_tuple_notls", - "absl_algorithm_notls", - "absl_base_config_notls", - "absl_base_core_headers_notls", - "absl_base_dynamic_annotations_notls", - "absl_base_iterator_traits_internal_notls", - "absl_base_throw_delegate_notls", - "absl_hash_weakly_mixed_integer_notls", - "absl_memory_notls", - ], - -} - -genrule { name: "absl_strings_has_ostream_operator_hdrs", srcs: [ "absl/strings/has_ostream_operator.h", @@ -4438,11 +4937,13 @@ export_generated_headers: ["absl_strings_cord_hdrs"], whole_static_libs: [ + "absl_strings_append_and_overwrite", "absl_strings_cord_internal", "absl_strings_cordz_info", "absl_strings_cordz_update_scope", "absl_strings_cordz_update_tracker", "absl_strings_internal", + "absl_strings_resize_and_overwrite", "absl_strings", "absl_base_config", "absl_base_core_headers", @@ -4461,11 +4962,13 @@ "absl_types_span", ], export_static_lib_headers: [ + "absl_strings_append_and_overwrite", "absl_strings_cord_internal", "absl_strings_cordz_info", "absl_strings_cordz_update_scope", "absl_strings_cordz_update_tracker", "absl_strings_internal", + "absl_strings_resize_and_overwrite", "absl_strings", "absl_base_config", "absl_base_core_headers", @@ -4498,11 +5001,13 @@ export_generated_headers: ["absl_strings_cord_hdrs"], whole_static_libs: [ + "absl_strings_append_and_overwrite_notls", "absl_strings_cord_internal_notls", "absl_strings_cordz_info_notls", "absl_strings_cordz_update_scope_notls", "absl_strings_cordz_update_tracker_notls", "absl_strings_internal_notls", + "absl_strings_resize_and_overwrite_notls", "absl_strings_notls", "absl_base_config_notls", "absl_base_core_headers_notls", @@ -4521,11 +5026,13 @@ "absl_types_span_notls", ], export_static_lib_headers: [ + "absl_strings_append_and_overwrite_notls", "absl_strings_cord_internal_notls", "absl_strings_cordz_info_notls", "absl_strings_cordz_update_scope_notls", "absl_strings_cordz_update_tracker_notls", "absl_strings_internal_notls", + "absl_strings_resize_and_overwrite_notls", "absl_strings_notls", "absl_base_config_notls", "absl_base_core_headers_notls", @@ -4772,65 +5279,6 @@ } genrule { - name: "absl_base_prefetch_hdrs", - srcs: [ - "absl/base/prefetch.h", - ], - out: [ - "my_include_dir/absl/base/prefetch.h", - ], - export_include_dirs: ["my_include_dir"], - cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + - "cp --parents $(in) $(genDir)/temp && " + - // delete empty folders automatically created by soong - "rm -rf $(genDir)/my_include_dir/* && " + - "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + - "rm -rf $(genDir)/temp", -} - -cc_library_static { - name: "absl_base_prefetch", - defaults: ["absl_defaults"], - visibility: ["//visibility:public"], - srcs: [ - - ], - generated_headers: ["absl_base_prefetch_hdrs"], - export_generated_headers: ["absl_base_prefetch_hdrs"], - - whole_static_libs: [ - "absl_base_config", - "absl_base_core_headers", - ], - export_static_lib_headers: [ - "absl_base_config", - "absl_base_core_headers", - ], - -} - -cc_library_static { - name: "absl_base_prefetch_notls", - defaults: ["absl_notls_defaults"], - visibility: ["//external/protobuf"], - srcs: [ - - ], - generated_headers: ["absl_base_prefetch_hdrs"], - export_generated_headers: ["absl_base_prefetch_hdrs"], - - whole_static_libs: [ - "absl_base_config_notls", - "absl_base_core_headers_notls", - ], - export_static_lib_headers: [ - "absl_base_config_notls", - "absl_base_core_headers_notls", - ], - -} - -genrule { name: "absl_crc_non_temporal_memcpy_hdrs", srcs: [ "absl/crc/internal/non_temporal_memcpy.h", @@ -5262,6 +5710,7 @@ "absl_base", "absl_base_config", "absl_base_core_headers", + "absl_base_no_destructor", "absl_base_raw_logging_internal", "absl_container_inlined_vector", "absl_debugging_stacktrace", @@ -5278,6 +5727,7 @@ "absl_base", "absl_base_config", "absl_base_core_headers", + "absl_base_no_destructor", "absl_base_raw_logging_internal", "absl_container_inlined_vector", "absl_debugging_stacktrace", @@ -5307,6 +5757,7 @@ "absl_base_notls", "absl_base_config_notls", "absl_base_core_headers_notls", + "absl_base_no_destructor_notls", "absl_base_raw_logging_internal_notls", "absl_container_inlined_vector_notls", "absl_debugging_stacktrace_notls", @@ -5323,6 +5774,7 @@ "absl_base_notls", "absl_base_config_notls", "absl_base_core_headers_notls", + "absl_base_no_destructor_notls", "absl_base_raw_logging_internal_notls", "absl_container_inlined_vector_notls", "absl_debugging_stacktrace_notls", @@ -5737,12 +6189,14 @@ whole_static_libs: [ "absl_base_config", + "absl_hash", "absl_memory", "absl_meta_type_traits", "absl_utility", ], export_static_lib_headers: [ "absl_base_config", + "absl_hash", "absl_memory", "absl_meta_type_traits", "absl_utility", @@ -5762,12 +6216,14 @@ whole_static_libs: [ "absl_base_config_notls", + "absl_hash_notls", "absl_memory_notls", "absl_meta_type_traits_notls", "absl_utility_notls", ], export_static_lib_headers: [ "absl_base_config_notls", + "absl_hash_notls", "absl_memory_notls", "absl_meta_type_traits_notls", "absl_utility_notls", @@ -7718,45 +8174,6 @@ } -cc_test_library { - name: "absl_log_scoped_mock_log_notls", - defaults: ["absl_notls_test_defaults"], - visibility: ["//external/protobuf"], - srcs: [ - "absl/log/scoped_mock_log.cc", - ], - generated_headers: ["absl_log_scoped_mock_log_hdrs"], - export_generated_headers: ["absl_log_scoped_mock_log_hdrs"], - - whole_static_libs: [ - "absl_log_log_entry_notls", - "absl_log_log_sink_notls", - "absl_log_log_sink_registry_notls", - "absl_base_config_notls", - "absl_base_log_severity_notls", - "absl_base_raw_logging_internal_notls", - "absl_strings_notls", - ], - export_static_lib_headers: [ - "absl_log_log_entry_notls", - "absl_log_log_sink_notls", - "absl_log_log_sink_registry_notls", - "absl_base_config_notls", - "absl_base_log_severity_notls", - "absl_base_raw_logging_internal_notls", - "absl_strings_notls", - ], - - static_libs: [ - "libgmock", - "libgtest", - ], - shared: { - enabled: false, - }, - -} - genrule { name: "absl_log_log_sink_registry_hdrs", srcs: [ @@ -8028,7 +8445,7 @@ defaults: ["absl_defaults"], visibility: ["//visibility:public"], srcs: [ - + "absl/log/log_entry.cc", ], generated_headers: ["absl_log_log_entry_hdrs"], export_generated_headers: ["absl_log_log_entry_hdrs"], @@ -8038,6 +8455,7 @@ "absl_base_core_headers", "absl_base_log_severity", "absl_log_internal_config", + "absl_log_internal_proto", "absl_strings", "absl_time", "absl_types_span", @@ -8047,6 +8465,7 @@ "absl_base_core_headers", "absl_base_log_severity", "absl_log_internal_config", + "absl_log_internal_proto", "absl_strings", "absl_time", "absl_types_span", @@ -8059,7 +8478,7 @@ defaults: ["absl_notls_defaults"], visibility: ["//external/protobuf"], srcs: [ - + "absl/log/log_entry.cc", ], generated_headers: ["absl_log_log_entry_hdrs"], export_generated_headers: ["absl_log_log_entry_hdrs"], @@ -8069,6 +8488,7 @@ "absl_base_core_headers_notls", "absl_base_log_severity_notls", "absl_log_internal_config_notls", + "absl_log_internal_proto_notls", "absl_strings_notls", "absl_time_notls", "absl_types_span_notls", @@ -8078,6 +8498,7 @@ "absl_base_core_headers_notls", "absl_base_log_severity_notls", "absl_log_internal_config_notls", + "absl_log_internal_proto_notls", "absl_strings_notls", "absl_time_notls", "absl_types_span_notls", @@ -8086,6 +8507,77 @@ } genrule { + name: "absl_log_internal_proto_hdrs", + srcs: [ + "absl/log/internal/proto.h", + ], + out: [ + "my_include_dir/absl/log/internal/proto.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_log_internal_proto", + defaults: ["absl_defaults"], + + srcs: [ + "absl/log/internal/proto.cc", + ], + generated_headers: ["absl_log_internal_proto_hdrs"], + export_generated_headers: ["absl_log_internal_proto_hdrs"], + + whole_static_libs: [ + "absl_base", + "absl_base_config", + "absl_base_core_headers", + "absl_strings", + "absl_types_span", + ], + export_static_lib_headers: [ + "absl_base", + "absl_base_config", + "absl_base_core_headers", + "absl_strings", + "absl_types_span", + ], + +} + +cc_library_static { + name: "absl_log_internal_proto_notls", + defaults: ["absl_notls_defaults"], + + srcs: [ + "absl/log/internal/proto.cc", + ], + generated_headers: ["absl_log_internal_proto_hdrs"], + export_generated_headers: ["absl_log_internal_proto_hdrs"], + + whole_static_libs: [ + "absl_base_notls", + "absl_base_config_notls", + "absl_base_core_headers_notls", + "absl_strings_notls", + "absl_types_span_notls", + ], + export_static_lib_headers: [ + "absl_base_notls", + "absl_base_config_notls", + "absl_base_core_headers_notls", + "absl_strings_notls", + "absl_types_span_notls", + ], + +} + +genrule { name: "absl_log_internal_config_hdrs", srcs: [ "absl/log/internal/config.h", @@ -8378,253 +8870,6 @@ } genrule { - name: "absl_hash_hdrs", - srcs: [ - "absl/hash/hash.h", - "absl/hash/internal/hash.h", - ], - out: [ - "my_include_dir/absl/hash/hash.h", - "my_include_dir/absl/hash/internal/hash.h", - ], - export_include_dirs: ["my_include_dir"], - cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + - "cp --parents $(in) $(genDir)/temp && " + - // delete empty folders automatically created by soong - "rm -rf $(genDir)/my_include_dir/* && " + - "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + - "rm -rf $(genDir)/temp", -} - -cc_library_static { - name: "absl_hash", - defaults: ["absl_defaults"], - visibility: ["//visibility:public"], - srcs: [ - "absl/hash/internal/hash.cc", - ], - generated_headers: ["absl_hash_hdrs"], - export_generated_headers: ["absl_hash_hdrs"], - - whole_static_libs: [ - "absl_hash_city", - "absl_hash_low_level_hash", - "absl_hash_weakly_mixed_integer", - "absl_base_config", - "absl_base_core_headers", - "absl_base_endian", - "absl_container_fixed_array", - "absl_functional_function_ref", - "absl_meta_type_traits", - "absl_numeric_bits", - "absl_numeric_int128", - "absl_strings", - "absl_types_optional", - "absl_types_variant", - "absl_utility", - ], - export_static_lib_headers: [ - "absl_hash_city", - "absl_hash_low_level_hash", - "absl_hash_weakly_mixed_integer", - "absl_base_config", - "absl_base_core_headers", - "absl_base_endian", - "absl_container_fixed_array", - "absl_functional_function_ref", - "absl_meta_type_traits", - "absl_numeric_bits", - "absl_numeric_int128", - "absl_strings", - "absl_types_optional", - "absl_types_variant", - "absl_utility", - ], - -} - -cc_library_static { - name: "absl_hash_notls", - defaults: ["absl_notls_defaults"], - visibility: ["//external/protobuf"], - srcs: [ - "absl/hash/internal/hash.cc", - ], - generated_headers: ["absl_hash_hdrs"], - export_generated_headers: ["absl_hash_hdrs"], - - whole_static_libs: [ - "absl_hash_city_notls", - "absl_hash_low_level_hash_notls", - "absl_hash_weakly_mixed_integer_notls", - "absl_base_config_notls", - "absl_base_core_headers_notls", - "absl_base_endian_notls", - "absl_container_fixed_array_notls", - "absl_functional_function_ref_notls", - "absl_meta_type_traits_notls", - "absl_numeric_bits_notls", - "absl_numeric_int128_notls", - "absl_strings_notls", - "absl_types_optional_notls", - "absl_types_variant_notls", - "absl_utility_notls", - ], - export_static_lib_headers: [ - "absl_hash_city_notls", - "absl_hash_low_level_hash_notls", - "absl_hash_weakly_mixed_integer_notls", - "absl_base_config_notls", - "absl_base_core_headers_notls", - "absl_base_endian_notls", - "absl_container_fixed_array_notls", - "absl_functional_function_ref_notls", - "absl_meta_type_traits_notls", - "absl_numeric_bits_notls", - "absl_numeric_int128_notls", - "absl_strings_notls", - "absl_types_optional_notls", - "absl_types_variant_notls", - "absl_utility_notls", - ], - -} - -genrule { - name: "absl_hash_low_level_hash_hdrs", - srcs: [ - "absl/hash/internal/low_level_hash.h", - ], - out: [ - "my_include_dir/absl/hash/internal/low_level_hash.h", - ], - export_include_dirs: ["my_include_dir"], - cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + - "cp --parents $(in) $(genDir)/temp && " + - // delete empty folders automatically created by soong - "rm -rf $(genDir)/my_include_dir/* && " + - "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + - "rm -rf $(genDir)/temp", -} - -cc_library_static { - name: "absl_hash_low_level_hash", - defaults: ["absl_defaults"], - - srcs: [ - "absl/hash/internal/low_level_hash.cc", - ], - generated_headers: ["absl_hash_low_level_hash_hdrs"], - export_generated_headers: ["absl_hash_low_level_hash_hdrs"], - - whole_static_libs: [ - "absl_base_config", - "absl_base_core_headers", - "absl_base_endian", - "absl_base_prefetch", - "absl_numeric_int128", - ], - export_static_lib_headers: [ - "absl_base_config", - "absl_base_core_headers", - "absl_base_endian", - "absl_base_prefetch", - "absl_numeric_int128", - ], - -} - -cc_library_static { - name: "absl_hash_low_level_hash_notls", - defaults: ["absl_notls_defaults"], - - srcs: [ - "absl/hash/internal/low_level_hash.cc", - ], - generated_headers: ["absl_hash_low_level_hash_hdrs"], - export_generated_headers: ["absl_hash_low_level_hash_hdrs"], - - whole_static_libs: [ - "absl_base_config_notls", - "absl_base_core_headers_notls", - "absl_base_endian_notls", - "absl_base_prefetch_notls", - "absl_numeric_int128_notls", - ], - export_static_lib_headers: [ - "absl_base_config_notls", - "absl_base_core_headers_notls", - "absl_base_endian_notls", - "absl_base_prefetch_notls", - "absl_numeric_int128_notls", - ], - -} - -genrule { - name: "absl_hash_city_hdrs", - srcs: [ - "absl/hash/internal/city.h", - ], - out: [ - "my_include_dir/absl/hash/internal/city.h", - ], - export_include_dirs: ["my_include_dir"], - cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + - "cp --parents $(in) $(genDir)/temp && " + - // delete empty folders automatically created by soong - "rm -rf $(genDir)/my_include_dir/* && " + - "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + - "rm -rf $(genDir)/temp", -} - -cc_library_static { - name: "absl_hash_city", - defaults: ["absl_defaults"], - visibility: ["//visibility:public"], - srcs: [ - "absl/hash/internal/city.cc", - ], - generated_headers: ["absl_hash_city_hdrs"], - export_generated_headers: ["absl_hash_city_hdrs"], - - whole_static_libs: [ - "absl_base_config", - "absl_base_core_headers", - "absl_base_endian", - ], - export_static_lib_headers: [ - "absl_base_config", - "absl_base_core_headers", - "absl_base_endian", - ], - -} - -cc_library_static { - name: "absl_hash_city_notls", - defaults: ["absl_notls_defaults"], - visibility: ["//external/protobuf"], - srcs: [ - "absl/hash/internal/city.cc", - ], - generated_headers: ["absl_hash_city_hdrs"], - export_generated_headers: ["absl_hash_city_hdrs"], - - whole_static_libs: [ - "absl_base_config_notls", - "absl_base_core_headers_notls", - "absl_base_endian_notls", - ], - export_static_lib_headers: [ - "absl_base_config_notls", - "absl_base_core_headers_notls", - "absl_base_endian_notls", - ], - -} - -genrule { name: "absl_cleanup_hdrs", srcs: [ "absl/cleanup/cleanup.h", @@ -8822,12 +9067,12 @@ } genrule { - name: "absl_log_hdrs", + name: "absl_log_log_streamer_hdrs", srcs: [ - "absl/log/log.h", + "absl/log/log_streamer.h", ], out: [ - "my_include_dir/absl/log/log.h", + "my_include_dir/absl/log/log_streamer.h", ], export_include_dirs: ["my_include_dir"], cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + @@ -8839,42 +9084,117 @@ } cc_library_static { - name: "absl_log", + name: "absl_log_log_streamer", defaults: ["absl_defaults"], visibility: ["//visibility:public"], srcs: [ ], - generated_headers: ["absl_log_hdrs"], - export_generated_headers: ["absl_log_hdrs"], + generated_headers: ["absl_log_log_streamer_hdrs"], + export_generated_headers: ["absl_log_log_streamer_hdrs"], whole_static_libs: [ - "absl_log_vlog_is_on", + "absl_log_absl_log", + "absl_base_config", + "absl_base_log_severity", + "absl_strings", + "absl_strings_internal", + "absl_types_optional", + "absl_utility", + ], + export_static_lib_headers: [ + "absl_log_absl_log", + "absl_base_config", + "absl_base_log_severity", + "absl_strings", + "absl_strings_internal", + "absl_types_optional", + "absl_utility", + ], + +} + +cc_library_static { + name: "absl_log_log_streamer_notls", + defaults: ["absl_notls_defaults"], + visibility: ["//external/protobuf"], + srcs: [ + + ], + generated_headers: ["absl_log_log_streamer_hdrs"], + export_generated_headers: ["absl_log_log_streamer_hdrs"], + + whole_static_libs: [ + "absl_log_absl_log_notls", + "absl_base_config_notls", + "absl_base_log_severity_notls", + "absl_strings_notls", + "absl_strings_internal_notls", + "absl_types_optional_notls", + "absl_utility_notls", + ], + export_static_lib_headers: [ + "absl_log_absl_log_notls", + "absl_base_config_notls", + "absl_base_log_severity_notls", + "absl_strings_notls", + "absl_strings_internal_notls", + "absl_types_optional_notls", + "absl_utility_notls", + ], + +} + +genrule { + name: "absl_log_absl_log_hdrs", + srcs: [ + "absl/log/absl_log.h", + ], + out: [ + "my_include_dir/absl/log/absl_log.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_log_absl_log", + defaults: ["absl_defaults"], + visibility: ["//visibility:public"], + srcs: [ + + ], + generated_headers: ["absl_log_absl_log_hdrs"], + export_generated_headers: ["absl_log_absl_log_hdrs"], + + whole_static_libs: [ "absl_log_internal_log_impl", ], export_static_lib_headers: [ - "absl_log_vlog_is_on", "absl_log_internal_log_impl", ], } cc_library_static { - name: "absl_log_notls", + name: "absl_log_absl_log_notls", defaults: ["absl_notls_defaults"], visibility: ["//external/protobuf"], srcs: [ ], - generated_headers: ["absl_log_hdrs"], - export_generated_headers: ["absl_log_hdrs"], + generated_headers: ["absl_log_absl_log_hdrs"], + export_generated_headers: ["absl_log_absl_log_hdrs"], whole_static_libs: [ - "absl_log_vlog_is_on_notls", "absl_log_internal_log_impl_notls", ], export_static_lib_headers: [ - "absl_log_vlog_is_on_notls", "absl_log_internal_log_impl_notls", ], @@ -9446,77 +9766,6 @@ } genrule { - name: "absl_log_internal_proto_hdrs", - srcs: [ - "absl/log/internal/proto.h", - ], - out: [ - "my_include_dir/absl/log/internal/proto.h", - ], - export_include_dirs: ["my_include_dir"], - cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + - "cp --parents $(in) $(genDir)/temp && " + - // delete empty folders automatically created by soong - "rm -rf $(genDir)/my_include_dir/* && " + - "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + - "rm -rf $(genDir)/temp", -} - -cc_library_static { - name: "absl_log_internal_proto", - defaults: ["absl_defaults"], - - srcs: [ - "absl/log/internal/proto.cc", - ], - generated_headers: ["absl_log_internal_proto_hdrs"], - export_generated_headers: ["absl_log_internal_proto_hdrs"], - - whole_static_libs: [ - "absl_base", - "absl_base_config", - "absl_base_core_headers", - "absl_strings", - "absl_types_span", - ], - export_static_lib_headers: [ - "absl_base", - "absl_base_config", - "absl_base_core_headers", - "absl_strings", - "absl_types_span", - ], - -} - -cc_library_static { - name: "absl_log_internal_proto_notls", - defaults: ["absl_notls_defaults"], - - srcs: [ - "absl/log/internal/proto.cc", - ], - generated_headers: ["absl_log_internal_proto_hdrs"], - export_generated_headers: ["absl_log_internal_proto_hdrs"], - - whole_static_libs: [ - "absl_base_notls", - "absl_base_config_notls", - "absl_base_core_headers_notls", - "absl_strings_notls", - "absl_types_span_notls", - ], - export_static_lib_headers: [ - "absl_base_notls", - "absl_base_config_notls", - "absl_base_core_headers_notls", - "absl_strings_notls", - "absl_types_span_notls", - ], - -} - -genrule { name: "absl_log_internal_nullguard_hdrs", srcs: [ "absl/log/internal/nullguard.h", @@ -9860,6 +10109,65 @@ } genrule { + name: "absl_log_hdrs", + srcs: [ + "absl/log/log.h", + ], + out: [ + "my_include_dir/absl/log/log.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_log", + defaults: ["absl_defaults"], + visibility: ["//visibility:public"], + srcs: [ + + ], + generated_headers: ["absl_log_hdrs"], + export_generated_headers: ["absl_log_hdrs"], + + whole_static_libs: [ + "absl_log_vlog_is_on", + "absl_log_internal_log_impl", + ], + export_static_lib_headers: [ + "absl_log_vlog_is_on", + "absl_log_internal_log_impl", + ], + +} + +cc_library_static { + name: "absl_log_notls", + defaults: ["absl_notls_defaults"], + visibility: ["//external/protobuf"], + srcs: [ + + ], + generated_headers: ["absl_log_hdrs"], + export_generated_headers: ["absl_log_hdrs"], + + whole_static_libs: [ + "absl_log_vlog_is_on_notls", + "absl_log_internal_log_impl_notls", + ], + export_static_lib_headers: [ + "absl_log_vlog_is_on_notls", + "absl_log_internal_log_impl_notls", + ], + +} + +genrule { name: "absl_log_vlog_is_on_hdrs", srcs: [ "absl/log/vlog_is_on.h", @@ -10012,12 +10320,16 @@ "absl_log", "absl_base_config", "absl_base_core_headers", + "absl_base_nullability", + "absl_base_nullability_traits_internal", "absl_strings", ], export_static_lib_headers: [ "absl_log", "absl_base_config", "absl_base_core_headers", + "absl_base_nullability", + "absl_base_nullability_traits_internal", "absl_strings", ], @@ -10037,18 +10349,81 @@ "absl_log_notls", "absl_base_config_notls", "absl_base_core_headers_notls", + "absl_base_nullability_notls", + "absl_base_nullability_traits_internal_notls", "absl_strings_notls", ], export_static_lib_headers: [ "absl_log_notls", "absl_base_config_notls", "absl_base_core_headers_notls", + "absl_base_nullability_notls", + "absl_base_nullability_traits_internal_notls", "absl_strings_notls", ], } genrule { + name: "absl_base_nullability_traits_internal_hdrs", + srcs: [ + "absl/base/internal/nullability_traits.h", + ], + out: [ + "my_include_dir/absl/base/internal/nullability_traits.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_base_nullability_traits_internal", + defaults: ["absl_defaults"], + + srcs: [ + + ], + generated_headers: ["absl_base_nullability_traits_internal_hdrs"], + export_generated_headers: ["absl_base_nullability_traits_internal_hdrs"], + + whole_static_libs: [ + "absl_base_config", + "absl_base_nullability", + ], + export_static_lib_headers: [ + "absl_base_config", + "absl_base_nullability", + ], + +} + +cc_library_static { + name: "absl_base_nullability_traits_internal_notls", + defaults: ["absl_notls_defaults"], + + srcs: [ + + ], + generated_headers: ["absl_base_nullability_traits_internal_hdrs"], + export_generated_headers: ["absl_base_nullability_traits_internal_hdrs"], + + whole_static_libs: [ + "absl_base_config_notls", + "absl_base_nullability_notls", + ], + export_static_lib_headers: [ + "absl_base_config_notls", + "absl_base_nullability_notls", + ], + +} + +genrule { name: "absl_log_check_hdrs", srcs: [ "absl/log/check.h", @@ -10156,6 +10531,7 @@ "absl_base_nullability", "absl_debugging_leak_check", "absl_strings", + "absl_strings_has_ostream_operator", ], export_static_lib_headers: [ "absl_log_internal_nullguard", @@ -10167,6 +10543,7 @@ "absl_base_nullability", "absl_debugging_leak_check", "absl_strings", + "absl_strings_has_ostream_operator", ], } @@ -10191,6 +10568,7 @@ "absl_base_nullability_notls", "absl_debugging_leak_check_notls", "absl_strings_notls", + "absl_strings_has_ostream_operator_notls", ], export_static_lib_headers: [ "absl_log_internal_nullguard_notls", @@ -10202,6 +10580,7 @@ "absl_base_nullability_notls", "absl_debugging_leak_check_notls", "absl_strings_notls", + "absl_strings_has_ostream_operator_notls", ], } @@ -10236,14 +10615,12 @@ whole_static_libs: [ "absl_log_internal_check_op", "absl_log_internal_conditions", - "absl_log_internal_log_message", "absl_log_internal_strip", "absl_base_core_headers", ], export_static_lib_headers: [ "absl_log_internal_check_op", "absl_log_internal_conditions", - "absl_log_internal_log_message", "absl_log_internal_strip", "absl_base_core_headers", ], @@ -10263,14 +10640,12 @@ whole_static_libs: [ "absl_log_internal_check_op_notls", "absl_log_internal_conditions_notls", - "absl_log_internal_log_message_notls", "absl_log_internal_strip_notls", "absl_base_core_headers_notls", ], export_static_lib_headers: [ "absl_log_internal_check_op_notls", "absl_log_internal_conditions_notls", - "absl_log_internal_log_message_notls", "absl_log_internal_strip_notls", "absl_base_core_headers_notls", ], @@ -10278,61 +10653,6 @@ } genrule { - name: "absl_log_absl_log_hdrs", - srcs: [ - "absl/log/absl_log.h", - ], - out: [ - "my_include_dir/absl/log/absl_log.h", - ], - export_include_dirs: ["my_include_dir"], - cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + - "cp --parents $(in) $(genDir)/temp && " + - // delete empty folders automatically created by soong - "rm -rf $(genDir)/my_include_dir/* && " + - "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + - "rm -rf $(genDir)/temp", -} - -cc_library_static { - name: "absl_log_absl_log", - defaults: ["absl_defaults"], - visibility: ["//visibility:public"], - srcs: [ - - ], - generated_headers: ["absl_log_absl_log_hdrs"], - export_generated_headers: ["absl_log_absl_log_hdrs"], - - whole_static_libs: [ - "absl_log_internal_log_impl", - ], - export_static_lib_headers: [ - "absl_log_internal_log_impl", - ], - -} - -cc_library_static { - name: "absl_log_absl_log_notls", - defaults: ["absl_notls_defaults"], - visibility: ["//external/protobuf"], - srcs: [ - - ], - generated_headers: ["absl_log_absl_log_hdrs"], - export_generated_headers: ["absl_log_absl_log_hdrs"], - - whole_static_libs: [ - "absl_log_internal_log_impl_notls", - ], - export_static_lib_headers: [ - "absl_log_internal_log_impl_notls", - ], - -} - -genrule { name: "absl_log_absl_check_hdrs", srcs: [ "absl/log/absl_check.h", @@ -10388,6 +10708,165 @@ } genrule { + name: "absl_hash_hash_testing_hdrs", + srcs: [ + "absl/hash/hash_testing.h", + ], + out: [ + "my_include_dir/absl/hash/hash_testing.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_test_library { + name: "absl_hash_hash_testing", + defaults: ["absl_test_defaults"], + visibility: ["//visibility:public"], + srcs: [ + + ], + generated_headers: ["absl_hash_hash_testing_hdrs"], + export_generated_headers: ["absl_hash_hash_testing_hdrs"], + + whole_static_libs: [ + "absl_hash_spy_hash_state", + "absl_meta_type_traits", + "absl_strings", + "absl_types_variant", + ], + export_static_lib_headers: [ + "absl_hash_spy_hash_state", + "absl_meta_type_traits", + "absl_strings", + "absl_types_variant", + ], + + static_libs: [ + "libgmock", + "libgtest", + ], + shared: { + enabled: false, + }, + +} + +genrule { + name: "absl_hash_spy_hash_state_hdrs", + srcs: [ + "absl/hash/internal/spy_hash_state.h", + ], + out: [ + "my_include_dir/absl/hash/internal/spy_hash_state.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_test_library { + name: "absl_hash_spy_hash_state", + defaults: ["absl_test_defaults"], + + srcs: [ + + ], + generated_headers: ["absl_hash_spy_hash_state_hdrs"], + export_generated_headers: ["absl_hash_spy_hash_state_hdrs"], + + whole_static_libs: [ + "absl_hash", + "absl_hash_weakly_mixed_integer", + "absl_strings", + "absl_strings_str_format", + ], + export_static_lib_headers: [ + "absl_hash", + "absl_hash_weakly_mixed_integer", + "absl_strings", + "absl_strings_str_format", + ], + + static_libs: [ + "libgmock", + "libgtest", + ], + shared: { + enabled: false, + }, + +} + +genrule { + name: "absl_functional_overload_hdrs", + srcs: [ + "absl/functional/overload.h", + ], + out: [ + "my_include_dir/absl/functional/overload.h", + ], + export_include_dirs: ["my_include_dir"], + cmd: "mkdir -p $(genDir)/my_include_dir $(genDir)/temp && " + + "cp --parents $(in) $(genDir)/temp && " + + // delete empty folders automatically created by soong + "rm -rf $(genDir)/my_include_dir/* && " + + "mv $(genDir)/temp/external/abseil-cpp/absl $(genDir)/my_include_dir/ && " + + "rm -rf $(genDir)/temp", +} + +cc_library_static { + name: "absl_functional_overload", + defaults: ["absl_defaults"], + visibility: ["//visibility:public"], + srcs: [ + + ], + generated_headers: ["absl_functional_overload_hdrs"], + export_generated_headers: ["absl_functional_overload_hdrs"], + + whole_static_libs: [ + "absl_base_config", + "absl_meta_type_traits", + ], + export_static_lib_headers: [ + "absl_base_config", + "absl_meta_type_traits", + ], + +} + +cc_library_static { + name: "absl_functional_overload_notls", + defaults: ["absl_notls_defaults"], + visibility: ["//external/protobuf"], + srcs: [ + + ], + generated_headers: ["absl_functional_overload_hdrs"], + export_generated_headers: ["absl_functional_overload_hdrs"], + + whole_static_libs: [ + "absl_base_config_notls", + "absl_meta_type_traits_notls", + ], + export_static_lib_headers: [ + "absl_base_config_notls", + "absl_meta_type_traits_notls", + ], + +} + +genrule { name: "absl_functional_bind_front_hdrs", srcs: [ "absl/functional/bind_front.h", @@ -10600,14 +11079,12 @@ "absl_algorithm", "absl_base_config", "absl_base_core_headers", - "absl_base_nullability", "absl_meta_type_traits", ], export_static_lib_headers: [ "absl_algorithm", "absl_base_config", "absl_base_core_headers", - "absl_base_nullability", "absl_meta_type_traits", ], @@ -10627,14 +11104,12 @@ "absl_algorithm_notls", "absl_base_config_notls", "absl_base_core_headers_notls", - "absl_base_nullability_notls", "absl_meta_type_traits_notls", ], export_static_lib_headers: [ "absl_algorithm_notls", "absl_base_config_notls", "absl_base_core_headers_notls", - "absl_base_nullability_notls", "absl_meta_type_traits_notls", ], @@ -11032,6 +11507,7 @@ "absl_container_hashtable_debug_hooks", "absl_container_hashtablez_sampler", "absl_container_raw_hash_set_resize_impl", + "absl_base", "absl_base_config", "absl_base_core_headers", "absl_base_dynamic_annotations", @@ -11058,6 +11534,7 @@ "absl_container_hashtable_debug_hooks", "absl_container_hashtablez_sampler", "absl_container_raw_hash_set_resize_impl", + "absl_base", "absl_base_config", "absl_base_core_headers", "absl_base_dynamic_annotations", @@ -11097,6 +11574,7 @@ "absl_container_hashtable_debug_hooks_notls", "absl_container_hashtablez_sampler_notls", "absl_container_raw_hash_set_resize_impl_notls", + "absl_base_notls", "absl_base_config_notls", "absl_base_core_headers_notls", "absl_base_dynamic_annotations_notls", @@ -11123,6 +11601,7 @@ "absl_container_hashtable_debug_hooks_notls", "absl_container_hashtablez_sampler_notls", "absl_container_raw_hash_set_resize_impl_notls", + "absl_base_notls", "absl_base_config_notls", "absl_base_core_headers_notls", "absl_base_dynamic_annotations_notls", @@ -11515,10 +11994,12 @@ whole_static_libs: [ "absl_container_common_policy_traits", + "absl_container_container_memory", "absl_meta_type_traits", ], export_static_lib_headers: [ "absl_container_common_policy_traits", + "absl_container_container_memory", "absl_meta_type_traits", ], @@ -11536,10 +12017,12 @@ whole_static_libs: [ "absl_container_common_policy_traits_notls", + "absl_container_container_memory_notls", "absl_meta_type_traits_notls", ], export_static_lib_headers: [ "absl_container_common_policy_traits_notls", + "absl_container_container_memory_notls", "absl_meta_type_traits_notls", ],
diff --git a/CMake/AbseilDll.cmake b/CMake/AbseilDll.cmake index f01021b..47d0efd 100644 --- a/CMake/AbseilDll.cmake +++ b/CMake/AbseilDll.cmake
@@ -6,6 +6,7 @@ "algorithm/container.h" "base/attributes.h" "base/call_once.h" + "base/casts.cc" "base/casts.h" "base/config.h" "base/const_init.h" @@ -20,12 +21,11 @@ "base/internal/endian.h" "base/internal/errno_saver.h" "base/internal/hide_ptr.h" - "base/internal/identity.h" "base/internal/iterator_traits.h" "base/internal/low_level_alloc.cc" "base/internal/low_level_alloc.h" "base/internal/low_level_scheduling.h" - "base/internal/nullability_deprecated.h" + "base/internal/nullability_traits.h" "base/internal/per_thread_tls.h" "base/internal/poison.cc" "base/internal/poison.h" @@ -69,6 +69,7 @@ "cleanup/internal/cleanup.h" "container/btree_map.h" "container/btree_set.h" + "container/chunked_queue.h" "container/hash_container_defaults.h" "container/fixed_array.h" "container/flat_hash_map.h" @@ -76,6 +77,7 @@ "container/inlined_vector.h" "container/internal/btree.h" "container/internal/btree_container.h" + "container/internal/chunked_queue.h" "container/internal/common.h" "container/internal/common_policy_traits.h" "container/internal/compressed_tuple.h" @@ -96,6 +98,8 @@ "container/internal/raw_hash_set.h" "container/internal/raw_hash_set_resize_impl.h" "container/internal/tracked.h" + "container/linked_hash_map.h" + "container/linked_hash_set.h" "container/node_hash_map.h" "container/node_hash_set.h" "crc/crc32c.cc" @@ -129,6 +133,8 @@ "debugging/internal/address_is_readable.h" "debugging/internal/addresses.h" "debugging/internal/bounded_utf8_length_sequence.h" + "debugging/internal/borrowed_fixup_buffer.h" + "debugging/internal/borrowed_fixup_buffer.cc" "debugging/internal/decode_rust_punycode.cc" "debugging/internal/decode_rust_punycode.h" "debugging/internal/demangle.cc" @@ -160,8 +166,6 @@ "hash/internal/hash.h" "hash/internal/hash.cc" "hash/internal/spy_hash_state.h" - "hash/internal/low_level_hash.h" - "hash/internal/low_level_hash.cc" "hash/internal/weakly_mixed_integer.h" "log/absl_check.h" "log/absl_log.h" @@ -178,6 +182,7 @@ "log/internal/conditions.cc" "log/internal/conditions.h" "log/internal/config.h" + "log/internal/container.h" "log/internal/fnmatch.h" "log/internal/fnmatch.cc" "log/internal/globals.cc" @@ -204,6 +209,7 @@ "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" @@ -213,15 +219,20 @@ "log/vlog_is_on.h" "memory/memory.h" "meta/type_traits.h" + "meta/internal/requires.h" "numeric/bits.h" "numeric/int128.cc" "numeric/int128.h" "numeric/internal/bits.h" "numeric/internal/representation.h" + "profiling/hashtable.cc" + "profiling/hashtable.h" "profiling/internal/exponential_biased.cc" "profiling/internal/exponential_biased.h" "profiling/internal/periodic_sampler.cc" "profiling/internal/periodic_sampler.h" + "profiling/internal/profile_builder.cc" + "profiling/internal/profile_builder.h" "profiling/internal/sample_recorder.h" "random/bernoulli_distribution.h" "random/beta_distribution.h" @@ -291,6 +302,7 @@ "strings/cord_buffer.h" "strings/escaping.cc" "strings/escaping.h" + "strings/internal/append_and_overwrite.h" "strings/internal/charconv_bigint.cc" "strings/internal/charconv_bigint.h" "strings/internal/charconv_parse.cc" @@ -322,6 +334,9 @@ "strings/internal/cordz_update_tracker.h" "strings/internal/damerau_levenshtein_distance.h" "strings/internal/damerau_levenshtein_distance.cc" + "strings/internal/generic_printer.cc" + "strings/internal/generic_printer.h" + "strings/internal/generic_printer_internal.h" "strings/internal/stl_type_traits.h" "strings/internal/string_constant.h" "strings/internal/stringify_sink.h" @@ -340,8 +355,6 @@ "strings/str_replace.h" "strings/str_split.cc" "strings/str_split.h" - "strings/string_view.cc" - "strings/string_view.h" "strings/strip.h" "strings/substitute.cc" "strings/substitute.h" @@ -372,6 +385,7 @@ "strings/internal/str_split_internal.h" "strings/internal/utf8.cc" "strings/internal/utf8.h" + "strings/resize_and_overwrite.h" "synchronization/barrier.cc" "synchronization/barrier.h" "synchronization/blocking_counter.cc" @@ -440,9 +454,15 @@ "types/variant.h" "utility/utility.h" "debugging/leak_check.cc" + "strings/string_view.h" ) -if(NOT MSVC) +if(MSVC) + list(APPEND ABSL_INTERNAL_DLL_FILES + "time/internal/cctz/src/time_zone_name_win.cc" + "time/internal/cctz/src/time_zone_name_win.h" + ) +else() list(APPEND ABSL_INTERNAL_DLL_FILES "flags/commandlineflag.cc" "flags/commandlineflag.h" @@ -719,8 +739,10 @@ if(ABSL_INTERNAL_AT_LEAST_CXX20) set(ABSL_INTERNAL_CXX_STD_FEATURE cxx_std_20) -else() +elseif(ABSL_INTERNAL_AT_LEAST_CXX17) set(ABSL_INTERNAL_CXX_STD_FEATURE cxx_std_17) +else() + message(FATAL_ERROR "The compiler defaults to or is configured for C++ < 17. C++ >= 17 is required and Abseil and all libraries that use Abseil must use the same C++ language standard") endif() function(absl_internal_dll_contains) @@ -825,6 +847,9 @@ ${_dll_libs} ${ABSL_DEFAULT_LINKOPTS} $<$<BOOL:${ANDROID}>:-llog> + $<$<BOOL:${MINGW}>:-ladvapi32> + $<$<BOOL:${MINGW}>:-ldbghelp> + $<$<BOOL:${MINGW}>:-lbcrypt> ) set_target_properties(${_dll} PROPERTIES LINKER_LANGUAGE "CXX"
diff --git a/CMake/AbseilHelpers.cmake b/CMake/AbseilHelpers.cmake index 624a3c7..61e1ae4 100644 --- a/CMake/AbseilHelpers.cmake +++ b/CMake/AbseilHelpers.cmake
@@ -326,7 +326,12 @@ ) if (_build_type STREQUAL "dll") - set(ABSL_CC_LIB_DEPS abseil_dll) + if(${_in_dll}) + set(ABSL_CC_LIB_DEPS abseil_dll) + endif() + if(${_in_test_dll}) + set(ABSL_CC_LIB_DEPS abseil_test_dll) + endif() endif() target_link_libraries(${_NAME}
diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d3059d..26dc8e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -23,8 +23,8 @@ cmake_policy(SET CMP0141 NEW) endif (POLICY CMP0141) -project(absl LANGUAGES CXX VERSION 20250512) -set(ABSL_SOVERSION "2505.0.0") +project(absl LANGUAGES CXX VERSION 20260107) +set(ABSL_SOVERSION "2601.0.0") include(CTest) # Output directory is correct by default for most build setups. However, when
diff --git a/METADATA b/METADATA index 8475b65..2500921 100644 --- a/METADATA +++ b/METADATA
@@ -7,14 +7,15 @@ third_party { license_type: NOTICE last_upgrade_date { - year: 2025 - month: 7 - day: 2 + year: 2026 + month: 2 + day: 18 } homepage: "https://abseil.io" identifier { type: "Git" value: "https://github.com/abseil/abseil-cpp" - version: "20250512.1" + version: "255c84dadd029fd8ad25c5efb5933e47beaa00c7" + closest_version: "20260107.1" } }
diff --git a/MODULE.bazel b/MODULE.bazel index 48a65c7..7f542c9 100644 --- a/MODULE.bazel +++ b/MODULE.bazel
@@ -16,7 +16,7 @@ module( name = "abseil-cpp", - version = "20250512.1", + version = "20260107.1", compatibility_level = 1, ) @@ -25,13 +25,13 @@ dev_dependency = True) use_repo(cc_configure, "local_config_cc") -bazel_dep(name = "rules_cc", version = "0.1.1") -bazel_dep(name = "bazel_skylib", version = "1.7.1") -bazel_dep(name = "platforms", version = "0.0.11") +bazel_dep(name = "rules_cc", version = "0.2.9") +bazel_dep(name = "bazel_skylib", version = "1.8.1") +bazel_dep(name = "platforms", version = "1.0.0") bazel_dep( name = "google_benchmark", - version = "1.9.2", + version = "1.9.4", dev_dependency = True, )
diff --git a/absl/abseil.podspec.gen.py b/absl/abseil.podspec.gen.py index e1afa21..e19f951 100755 --- a/absl/abseil.podspec.gen.py +++ b/absl/abseil.podspec.gen.py
@@ -42,6 +42,7 @@ 'USER_HEADER_SEARCH_PATHS' => '$(inherited) "$(PODS_TARGET_SRCROOT)"', 'USE_HEADERMAP' => 'NO', 'ALWAYS_SEARCH_USER_PATHS' => 'NO', + 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++17', } s.ios.deployment_target = '12.0' s.osx.deployment_target = '10.13'
diff --git a/absl/algorithm/BUILD.bazel b/absl/algorithm/BUILD.bazel index 0ec8b92..7d8350c 100644 --- a/absl/algorithm/BUILD.bazel +++ b/absl/algorithm/BUILD.bazel
@@ -14,6 +14,8 @@ # limitations under the License. # +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load( "//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS", @@ -67,7 +69,6 @@ ":algorithm", "//absl/base:config", "//absl/base:core_headers", - "//absl/base:nullability", "//absl/meta:type_traits", ], )
diff --git a/absl/algorithm/CMakeLists.txt b/absl/algorithm/CMakeLists.txt index f3dd138..cdd5d14 100644 --- a/absl/algorithm/CMakeLists.txt +++ b/absl/algorithm/CMakeLists.txt
@@ -51,7 +51,6 @@ absl::config absl::core_headers absl::meta - absl::nullability PUBLIC ) @@ -64,7 +63,6 @@ ${ABSL_TEST_COPTS} DEPS absl::algorithm_container - absl::base absl::config absl::core_headers absl::memory
diff --git a/absl/algorithm/container.h b/absl/algorithm/container.h index 6f9c193..c0b8a10 100644 --- a/absl/algorithm/container.h +++ b/absl/algorithm/container.h
@@ -53,7 +53,6 @@ #include "absl/algorithm/algorithm.h" #include "absl/base/config.h" #include "absl/base/macros.h" -#include "absl/base/nullability.h" #include "absl/meta/type_traits.h" namespace absl { @@ -522,7 +521,8 @@ // Container-based version of the <algorithm> `std::copy()` function to copy a // container's elements into an iterator. template <typename InputSequence, typename OutputIterator> -OutputIterator c_copy(const InputSequence& input, OutputIterator output) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator +c_copy(const InputSequence& input, OutputIterator output) { return std::copy(container_algorithm_internal::c_begin(input), container_algorithm_internal::c_end(input), output); } @@ -532,7 +532,8 @@ // Container-based version of the <algorithm> `std::copy_n()` function to copy a // container's first N elements into an iterator. template <typename C, typename Size, typename OutputIterator> -OutputIterator c_copy_n(const C& input, Size n, OutputIterator output) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator +c_copy_n(const C& input, Size n, OutputIterator output) { return std::copy_n(container_algorithm_internal::c_begin(input), n, output); } @@ -541,8 +542,8 @@ // Container-based version of the <algorithm> `std::copy_if()` function to copy // a container's elements satisfying some condition into an iterator. template <typename InputSequence, typename OutputIterator, typename Pred> -OutputIterator c_copy_if(const InputSequence& input, OutputIterator output, - Pred&& pred) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator +c_copy_if(const InputSequence& input, OutputIterator output, Pred&& pred) { return std::copy_if(container_algorithm_internal::c_begin(input), container_algorithm_internal::c_end(input), output, std::forward<Pred>(pred)); @@ -553,8 +554,8 @@ // Container-based version of the <algorithm> `std::copy_backward()` function to // copy a container's elements in reverse order into an iterator. template <typename C, typename BidirectionalIterator> -BidirectionalIterator c_copy_backward(const C& src, - BidirectionalIterator dest) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 BidirectionalIterator +c_copy_backward(const C& src, BidirectionalIterator dest) { return std::copy_backward(container_algorithm_internal::c_begin(src), container_algorithm_internal::c_end(src), dest); } @@ -564,7 +565,8 @@ // Container-based version of the <algorithm> `std::move()` function to move // a container's elements into an iterator. template <typename C, typename OutputIterator> -OutputIterator c_move(C&& src, OutputIterator dest) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator c_move(C&& src, + OutputIterator dest) { return std::move(container_algorithm_internal::c_begin(src), container_algorithm_internal::c_end(src), dest); } @@ -574,7 +576,8 @@ // Container-based version of the <algorithm> `std::move_backward()` function to // move a container's elements into an iterator in reverse order. template <typename C, typename BidirectionalIterator> -BidirectionalIterator c_move_backward(C&& src, BidirectionalIterator dest) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 BidirectionalIterator +c_move_backward(C&& src, BidirectionalIterator dest) { return std::move_backward(container_algorithm_internal::c_begin(src), container_algorithm_internal::c_end(src), dest); } @@ -585,7 +588,9 @@ // swap a container's elements with another container's elements. Swaps the // first N elements of `c1` and `c2`, where N = min(size(c1), size(c2)). template <typename C1, typename C2> -container_algorithm_internal::ContainerIter<C2> c_swap_ranges(C1& c1, C2& c2) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 + container_algorithm_internal::ContainerIter<C2> + c_swap_ranges(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); @@ -605,8 +610,8 @@ // result in an iterator pointing to the last transformed element in the output // range. template <typename InputSequence, typename OutputIterator, typename UnaryOp> -OutputIterator c_transform(const InputSequence& input, OutputIterator output, - UnaryOp&& unary_op) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator c_transform( + const InputSequence& input, OutputIterator output, UnaryOp&& unary_op) { return std::transform(container_algorithm_internal::c_begin(input), container_algorithm_internal::c_end(input), output, std::forward<UnaryOp>(unary_op)); @@ -617,9 +622,9 @@ // where N = min(size(c1), size(c2)). template <typename InputSequence1, typename InputSequence2, typename OutputIterator, typename BinaryOp> -OutputIterator c_transform(const InputSequence1& input1, - const InputSequence2& input2, OutputIterator output, - BinaryOp&& binary_op) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator +c_transform(const InputSequence1& input1, const InputSequence2& input2, + OutputIterator output, BinaryOp&& binary_op) { auto first1 = container_algorithm_internal::c_begin(input1); auto last1 = container_algorithm_internal::c_end(input1); auto first2 = container_algorithm_internal::c_begin(input2); @@ -638,7 +643,9 @@ // replace a container's elements of some value with a new value. The container // is modified in place. template <typename Sequence, typename T> -void c_replace(Sequence& sequence, const T& old_value, const T& new_value) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 void c_replace(Sequence& sequence, + const T& old_value, + const T& new_value) { std::replace(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), old_value, new_value); @@ -650,7 +657,8 @@ // replace a container's elements of some value with a new value based on some // condition. The container is modified in place. template <typename C, typename Pred, typename T> -void c_replace_if(C& c, Pred&& pred, T&& new_value) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 void c_replace_if(C& c, Pred&& pred, + T&& new_value) { std::replace_if(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), std::forward<Pred>(pred), std::forward<T>(new_value)); @@ -662,8 +670,8 @@ // replace a container's elements of some value with a new value and return the // results within an iterator. template <typename C, typename OutputIterator, typename T> -OutputIterator c_replace_copy(const C& c, OutputIterator result, T&& old_value, - T&& new_value) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator c_replace_copy( + const C& c, OutputIterator result, T&& old_value, T&& new_value) { return std::replace_copy(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), result, std::forward<T>(old_value), @@ -676,8 +684,8 @@ // to replace a container's elements of some value with a new value based on // 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, - const T& new_value) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator c_replace_copy_if( + const C& c, OutputIterator result, Pred&& pred, 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), new_value); @@ -688,7 +696,7 @@ // 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, const T& value) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 void c_fill(C& c, const T& value) { std::fill(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), value); } @@ -698,7 +706,8 @@ // 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, const T& value) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 void c_fill_n(C& c, Size n, + const T& value) { std::fill_n(container_algorithm_internal::c_begin(c), n, value); } @@ -707,7 +716,7 @@ // Container-based version of the <algorithm> `std::generate()` function to // assign a container's elements to the values provided by the given generator. template <typename C, typename Generator> -void c_generate(C& c, Generator&& gen) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 void c_generate(C& c, Generator&& gen) { std::generate(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), std::forward<Generator>(gen)); @@ -719,8 +728,9 @@ // assign a container's first N elements to the values provided by the given // generator. template <typename C, typename Size, typename Generator> -container_algorithm_internal::ContainerIter<C> c_generate_n(C& c, Size n, - Generator&& gen) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 + container_algorithm_internal::ContainerIter<C> + c_generate_n(C& c, Size n, Generator&& gen) { return std::generate_n(container_algorithm_internal::c_begin(c), n, std::forward<Generator>(gen)); } @@ -736,8 +746,8 @@ // 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, - const T& value) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 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, value); @@ -749,8 +759,8 @@ // to copy a container's elements while removing any elements matching the given // condition. template <typename C, typename OutputIterator, typename Pred> -OutputIterator c_remove_copy_if(const C& c, OutputIterator result, - Pred&& pred) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator +c_remove_copy_if(const C& c, OutputIterator result, Pred&& pred) { return std::remove_copy_if(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), result, std::forward<Pred>(pred)); @@ -762,7 +772,8 @@ // copy a container's elements while removing any elements containing duplicate // values. template <typename C, typename OutputIterator> -OutputIterator c_unique_copy(const C& c, OutputIterator result) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator +c_unique_copy(const C& c, OutputIterator result) { return std::unique_copy(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), result); } @@ -770,8 +781,8 @@ // Overload of c_unique_copy() for using a predicate evaluation other than // `==` for comparing uniqueness of the element values. template <typename C, typename OutputIterator, typename BinaryPredicate> -OutputIterator c_unique_copy(const C& c, OutputIterator result, - BinaryPredicate&& pred) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator +c_unique_copy(const C& c, OutputIterator result, BinaryPredicate&& pred) { return std::unique_copy(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), result, std::forward<BinaryPredicate>(pred)); @@ -782,7 +793,7 @@ // Container-based version of the <algorithm> `std::reverse()` function to // reverse a container's elements. template <typename Sequence> -void c_reverse(Sequence& sequence) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 void c_reverse(Sequence& sequence) { std::reverse(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence)); } @@ -792,7 +803,8 @@ // Container-based version of the <algorithm> `std::reverse()` function to // reverse a container's elements and write them to an iterator range. template <typename C, typename OutputIterator> -OutputIterator c_reverse_copy(const C& sequence, OutputIterator result) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator +c_reverse_copy(const C& sequence, OutputIterator result) { return std::reverse_copy(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), result); @@ -805,7 +817,8 @@ // the first element in the container. template <typename C, typename Iterator = container_algorithm_internal::ContainerIter<C>> -Iterator c_rotate(C& sequence, Iterator middle) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 Iterator c_rotate(C& sequence, + Iterator middle) { return absl::rotate(container_algorithm_internal::c_begin(sequence), middle, container_algorithm_internal::c_end(sequence)); } @@ -816,10 +829,10 @@ // shift a container's elements leftward such that the `middle` element becomes // the first element in a new iterator range. template <typename C, typename OutputIterator> -OutputIterator c_rotate_copy( - const C& sequence, - container_algorithm_internal::ContainerIter<const C> middle, - OutputIterator result) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator +c_rotate_copy(const C& sequence, + container_algorithm_internal::ContainerIter<const C> middle, + OutputIterator result) { return std::rotate_copy(container_algorithm_internal::c_begin(sequence), middle, container_algorithm_internal::c_end(sequence), result); @@ -861,7 +874,8 @@ // to test whether all elements in the container for which `pred` returns `true` // precede those for which `pred` is `false`. template <typename C, typename Pred> -bool c_is_partitioned(const C& c, Pred&& pred) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 bool c_is_partitioned(const C& c, + Pred&& pred) { return std::is_partitioned(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), std::forward<Pred>(pred)); @@ -874,7 +888,9 @@ // which `pred` returns `true` precede all those for which it returns `false`, // returning an iterator to the first element of the second group. template <typename C, typename Pred> -container_algorithm_internal::ContainerIter<C> c_partition(C& c, Pred&& pred) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 + container_algorithm_internal::ContainerIter<C> + c_partition(C& c, Pred&& pred) { return std::partition(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), std::forward<Pred>(pred)); @@ -903,9 +919,9 @@ template <typename C, typename OutputIterator1, typename OutputIterator2, typename Pred> -std::pair<OutputIterator1, OutputIterator2> c_partition_copy( - const C& c, OutputIterator1 out_true, OutputIterator2 out_false, - Pred&& pred) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 std::pair<OutputIterator1, OutputIterator2> +c_partition_copy(const C& c, OutputIterator1 out_true, + OutputIterator2 out_false, Pred&& pred) { return std::partition_copy(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), out_true, out_false, std::forward<Pred>(pred)); @@ -917,8 +933,9 @@ // to return the first element of an already partitioned container for which // the given `pred` is not `true`. template <typename C, typename Pred> -container_algorithm_internal::ContainerIter<C> c_partition_point(C& c, - Pred&& pred) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 + container_algorithm_internal::ContainerIter<C> + c_partition_point(C& c, Pred&& pred) { return std::partition_point(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), std::forward<Pred>(pred)); @@ -933,7 +950,7 @@ // Container-based version of the <algorithm> `std::sort()` function // to sort elements in ascending order of their values. template <typename C> -void c_sort(C& c) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 void c_sort(C& c) { std::sort(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c)); } @@ -941,7 +958,7 @@ // Overload of c_sort() for performing a `comp` comparison other than the // default `operator<`. template <typename C, typename LessThan> -void c_sort(C& c, LessThan&& comp) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 void c_sort(C& c, LessThan&& comp) { std::sort(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), std::forward<LessThan>(comp)); @@ -972,7 +989,7 @@ // Container-based version of the <algorithm> `std::is_sorted()` function // to evaluate whether the given container is sorted in ascending order. template <typename C> -bool c_is_sorted(const C& c) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 bool c_is_sorted(const C& c) { return std::is_sorted(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c)); } @@ -980,7 +997,8 @@ // c_is_sorted() overload for performing a `comp` comparison other than the // default `operator<`. template <typename C, typename LessThan> -bool c_is_sorted(const C& c, LessThan&& comp) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 bool c_is_sorted(const C& c, + LessThan&& comp) { return std::is_sorted(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), std::forward<LessThan>(comp)); @@ -992,7 +1010,7 @@ // to rearrange elements within a container such that elements before `middle` // are sorted in ascending order. template <typename RandomAccessContainer> -void c_partial_sort( +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 void c_partial_sort( RandomAccessContainer& sequence, container_algorithm_internal::ContainerIter<RandomAccessContainer> middle) { std::partial_sort(container_algorithm_internal::c_begin(sequence), middle, @@ -1002,7 +1020,7 @@ // Overload of c_partial_sort() for performing a `comp` comparison other than // the default `operator<`. template <typename RandomAccessContainer, typename LessThan> -void c_partial_sort( +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 void c_partial_sort( RandomAccessContainer& sequence, container_algorithm_internal::ContainerIter<RandomAccessContainer> middle, LessThan&& comp) { @@ -1019,8 +1037,9 @@ // At most min(result.last - result.first, sequence.last - sequence.first) // elements from the sequence will be stored in the result. template <typename C, typename RandomAccessContainer> -container_algorithm_internal::ContainerIter<RandomAccessContainer> -c_partial_sort_copy(const C& sequence, RandomAccessContainer& result) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 + container_algorithm_internal::ContainerIter<RandomAccessContainer> + c_partial_sort_copy(const C& sequence, RandomAccessContainer& result) { return std::partial_sort_copy(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), container_algorithm_internal::c_begin(result), @@ -1030,9 +1049,10 @@ // Overload of c_partial_sort_copy() for performing a `comp` comparison other // than the default `operator<`. template <typename C, typename RandomAccessContainer, typename LessThan> -container_algorithm_internal::ContainerIter<RandomAccessContainer> -c_partial_sort_copy(const C& sequence, RandomAccessContainer& result, - LessThan&& comp) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 + container_algorithm_internal::ContainerIter<RandomAccessContainer> + c_partial_sort_copy(const C& sequence, RandomAccessContainer& result, + LessThan&& comp) { return std::partial_sort_copy(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), container_algorithm_internal::c_begin(result), @@ -1046,7 +1066,9 @@ // to return the first element within a container that is not sorted in // ascending order as an iterator. template <typename C> -container_algorithm_internal::ContainerIter<C> c_is_sorted_until(C& c) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 + container_algorithm_internal::ContainerIter<C> + c_is_sorted_until(C& c) { return std::is_sorted_until(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c)); } @@ -1054,8 +1076,9 @@ // Overload of c_is_sorted_until() for performing a `comp` comparison other than // the default `operator<`. template <typename C, typename LessThan> -container_algorithm_internal::ContainerIter<C> c_is_sorted_until( - C& c, LessThan&& comp) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 + container_algorithm_internal::ContainerIter<C> + c_is_sorted_until(C& c, LessThan&& comp) { return std::is_sorted_until(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), std::forward<LessThan>(comp)); @@ -1069,7 +1092,7 @@ // any order, except that all preceding `nth` will be less than that element, // and all following `nth` will be greater than that element. template <typename RandomAccessContainer> -void c_nth_element( +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 void c_nth_element( RandomAccessContainer& sequence, container_algorithm_internal::ContainerIter<RandomAccessContainer> nth) { std::nth_element(container_algorithm_internal::c_begin(sequence), nth, @@ -1079,7 +1102,7 @@ // Overload of c_nth_element() for performing a `comp` comparison other than // the default `operator<`. template <typename RandomAccessContainer, typename LessThan> -void c_nth_element( +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 void c_nth_element( RandomAccessContainer& sequence, container_algorithm_internal::ContainerIter<RandomAccessContainer> nth, LessThan&& comp) { @@ -1098,8 +1121,9 @@ // to return an iterator pointing to the first element in a sorted container // which does not compare less than `value`. template <typename Sequence, typename T> -container_algorithm_internal::ContainerIter<Sequence> c_lower_bound( - Sequence& sequence, const T& value) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 + container_algorithm_internal::ContainerIter<Sequence> + c_lower_bound(Sequence& sequence, const T& value) { return std::lower_bound(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), value); } @@ -1107,8 +1131,9 @@ // 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, const T& value, LessThan&& comp) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 + container_algorithm_internal::ContainerIter<Sequence> + c_lower_bound(Sequence& sequence, const T& value, LessThan&& comp) { return std::lower_bound(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), value, std::forward<LessThan>(comp)); @@ -1120,8 +1145,9 @@ // to return an iterator pointing to the first element in a sorted container // which is greater than `value`. template <typename Sequence, typename T> -container_algorithm_internal::ContainerIter<Sequence> c_upper_bound( - Sequence& sequence, const T& value) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 + container_algorithm_internal::ContainerIter<Sequence> + c_upper_bound(Sequence& sequence, const T& value) { return std::upper_bound(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), value); } @@ -1129,8 +1155,9 @@ // 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, const T& value, LessThan&& comp) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 + container_algorithm_internal::ContainerIter<Sequence> + c_upper_bound(Sequence& sequence, const T& value, LessThan&& comp) { return std::upper_bound(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), value, std::forward<LessThan>(comp)); @@ -1142,8 +1169,9 @@ // to return an iterator pair pointing to the first and last elements in a // sorted container which compare equal to `value`. template <typename Sequence, typename T> -container_algorithm_internal::ContainerIterPairType<Sequence, Sequence> -c_equal_range(Sequence& sequence, const T& value) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 + container_algorithm_internal::ContainerIterPairType<Sequence, Sequence> + c_equal_range(Sequence& sequence, const T& value) { return std::equal_range(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), value); } @@ -1151,8 +1179,9 @@ // 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, const T& value, LessThan&& comp) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 + container_algorithm_internal::ContainerIterPairType<Sequence, Sequence> + 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), value, std::forward<LessThan>(comp)); @@ -1164,7 +1193,8 @@ // to test if any element in the sorted container contains a value equivalent to // 'value'. template <typename Sequence, typename T> -bool c_binary_search(const Sequence& sequence, const T& value) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 bool c_binary_search( + const Sequence& sequence, const T& value) { return std::binary_search(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), value); @@ -1173,8 +1203,8 @@ // 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(const Sequence& sequence, const T& value, - LessThan&& comp) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 bool c_binary_search( + const Sequence& sequence, const T& value, LessThan&& comp) { return std::binary_search(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), value, std::forward<LessThan>(comp)); @@ -1189,7 +1219,8 @@ // Container-based version of the <algorithm> `std::merge()` function // to merge two sorted containers into a single sorted iterator. template <typename C1, typename C2, typename OutputIterator> -OutputIterator c_merge(const C1& c1, const C2& c2, OutputIterator result) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator +c_merge(const C1& c1, const C2& c2, OutputIterator result) { return std::merge(container_algorithm_internal::c_begin(c1), container_algorithm_internal::c_end(c1), container_algorithm_internal::c_begin(c2), @@ -1199,8 +1230,8 @@ // Overload of c_merge() for performing a `comp` comparison other than // the default `operator<`. template <typename C1, typename C2, typename OutputIterator, typename LessThan> -OutputIterator c_merge(const C1& c1, const C2& c2, OutputIterator result, - LessThan&& comp) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator +c_merge(const C1& c1, const C2& c2, OutputIterator result, LessThan&& comp) { return std::merge(container_algorithm_internal::c_begin(c1), container_algorithm_internal::c_end(c1), container_algorithm_internal::c_begin(c2), @@ -1236,7 +1267,8 @@ // to test whether a sorted container `c1` entirely contains another sorted // container `c2`. template <typename C1, typename C2> -bool c_includes(const C1& c1, const C2& c2) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 bool c_includes(const C1& c1, + const C2& c2) { return std::includes(container_algorithm_internal::c_begin(c1), container_algorithm_internal::c_end(c1), container_algorithm_internal::c_begin(c2), @@ -1246,7 +1278,8 @@ // Overload of c_includes() for performing a merge using a `comp` other than // `operator<`. template <typename C1, typename C2, typename LessThan> -bool c_includes(const C1& c1, const C2& c2, LessThan&& comp) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 bool c_includes(const C1& c1, const C2& c2, + LessThan&& comp) { return std::includes(container_algorithm_internal::c_begin(c1), container_algorithm_internal::c_end(c1), container_algorithm_internal::c_begin(c2), @@ -1266,7 +1299,8 @@ typename = typename std::enable_if< !container_algorithm_internal::IsUnorderedContainer<C2>::value, void>::type> -OutputIterator c_set_union(const C1& c1, const C2& c2, OutputIterator output) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator +c_set_union(const C1& c1, const C2& c2, OutputIterator output) { return std::set_union(container_algorithm_internal::c_begin(c1), container_algorithm_internal::c_end(c1), container_algorithm_internal::c_begin(c2), @@ -1282,8 +1316,8 @@ typename = typename std::enable_if< !container_algorithm_internal::IsUnorderedContainer<C2>::value, void>::type> -OutputIterator c_set_union(const C1& c1, const C2& c2, OutputIterator output, - LessThan&& comp) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator c_set_union( + const C1& c1, const C2& c2, OutputIterator output, LessThan&& comp) { return std::set_union(container_algorithm_internal::c_begin(c1), container_algorithm_internal::c_end(c1), container_algorithm_internal::c_begin(c2), @@ -1302,13 +1336,13 @@ typename = typename std::enable_if< !container_algorithm_internal::IsUnorderedContainer<C2>::value, void>::type> -OutputIterator c_set_intersection(const C1& c1, const C2& c2, - OutputIterator output) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator +c_set_intersection(const C1& c1, const C2& c2, OutputIterator output) { // In debug builds, ensure that both containers are sorted with respect to the // default comparator. std::set_intersection requires the containers be sorted // using operator<. - assert(absl::c_is_sorted(c1)); - assert(absl::c_is_sorted(c2)); + ABSL_ASSERT(absl::c_is_sorted(c1)); + ABSL_ASSERT(absl::c_is_sorted(c2)); return std::set_intersection(container_algorithm_internal::c_begin(c1), container_algorithm_internal::c_end(c1), container_algorithm_internal::c_begin(c2), @@ -1324,13 +1358,13 @@ typename = typename std::enable_if< !container_algorithm_internal::IsUnorderedContainer<C2>::value, void>::type> -OutputIterator c_set_intersection(const C1& c1, const C2& c2, - OutputIterator output, LessThan&& comp) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator c_set_intersection( + const C1& c1, const C2& c2, OutputIterator output, LessThan&& comp) { // In debug builds, ensure that both containers are sorted with respect to the // default comparator. std::set_intersection requires the containers be sorted // using the same comparator. - assert(absl::c_is_sorted(c1, comp)); - assert(absl::c_is_sorted(c2, comp)); + ABSL_ASSERT(absl::c_is_sorted(c1, comp)); + ABSL_ASSERT(absl::c_is_sorted(c2, comp)); return std::set_intersection(container_algorithm_internal::c_begin(c1), container_algorithm_internal::c_end(c1), container_algorithm_internal::c_begin(c2), @@ -1350,8 +1384,8 @@ typename = typename std::enable_if< !container_algorithm_internal::IsUnorderedContainer<C2>::value, void>::type> -OutputIterator c_set_difference(const C1& c1, const C2& c2, - OutputIterator output) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator +c_set_difference(const C1& c1, const C2& c2, OutputIterator output) { return std::set_difference(container_algorithm_internal::c_begin(c1), container_algorithm_internal::c_end(c1), container_algorithm_internal::c_begin(c2), @@ -1367,8 +1401,8 @@ typename = typename std::enable_if< !container_algorithm_internal::IsUnorderedContainer<C2>::value, void>::type> -OutputIterator c_set_difference(const C1& c1, const C2& c2, - OutputIterator output, LessThan&& comp) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator c_set_difference( + const C1& c1, const C2& c2, OutputIterator output, LessThan&& comp) { return std::set_difference(container_algorithm_internal::c_begin(c1), container_algorithm_internal::c_end(c1), container_algorithm_internal::c_begin(c2), @@ -1388,8 +1422,8 @@ typename = typename std::enable_if< !container_algorithm_internal::IsUnorderedContainer<C2>::value, void>::type> -OutputIterator c_set_symmetric_difference(const C1& c1, const C2& c2, - OutputIterator output) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator +c_set_symmetric_difference(const C1& c1, const C2& c2, OutputIterator output) { return std::set_symmetric_difference( container_algorithm_internal::c_begin(c1), container_algorithm_internal::c_end(c1), @@ -1406,9 +1440,8 @@ typename = typename std::enable_if< !container_algorithm_internal::IsUnorderedContainer<C2>::value, void>::type> -OutputIterator c_set_symmetric_difference(const C1& c1, const C2& c2, - OutputIterator output, - LessThan&& comp) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIterator c_set_symmetric_difference( + const C1& c1, const C2& c2, OutputIterator output, LessThan&& comp) { return std::set_symmetric_difference( container_algorithm_internal::c_begin(c1), container_algorithm_internal::c_end(c1), @@ -1426,7 +1459,8 @@ // Container-based version of the <algorithm> `std::push_heap()` function // to push a value onto a container heap. template <typename RandomAccessContainer> -void c_push_heap(RandomAccessContainer& sequence) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 void c_push_heap( + RandomAccessContainer& sequence) { std::push_heap(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence)); } @@ -1434,7 +1468,8 @@ // Overload of c_push_heap() for performing a push operation on a heap using a // `comp` other than `operator<`. template <typename RandomAccessContainer, typename LessThan> -void c_push_heap(RandomAccessContainer& sequence, LessThan&& comp) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 void c_push_heap( + RandomAccessContainer& sequence, LessThan&& comp) { std::push_heap(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), std::forward<LessThan>(comp)); @@ -1445,7 +1480,8 @@ // Container-based version of the <algorithm> `std::pop_heap()` function // to pop a value from a heap container. template <typename RandomAccessContainer> -void c_pop_heap(RandomAccessContainer& sequence) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 void c_pop_heap( + RandomAccessContainer& sequence) { std::pop_heap(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence)); } @@ -1453,7 +1489,8 @@ // Overload of c_pop_heap() for performing a pop operation on a heap using a // `comp` other than `operator<`. template <typename RandomAccessContainer, typename LessThan> -void c_pop_heap(RandomAccessContainer& sequence, LessThan&& comp) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 void c_pop_heap( + RandomAccessContainer& sequence, LessThan&& comp) { std::pop_heap(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), std::forward<LessThan>(comp)); @@ -1464,7 +1501,8 @@ // Container-based version of the <algorithm> `std::make_heap()` function // to make a container a heap. template <typename RandomAccessContainer> -void c_make_heap(RandomAccessContainer& sequence) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 void c_make_heap( + RandomAccessContainer& sequence) { std::make_heap(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence)); } @@ -1472,7 +1510,8 @@ // Overload of c_make_heap() for performing heap comparisons using a // `comp` other than `operator<` template <typename RandomAccessContainer, typename LessThan> -void c_make_heap(RandomAccessContainer& sequence, LessThan&& comp) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 void c_make_heap( + RandomAccessContainer& sequence, LessThan&& comp) { std::make_heap(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), std::forward<LessThan>(comp)); @@ -1483,7 +1522,8 @@ // Container-based version of the <algorithm> `std::sort_heap()` function // to sort a heap into ascending order (after which it is no longer a heap). template <typename RandomAccessContainer> -void c_sort_heap(RandomAccessContainer& sequence) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 void c_sort_heap( + RandomAccessContainer& sequence) { std::sort_heap(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence)); } @@ -1491,7 +1531,8 @@ // Overload of c_sort_heap() for performing heap comparisons using a // `comp` other than `operator<` template <typename RandomAccessContainer, typename LessThan> -void c_sort_heap(RandomAccessContainer& sequence, LessThan&& comp) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 void c_sort_heap( + RandomAccessContainer& sequence, LessThan&& comp) { std::sort_heap(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), std::forward<LessThan>(comp)); @@ -1502,7 +1543,8 @@ // Container-based version of the <algorithm> `std::is_heap()` function // to check whether the given container is a heap. template <typename RandomAccessContainer> -bool c_is_heap(const RandomAccessContainer& sequence) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 bool c_is_heap( + const RandomAccessContainer& sequence) { return std::is_heap(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence)); } @@ -1510,7 +1552,8 @@ // Overload of c_is_heap() for performing heap comparisons using a // `comp` other than `operator<` template <typename RandomAccessContainer, typename LessThan> -bool c_is_heap(const RandomAccessContainer& sequence, LessThan&& comp) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 bool c_is_heap( + const RandomAccessContainer& sequence, LessThan&& comp) { return std::is_heap(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), std::forward<LessThan>(comp)); @@ -1521,8 +1564,9 @@ // Container-based version of the <algorithm> `std::is_heap_until()` function // to find the first element in a given container which is not in heap order. template <typename RandomAccessContainer> -container_algorithm_internal::ContainerIter<RandomAccessContainer> -c_is_heap_until(RandomAccessContainer& sequence) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 + container_algorithm_internal::ContainerIter<RandomAccessContainer> + c_is_heap_until(RandomAccessContainer& sequence) { return std::is_heap_until(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence)); } @@ -1530,8 +1574,9 @@ // Overload of c_is_heap_until() for performing heap comparisons using a // `comp` other than `operator<` template <typename RandomAccessContainer, typename LessThan> -container_algorithm_internal::ContainerIter<RandomAccessContainer> -c_is_heap_until(RandomAccessContainer& sequence, LessThan&& comp) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 + container_algorithm_internal::ContainerIter<RandomAccessContainer> + c_is_heap_until(RandomAccessContainer& sequence, LessThan&& comp) { return std::is_heap_until(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), std::forward<LessThan>(comp)); @@ -1626,8 +1671,8 @@ // that capital letters ("A-Z") have ASCII values less than lowercase letters // ("a-z"). template <typename Sequence1, typename Sequence2> -bool c_lexicographical_compare(const Sequence1& sequence1, - const Sequence2& sequence2) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 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), @@ -1638,8 +1683,8 @@ // 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(const Sequence1& sequence1, - const Sequence2& sequence2, LessThan&& comp) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 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), @@ -1654,7 +1699,7 @@ // to rearrange a container's elements into the next lexicographically greater // permutation. template <typename C> -bool c_next_permutation(C& c) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 bool c_next_permutation(C& c) { return std::next_permutation(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c)); } @@ -1662,7 +1707,8 @@ // Overload of c_next_permutation() for performing a lexicographical // comparison using a `comp` operator instead of `operator<`. template <typename C, typename LessThan> -bool c_next_permutation(C& c, LessThan&& comp) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 bool c_next_permutation(C& c, + LessThan&& comp) { return std::next_permutation(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), std::forward<LessThan>(comp)); @@ -1674,7 +1720,7 @@ // to rearrange a container's elements into the next lexicographically lesser // permutation. template <typename C> -bool c_prev_permutation(C& c) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 bool c_prev_permutation(C& c) { return std::prev_permutation(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c)); } @@ -1682,7 +1728,8 @@ // Overload of c_prev_permutation() for performing a lexicographical // comparison using a `comp` operator instead of `operator<`. template <typename C, typename LessThan> -bool c_prev_permutation(C& c, LessThan&& comp) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 bool c_prev_permutation(C& c, + LessThan&& comp) { return std::prev_permutation(container_algorithm_internal::c_begin(c), container_algorithm_internal::c_end(c), std::forward<LessThan>(comp)); @@ -1698,7 +1745,8 @@ // 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, const T& value) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 void c_iota(Sequence& sequence, + const T& value) { std::iota(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), value); } @@ -1713,7 +1761,8 @@ // absl::decay_t<T>. As a user of this function you can casually read // this as "returns T by value" and assume it does the right thing. template <typename Sequence, typename T> -decay_t<T> c_accumulate(const Sequence& sequence, T&& init) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 decay_t<T> c_accumulate( + const Sequence& sequence, T&& init) { return std::accumulate(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), std::forward<T>(init)); @@ -1722,8 +1771,8 @@ // Overload of c_accumulate() for using a binary operations other than // addition for computing the accumulation. template <typename Sequence, typename T, typename BinaryOp> -decay_t<T> c_accumulate(const Sequence& sequence, T&& init, - BinaryOp&& binary_op) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 decay_t<T> c_accumulate( + const Sequence& sequence, T&& init, BinaryOp&& binary_op) { return std::accumulate(container_algorithm_internal::c_begin(sequence), container_algorithm_internal::c_end(sequence), std::forward<T>(init), @@ -1739,8 +1788,8 @@ // absl::decay_t<T>. As a user of this function you can casually read // this as "returns T by value" and assume it does the right thing. template <typename Sequence1, typename Sequence2, typename T> -decay_t<T> c_inner_product(const Sequence1& factors1, const Sequence2& factors2, - T&& sum) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 decay_t<T> c_inner_product( + const Sequence1& factors1, const Sequence2& factors2, T&& sum) { return std::inner_product(container_algorithm_internal::c_begin(factors1), container_algorithm_internal::c_end(factors1), container_algorithm_internal::c_begin(factors2), @@ -1752,8 +1801,9 @@ // the product between the two container's element pair). template <typename Sequence1, typename Sequence2, typename T, typename BinaryOp1, typename BinaryOp2> -decay_t<T> c_inner_product(const Sequence1& factors1, const Sequence2& factors2, - T&& sum, BinaryOp1&& op1, BinaryOp2&& op2) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 decay_t<T> c_inner_product( + const Sequence1& factors1, const Sequence2& factors2, T&& sum, + BinaryOp1&& op1, BinaryOp2&& op2) { return std::inner_product(container_algorithm_internal::c_begin(factors1), container_algorithm_internal::c_end(factors1), container_algorithm_internal::c_begin(factors2), @@ -1767,8 +1817,8 @@ // function to compute the difference between each element and the one preceding // it and write it to an iterator. template <typename InputSequence, typename OutputIt> -OutputIt c_adjacent_difference(const InputSequence& input, - OutputIt output_first) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIt +c_adjacent_difference(const InputSequence& input, OutputIt output_first) { return std::adjacent_difference(container_algorithm_internal::c_begin(input), container_algorithm_internal::c_end(input), output_first); @@ -1777,8 +1827,8 @@ // Overload of c_adjacent_difference() for using a binary operation other than // subtraction to compute the adjacent difference. template <typename InputSequence, typename OutputIt, typename BinaryOp> -OutputIt c_adjacent_difference(const InputSequence& input, - OutputIt output_first, BinaryOp&& op) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIt c_adjacent_difference( + const InputSequence& input, OutputIt output_first, BinaryOp&& op) { return std::adjacent_difference(container_algorithm_internal::c_begin(input), container_algorithm_internal::c_end(input), output_first, std::forward<BinaryOp>(op)); @@ -1791,7 +1841,8 @@ // to an iterator. The partial sum is the sum of all element values so far in // the sequence. template <typename InputSequence, typename OutputIt> -OutputIt c_partial_sum(const InputSequence& input, OutputIt output_first) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIt +c_partial_sum(const InputSequence& input, OutputIt output_first) { return std::partial_sum(container_algorithm_internal::c_begin(input), container_algorithm_internal::c_end(input), output_first); @@ -1800,8 +1851,8 @@ // Overload of c_partial_sum() for using a binary operation other than addition // to compute the "partial sum". template <typename InputSequence, typename OutputIt, typename BinaryOp> -OutputIt c_partial_sum(const InputSequence& input, OutputIt output_first, - BinaryOp&& op) { +ABSL_INTERNAL_CONSTEXPR_SINCE_CXX20 OutputIt c_partial_sum( + const InputSequence& input, OutputIt output_first, BinaryOp&& op) { return std::partial_sum(container_algorithm_internal::c_begin(input), container_algorithm_internal::c_end(input), output_first, std::forward<BinaryOp>(op));
diff --git a/absl/algorithm/container_test.cc b/absl/algorithm/container_test.cc index cb06335..347b41f 100644 --- a/absl/algorithm/container_test.cc +++ b/absl/algorithm/container_test.cc
@@ -1408,6 +1408,824 @@ kArray.begin()); } +TEST(ConstexprTest, Copy) { + static constexpr std::array kArray = {1, 2, 3}; + static constexpr auto kArrayCopy = [] { + std::array<int, 3> array; + absl::c_copy(kArray, array.begin()); + return array; + }(); + static_assert(kArrayCopy == kArray); +} + +TEST(ConstexprTest, CopyN) { + static constexpr std::array kArray = {1, 2, 3}; + static constexpr auto kArrayCopy = [] { + std::array<int, 2> array; + absl::c_copy_n(kArray, 2, array.begin()); + return array; + }(); + static_assert(kArrayCopy == std::array{1, 2}); +} + +TEST(ConstexprTest, CopyIf) { + static constexpr std::array kArray = {1, 2, 3, 4}; + static constexpr auto kArrayCopy = [] { + std::array<int, 3> array; + absl::c_copy_if(kArray, array.begin(), [](int x) { return x > 1; }); + return array; + }(); + static_assert(kArrayCopy == std::array{2, 3, 4}); +} + +TEST(ConstexprTest, CopyBackward) { + static constexpr std::array kArray = {1, 2, 3}; + static constexpr auto kArrayCopy = [] { + std::array<int, 3> array; + absl::c_copy_backward(kArray, array.end()); + return array; + }(); + static_assert(kArrayCopy == kArray); +} + +TEST(ConstexprTest, Move) { + static constexpr std::array kArray = {1, 2, 3}; + static constexpr auto kArrayMove = [] { + std::array<int, 3> array; + absl::c_move(kArray, array.begin()); + return array; + }(); + static_assert(kArrayMove == kArray); +} + +TEST(ConstexprTest, MoveBackward) { + static constexpr std::array kArray = {1, 2, 3}; + static constexpr auto kArrayMove = [] { + std::array<int, 3> array; + absl::c_move_backward(kArray, array.end()); + return array; + }(); + static_assert(kArrayMove == kArray); +} + +TEST(ConstexprTest, SwapRanges) { + static constexpr std::array kArray1 = {1, 2, 3}; + static constexpr std::array kArray2 = {4, 5, 6}; + + static constexpr auto kSwapped = [] { + std::array arr1 = kArray1; + std::array arr2 = kArray2; + absl::c_swap_ranges(arr1, arr2); + return std::make_pair(arr1, arr2); + }(); + + static_assert(kSwapped.first == kArray2); + static_assert(kSwapped.second == kArray1); +} + +TEST(ConstexprTest, Transform) { + static constexpr std::array kArray = {1, 2, 3}; + static constexpr auto kArrayTransform = [] { + std::array<int, 3> array; + absl::c_transform(kArray, array.begin(), [](int x) { return x + 1; }); + return array; + }(); + static_assert(kArrayTransform == std::array{2, 3, 4}); +} + +TEST(ConstexprTest, Replace) { + static constexpr std::array kArray = {1, 2, 3}; + static constexpr auto kArrayReplace = [] { + std::array array = kArray; + absl::c_replace(array, 1, 4); + return array; + }(); + static_assert(kArrayReplace == std::array{4, 2, 3}); +} + +TEST(ConstexprTest, ReplaceIf) { + static constexpr std::array kArray = {1, 2, 3}; + static constexpr auto kArrayReplaceIf = [] { + std::array array = kArray; + absl::c_replace_if(array, [](int x) { return x == 1; }, 4); + return array; + }(); + static_assert(kArrayReplaceIf == std::array{4, 2, 3}); +} + +TEST(ConstexprTest, ReplaceCopy) { + static constexpr std::array kArray = {1, 2, 3}; + static constexpr auto kArrayReplaceCopy = [] { + std::array<int, 3> array; + absl::c_replace_copy(kArray, array.begin(), 1, 4); + return array; + }(); + static_assert(kArrayReplaceCopy == std::array{4, 2, 3}); +} + +TEST(ConstexprTest, ReplaceCopyIf) { + static constexpr std::array kArray = {1, 2, 3}; + static constexpr auto kArrayReplaceCopyIf = [] { + std::array<int, 3> array; + absl::c_replace_copy_if( + kArray, array.begin(), [](int x) { return x == 1; }, 4); + return array; + }(); + static_assert(kArrayReplaceCopyIf == std::array{4, 2, 3}); +} + +TEST(ConstexprTest, Fill) { + static constexpr auto kArrayFill = [] { + std::array<int, 3> array; + absl::c_fill(array, 4); + return array; + }(); + static_assert(kArrayFill == std::array{4, 4, 4}); +} + +TEST(ConstexprTest, FillN) { + static constexpr auto kArrayFillN = [] { + std::array array = {0, 0, 0}; + absl::c_fill_n(array, 2, 4); + return array; + }(); + static_assert(kArrayFillN == std::array{4, 4, 0}); +} + +TEST(ConstexprTest, Generate) { + static constexpr auto kArrayGenerate = [] { + std::array<int, 3> array; + absl::c_generate(array, []() { return 4; }); + return array; + }(); + static_assert(kArrayGenerate == std::array{4, 4, 4}); +} + +TEST(ConstexprTest, GenerateN) { + static constexpr auto kArrayGenerateN = [] { + std::array array = {0, 0, 0}; + absl::c_generate_n(array, 2, []() { return 4; }); + return array; + }(); + static_assert(kArrayGenerateN == std::array{4, 4, 0}); +} + +TEST(ConstexprTest, RemoveCopy) { + static constexpr std::array kArray = {1, 2, 3}; + static constexpr auto kArrayRemoveCopy = [] { + std::array<int, 2> array; + absl::c_remove_copy(kArray, array.begin(), 1); + return array; + }(); + static_assert(kArrayRemoveCopy == std::array{2, 3}); +} + +TEST(ConstexprTest, RemoveCopyIf) { + static constexpr std::array kArray = {1, 2, 3}; + static constexpr auto kArrayRemoveCopyIf = [] { + std::array<int, 2> array; + absl::c_remove_copy_if(kArray, array.begin(), [](int x) { return x == 1; }); + return array; + }(); + static_assert(kArrayRemoveCopyIf == std::array{2, 3}); +} + +TEST(ConstexprTest, UniqueCopy) { + static constexpr std::array kArray = {1, 2, 2, 3}; + static constexpr auto kArrayUniqueCopy = [] { + std::array<int, 3> array; + absl::c_unique_copy(kArray, array.begin()); + return array; + }(); + static_assert(kArrayUniqueCopy == std::array{1, 2, 3}); +} + +TEST(ConstexprTest, UniqueCopyWithPredicate) { + static constexpr std::array kArray = {1, 2, 2, 3}; + static constexpr auto kArrayUniqueCopy = [] { + std::array<int, 3> array; + absl::c_unique_copy(kArray, array.begin(), std::equal_to<>()); + return array; + }(); + static_assert(kArrayUniqueCopy == std::array{1, 2, 3}); +} + +TEST(ConstexprTest, Reverse) { + static constexpr std::array kArray = {1, 2, 3}; + static constexpr auto kArrayReverse = [] { + std::array array = kArray; + absl::c_reverse(array); + return array; + }(); + static_assert(kArrayReverse == std::array{3, 2, 1}); +} + +TEST(ConstexprTest, ReverseCopy) { + static constexpr std::array kArray = {1, 2, 3}; + static constexpr auto kArrayReverseCopy = [] { + std::array<int, 3> array; + absl::c_reverse_copy(kArray, array.begin()); + return array; + }(); + static_assert(kArrayReverseCopy == std::array{3, 2, 1}); +} + +TEST(ConstexprTest, Rotate) { + static constexpr std::array kArray = {1, 2, 3}; + static constexpr auto kArrayRotate = [] { + std::array array = kArray; + absl::c_rotate(array, array.begin() + 1); + return array; + }(); + static_assert(kArrayRotate == std::array{2, 3, 1}); +} + +TEST(ConstexprTest, RotateCopy) { + static constexpr std::array kArray = {1, 2, 3}; + static constexpr auto kArrayRotateCopy = [] { + std::array<int, 3> array; + absl::c_rotate_copy(kArray, kArray.begin() + 1, array.begin()); + return array; + }(); + static_assert(kArrayRotateCopy == std::array{2, 3, 1}); +} + +TEST(ConstexprTest, IsPartitioned) { + static constexpr std::array kArray = {1, 2, 3}; + static_assert(!absl::c_is_partitioned(kArray, [](int x) { return x > 1; })); + + static constexpr std::array kPartitionedArray = {2, 3, 1}; + static_assert( + absl::c_is_partitioned(kPartitionedArray, [](int x) { return x > 1; })); +} + +TEST(ConstexprTest, Partition) { + static constexpr std::array kArray = {1, 2, 3}; + static constexpr auto kArrayPartition = [] { + std::array array = kArray; + absl::c_partition(array, [](int x) { return x > 1; }); + return array; + }(); + static_assert( + absl::c_is_partitioned(kArrayPartition, [](int x) { return x > 1; })); +} + +TEST(ConstexprTest, PartitionCopy) { + static constexpr std::array kArray = {1, 2, 3}; + static constexpr auto kPartitioned = [] { + std::array<int, 2> true_part; + std::array<int, 1> false_part; + absl::c_partition_copy(kArray, true_part.begin(), false_part.begin(), + [](int x) { return x > 1; }); + return std::make_pair(true_part, false_part); + }(); + static_assert(kPartitioned.first == std::array{2, 3}); + static_assert(kPartitioned.second == std::array{1}); +} + +TEST(ConstexprTest, PartitionPoint) { + static constexpr std::array kArray = {1, 2, 3}; + static constexpr auto kPartitionPoint = + absl::c_partition_point(kArray, [](int x) { return x > 1; }); + static_assert(kPartitionPoint == kArray.end()); +} + +TEST(ConstexprTest, Sort) { + static constexpr std::array kArray = {2, 1, 3}; + static constexpr auto kArraySort = [] { + std::array array = kArray; + absl::c_sort(array); + return array; + }(); + static_assert(kArraySort == std::array{1, 2, 3}); +} + +TEST(ConstexprTest, SortWithPredicate) { + static constexpr std::array kArray = {1, 2, 3}; + static constexpr auto kArraySort = [] { + std::array array = kArray; + absl::c_sort(array, std::greater<>()); + return array; + }(); + static_assert(kArraySort == std::array{3, 2, 1}); +} + +TEST(ConstexprTest, IsSorted) { + static constexpr std::array kSortedArray = {1, 2, 3}; + static_assert(absl::c_is_sorted(kSortedArray)); + static constexpr std::array kUnsortedArray = {1, 3, 2}; + static_assert(!absl::c_is_sorted(kUnsortedArray)); +} + +TEST(ConstexprTest, IsSortedWithPredicate) { + static constexpr std::array kSortedArray = {3, 2, 1}; + static_assert(absl::c_is_sorted(kSortedArray, std::greater<>())); + static constexpr std::array kUnsortedArray = {1, 3, 2}; + static_assert(!absl::c_is_sorted(kUnsortedArray, std::greater<>())); +} + +TEST(ConstexprTest, PartialSort) { + static constexpr std::array kArray = {3, 1, 4, 2}; + static constexpr auto kArrayPartialSort = [] { + std::array array = kArray; + absl::c_partial_sort(array, array.begin() + 2); + return array; + }(); + static_assert(kArrayPartialSort[0] == 1); + static_assert(kArrayPartialSort[1] == 2); +} + +TEST(ConstexprTest, PartialSortWithPredicate) { + static constexpr std::array kArray = {3, 1, 4, 2}; + static constexpr auto kArrayPartialSort = [] { + std::array array = kArray; + absl::c_partial_sort(array, array.begin() + 2, std::greater<>()); + return array; + }(); + static_assert(kArrayPartialSort[0] == 4); + static_assert(kArrayPartialSort[1] == 3); +} + +TEST(ConstexprTest, PartialSortCopy) { + static constexpr std::array kArray = {3, 1, 4, 2}; + static constexpr auto kArrayPartialSort = [] { + std::array<int, 4> array; + absl::c_partial_sort_copy(kArray, array); + return array; + }(); + static_assert(kArrayPartialSort[0] == 1); + static_assert(kArrayPartialSort[1] == 2); +} + +TEST(ConstexprTest, PartialSortCopyWithPredicate) { + static constexpr std::array kArray = {3, 1, 4, 2}; + static constexpr auto kArrayPartialSort = [] { + std::array<int, 4> array; + absl::c_partial_sort_copy(kArray, array, std::greater<>()); + return array; + }(); + static_assert(kArrayPartialSort[0] == 4); + static_assert(kArrayPartialSort[1] == 3); +} + +TEST(ConstexprTest, IsSortedUntil) { + static constexpr std::array kSortedArray = {1, 2, 3}; + static_assert(absl::c_is_sorted_until(kSortedArray) == kSortedArray.end()); + static constexpr std::array kUnsortedArray = {1, 3, 2}; + static_assert(absl::c_is_sorted_until(kUnsortedArray) == + kUnsortedArray.begin() + 2); +} + +TEST(ConstexprTest, IsSortedUntilWithPredicate) { + static constexpr std::array kSortedArray = {3, 2, 1}; + static_assert(absl::c_is_sorted_until(kSortedArray, std::greater<>()) == + kSortedArray.end()); + static constexpr std::array kUnsortedArray = {1, 3, 2}; + static_assert(absl::c_is_sorted_until(kUnsortedArray, std::greater<>()) == + kUnsortedArray.begin() + 1); +} + +TEST(ConstexprTest, NthElement) { + static constexpr std::array kArray = {2, 1, 3, 4}; + static constexpr auto kArrayNthElement = [] { + std::array array = kArray; + absl::c_nth_element(array, array.begin() + 2); + return array; + }(); + static_assert(kArrayNthElement[2] == 3); + static_assert(kArrayNthElement[0] <= kArrayNthElement[2]); + static_assert(kArrayNthElement[1] <= kArrayNthElement[2]); + static_assert(kArrayNthElement[3] >= kArrayNthElement[2]); +} + +TEST(ConstexprTest, NthElementWithPredicate) { + static constexpr std::array kArray = {1, 2, 3, 4}; + static constexpr auto kArrayNthElement = [] { + std::array array = kArray; + absl::c_nth_element(array, array.begin() + 2, std::greater<>()); + return array; + }(); + static_assert(kArrayNthElement[2] == 2); + static_assert(std::greater<>()(kArrayNthElement[0], kArrayNthElement[2]) || + kArrayNthElement[0] == kArrayNthElement[2]); + static_assert(std::greater<>()(kArrayNthElement[1], kArrayNthElement[2]) || + kArrayNthElement[1] == kArrayNthElement[2]); + static_assert(std::greater<>()(kArrayNthElement[2], kArrayNthElement[3]) || + kArrayNthElement[2] == kArrayNthElement[3]); +} + +TEST(ConstexprTest, LowerBound) { + static constexpr std::array kArray = {1, 2, 3, 4}; + static constexpr auto kLowerBound = absl::c_lower_bound(kArray, 2); + static_assert(kLowerBound == kArray.begin() + 1); +} + +TEST(ConstexprTest, LowerBoundWithPredicate) { + static constexpr std::array kArray = {4, 3, 2, 1}; + static constexpr auto kLowerBound = + absl::c_lower_bound(kArray, 2, std::greater<>()); + static_assert(kLowerBound == kArray.begin() + 2); +} + +TEST(ConstexprTest, UpperBound) { + static constexpr std::array kArray = {1, 2, 3, 4}; + static constexpr auto kUpperBound = absl::c_upper_bound(kArray, 2); + static_assert(kUpperBound == kArray.begin() + 2); +} + +TEST(ConstexprTest, UpperBoundWithPredicate) { + static constexpr std::array kArray = {4, 3, 2, 1}; + static constexpr auto kUpperBound = + absl::c_upper_bound(kArray, 2, std::greater<>()); + static_assert(kUpperBound == kArray.begin() + 3); +} + +TEST(ConstexprTest, EqualRange) { + static constexpr std::array kArray = {1, 2, 3, 4}; + static constexpr auto kEqualRange = absl::c_equal_range(kArray, 2); + static_assert(kEqualRange.first == kArray.begin() + 1); + static_assert(kEqualRange.second == kArray.begin() + 2); +} + +TEST(ConstexprTest, EqualRangeWithPredicate) { + static constexpr std::array kArray = {4, 3, 2, 1}; + static constexpr auto kEqualRange = + absl::c_equal_range(kArray, 2, std::greater<>()); + static_assert(kEqualRange.first == kArray.begin() + 2); + static_assert(kEqualRange.second == kArray.begin() + 3); +} + +TEST(ConstexprTest, BinarySearch) { + static constexpr std::array kArray = {1, 2, 3, 4}; + static constexpr bool kBinarySearch = absl::c_binary_search(kArray, 2); + static_assert(kBinarySearch); +} + +TEST(ConstexprTest, BinarySearchWithPredicate) { + static constexpr std::array kArray = {4, 3, 2, 1}; + static constexpr bool kBinarySearch = + absl::c_binary_search(kArray, 2, std::greater<>()); + static_assert(kBinarySearch); +} + +TEST(ConstexprTest, Merge) { + static constexpr std::array kArray1 = {1, 2, 3}; + static constexpr std::array kArray2 = {4, 5, 6}; + static constexpr auto kArrayMerge = [] { + std::array<int, 6> array; + absl::c_merge(kArray1, kArray2, array.begin()); + return array; + }(); + static_assert(kArrayMerge == std::array{1, 2, 3, 4, 5, 6}); +} + +TEST(ConstexprTest, MergeWithPredicate) { + static constexpr std::array kArray1 = {3, 2, 1}; + static constexpr std::array kArray2 = {6, 5, 4}; + static constexpr auto kArrayMerge = [] { + std::array<int, 6> array; + absl::c_merge(kArray1, kArray2, array.begin(), std::greater<>()); + return array; + }(); + static_assert(kArrayMerge == std::array{6, 5, 4, 3, 2, 1}); +} + +TEST(ConstexprTest, Includes) { + static constexpr std::array kArray1 = {1, 2, 3, 4, 5, 6}; + static constexpr std::array kArray2 = {2, 3, 5}; + static constexpr bool kIncludes = absl::c_includes(kArray1, kArray2); + static_assert(kIncludes); +} + +TEST(ConstexprTest, IncludesWithPredicate) { + static constexpr std::array kArray1 = {6, 5, 4, 3, 2, 1}; + static constexpr std::array kArray2 = {5, 3, 2}; + static constexpr bool kIncludes = + absl::c_includes(kArray1, kArray2, std::greater<>()); + static_assert(kIncludes); +} + +TEST(ConstexprTest, SetUnion) { + static constexpr std::array kArray1 = {1, 2, 3}; + static constexpr std::array kArray2 = {1, 3, 4}; + static constexpr auto kArraySetUnion = [] { + std::array<int, 4> array; + absl::c_set_union(kArray1, kArray2, array.begin()); + return array; + }(); + static_assert(kArraySetUnion == std::array{1, 2, 3, 4}); +} + +TEST(ConstexprTest, SetUnionWithPredicate) { + static constexpr std::array kArray1 = {3, 2, 1}; + static constexpr std::array kArray2 = {4, 3, 1}; + static constexpr auto kArraySetUnion = [] { + std::array<int, 4> array; + absl::c_set_union(kArray1, kArray2, array.begin(), std::greater<>()); + return array; + }(); + static_assert(kArraySetUnion == std::array{4, 3, 2, 1}); +} + +TEST(ConstexprTest, SetIntersection) { + static constexpr std::array kArray1 = {1, 2, 3}; + static constexpr std::array kArray2 = {1, 3, 4}; + static constexpr auto kArraySetIntersection = [] { + std::array<int, 2> array; + absl::c_set_intersection(kArray1, kArray2, array.begin()); + return array; + }(); + static_assert(kArraySetIntersection == std::array{1, 3}); +} + +TEST(ConstexprTest, SetIntersectionWithPredicate) { + static constexpr std::array kArray1 = {3, 2, 1}; + static constexpr std::array kArray2 = {4, 3, 1}; + static constexpr auto kArraySetIntersection = [] { + std::array<int, 2> array; + absl::c_set_intersection(kArray1, kArray2, array.begin(), std::greater<>()); + return array; + }(); + static_assert(kArraySetIntersection == std::array{3, 1}); +} + +TEST(ConstexprTest, SetDifference) { + static constexpr std::array kArray1 = {1, 2, 3}; + static constexpr std::array kArray2 = {1, 3, 4}; + static constexpr auto kArraySetDifference = [] { + std::array<int, 1> array; + absl::c_set_difference(kArray1, kArray2, array.begin()); + return array; + }(); + static_assert(kArraySetDifference == std::array{2}); +} + +TEST(ConstexprTest, SetDifferenceWithPredicate) { + static constexpr std::array kArray1 = {3, 2, 1}; + static constexpr std::array kArray2 = {4, 3, 1}; + static constexpr auto kArraySetDifference = [] { + std::array<int, 1> array; + absl::c_set_difference(kArray1, kArray2, array.begin(), std::greater<>()); + return array; + }(); + static_assert(kArraySetDifference == std::array{2}); +} + +TEST(ConstexprTest, SetSymmetricDifference) { + static constexpr std::array kArray1 = {1, 2, 3}; + static constexpr std::array kArray2 = {1, 3, 4}; + static constexpr auto kArraySetSymmetricDifference = [] { + std::array<int, 2> array; + absl::c_set_symmetric_difference(kArray1, kArray2, array.begin()); + return array; + }(); + static_assert(kArraySetSymmetricDifference == std::array{2, 4}); +} + +TEST(ConstexprTest, SetSymmetricDifferenceWithPredicate) { + static constexpr std::array kArray1 = {3, 2, 1}; + static constexpr std::array kArray2 = {4, 3, 1}; + static constexpr auto kArraySetSymmetricDifference = [] { + std::array<int, 2> array; + absl::c_set_symmetric_difference(kArray1, kArray2, array.begin(), + std::greater<>()); + return array; + }(); + static_assert(kArraySetSymmetricDifference == std::array{4, 2}); +} + +TEST(ConstexprTest, PushHeap) { + static constexpr auto kArray = [] { + std::array array = {1, 2, 3, 4}; + absl::c_push_heap(array); + return array; + }(); + static_assert(kArray[0] == 4); +} + +TEST(ConstexprTest, PushHeapWithPredicate) { + static constexpr auto kArray = [] { + std::array array = {4, 3, 2, 1}; + absl::c_push_heap(array, std::greater<>()); + return array; + }(); + static_assert(kArray[0] == 1); +} + +TEST(ConstexprTest, PopHeap) { + static constexpr auto kArray = [] { + std::array array = {4, 3, 2, 1}; + absl::c_pop_heap(array); + return array; + }(); + static_assert(kArray[3] == 4); +} + +TEST(ConstexprTest, PopHeapWithPredicate) { + static constexpr auto kArray = [] { + std::array array = {1, 2, 3, 4}; + absl::c_pop_heap(array, std::greater<>()); + return array; + }(); + static_assert(kArray[3] == 1); +} + +TEST(ConstexprTest, MakeHeap) { + static constexpr auto kArray = [] { + std::array array = {1, 2, 3, 4}; + absl::c_make_heap(array); + return array; + }(); + static_assert(absl::c_is_heap(kArray)); +} + +TEST(ConstexprTest, MakeHeapWithPredicate) { + static constexpr auto kArray = [] { + std::array array = {4, 3, 2, 1}; + absl::c_make_heap(array, std::greater<>()); + return array; + }(); + static_assert(absl::c_is_heap(kArray, std::greater<>())); +} + +TEST(ConstexprTest, SortHeap) { + static constexpr auto kArray = [] { + std::array array = {1, 2, 3, 4}; + absl::c_make_heap(array); + absl::c_sort_heap(array); + return array; + }(); + static_assert(kArray == std::array{1, 2, 3, 4}); +} + +TEST(ConstexprTest, SortHeapWithPredicate) { + static constexpr auto kArray = [] { + std::array array = {4, 3, 2, 1}; + absl::c_make_heap(array, std::greater<>()); + absl::c_sort_heap(array, std::greater<>()); + return array; + }(); + static_assert(kArray == std::array{4, 3, 2, 1}); +} + +TEST(ConstexprTest, IsHeap) { + static constexpr std::array kHeap = {4, 2, 3, 1}; + static_assert(absl::c_is_heap(kHeap)); + static constexpr std::array kNotHeap = {1, 2, 3, 4}; + static_assert(!absl::c_is_heap(kNotHeap)); +} + +TEST(ConstexprTest, IsHeapWithPredicate) { + static constexpr std::array kHeap = {1, 2, 3, 4}; + static_assert(absl::c_is_heap(kHeap, std::greater<>())); + static constexpr std::array kNotHeap = {4, 3, 2, 1}; + static_assert(!absl::c_is_heap(kNotHeap, std::greater<>())); +} + +TEST(ConstexprTest, IsHeapUntil) { + static constexpr std::array kHeap = {4, 2, 3, 1}; + static_assert(absl::c_is_heap_until(kHeap) == kHeap.end()); + static constexpr std::array kNotHeap = {4, 2, 3, 5}; + static_assert(absl::c_is_heap_until(kNotHeap) == kNotHeap.begin() + 3); +} + +TEST(ConstexprTest, IsHeapUntilWithPredicate) { + static constexpr std::array kHeap = {1, 2, 3, 4}; + static_assert(absl::c_is_heap_until(kHeap, std::greater<>()) == kHeap.end()); + static constexpr std::array kNotHeap = {1, 2, 3, 0}; + static_assert(absl::c_is_heap_until(kNotHeap, std::greater<>()) == + kNotHeap.begin() + 3); +} + +TEST(ConstexprTest, LexicographicalCompare) { + static constexpr std::array kArray1 = {1, 2, 3}; + static constexpr std::array kArray2 = {1, 2, 4}; + static constexpr std::array kArray3 = {1, 2, 3}; + static_assert(absl::c_lexicographical_compare(kArray1, kArray2)); + static_assert(!absl::c_lexicographical_compare(kArray2, kArray1)); + static_assert(!absl::c_lexicographical_compare(kArray1, kArray3)); +} + +TEST(ConstexprTest, LexicographicalCompareWithPredicate) { + static constexpr std::array kArray1 = {1, 2, 3}; + static constexpr std::array kArray2 = {1, 2, 4}; + static constexpr std::array kArray3 = {1, 2, 3}; + static_assert( + !absl::c_lexicographical_compare(kArray1, kArray2, std::greater<>())); + static_assert( + absl::c_lexicographical_compare(kArray2, kArray1, std::greater<>())); + static_assert( + !absl::c_lexicographical_compare(kArray1, kArray3, std::greater<>())); +} + +TEST(ConstexprTest, NextPermutation) { + static constexpr auto kArray = [] { + std::array array = {1, 2, 3}; + absl::c_next_permutation(array); + return array; + }(); + static_assert(kArray == std::array{1, 3, 2}); +} + +TEST(ConstexprTest, NextPermutationWithPredicate) { + static constexpr auto kArray = [] { + std::array array = {3, 2, 1}; + absl::c_next_permutation(array, std::greater<>()); + return array; + }(); + static_assert(kArray == std::array{3, 1, 2}); +} + +TEST(ConstexprTest, PrevPermutation) { + static constexpr auto kArray = [] { + std::array array = {1, 3, 2}; + absl::c_prev_permutation(array); + return array; + }(); + static_assert(kArray == std::array{1, 2, 3}); +} + +TEST(ConstexprTest, PrevPermutationWithPredicate) { + static constexpr auto kArray = [] { + std::array array = {1, 2, 3}; + absl::c_prev_permutation(array, std::greater<>()); + return array; + }(); + static_assert(kArray == std::array{1, 3, 2}); +} + +TEST(ConstexprTest, Iota) { + static constexpr auto kArray = [] { + std::array<int, 3> array; + absl::c_iota(array, 1); + return array; + }(); + static_assert(kArray == std::array{1, 2, 3}); +} + +TEST(ConstexprTest, Accumulate) { + static constexpr std::array kArray = {1, 2, 3}; + static_assert(absl::c_accumulate(kArray, 0) == 6); +} + +TEST(ConstexprTest, AccumulateWithPredicate) { + static constexpr std::array kArray = {1, 2, 3}; + static_assert(absl::c_accumulate(kArray, 1, std::multiplies<>()) == 6); +} + +TEST(ConstexprTest, InnerProduct) { + static constexpr std::array kArray1 = {1, 2, 3}; + static constexpr std::array kArray2 = {4, 5, 6}; + static_assert(absl::c_inner_product(kArray1, kArray2, 0) == 32); +} + +TEST(ConstexprTest, InnerProductWithPredicate) { + static constexpr std::array kArray1 = {1, 2, 3}; + static constexpr std::array kArray2 = {4, 5, 6}; + static_assert(absl::c_inner_product(kArray1, kArray2, 1, std::multiplies<>(), + std::plus<>()) == 315); +} + +TEST(ConstexprTest, AdjacentDifference) { + static constexpr std::array kArray = {1, 2, 4}; + static constexpr auto kArrayAdjacentDifference = [] { + std::array<int, 3> array; + absl::c_adjacent_difference(kArray, array.begin()); + return array; + }(); + static_assert(kArrayAdjacentDifference == std::array{1, 1, 2}); +} + +TEST(ConstexprTest, AdjacentDifferenceWithPredicate) { + static constexpr std::array kArray = {1, 2, 4}; + static constexpr auto kArrayAdjacentDifference = [] { + std::array<int, 3> array; + absl::c_adjacent_difference(kArray, array.begin(), std::multiplies<>()); + return array; + }(); + static_assert(kArrayAdjacentDifference == std::array{1, 2, 8}); +} + +TEST(ConstexprTest, PartialSum) { + static constexpr std::array kArray = {1, 2, 3}; + static constexpr auto kArrayPartialSum = [] { + std::array<int, 3> array; + absl::c_partial_sum(kArray, array.begin()); + return array; + }(); + static_assert(kArrayPartialSum == std::array{1, 3, 6}); +} + +TEST(ConstexprTest, PartialSumWithPredicate) { + static constexpr std::array kArray = {1, 2, 3}; + static constexpr auto kArrayPartialSum = [] { + std::array<int, 3> array; + absl::c_partial_sum(kArray, array.begin(), std::multiplies<>()); + return array; + }(); + static_assert(kArrayPartialSum == std::array{1, 2, 6}); +} + #endif // defined(ABSL_INTERNAL_CPLUSPLUS_LANG) && // ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L
diff --git a/absl/base/BUILD.bazel b/absl/base/BUILD.bazel index ef97b4e..1d27796 100644 --- a/absl/base/BUILD.bazel +++ b/absl/base/BUILD.bazel
@@ -14,6 +14,9 @@ # limitations under the License. # +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load( "//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS", @@ -82,13 +85,23 @@ cc_library( name = "nullability", - srcs = ["internal/nullability_deprecated.h"], hdrs = ["nullability.h"], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [":config"], +) + +cc_library( + name = "nullability_traits_internal", + hdrs = ["internal/nullability_traits.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl:__subpackages__", + ], deps = [ ":config", - ":core_headers", + ":nullability", ], ) @@ -228,7 +241,6 @@ name = "base_internal", hdrs = [ "internal/hide_ptr.h", - "internal/identity.h", "internal/scheduling_mode.h", ], copts = ABSL_DEFAULT_COPTS, @@ -245,6 +257,7 @@ cc_library( name = "base", srcs = [ + "casts.cc", "internal/cycleclock.cc", "internal/spinlock.cc", "internal/sysinfo.cc", @@ -284,8 +297,6 @@ ":config", ":core_headers", ":cycleclock_internal", - ":dynamic_annotations", - ":log_severity", ":nullability", ":raw_logging_internal", ":spinlock_wait", @@ -591,6 +602,22 @@ ) cc_test( + name = "casts_test", + size = "small", + srcs = [ + "casts_test.cc", + ], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":base", + ":config", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_test( name = "no_destructor_test", srcs = ["no_destructor_test.cc"], copts = ABSL_TEST_COPTS, @@ -623,7 +650,6 @@ name = "nullability_test", srcs = ["nullability_test.cc"], deps = [ - ":core_headers", ":nullability", "@googletest//:gtest", "@googletest//:gtest_main", @@ -641,6 +667,18 @@ ) cc_test( + name = "nullability_traits_test", + srcs = ["internal/nullability_traits_test.cc"], + deps = [ + ":config", + ":nullability", + ":nullability_traits_internal", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_test( name = "raw_logging_test", srcs = ["raw_logging_test.cc"], copts = ABSL_TEST_COPTS, @@ -685,7 +723,6 @@ cc_test( name = "thread_identity_test", - size = "small", srcs = ["internal/thread_identity_test.cc"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, @@ -721,6 +758,7 @@ testonly = True, srcs = ["internal/scoped_set_env.cc"], hdrs = ["internal/scoped_set_env.h"], + copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, visibility = [ "//absl:__subpackages__", @@ -925,6 +963,9 @@ hdrs = ["internal/iterator_traits.h"], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl:__subpackages__", + ], deps = [ ":config", "//absl/meta:type_traits",
diff --git a/absl/base/CMakeLists.txt b/absl/base/CMakeLists.txt index 23942c0..84decab 100644 --- a/absl/base/CMakeLists.txt +++ b/absl/base/CMakeLists.txt
@@ -72,11 +72,8 @@ nullability HDRS "nullability.h" - SRCS - "internal/nullability_deprecated.h" DEPS absl::config - absl::core_headers COPTS ${ABSL_DEFAULT_COPTS} ) @@ -89,7 +86,6 @@ COPTS ${ABSL_TEST_COPTS} DEPS - absl::core_headers absl::nullability GTest::gtest_main ) @@ -109,6 +105,34 @@ # Internal-only target, do not depend on directly. absl_cc_library( NAME + nullability_traits_internal + HDRS + "internal/nullability_traits.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::config + absl::nullability + PUBLIC +) + +absl_cc_test( + NAME + nullability_traits_test + SRCS + "internal/nullability_traits_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::config + absl::nullability + absl::nullability_traits_internal + GTest::gtest_main +) + +# Internal-only target, do not depend on directly. +absl_cc_library( + NAME raw_logging_internal HDRS "internal/raw_logging.h" @@ -214,7 +238,6 @@ base_internal HDRS "internal/hide_ptr.h" - "internal/identity.h" "internal/scheduling_mode.h" COPTS ${ABSL_DEFAULT_COPTS} @@ -240,6 +263,7 @@ "internal/unscaledcycleclock.h" "internal/unscaledcycleclock_config.h" SRCS + "casts.cc" "internal/cycleclock.cc" "internal/spinlock.cc" "internal/sysinfo.cc" @@ -396,6 +420,19 @@ absl_cc_test( NAME + casts_test + SRCS + "casts_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::base + absl::core_headers + GTest::gtest_main +) + +absl_cc_test( + NAME errno_saver_test SRCS "internal/errno_saver_test.cc"
diff --git a/absl/base/attributes.h b/absl/base/attributes.h index d009f6d..2261747 100644 --- a/absl/base/attributes.h +++ b/absl/base/attributes.h
@@ -277,6 +277,16 @@ #define ABSL_ATTRIBUTE_NO_SANITIZE_UNDEFINED #endif +// Android local modification: add attribute for disabling unsigned overflow +// sanitization for code where it's intentional, to support vendor code that +// enables it. +#if ABSL_HAVE_ATTRIBUTE(no_sanitize) +#define ABSL_ATTRIBUTE_NO_SANITIZE_UNSIGNED_OVERFLOW \ + __attribute__((no_sanitize("unsigned-integer-overflow"))) +#else +#define ABSL_ATTRIBUTE_NO_SANITIZE_UNSIGNED_OVERFLOW +#endif + // ABSL_ATTRIBUTE_NO_SANITIZE_CFI // // Tells the ControlFlowIntegrity sanitizer to not instrument a given function. @@ -553,7 +563,7 @@ // // Prevents the compiler from complaining about variables that appear unused. // -// Deprecated: Use the standard C++17 `[[maybe_unused]` instead. +// Deprecated: Use the standard C++17 `[[maybe_unused]]` instead. // // Due to differences in positioning requirements between the old, compiler // specific __attribute__ syntax and the now standard `[[maybe_unused]]`, this @@ -580,7 +590,11 @@ // Instructs the compiler not to use natural alignment for a tagged data // structure, but instead to reduce its alignment to 1. // -// Therefore, DO NOT APPLY THIS ATTRIBUTE TO STRUCTS CONTAINING ATOMICS. Doing +// Use of this attribute is HIGHLY DISCOURAGED. Taking the address of or +// binding a reference to any unaligned member is UB, and it is very easy to +// do so unintentionally when passing such members as function arguments. +// +// DO NOT APPLY THIS ATTRIBUTE TO STRUCTS CONTAINING ATOMICS. Doing // so can cause atomic variables to be mis-aligned and silently violate // atomicity on x86. //
diff --git a/absl/base/call_once_test.cc b/absl/base/call_once_test.cc index 11d26c4..630e7ce 100644 --- a/absl/base/call_once_test.cc +++ b/absl/base/call_once_test.cc
@@ -39,25 +39,25 @@ // Function to be called from absl::call_once. Waits for a notification. void WaitAndIncrement() { - counters_mu.Lock(); + counters_mu.lock(); ++call_once_invoke_count; - counters_mu.Unlock(); + counters_mu.unlock(); counters_mu.LockWhen(Condition(&done_blocking)); ++call_once_finished_count; - counters_mu.Unlock(); + counters_mu.unlock(); } void ThreadBody() { - counters_mu.Lock(); + counters_mu.lock(); ++running_thread_count; - counters_mu.Unlock(); + counters_mu.unlock(); absl::call_once(once, WaitAndIncrement); - counters_mu.Lock(); + counters_mu.lock(); ++call_once_return_count; - counters_mu.Unlock(); + counters_mu.unlock(); } // Returns true if all threads are set up for the test. @@ -89,17 +89,17 @@ // Allow WaitAndIncrement to finish executing. Once it does, the other // call_once waiters will be unblocked. done_blocking = true; - counters_mu.Unlock(); + counters_mu.unlock(); for (std::thread& thread : threads) { thread.join(); } - counters_mu.Lock(); + counters_mu.lock(); EXPECT_EQ(call_once_invoke_count, 1); EXPECT_EQ(call_once_finished_count, 1); EXPECT_EQ(call_once_return_count, 10); - counters_mu.Unlock(); + counters_mu.unlock(); } } // namespace
diff --git a/absl/base/casts.cc b/absl/base/casts.cc new file mode 100644 index 0000000..d864a8c --- /dev/null +++ b/absl/base/casts.cc
@@ -0,0 +1,61 @@ +// Copyright 2025 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/casts.h" + +#include <cstdlib> +#include <string> + +#include "absl/base/config.h" +#include "absl/base/internal/raw_logging.h" + +#ifdef ABSL_INTERNAL_HAS_CXA_DEMANGLE +#include <cxxabi.h> +#endif + +namespace absl { +ABSL_NAMESPACE_BEGIN + +namespace base_internal { + +namespace { + +std::string DemangleCppString(const char* mangled) { + std::string out; + int status = 0; + char* demangled = nullptr; +#ifdef ABSL_INTERNAL_HAS_CXA_DEMANGLE + demangled = abi::__cxa_demangle(mangled, nullptr, nullptr, &status); +#endif + if (status == 0 && demangled != nullptr) { + out.append(demangled); + free(demangled); + } else { + out.append(mangled); + } + return out; +} + +} // namespace + +void BadDownCastCrash(const char* source_type, const char* target_type) { + ABSL_RAW_LOG(FATAL, "down cast from %s to %s failed", + DemangleCppString(source_type).c_str(), + DemangleCppString(target_type).c_str()); +} + +} // namespace base_internal + +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/base/casts.h b/absl/base/casts.h index e0b11bb..480855a 100644 --- a/absl/base/casts.h +++ b/absl/base/casts.h
@@ -33,8 +33,11 @@ #include <bit> // For std::bit_cast. #endif // defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L -#include "absl/base/internal/identity.h" +#include "absl/base/attributes.h" +#include "absl/base/config.h" #include "absl/base/macros.h" +#include "absl/base/optimization.h" +#include "absl/base/options.h" #include "absl/meta/type_traits.h" namespace absl { @@ -90,9 +93,26 @@ // // Such implicit cast chaining may be useful within template logic. template <typename To> -constexpr To implicit_cast(typename absl::internal::type_identity_t<To> to) { +constexpr std::enable_if_t< + !type_traits_internal::IsView<std::enable_if_t< + !std::is_reference_v<To>, std::remove_cv_t<To>>>::value, + To> +implicit_cast(absl::type_identity_t<To> to) { return to; } +template <typename To> +constexpr std::enable_if_t< + type_traits_internal::IsView<std::enable_if_t<!std::is_reference_v<To>, + std::remove_cv_t<To>>>::value, + To> +implicit_cast(absl::type_identity_t<To> to ABSL_ATTRIBUTE_LIFETIME_BOUND) { + return to; +} +template <typename To> +constexpr std::enable_if_t<std::is_reference_v<To>, To> implicit_cast( + absl::type_identity_t<To> to ABSL_ATTRIBUTE_LIFETIME_BOUND) { + return std::forward<absl::type_identity_t<To>>(to); +} // bit_cast() // @@ -174,6 +194,112 @@ #endif // defined(__cpp_lib_bit_cast) && __cpp_lib_bit_cast >= 201806L +namespace base_internal { + +[[noreturn]] ABSL_ATTRIBUTE_NOINLINE void BadDownCastCrash( + const char* source_type, const char* target_type); + +template <typename To, typename From> +inline void ValidateDownCast(From* f ABSL_ATTRIBUTE_UNUSED) { + // Assert only if RTTI is enabled and in debug mode or hardened asserts are + // enabled. +#ifdef ABSL_INTERNAL_HAS_RTTI +#if !defined(NDEBUG) || (ABSL_OPTION_HARDENED == 1 || ABSL_OPTION_HARDENED == 2) + // Suppress erroneous nonnull comparison warning on older GCC. +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wnonnull-compare" +#endif + if (ABSL_PREDICT_FALSE(f != nullptr && dynamic_cast<To>(f) == nullptr)) { +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + absl::base_internal::BadDownCastCrash( + typeid(*f).name(), typeid(std::remove_pointer_t<To>).name()); + } +#endif +#endif +} + +} // namespace base_internal + +// An "upcast", i.e. a conversion from a pointer to an object to a pointer to a +// base subobject, always succeeds if the base is unambiguous and accessible, +// and so it's fine to use implicit_cast. +// +// A "downcast", i.e. a conversion from a pointer to an object to a pointer +// to a more-derived object that may contain the original object as a base +// subobject, cannot safely be done using static_cast, because you do not +// generally know whether the source object is really the base subobject of +// a containing, more-derived object of the target type. Thus, when you +// downcast in a polymorphic type hierarchy, you should use the following +// function template. +// +// This function only returns null when the input is null. In debug mode, we +// use dynamic_cast to double-check whether the downcast is legal (we die if +// it's not). In normal mode, we do the efficient static_cast instead. Because +// the process will die in debug mode, it's important to test to make sure the +// cast is legal before calling this function! +// +// dynamic_cast should be avoided except as allowed by the style guide +// (https://google.github.io/styleguide/cppguide.html#Run-Time_Type_Information__RTTI_). + +template <typename To, typename From> // use like this: down_cast<T*>(foo); +[[nodiscard]] +inline To down_cast(From* f) { // so we only accept pointers + static_assert(std::is_pointer<To>::value, "target type not a pointer"); + // dynamic_cast allows casting to the same type or a more cv-qualified + // version of the same type without them being polymorphic. + if constexpr (!std::is_same<std::remove_cv_t<std::remove_pointer_t<To>>, + std::remove_cv_t<From>>::value) { + static_assert(std::is_polymorphic<From>::value, + "source type must be polymorphic"); + static_assert(std::is_polymorphic<std::remove_pointer_t<To>>::value, + "target type must be polymorphic"); + } + static_assert( + std::is_convertible<std::remove_cv_t<std::remove_pointer_t<To>>*, + std::remove_cv_t<From>*>::value, + "target type not derived from source type"); + + absl::base_internal::ValidateDownCast<To>(f); + + return static_cast<To>(f); +} + +// Overload of down_cast for references. Use like this: +// absl::down_cast<T&>(foo). The code is slightly convoluted because we're still +// using the pointer form of dynamic cast. (The reference form throws an +// exception if it fails.) +// +// There's no need for a special const overload either for the pointer +// or the reference form. If you call down_cast with a const T&, the +// compiler will just bind From to const T. +template <typename To, typename From> +[[nodiscard]] +inline To down_cast(From& f) { + static_assert(std::is_lvalue_reference<To>::value, + "target type not a reference"); + // dynamic_cast allows casting to the same type or a more cv-qualified + // version of the same type without them being polymorphic. + if constexpr (!std::is_same<std::remove_cv_t<std::remove_reference_t<To>>, + std::remove_cv_t<From>>::value) { + static_assert(std::is_polymorphic<From>::value, + "source type must be polymorphic"); + static_assert(std::is_polymorphic<std::remove_reference_t<To>>::value, + "target type must be polymorphic"); + } + static_assert( + std::is_convertible<std::remove_cv_t<std::remove_reference_t<To>>*, + std::remove_cv_t<From>*>::value, + "target type not derived from source type"); + + absl::base_internal::ValidateDownCast<std::remove_reference_t<To>*>( + std::addressof(f)); + + return static_cast<To>(f); +} + ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/base/casts_test.cc b/absl/base/casts_test.cc new file mode 100644 index 0000000..772225e --- /dev/null +++ b/absl/base/casts_test.cc
@@ -0,0 +1,151 @@ +// Copyright 2025 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/casts.h" + +#include <type_traits> +#include <utility> + +#include "gtest/gtest.h" +#include "absl/base/options.h" + +namespace { + +struct BaseForImplicitCast { + explicit BaseForImplicitCast(int value) : x(value) {} + BaseForImplicitCast(const BaseForImplicitCast& other) = delete; + BaseForImplicitCast& operator=(const BaseForImplicitCast& other) = delete; + int x; +}; +struct DerivedForImplicitCast : BaseForImplicitCast { + explicit DerivedForImplicitCast(int value) : BaseForImplicitCast(value) {} +}; + +static_assert(std::is_same_v<decltype(absl::implicit_cast<BaseForImplicitCast&>( + std::declval<DerivedForImplicitCast&>())), + BaseForImplicitCast&>); +static_assert( + std::is_same_v<decltype(absl::implicit_cast<const BaseForImplicitCast&>( + std::declval<DerivedForImplicitCast>())), + const BaseForImplicitCast&>); + +TEST(ImplicitCastTest, LValueReference) { + DerivedForImplicitCast derived(5); + EXPECT_EQ(&absl::implicit_cast<BaseForImplicitCast&>(derived), &derived); + EXPECT_EQ(&absl::implicit_cast<const BaseForImplicitCast&>(derived), + &derived); +} + +TEST(ImplicitCastTest, RValueReference) { + DerivedForImplicitCast derived(5); + BaseForImplicitCast&& base = + absl::implicit_cast<BaseForImplicitCast&&>(std::move(derived)); + EXPECT_EQ(&base, &derived); + + const DerivedForImplicitCast cderived(6); + const BaseForImplicitCast&& cbase = + absl::implicit_cast<const BaseForImplicitCast&&>(std::move(cderived)); + EXPECT_EQ(&cbase, &cderived); +} + +class BaseForDownCast { + public: + virtual ~BaseForDownCast() = default; +}; + +class DerivedForDownCast : public BaseForDownCast {}; +class Derived2ForDownCast : public BaseForDownCast {}; + +TEST(DownCastTest, Pointer) { + DerivedForDownCast derived; + BaseForDownCast* const base_ptr = &derived; + + // Tests casting a BaseForDownCast* to a DerivedForDownCast*. + EXPECT_EQ(&derived, absl::down_cast<DerivedForDownCast*>(base_ptr)); + + // Tests casting a const BaseForDownCast* to a const DerivedForDownCast*. + const BaseForDownCast* const_base_ptr = base_ptr; + EXPECT_EQ(&derived, + absl::down_cast<const DerivedForDownCast*>(const_base_ptr)); + + // Tests casting a BaseForDownCast* to a const DerivedForDownCast*. + EXPECT_EQ(&derived, absl::down_cast<const DerivedForDownCast*>(base_ptr)); + + // Tests casting a BaseForDownCast* to a BaseForDownCast* (an identity cast). + EXPECT_EQ(base_ptr, absl::down_cast<BaseForDownCast*>(base_ptr)); + + // Tests down casting NULL. + EXPECT_EQ(nullptr, + (absl::down_cast<DerivedForDownCast*, BaseForDownCast>(nullptr))); + + // Tests a bad downcast. We have to disguise the badness just enough + // that the compiler doesn't warn about it at compile time. + BaseForDownCast* base2 = new BaseForDownCast(); +#if GTEST_HAS_DEATH_TEST && (!defined(NDEBUG) || (ABSL_OPTION_HARDENED == 1 || \ + ABSL_OPTION_HARDENED == 2)) + EXPECT_DEATH(static_cast<void>(absl::down_cast<DerivedForDownCast*>(base2)), + ".*down cast from .*BaseForDownCast.* to " + ".*DerivedForDownCast.* failed.*"); +#endif + delete base2; +} + +TEST(DownCastTest, Reference) { + DerivedForDownCast derived; + BaseForDownCast& base_ref = derived; + + // Tests casting a BaseForDownCast& to a DerivedForDownCast&. + // NOLINTNEXTLINE(runtime/casting) + EXPECT_EQ(&derived, &absl::down_cast<DerivedForDownCast&>(base_ref)); + + // Tests casting a const BaseForDownCast& to a const DerivedForDownCast&. + const BaseForDownCast& const_base_ref = base_ref; + // NOLINTNEXTLINE(runtime/casting) + EXPECT_EQ(&derived, + &absl::down_cast<const DerivedForDownCast&>(const_base_ref)); + + // Tests casting a BaseForDownCast& to a const DerivedForDownCast&. + // NOLINTNEXTLINE(runtime/casting) + EXPECT_EQ(&derived, &absl::down_cast<const DerivedForDownCast&>(base_ref)); + + // Tests casting a BaseForDownCast& to a BaseForDownCast& (an identity cast). + // NOLINTNEXTLINE(runtime/casting) + EXPECT_EQ(&base_ref, &absl::down_cast<BaseForDownCast&>(base_ref)); + + // Tests a bad downcast. We have to disguise the badness just enough + // that the compiler doesn't warn about it at compile time. + BaseForDownCast& base2 = *new BaseForDownCast(); +#if GTEST_HAS_DEATH_TEST && (!defined(NDEBUG) || (ABSL_OPTION_HARDENED == 1 || \ + ABSL_OPTION_HARDENED == 2)) + EXPECT_DEATH(static_cast<void>(absl::down_cast<DerivedForDownCast&>(base2)), + ".*down cast from .*BaseForDownCast.* to " + ".*DerivedForDownCast.* failed.*"); +#endif + delete &base2; +} + +TEST(DownCastTest, ErrorMessage) { + DerivedForDownCast derived; + BaseForDownCast& base = derived; + (void)base; + +#if GTEST_HAS_DEATH_TEST && (!defined(NDEBUG) || (ABSL_OPTION_HARDENED == 1 || \ + ABSL_OPTION_HARDENED == 2)) + EXPECT_DEATH(static_cast<void>(absl::down_cast<Derived2ForDownCast&>(base)), + ".*down cast from .*DerivedForDownCast.* to " + ".*Derived2ForDownCast.* failed.*"); +#endif +} + +} // namespace
diff --git a/absl/base/config.h b/absl/base/config.h index 8e5feb5..0de5b8c 100644 --- a/absl/base/config.h +++ b/absl/base/config.h
@@ -117,7 +117,7 @@ // // LTS releases can be obtained from // https://github.com/abseil/abseil-cpp/releases. -#define ABSL_LTS_RELEASE_VERSION 20250512 +#define ABSL_LTS_RELEASE_VERSION 20260107 #define ABSL_LTS_RELEASE_PATCH_LEVEL 1 // Helper macro to convert a CPP variable to a string literal. @@ -237,6 +237,8 @@ #error ABSL_HAVE_TLS cannot be directly set #elif (defined(__linux__)) && (defined(__clang__) || defined(_GLIBCXX_HAVE_TLS)) #define ABSL_HAVE_TLS 1 +#elif defined(__INTEL_LLVM_COMPILER) +#define ABSL_HAVE_TLS 1 #endif // ABSL_HAVE_STD_IS_TRIVIALLY_DESTRUCTIBLE @@ -358,10 +360,10 @@ // Darwin (macOS and iOS) __APPLE__ // Akaros (http://akaros.org) __ros__ // Windows _WIN32 -// NaCL __native_client__ // AsmJS __asmjs__ // WebAssembly (Emscripten) __EMSCRIPTEN__ // Fuchsia __Fuchsia__ +// WebAssembly (WASI) _WASI_EMULATED_MMAN (implies __wasi__) // // Note that since Android defines both __ANDROID__ and __linux__, one // may probe for either Linux or Android by simply testing for __linux__. @@ -372,12 +374,13 @@ // POSIX.1-2001. #ifdef ABSL_HAVE_MMAP #error ABSL_HAVE_MMAP cannot be directly set -#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \ - defined(_AIX) || defined(__ros__) || defined(__native_client__) || \ - defined(__asmjs__) || defined(__EMSCRIPTEN__) || defined(__Fuchsia__) || \ - defined(__sun) || defined(__myriad2__) || defined(__HAIKU__) || \ - defined(__OpenBSD__) || defined(__NetBSD__) || defined(__QNX__) || \ - defined(__VXWORKS__) || defined(__hexagon__) || defined(__XTENSA__) +#elif defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \ + defined(_AIX) || defined(__ros__) || defined(__asmjs__) || \ + defined(__EMSCRIPTEN__) || defined(__Fuchsia__) || defined(__sun) || \ + defined(__myriad2__) || defined(__HAIKU__) || defined(__OpenBSD__) || \ + defined(__NetBSD__) || defined(__QNX__) || defined(__VXWORKS__) || \ + defined(__hexagon__) || defined(__XTENSA__) || \ + defined(_WASI_EMULATED_MMAN) #define ABSL_HAVE_MMAP 1 #endif @@ -453,8 +456,6 @@ // WASI doesn't support signals #elif defined(__Fuchsia__) // Signals don't exist on fuchsia. -#elif defined(__native_client__) -// Signals don't exist on hexagon/QuRT #elif defined(__hexagon__) #else // other standard libraries @@ -525,20 +526,11 @@ #define ABSL_USES_STD_ANY 1 #define ABSL_HAVE_STD_OPTIONAL 1 #define ABSL_USES_STD_OPTIONAL 1 +#define ABSL_HAVE_STD_STRING_VIEW 1 +#define ABSL_USES_STD_STRING_VIEW 1 #define ABSL_HAVE_STD_VARIANT 1 #define ABSL_USES_STD_VARIANT 1 -// ABSL_HAVE_STD_STRING_VIEW -// -// Deprecated: always defined to 1. -// std::string_view was added in C++17, which means all versions of C++ -// supported by Abseil have it. -#ifdef ABSL_HAVE_STD_STRING_VIEW -#error "ABSL_HAVE_STD_STRING_VIEW cannot be directly set." -#else -#define ABSL_HAVE_STD_STRING_VIEW 1 -#endif - // ABSL_HAVE_STD_ORDERING // // Checks whether C++20 std::{partial,weak,strong}_ordering are available. @@ -555,20 +547,6 @@ #define ABSL_HAVE_STD_ORDERING 1 #endif -// ABSL_USES_STD_STRING_VIEW -// -// Indicates whether absl::string_view is an alias for std::string_view. -#if !defined(ABSL_OPTION_USE_STD_STRING_VIEW) -#error options.h is misconfigured. -#elif ABSL_OPTION_USE_STD_STRING_VIEW == 0 -#undef ABSL_USES_STD_STRING_VIEW -#elif ABSL_OPTION_USE_STD_STRING_VIEW == 1 || \ - ABSL_OPTION_USE_STD_STRING_VIEW == 2 -#define ABSL_USES_STD_STRING_VIEW 1 -#else -#error options.h is misconfigured. -#endif - // ABSL_USES_STD_ORDERING // // Indicates whether absl::{partial,weak,strong}_ordering are aliases for the @@ -754,7 +732,7 @@ #ifdef ABSL_INTERNAL_HAS_CXA_DEMANGLE #error ABSL_INTERNAL_HAS_CXA_DEMANGLE cannot be directly set #elif defined(OS_ANDROID) && (defined(__i386__) || defined(__x86_64__)) -#define ABSL_INTERNAL_HAS_CXA_DEMANGLE 0 +#undef ABSL_INTERNAL_HAS_CXA_DEMANGLE #elif defined(__GNUC__) #define ABSL_INTERNAL_HAS_CXA_DEMANGLE 1 #elif defined(__clang__) && !defined(_MSC_VER)
diff --git a/absl/base/internal/dynamic_annotations.h b/absl/base/internal/dynamic_annotations.h index b23c5ec..537a2fe 100644 --- a/absl/base/internal/dynamic_annotations.h +++ b/absl/base/internal/dynamic_annotations.h
@@ -89,7 +89,7 @@ #endif // Memory annotations are also made available to LLVM's Memory Sanitizer -#if defined(ABSL_HAVE_MEMORY_SANITIZER) && !defined(__native_client__) +#if defined(ABSL_HAVE_MEMORY_SANITIZER) #define ABSL_INTERNAL_MEMORY_ANNOTATIONS_ENABLED 1 #endif
diff --git a/absl/base/internal/identity.h b/absl/base/internal/identity.h deleted file mode 100644 index 365207b..0000000 --- a/absl/base/internal/identity.h +++ /dev/null
@@ -1,39 +0,0 @@ -// Copyright 2017 The Abseil Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// - -#ifndef ABSL_BASE_INTERNAL_IDENTITY_H_ -#define ABSL_BASE_INTERNAL_IDENTITY_H_ - -#include "absl/base/config.h" - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace internal { - -// This is a back-fill of C++20's `std::type_identity`. -template <typename T> -struct type_identity { - typedef T type; -}; - -// This is a back-fill of C++20's `std::type_identity_t`. -template <typename T> -using type_identity_t = typename type_identity<T>::type; - -} // namespace internal -ABSL_NAMESPACE_END -} // namespace absl - -#endif // ABSL_BASE_INTERNAL_IDENTITY_H_
diff --git a/absl/base/internal/iterator_traits.h b/absl/base/internal/iterator_traits.h index 472c436..5fa4df8 100644 --- a/absl/base/internal/iterator_traits.h +++ b/absl/base/internal/iterator_traits.h
@@ -61,6 +61,10 @@ std::is_convertible<IteratorConcept<Iterator>, IteratorTag>; template <typename Iterator> +using IsAtLeastInputIterator = + IsAtLeastIterator<std::input_iterator_tag, Iterator>; + +template <typename Iterator> using IsAtLeastForwardIterator = IsAtLeastIterator<std::forward_iterator_tag, Iterator>;
diff --git a/absl/base/internal/low_level_alloc.cc b/absl/base/internal/low_level_alloc.cc index 158b609..a5bd71d 100644 --- a/absl/base/internal/low_level_alloc.cc +++ b/absl/base/internal/low_level_alloc.cc
@@ -19,6 +19,9 @@ #include "absl/base/internal/low_level_alloc.h" +#include <stdint.h> + +#include <optional> #include <type_traits> #include "absl/base/call_once.h" @@ -219,6 +222,32 @@ uint32_t random ABSL_GUARDED_BY(mu); }; +// --------------------------------------------------------------- +// An async-signal-safe arena for LowLevelAlloc +static std::atomic<base_internal::LowLevelAlloc::Arena *> g_sig_safe_arena; + +base_internal::LowLevelAlloc::Arena *SigSafeArena() { + return g_sig_safe_arena.load(std::memory_order_acquire); +} + +void InitSigSafeArena() { + if (SigSafeArena() == nullptr) { + uint32_t flags = 0; +#ifndef ABSL_LOW_LEVEL_ALLOC_ASYNC_SIGNAL_SAFE_MISSING + flags |= base_internal::LowLevelAlloc::kAsyncSignalSafe; +#endif + base_internal::LowLevelAlloc::Arena *new_arena = + base_internal::LowLevelAlloc::NewArena(flags); + base_internal::LowLevelAlloc::Arena *old_value = nullptr; + if (!g_sig_safe_arena.compare_exchange_strong(old_value, new_arena, + std::memory_order_release, + std::memory_order_relaxed)) { + // We lost a race to allocate an arena; deallocate. + base_internal::LowLevelAlloc::DeleteArena(new_arena); + } + } +} + namespace { // Static storage space for the lazily-constructed, default global arena // instances. We require this space because the whole point of LowLevelAlloc @@ -289,11 +318,11 @@ mask_valid_ = pthread_sigmask(SIG_BLOCK, &all, &mask_) == 0; } #endif - arena_->mu.Lock(); + arena_->mu.lock(); } ~ArenaLock() { ABSL_RAW_CHECK(left_, "haven't left Arena region"); } void Leave() ABSL_UNLOCK_FUNCTION() { - arena_->mu.Unlock(); + arena_->mu.unlock(); #ifndef ABSL_LOW_LEVEL_ALLOC_ASYNC_SIGNAL_SAFE_MISSING if (mask_valid_) { const int err = pthread_sigmask(SIG_SETMASK, &mask_, nullptr); @@ -544,7 +573,7 @@ } // we unlock before mmap() both because mmap() may call a callback hook, // and because it may be slow. - arena->mu.Unlock(); + arena->mu.unlock(); // mmap generous 64K chunks to decrease // the chances/impact of fragmentation: size_t new_pages_size = RoundUp(req_rnd, arena->pagesize * 16); @@ -583,7 +612,7 @@ #endif #endif // __linux__ #endif // _WIN32 - arena->mu.Lock(); + arena->mu.lock(); s = reinterpret_cast<AllocList *>(new_pages); s->header.size = new_pages_size; // Pretend the block is allocated; call AddToFreelist() to free it.
diff --git a/absl/base/internal/low_level_alloc.h b/absl/base/internal/low_level_alloc.h index c2f1f25..23218dd 100644 --- a/absl/base/internal/low_level_alloc.h +++ b/absl/base/internal/low_level_alloc.h
@@ -120,6 +120,12 @@ LowLevelAlloc(); // no instances }; +// Returns a global async-signal-safe arena for LowLevelAlloc. +LowLevelAlloc::Arena *SigSafeArena(); + +// Ensures the global async-signal-safe arena for LowLevelAlloc is initialized. +void InitSigSafeArena(); + } // namespace base_internal ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/base/internal/nullability_deprecated.h b/absl/base/internal/nullability_deprecated.h deleted file mode 100644 index 1174a96..0000000 --- a/absl/base/internal/nullability_deprecated.h +++ /dev/null
@@ -1,106 +0,0 @@ -// Copyright 2023 The Abseil Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -#ifndef ABSL_BASE_INTERNAL_NULLABILITY_DEPRECATED_H_ -#define ABSL_BASE_INTERNAL_NULLABILITY_DEPRECATED_H_ - -#include "absl/base/attributes.h" -#include "absl/base/config.h" - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace nullability_internal { - -template <typename T> -using NullableImpl -#if ABSL_HAVE_CPP_ATTRIBUTE(clang::annotate) - [[clang::annotate("Nullable")]] -#endif -// Don't add the _Nullable attribute in Objective-C compiles. Many Objective-C -// projects enable the `-Wnullable-to-nonnull-conversion warning`, which is -// liable to produce false positives. -#if ABSL_HAVE_FEATURE(nullability_on_classes) && !defined(__OBJC__) - = T _Nullable; -#else - = T; -#endif - -template <typename T> -using NonnullImpl -#if ABSL_HAVE_CPP_ATTRIBUTE(clang::annotate) - [[clang::annotate("Nonnull")]] -#endif -#if ABSL_HAVE_FEATURE(nullability_on_classes) && !defined(__OBJC__) - = T _Nonnull; -#else - = T; -#endif - -template <typename T> -using NullabilityUnknownImpl -#if ABSL_HAVE_CPP_ATTRIBUTE(clang::annotate) - [[clang::annotate("Nullability_Unspecified")]] -#endif -#if ABSL_HAVE_FEATURE(nullability_on_classes) && !defined(__OBJC__) - = T _Null_unspecified; -#else - = T; -#endif - -} // namespace nullability_internal - -// The following template aliases are deprecated forms of nullability -// annotations. They have some limitations, for example, an incompatibility with -// `auto*` pointers, as `auto` cannot be used in a template argument. -// -// It is important to note that these annotations are not distinct strong -// *types*. They are alias templates defined to be equal to the underlying -// pointer type. A pointer annotated `Nonnull<T*>`, for example, is simply a -// pointer of type `T*`. -// -// Prefer the macro style annotations in `absl/base/nullability.h` instead. - -// absl::Nonnull, analogous to absl_nonnull -// -// Example: -// absl::Nonnull<int*> foo; -// Is equivalent to: -// int* absl_nonnull foo; -template <typename T> -using Nonnull [[deprecated("Use `absl_nonnull`.")]] = - nullability_internal::NonnullImpl<T>; - -// absl::Nullable, analogous to absl_nullable -// -// Example: -// absl::Nullable<int*> foo; -// Is equivalent to: -// int* absl_nullable foo; -template <typename T> -using Nullable [[deprecated("Use `absl_nullable`.")]] = - nullability_internal::NullableImpl<T>; - -// absl::NullabilityUnknown, analogous to absl_nullability_unknown -// -// Example: -// absl::NullabilityUnknown<int*> foo; -// Is equivalent to: -// int* absl_nullability_unknown foo; -template <typename T> -using NullabilityUnknown [[deprecated("Use `absl_nullability_unknown`.")]] = - nullability_internal::NullabilityUnknownImpl<T>; - -ABSL_NAMESPACE_END -} // namespace absl - -#endif // ABSL_BASE_INTERNAL_NULLABILITY_DEPRECATED_H_
diff --git a/absl/base/internal/nullability_traits.h b/absl/base/internal/nullability_traits.h new file mode 100644 index 0000000..790ec90 --- /dev/null +++ b/absl/base/internal/nullability_traits.h
@@ -0,0 +1,71 @@ +// Copyright 2025 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef ABSL_BASE_INTERNAL_NULLABILITY_TRAITS_H_ +#define ABSL_BASE_INTERNAL_NULLABILITY_TRAITS_H_ + +#include <type_traits> + +#include "absl/base/config.h" +#include "absl/base/nullability.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace base_internal { + +// `value` is true if the type `T` is compatible with nullability annotations +// (is a raw pointer, a smart pointer, or marked with +// ABSL_NULLABILITY_COMPATIBLE). Prefer to use the higher-level +// `AddNonnullIfCompatible` if that is sufficient. +// +// NOTE: This should not be used to detect if the compiler is Clang (since +// Clang is the only compiler that supports nullability annotations). +#if defined(__clang__) && !defined(__OBJC__) && \ + ABSL_HAVE_FEATURE(nullability_on_classes) +template <class T, class = void> +struct IsNullabilityCompatibleType { + constexpr static bool value = false; +}; + +template <class T> +struct IsNullabilityCompatibleType<T, std::void_t<absl_nullable T>> { + constexpr static bool value = true; +}; +#else +// False when absl_nullable is a no-op (for non-Clang compilers or Objective-C.) +template <class T, class = void> +struct IsNullabilityCompatibleType { + constexpr static bool value = false; +}; +#endif + +// A trait to add `absl_nonnull` to a type if it is compatible with nullability +// annotations. +template <typename T, bool ShouldAdd = IsNullabilityCompatibleType<T>::value> +struct AddNonnullIfCompatible; + +template <typename T> +struct AddNonnullIfCompatible<T, false> { + using type = T; +}; +template <typename T> +struct AddNonnullIfCompatible<T, true> { + using type = absl_nonnull T; +}; + +} // namespace base_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_BASE_INTERNAL_NULLABILITY_TRAITS_H_
diff --git a/absl/base/internal/nullability_traits_test.cc b/absl/base/internal/nullability_traits_test.cc new file mode 100644 index 0000000..2451239 --- /dev/null +++ b/absl/base/internal/nullability_traits_test.cc
@@ -0,0 +1,98 @@ +// Copyright 2025 The Abseil Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "absl/base/internal/nullability_traits.h" + +#include <memory> +#include <type_traits> + +#include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/base/nullability.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace base_internal { +namespace { + +struct NotSmartPtr { + int* x; +}; + +class ABSL_NULLABILITY_COMPATIBLE MySmartPtr : std::unique_ptr<int> {}; + +// The IsNullabilityCompatibleType trait value can only be true when we define +// `absl_nullable` (isn't a no-op). +#if defined(__clang__) && !defined(__OBJC__) && \ + ABSL_HAVE_FEATURE(nullability_on_classes) +#define EXPECT_TRUE_IF_SUPPORTED EXPECT_TRUE +#else +#define EXPECT_TRUE_IF_SUPPORTED EXPECT_FALSE +#endif + +TEST(NullabilityTraitsTest, IsNullabilityEligibleTypePrimitives) { + EXPECT_FALSE(IsNullabilityCompatibleType<int>::value); + EXPECT_FALSE(IsNullabilityCompatibleType<int*&>::value); + + EXPECT_TRUE_IF_SUPPORTED(IsNullabilityCompatibleType<int*>::value); + EXPECT_TRUE_IF_SUPPORTED(IsNullabilityCompatibleType<int**>::value); + EXPECT_TRUE_IF_SUPPORTED(IsNullabilityCompatibleType<int* const>::value); + EXPECT_TRUE_IF_SUPPORTED(IsNullabilityCompatibleType<const int*>::value); + EXPECT_TRUE_IF_SUPPORTED(IsNullabilityCompatibleType<void (*)(int)>::value); +} + +TEST(NullabilityTraitsTest, IsNullabilityCompatibleTypeAliases) { + using MyInt = int; + using MyIntPtr = int*; + EXPECT_FALSE(IsNullabilityCompatibleType<MyInt>::value); + EXPECT_TRUE_IF_SUPPORTED(IsNullabilityCompatibleType<MyIntPtr>::value); +} + +TEST(NullabilityTraitsTest, IsNullabilityCompatibleTypeSmartPointers) { + EXPECT_TRUE_IF_SUPPORTED( + IsNullabilityCompatibleType<std::unique_ptr<int>>::value); + EXPECT_TRUE_IF_SUPPORTED( + IsNullabilityCompatibleType<std::shared_ptr<int>>::value); + + EXPECT_FALSE(IsNullabilityCompatibleType<NotSmartPtr>::value); + EXPECT_TRUE_IF_SUPPORTED(IsNullabilityCompatibleType<NotSmartPtr*>::value); + EXPECT_TRUE_IF_SUPPORTED(IsNullabilityCompatibleType<MySmartPtr>::value); +} + +#undef EXPECT_TRUE_IF_SUPPORTED + +TEST(NullabilityTraitsTest, AddNonnullIfCompatiblePassThroughPrimitives) { + EXPECT_TRUE((std::is_same_v<AddNonnullIfCompatible<int>::type, int>)); + EXPECT_TRUE((std::is_same_v<AddNonnullIfCompatible<int*>::type, int*>)); + EXPECT_TRUE( + (std::is_same_v<AddNonnullIfCompatible<int* const>::type, int* const>)); +} + +TEST(NullabilityTraitsTest, AddNonnullIfCompatiblePassThroughSmartPointers) { + EXPECT_TRUE( + (std::is_same_v<AddNonnullIfCompatible<std::unique_ptr<int>>::type, + std::unique_ptr<int>>)); + EXPECT_TRUE( + (std::is_same_v<AddNonnullIfCompatible<std::shared_ptr<int>>::type, + std::shared_ptr<int>>)); + EXPECT_TRUE( + (std::is_same_v<AddNonnullIfCompatible<NotSmartPtr>::type, NotSmartPtr>)); + EXPECT_TRUE( + (std::is_same_v<AddNonnullIfCompatible<MySmartPtr>::type, MySmartPtr>)); +} + +} // namespace +} // namespace base_internal +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/base/internal/poison.cc b/absl/base/internal/poison.cc index b33d4c2..c639c96 100644 --- a/absl/base/internal/poison.cc +++ b/absl/base/internal/poison.cc
@@ -57,19 +57,20 @@ void* InitializePoisonedPointerInternal() { const size_t block_size = GetPageSize(); + void* data = nullptr; #if defined(ABSL_HAVE_ADDRESS_SANITIZER) - void* data = malloc(block_size); + data = malloc(block_size); ASAN_POISON_MEMORY_REGION(data, block_size); #elif defined(ABSL_HAVE_MEMORY_SANITIZER) - void* data = malloc(block_size); + data = malloc(block_size); __msan_poison(data, block_size); #elif defined(ABSL_HAVE_MMAP) - void* data = DirectMmap(nullptr, block_size, PROT_NONE, - MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); + data = DirectMmap(nullptr, block_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, + -1, 0); if (data == MAP_FAILED) return GetBadPointerInternal(); #elif defined(_WIN32) - void* data = VirtualAlloc(nullptr, block_size, MEM_RESERVE | MEM_COMMIT, - PAGE_NOACCESS); + data = VirtualAlloc(nullptr, block_size, MEM_RESERVE | MEM_COMMIT, + PAGE_NOACCESS); if (data == nullptr) return GetBadPointerInternal(); #else return GetBadPointerInternal();
diff --git a/absl/base/internal/raw_logging.cc b/absl/base/internal/raw_logging.cc index 35a08f0..8537f3e 100644 --- a/absl/base/internal/raw_logging.cc +++ b/absl/base/internal/raw_logging.cc
@@ -41,9 +41,8 @@ // // This preprocessor token is also defined in raw_io.cc. If you need to copy // this, consider moving both to config.h instead. -#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \ - defined(__hexagon__) || defined(__Fuchsia__) || \ - defined(__native_client__) || defined(__OpenBSD__) || \ +#if defined(__linux__) || defined(__APPLE__) || defined(__FreeBSD__) || \ + defined(__hexagon__) || defined(__Fuchsia__) || defined(__OpenBSD__) || \ defined(__EMSCRIPTEN__) || defined(__ASYLO__) #include <unistd.h> @@ -158,7 +157,7 @@ #endif #ifdef ABSL_MIN_LOG_LEVEL - if (severity < static_cast<absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) && + if (severity < static_cast<absl::LogSeverityAtLeast>(ABSL_MIN_LOG_LEVEL) && severity < absl::LogSeverity::kFatal) { enabled = false; }
diff --git a/absl/base/internal/spinlock.cc b/absl/base/internal/spinlock.cc index 430f775..41d2b48 100644 --- a/absl/base/internal/spinlock.cc +++ b/absl/base/internal/spinlock.cc
@@ -16,15 +16,18 @@ #include <algorithm> #include <atomic> +#include <cstdint> #include <limits> #include "absl/base/attributes.h" +#include "absl/base/call_once.h" #include "absl/base/config.h" #include "absl/base/internal/atomic_hook.h" #include "absl/base/internal/cycleclock.h" +#include "absl/base/internal/scheduling_mode.h" #include "absl/base/internal/spinlock_wait.h" #include "absl/base/internal/sysinfo.h" /* For NumCPUs() */ -#include "absl/base/call_once.h" +#include "absl/base/internal/tsan_mutex_interface.h" // Description of lock-word: // 31..00: [............................3][2][1][0] @@ -58,7 +61,7 @@ ABSL_NAMESPACE_BEGIN namespace base_internal { -ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES static base_internal::AtomicHook<void (*)( +ABSL_INTERNAL_ATOMIC_HOOK_ATTRIBUTES static AtomicHook<void (*)( const void *lock, int64_t wait_cycles)> submit_profile_data; @@ -67,25 +70,24 @@ submit_profile_data.Store(fn); } -// Uncommon constructors. -SpinLock::SpinLock(base_internal::SchedulingMode mode) - : lockword_(IsCooperative(mode) ? kSpinLockCooperative : 0) { - ABSL_TSAN_MUTEX_CREATE(this, __tsan_mutex_not_static); -} - // Monitor the lock to see if its value changes within some time period -// (adaptive_spin_count loop iterations). The last value read from the lock +// (adaptive_spin_count_ loop iterations). The last value read from the lock // is returned from the method. +ABSL_CONST_INIT std::atomic<int> SpinLock::adaptive_spin_count_{0}; uint32_t SpinLock::SpinLoop() { // We are already in the slow path of SpinLock, initialize the // adaptive_spin_count here. - ABSL_CONST_INIT static absl::once_flag init_adaptive_spin_count; - ABSL_CONST_INIT static int adaptive_spin_count = 0; - base_internal::LowLevelCallOnce(&init_adaptive_spin_count, []() { - adaptive_spin_count = base_internal::NumCPUs() > 1 ? 1000 : 1; - }); - - int c = adaptive_spin_count; + if (adaptive_spin_count_.load(std::memory_order_relaxed) == 0) { + int current_spin_count = 0; + int new_spin_count = NumCPUs() > 1 ? 1000 : 1; + // If this fails, the value will remain unchanged. We may not spin for the + // intended duration, but that is still safe. We will try again on the next + // call to SpinLoop. + adaptive_spin_count_.compare_exchange_weak( + current_spin_count, new_spin_count, std::memory_order_relaxed, + std::memory_order_relaxed); + } + int c = adaptive_spin_count_.load(std::memory_order_relaxed); uint32_t lock_value; do { lock_value = lockword_.load(std::memory_order_relaxed); @@ -100,11 +102,11 @@ return; } - base_internal::SchedulingMode scheduling_mode; + SchedulingMode scheduling_mode; if ((lock_value & kSpinLockCooperative) != 0) { - scheduling_mode = base_internal::SCHEDULE_COOPERATIVE_AND_KERNEL; + scheduling_mode = SCHEDULE_COOPERATIVE_AND_KERNEL; } else { - scheduling_mode = base_internal::SCHEDULE_KERNEL_ONLY; + scheduling_mode = SCHEDULE_KERNEL_ONLY; } // The lock was not obtained initially, so this thread needs to wait for @@ -134,7 +136,7 @@ // new lock state will be the number of cycles this thread waited if // this thread obtains the lock. lock_value = TryLockInternal(lock_value, wait_cycles); - continue; // Skip the delay at the end of the loop. + continue; // Skip the delay at the end of the loop. } else if ((lock_value & kWaitTimeMask) == 0) { // The lock is still held, without a waiter being marked, but something // else about the lock word changed, causing our CAS to fail. For @@ -150,8 +152,8 @@ // synchronization there to avoid false positives. ABSL_TSAN_MUTEX_PRE_DIVERT(this, 0); // Wait for an OS specific delay. - base_internal::SpinLockDelay(&lockword_, lock_value, ++lock_wait_call_count, - scheduling_mode); + SpinLockDelay(&lockword_, lock_value, ++lock_wait_call_count, + scheduling_mode); ABSL_TSAN_MUTEX_POST_DIVERT(this, 0); // Spin again after returning from the wait routine to give this thread // some chance of obtaining the lock. @@ -162,8 +164,8 @@ } void SpinLock::SlowUnlock(uint32_t lock_value) { - base_internal::SpinLockWake(&lockword_, - false); // wake waiter if necessary + SpinLockWake(&lockword_, + false); // wake waiter if necessary // If our acquisition was contended, collect contentionz profile info. We // reserve a unitary wait time to represent that a waiter exists without our
diff --git a/absl/base/internal/spinlock.h b/absl/base/internal/spinlock.h index 2a10896..d535093 100644 --- a/absl/base/internal/spinlock.h +++ b/absl/base/internal/spinlock.h
@@ -19,7 +19,7 @@ // - for use by Abseil internal code that Mutex itself depends on // - for async signal safety (see below) -// SpinLock with a base_internal::SchedulingMode::SCHEDULE_KERNEL_ONLY is async +// SpinLock with a SchedulingMode::SCHEDULE_KERNEL_ONLY is async // signal safe. If a spinlock is used within a signal handler, all code that // acquires the lock must ensure that the signal cannot arrive while they are // holding the lock. Typically, this is done by blocking the signal. @@ -31,20 +31,24 @@ #include <atomic> #include <cstdint> +#include <mutex> +#include <type_traits> #include "absl/base/attributes.h" +#include "absl/base/config.h" #include "absl/base/const_init.h" -#include "absl/base/dynamic_annotations.h" #include "absl/base/internal/low_level_scheduling.h" #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/thread_annotations.h" namespace tcmalloc { namespace tcmalloc_internal { class AllocationGuardSpinLockHolder; +class Static; } // namespace tcmalloc_internal } // namespace tcmalloc @@ -55,17 +59,31 @@ class ABSL_LOCKABLE ABSL_ATTRIBUTE_WARN_UNUSED SpinLock { public: - SpinLock() : lockword_(kSpinLockCooperative) { - ABSL_TSAN_MUTEX_CREATE(this, __tsan_mutex_not_static); - } + constexpr SpinLock() : lockword_(kSpinLockCooperative) { RegisterWithTsan(); } // Constructors that allow non-cooperative spinlocks to be created for use // inside thread schedulers. Normal clients should not use these. - explicit SpinLock(base_internal::SchedulingMode mode); + constexpr explicit SpinLock(SchedulingMode mode) + : lockword_(IsCooperative(mode) ? kSpinLockCooperative : 0) { + RegisterWithTsan(); + } + +#if ABSL_HAVE_ATTRIBUTE(enable_if) && !defined(_WIN32) + // Constructor to inline users of the default scheduling mode. + // + // This only needs to exists for inliner runs, but doesn't work correctly in + // clang+windows builds, likely due to mangling differences. + ABSL_DEPRECATE_AND_INLINE() + constexpr explicit SpinLock(SchedulingMode mode) + __attribute__((enable_if(mode == SCHEDULE_COOPERATIVE_AND_KERNEL, + "Cooperative use default constructor"))) + : SpinLock() {} +#endif // Constructor for global SpinLock instances. See absl/base/const_init.h. - constexpr SpinLock(absl::ConstInitType, base_internal::SchedulingMode mode) - : lockword_(IsCooperative(mode) ? kSpinLockCooperative : 0) {} + ABSL_DEPRECATE_AND_INLINE() + constexpr SpinLock(absl::ConstInitType, SchedulingMode mode) + : SpinLock(mode) {} // For global SpinLock instances prefer trivial destructor when possible. // Default but non-trivial destructor in some build configurations causes an @@ -77,7 +95,7 @@ #endif // Acquire this SpinLock. - inline void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() { + inline void lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() { ABSL_TSAN_MUTEX_PRE_LOCK(this, 0); if (!TryLockImpl()) { SlowLock(); @@ -85,11 +103,14 @@ ABSL_TSAN_MUTEX_POST_LOCK(this, 0, 0); } + ABSL_DEPRECATE_AND_INLINE() + inline void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() { return lock(); } + // Try to acquire this SpinLock without blocking and return true if the // acquisition was successful. If the lock was not acquired, false is - // returned. If this SpinLock is free at the time of the call, TryLock - // will return true with high probability. - [[nodiscard]] inline bool TryLock() ABSL_EXCLUSIVE_TRYLOCK_FUNCTION(true) { + // returned. If this SpinLock is free at the time of the call, try_lock will + // return true with high probability. + [[nodiscard]] inline bool try_lock() ABSL_EXCLUSIVE_TRYLOCK_FUNCTION(true) { ABSL_TSAN_MUTEX_PRE_LOCK(this, __tsan_mutex_try_lock); bool res = TryLockImpl(); ABSL_TSAN_MUTEX_POST_LOCK( @@ -98,15 +119,20 @@ return res; } + ABSL_DEPRECATE_AND_INLINE() + [[nodiscard]] inline bool TryLock() ABSL_EXCLUSIVE_TRYLOCK_FUNCTION(true) { + return try_lock(); + } + // Release this SpinLock, which must be held by the calling thread. - inline void Unlock() ABSL_UNLOCK_FUNCTION() { + inline void unlock() ABSL_UNLOCK_FUNCTION() { ABSL_TSAN_MUTEX_PRE_UNLOCK(this, 0); uint32_t lock_value = lockword_.load(std::memory_order_relaxed); lock_value = lockword_.exchange(lock_value & kSpinLockCooperative, std::memory_order_release); if ((lock_value & kSpinLockDisabledScheduling) != 0) { - base_internal::SchedulingGuard::EnableRescheduling(true); + SchedulingGuard::EnableRescheduling(true); } if ((lock_value & kWaitTimeMask) != 0) { // Collect contentionz profile info, and speed the wakeup of any waiter. @@ -117,6 +143,9 @@ ABSL_TSAN_MUTEX_POST_UNLOCK(this, 0); } + ABSL_DEPRECATE_AND_INLINE() + inline void Unlock() ABSL_UNLOCK_FUNCTION() { unlock(); } + // Determine if the lock is held. When the lock is held by the invoking // thread, true will always be returned. Intended to be used as // CHECK(lock.IsHeld()). @@ -146,6 +175,16 @@ // Provide access to protected method above. Use for testing only. friend struct SpinLockTest; friend class tcmalloc::tcmalloc_internal::AllocationGuardSpinLockHolder; + friend class tcmalloc::tcmalloc_internal::Static; + + static int GetAdaptiveSpinCount() { + return adaptive_spin_count_.load(std::memory_order_relaxed); + } + static void SetAdaptiveSpinCount(int count) { + adaptive_spin_count_.store(count, std::memory_order_relaxed); + } + + static std::atomic<int> adaptive_spin_count_; private: // lockword_ is used to store the following: @@ -175,9 +214,16 @@ ~(kSpinLockHeld | kSpinLockCooperative | kSpinLockDisabledScheduling); // Returns true if the provided scheduling mode is cooperative. - static constexpr bool IsCooperative( - base_internal::SchedulingMode scheduling_mode) { - return scheduling_mode == base_internal::SCHEDULE_COOPERATIVE_AND_KERNEL; + static constexpr bool IsCooperative(SchedulingMode scheduling_mode) { + return scheduling_mode == SCHEDULE_COOPERATIVE_AND_KERNEL; + } + + constexpr void RegisterWithTsan() { +#if ABSL_HAVE_BUILTIN(__builtin_is_constant_evaluated) + if (!__builtin_is_constant_evaluated()) { + ABSL_TSAN_MUTEX_CREATE(this, __tsan_mutex_not_static); + } +#endif } bool IsCooperative() const { @@ -202,19 +248,18 @@ // Corresponding locker object that arranges to acquire a spinlock for // the duration of a C++ scope. -class ABSL_SCOPED_LOCKABLE [[nodiscard]] SpinLockHolder { +class ABSL_SCOPED_LOCKABLE [[nodiscard]] SpinLockHolder + : public std::lock_guard<SpinLock> { public: + inline explicit SpinLockHolder( + SpinLock& l ABSL_INTERNAL_ATTRIBUTE_CAPTURED_BY(this)) + ABSL_EXCLUSIVE_LOCK_FUNCTION(l) + : std::lock_guard<SpinLock>(l) {} + ABSL_DEPRECATE_AND_INLINE() inline explicit SpinLockHolder(SpinLock* l) ABSL_EXCLUSIVE_LOCK_FUNCTION(l) - : lock_(l) { - l->Lock(); - } - inline ~SpinLockHolder() ABSL_UNLOCK_FUNCTION() { lock_->Unlock(); } + : SpinLockHolder(*l) {} - SpinLockHolder(const SpinLockHolder&) = delete; - SpinLockHolder& operator=(const SpinLockHolder&) = delete; - - private: - SpinLock* lock_; + inline ~SpinLockHolder() ABSL_UNLOCK_FUNCTION() = default; }; // Register a hook for profiling support. @@ -243,7 +288,7 @@ if ((lock_value & kSpinLockCooperative) == 0) { // For non-cooperative locks we must make sure we mark ourselves as // non-reschedulable before we attempt to CompareAndSwap. - if (base_internal::SchedulingGuard::DisableRescheduling()) { + if (SchedulingGuard::DisableRescheduling()) { sched_disabled_bit = kSpinLockDisabledScheduling; } } @@ -252,7 +297,7 @@ lock_value, kSpinLockHeld | lock_value | wait_cycles | sched_disabled_bit, std::memory_order_acquire, std::memory_order_relaxed)) { - base_internal::SchedulingGuard::EnableRescheduling(sched_disabled_bit != 0); + SchedulingGuard::EnableRescheduling(sched_disabled_bit != 0); } return lock_value;
diff --git a/absl/base/internal/spinlock_benchmark.cc b/absl/base/internal/spinlock_benchmark.cc index 1790d96..c79f49f 100644 --- a/absl/base/internal/spinlock_benchmark.cc +++ b/absl/base/internal/spinlock_benchmark.cc
@@ -35,7 +35,7 @@ static absl::NoDestructor<absl::base_internal::SpinLock> spinlock( scheduling_mode); for (auto _ : state) { - if (spinlock->TryLock()) spinlock->Unlock(); + if (spinlock->try_lock()) spinlock->unlock(); } } @@ -50,7 +50,7 @@ static absl::NoDestructor<absl::base_internal::SpinLock> spinlock( scheduling_mode); for (auto _ : state) { - absl::base_internal::SpinLockHolder holder(spinlock.get()); + absl::base_internal::SpinLockHolder holder(*spinlock.get()); } }
diff --git a/absl/base/internal/strerror_test.cc b/absl/base/internal/strerror_test.cc index e32d5b5..d12c537 100644 --- a/absl/base/internal/strerror_test.cc +++ b/absl/base/internal/strerror_test.cc
@@ -39,7 +39,8 @@ TEST(StrErrorTest, InvalidErrorCode) { errno = ERANGE; EXPECT_THAT(absl::base_internal::StrError(-1), - AnyOf(Eq("No error information"), Eq("Unknown error -1"))); + AnyOf(Eq("No error information"), Eq("Unknown error -1"), + Eq("Unknown error"))); EXPECT_THAT(errno, Eq(ERANGE)); }
diff --git a/absl/base/internal/sysinfo.cc b/absl/base/internal/sysinfo.cc index 1937db3..a62dd31 100644 --- a/absl/base/internal/sysinfo.cc +++ b/absl/base/internal/sysinfo.cc
@@ -456,15 +456,6 @@ pid_t GetTID() { return static_cast<pid_t>(_lwp_self()); } -#elif defined(__native_client__) - -pid_t GetTID() { - auto* thread = pthread_self(); - static_assert(sizeof(pid_t) == sizeof(thread), - "In NaCL int expected to be the same size as a pointer"); - return reinterpret_cast<pid_t>(thread); -} - #elif defined(__Fuchsia__) pid_t GetTID() {
diff --git a/absl/base/internal/sysinfo_test.cc b/absl/base/internal/sysinfo_test.cc index c2b59aa..b4c75f5 100644 --- a/absl/base/internal/sysinfo_test.cc +++ b/absl/base/internal/sysinfo_test.cc
@@ -39,12 +39,6 @@ TEST(SysinfoTest, GetTID) { EXPECT_EQ(GetTID(), GetTID()); // Basic compile and equality test. -#ifdef __native_client__ - // Native Client has a race condition bug that leads to memory - // exhaustion when repeatedly creating and joining threads. - // https://bugs.chromium.org/p/nativeclient/issues/detail?id=1027 - return; -#endif // Test that TIDs are unique to each thread. // Uses a few loops to exercise implementations that reallocate IDs. for (int i = 0; i < 10; ++i) { @@ -59,7 +53,7 @@ threads.push_back(std::thread([&]() { pid_t id = GetTID(); { - MutexLock lock(&mutex); + MutexLock lock(mutex); ASSERT_TRUE(tids.find(id) == tids.end()); tids.insert(id); }
diff --git a/absl/base/internal/thread_identity_test.cc b/absl/base/internal/thread_identity_test.cc index 5f17553..3ef2ffe 100644 --- a/absl/base/internal/thread_identity_test.cc +++ b/absl/base/internal/thread_identity_test.cc
@@ -31,7 +31,7 @@ namespace { ABSL_CONST_INIT static absl::base_internal::SpinLock map_lock( - absl::kConstInit, base_internal::SCHEDULE_KERNEL_ONLY); + base_internal::SCHEDULE_KERNEL_ONLY); ABSL_CONST_INIT static int num_identities_reused ABSL_GUARDED_BY(map_lock); static const void* const kCheckNoIdentity = reinterpret_cast<void*>(1); @@ -58,7 +58,7 @@ PerThreadSynch::kAlignment); EXPECT_EQ(identity, identity->per_thread_synch.thread_identity()); - absl::base_internal::SpinLockHolder l(&map_lock); + absl::base_internal::SpinLockHolder l(map_lock); num_identities_reused++; } @@ -90,7 +90,7 @@ // We should have recycled ThreadIdentity objects above; while (external) // library threads allocating their own identities may preclude some // reuse, we should have sufficient repetitions to exclude this. - absl::base_internal::SpinLockHolder l(&map_lock); + absl::base_internal::SpinLockHolder l(map_lock); EXPECT_LT(kNumThreads, num_identities_reused); } @@ -112,7 +112,7 @@ threads.push_back(std::thread([&]() { for (int l = 0; l < kNumLockLoops; ++l) { for (int m = 0; m < kNumMutexes; ++m) { - MutexLock lock(&mutexes[m]); + MutexLock lock(mutexes[m]); } } }));
diff --git a/absl/base/internal/unscaledcycleclock.cc b/absl/base/internal/unscaledcycleclock.cc index 68f9273..73e4145 100644 --- a/absl/base/internal/unscaledcycleclock.cc +++ b/absl/base/internal/unscaledcycleclock.cc
@@ -62,7 +62,7 @@ int64_t UnscaledCycleClock::Now() { #ifdef __GLIBC__ - return __ppc_get_timebase(); + return static_cast<int64_t>(__ppc_get_timebase()); #else #ifdef __powerpc64__ int64_t tbr; @@ -85,6 +85,10 @@ double UnscaledCycleClock::Frequency() { #ifdef __GLIBC__ return __ppc_get_timebase_freq(); +#elif defined(__linux__) + // Fallback for musl + ppc64le: use constant timebase frequency (512 MHz) + // Must come after __GLIBC__. + return static_cast<double>(512000000); #elif defined(_AIX) // This is the same constant value as returned by // __ppc_get_timebase_freq().
diff --git a/absl/base/internal/unscaledcycleclock_config.h b/absl/base/internal/unscaledcycleclock_config.h index 43a3dab..9a0841d 100644 --- a/absl/base/internal/unscaledcycleclock_config.h +++ b/absl/base/internal/unscaledcycleclock_config.h
@@ -34,7 +34,7 @@ // 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__)) || \ +#if defined(__APPLE__) || \ (defined(__ANDROID__) && defined(__aarch64__)) #define ABSL_USE_UNSCALED_CYCLECLOCK_DEFAULT 0 #else
diff --git a/absl/base/macros.h b/absl/base/macros.h index ff89944..446a445 100644 --- a/absl/base/macros.h +++ b/absl/base/macros.h
@@ -169,42 +169,65 @@ #define ABSL_INTERNAL_RETHROW do {} while (false) #endif // ABSL_HAVE_EXCEPTIONS -// ABSL_DEPRECATE_AND_INLINE() +// ABSL_REFACTOR_INLINE // -// Marks a function or type alias as deprecated and tags it to be picked up for -// automated refactoring by go/cpp-inliner. It can added to inline function -// definitions or type aliases. It should only be used within a header file. It -// differs from `ABSL_DEPRECATED` in the following ways: +// Marks a function or type for automated refactoring by go/cpp-inliner. It can +// be used on inline function definitions or type aliases in header files and +// should be combined with the `[[deprecated]]` attribute. +// +// Using `ABSL_REFACTOR_INLINE` differs from using the `[[deprecated]]` alone in +// the following ways: // // 1. New uses of the function or type will be discouraged via Tricorder // warnings. // 2. If enabled via `METADATA`, automated changes will be sent out inlining the // functions's body or replacing the type where it is used. // -// For example: +// Examples: // -// ABSL_DEPRECATE_AND_INLINE() inline int OldFunc(int x) { +// [[deprecated("Use NewFunc() instead")]] ABSL_REFACTOR_INLINE +// inline int OldFunc(int x) { // return NewFunc(x, 0); // } // -// will mark `OldFunc` as deprecated, and the go/cpp-inliner service will -// replace calls to `OldFunc(x)` with calls to `NewFunc(x, 0)`. Once all calls -// to `OldFunc` have been replaced, `OldFunc` can be deleted. +// using OldType [[deprecated("Use NewType instead")]] ABSL_REFACTOR_INLINE = +// NewType; +// +// will mark `OldFunc` and `OldType` as deprecated, and the go/cpp-inliner +// service will replace calls to `OldFunc(x)` with calls to `NewFunc(x, 0)` and +// `OldType` with `NewType`. Once all replacements have been completed, the old +// function or type can be deleted. // // See go/cpp-inliner for more information. // // Note: go/cpp-inliner is Google-internal service for automated refactoring. // While open-source users do not have access to this service, the macro is -// provided for compatibility, and so that users receive deprecation warnings. -#if ABSL_HAVE_CPP_ATTRIBUTE(deprecated) && \ - ABSL_HAVE_CPP_ATTRIBUTE(clang::annotate) -#define ABSL_DEPRECATE_AND_INLINE() [[deprecated, clang::annotate("inline-me")]] -#elif ABSL_HAVE_CPP_ATTRIBUTE(deprecated) -#define ABSL_DEPRECATE_AND_INLINE() [[deprecated]] +// provided for compatibility. +#if ABSL_HAVE_CPP_ATTRIBUTE(clang::annotate) +#define ABSL_REFACTOR_INLINE [[clang::annotate("inline-me")]] #else -#define ABSL_DEPRECATE_AND_INLINE() +#define ABSL_REFACTOR_INLINE #endif +// ABSL_DEPRECATE_AND_INLINE() +// +// This is the original macro used by go/cpp-inliner that combines +// [[deprecated]] and ABSL_REFACTOR_INLINE. +// +// Examples: +// +// ABSL_DEPRECATE_AND_INLINE() inline int OldFunc(int x) { +// return NewFunc(x, 0); +// } +// +// using OldType ABSL_DEPRECATE_AND_INLINE() = NewType; +// +// The combination of `[[deprecated("Use X instead")]]` and +// `ABSL_REFACTOR_INLINE` is preferred because it provides a more informative +// deprecation message to developers, especially those that do not have access +// to the automated refactoring capabilities of go/cpp-inliner. +#define ABSL_DEPRECATE_AND_INLINE() [[deprecated]] ABSL_REFACTOR_INLINE + // Requires the compiler to prove that the size of the given object is at least // the expected amount. #if ABSL_HAVE_ATTRIBUTE(diagnose_if) && ABSL_HAVE_BUILTIN(__builtin_object_size)
diff --git a/absl/base/nullability.h b/absl/base/nullability.h index 3a5d6e8..facc642 100644 --- a/absl/base/nullability.h +++ b/absl/base/nullability.h
@@ -81,7 +81,7 @@ // const Employee* absl_nonnull e; // // // A non-null pointer to a const nullable pointer to an `Employee`. -// Employee* absl_nullable const* absl_nonnull e = nullptr; +// Employee* absl_nullable const* absl_nonnull e; // // // A non-null function pointer. // void (*absl_nonnull func)(int, double); @@ -184,7 +184,6 @@ #define ABSL_BASE_NULLABILITY_H_ #include "absl/base/config.h" -#include "absl/base/internal/nullability_deprecated.h" // ABSL_POINTERS_DEFAULT_NONNULL //
diff --git a/absl/base/nullability_test.cc b/absl/base/nullability_test.cc index bccc388..bccf1af 100644 --- a/absl/base/nullability_test.cc +++ b/absl/base/nullability_test.cc
@@ -14,16 +14,13 @@ #include "absl/base/nullability.h" -#include <cassert> #include <memory> #include <type_traits> #include <utility> #include "gtest/gtest.h" -#include "absl/base/attributes.h" namespace { -namespace macro_annotations { void funcWithNonnullArg(int* absl_nonnull /*arg*/) {} template <typename T> void funcWithDeducedNonnullArg(T* absl_nonnull /*arg*/) {} @@ -90,117 +87,4 @@ EXPECT_TRUE((std::is_same<absl_nullable T, T>::value)); EXPECT_TRUE((std::is_same<absl_nullability_unknown T, T>::value)); } -} // namespace macro_annotations - -// Allow testing of the deprecated type alias annotations. -ABSL_INTERNAL_DISABLE_DEPRECATED_DECLARATION_WARNING - -using ::absl::Nonnull; -using ::absl::NullabilityUnknown; -using ::absl::Nullable; -namespace type_alias_annotations { - -void funcWithNonnullArg(Nonnull<int*> /*arg*/) {} -template <typename T> -void funcWithDeducedNonnullArg(Nonnull<T*> /*arg*/) {} - -TEST(NonnullTest, NonnullArgument) { - int var = 0; - funcWithNonnullArg(&var); - funcWithDeducedNonnullArg(&var); -} - -Nonnull<int*> funcWithNonnullReturn() { - static int var = 0; - return &var; -} - -TEST(NonnullTest, NonnullReturn) { - auto var = funcWithNonnullReturn(); - (void)var; -} - -TEST(PassThroughTest, PassesThroughRawPointerToInt) { - EXPECT_TRUE((std::is_same<Nonnull<int*>, int*>::value)); - EXPECT_TRUE((std::is_same<Nullable<int*>, int*>::value)); - EXPECT_TRUE((std::is_same<NullabilityUnknown<int*>, int*>::value)); -} - -TEST(PassThroughTest, PassesThroughRawPointerToVoid) { - EXPECT_TRUE((std::is_same<Nonnull<void*>, void*>::value)); - EXPECT_TRUE((std::is_same<Nullable<void*>, void*>::value)); - EXPECT_TRUE((std::is_same<NullabilityUnknown<void*>, void*>::value)); -} - -TEST(PassThroughTest, PassesThroughUniquePointerToInt) { - using T = std::unique_ptr<int>; - EXPECT_TRUE((std::is_same<Nonnull<T>, T>::value)); - EXPECT_TRUE((std::is_same<Nullable<T>, T>::value)); - EXPECT_TRUE((std::is_same<NullabilityUnknown<T>, T>::value)); -} - -TEST(PassThroughTest, PassesThroughSharedPointerToInt) { - using T = std::shared_ptr<int>; - EXPECT_TRUE((std::is_same<Nonnull<T>, T>::value)); - EXPECT_TRUE((std::is_same<Nullable<T>, T>::value)); - EXPECT_TRUE((std::is_same<NullabilityUnknown<T>, T>::value)); -} - -TEST(PassThroughTest, PassesThroughSharedPointerToVoid) { - using T = std::shared_ptr<void>; - EXPECT_TRUE((std::is_same<Nonnull<T>, T>::value)); - EXPECT_TRUE((std::is_same<Nullable<T>, T>::value)); - EXPECT_TRUE((std::is_same<NullabilityUnknown<T>, T>::value)); -} - -TEST(PassThroughTest, PassesThroughPointerToMemberObject) { - using T = decltype(&std::pair<int, int>::first); - EXPECT_TRUE((std::is_same<Nonnull<T>, T>::value)); - EXPECT_TRUE((std::is_same<Nullable<T>, T>::value)); - EXPECT_TRUE((std::is_same<NullabilityUnknown<T>, T>::value)); -} - -TEST(PassThroughTest, PassesThroughPointerToMemberFunction) { - using T = decltype(&std::unique_ptr<int>::reset); - EXPECT_TRUE((std::is_same<Nonnull<T>, T>::value)); - EXPECT_TRUE((std::is_same<Nullable<T>, T>::value)); - EXPECT_TRUE((std::is_same<NullabilityUnknown<T>, T>::value)); -} - -} // namespace type_alias_annotations -} // namespace - -// Nullable ADL lookup test -namespace util { -// Helper for NullableAdlTest. Returns true, denoting that argument-dependent -// lookup found this implementation of DidAdlWin. Must be in namespace -// util itself, not a nested anonymous namespace. -template <typename T> -bool DidAdlWin(T*) { - return true; -} - -// Because this type is defined in namespace util, an unqualified call to -// DidAdlWin with a pointer to MakeAdlWin will find the above implementation. -struct MakeAdlWin {}; -} // namespace util - -namespace { -// Returns false, denoting that ADL did not inspect namespace util. If it -// had, the better match (T*) above would have won out over the (...) here. -bool DidAdlWin(...) { return false; } - -TEST(NullableAdlTest, NullableAddsNothingToArgumentDependentLookup) { - // Treatment: util::Nullable<int*> contributes nothing to ADL because - // int* itself doesn't. - EXPECT_FALSE(DidAdlWin((int*)nullptr)); - EXPECT_FALSE(DidAdlWin((Nullable<int*>)nullptr)); - - // Control: Argument-dependent lookup does find the implementation in - // namespace util when the underlying pointee type resides there. - EXPECT_TRUE(DidAdlWin((util::MakeAdlWin*)nullptr)); - EXPECT_TRUE(DidAdlWin((Nullable<util::MakeAdlWin*>)nullptr)); -} - -ABSL_INTERNAL_RESTORE_DEPRECATED_DECLARATION_WARNING } // namespace
diff --git a/absl/base/optimization.h b/absl/base/optimization.h index 429ea9c..04678c4 100644 --- a/absl/base/optimization.h +++ b/absl/base/optimization.h
@@ -53,9 +53,7 @@ // ABSL_BLOCK_TAIL_CALL_OPTIMIZATION(); // return result; // } -#if defined(__pnacl__) -#define ABSL_BLOCK_TAIL_CALL_OPTIMIZATION() if (volatile int x = 0) { (void)x; } -#elif defined(__clang__) +#if defined(__clang__) // Clang will not tail call given inline volatile assembly. #define ABSL_BLOCK_TAIL_CALL_OPTIMIZATION() __asm__ __volatile__("") #elif defined(__GNUC__)
diff --git a/absl/base/options.h b/absl/base/options.h index cba75b9..4067d6a 100644 --- a/absl/base/options.h +++ b/absl/base/options.h
@@ -73,32 +73,6 @@ // Type Compatibility Options // ----------------------------------------------------------------------------- -// ABSL_OPTION_USE_STD_STRING_VIEW -// -// This option controls whether absl::string_view is implemented as an alias to -// std::string_view, or as an independent implementation. -// -// A value of 0 means to use Abseil's implementation. This requires only C++11 -// support, and is expected to work on every toolchain we support. -// -// A value of 1 means to use an alias to std::string_view. This requires that -// all code using Abseil is built in C++17 mode or later. -// -// A value of 2 means to detect the C++ version being used to compile Abseil, -// and use an alias only if a working std::string_view is available. This -// option is useful when you are building your program from source. It should -// not be used otherwise -- for example, if you are distributing Abseil in a -// binary package manager -- since in mode 2, absl::string_view will name a -// different type, with a different mangled name and binary layout, depending on -// the compiler flags passed by the end user. For more info, see -// https://abseil.io/about/design/dropin-types. -// -// User code should not inspect this macro. To check in the preprocessor if -// absl::string_view is a typedef of std::string_view, use the feature macro -// ABSL_USES_STD_STRING_VIEW. - -#define ABSL_OPTION_USE_STD_STRING_VIEW 1 - // ABSL_OPTION_USE_STD_ORDERING // // This option controls whether absl::{partial,weak,strong}_ordering are @@ -150,9 +124,9 @@ #define ABSL_OPTION_USE_INLINE_NAMESPACE 1 #ifdef ANDROID_DISABLE_TLS_FOR_LINKER -#define ABSL_OPTION_INLINE_NAMESPACE_NAME lts_20250512_notls +#define ABSL_OPTION_INLINE_NAMESPACE_NAME lts_20260107_notls #else -#define ABSL_OPTION_INLINE_NAMESPACE_NAME lts_20250512 +#define ABSL_OPTION_INLINE_NAMESPACE_NAME lts_20260107 #endif // ABSL_OPTION_HARDENED
diff --git a/absl/base/spinlock_test_common.cc b/absl/base/spinlock_test_common.cc index e904715..6ac2e36 100644 --- a/absl/base/spinlock_test_common.cc +++ b/absl/base/spinlock_test_common.cc
@@ -18,6 +18,7 @@ #include <cstdint> #include <limits> +#include <mutex> // NOLINT(build/c++11) #include <random> #include <thread> // NOLINT(build/c++11) #include <type_traits> @@ -60,30 +61,47 @@ static constexpr size_t kArrayLength = 10; static uint32_t values[kArrayLength]; -ABSL_CONST_INIT static SpinLock static_cooperative_spinlock( - absl::kConstInit, base_internal::SCHEDULE_COOPERATIVE_AND_KERNEL); +ABSL_CONST_INIT static SpinLock static_cooperative_spinlock; ABSL_CONST_INIT static SpinLock static_noncooperative_spinlock( - absl::kConstInit, base_internal::SCHEDULE_KERNEL_ONLY); + base_internal::SCHEDULE_KERNEL_ONLY); // Simple integer hash function based on the public domain lookup2 hash. // http://burtleburtle.net/bob/c/lookup2.c static uint32_t Hash32(uint32_t a, uint32_t c) { uint32_t b = 0x9e3779b9UL; // The golden ratio; an arbitrary value. - a -= b; a -= c; a ^= (c >> 13); - b -= c; b -= a; b ^= (a << 8); - c -= a; c -= b; c ^= (b >> 13); - a -= b; a -= c; a ^= (c >> 12); - b -= c; b -= a; b ^= (a << 16); - c -= a; c -= b; c ^= (b >> 5); - a -= b; a -= c; a ^= (c >> 3); - b -= c; b -= a; b ^= (a << 10); - c -= a; c -= b; c ^= (b >> 15); + a -= b; + a -= c; + a ^= (c >> 13); + b -= c; + b -= a; + b ^= (a << 8); + c -= a; + c -= b; + c ^= (b >> 13); + a -= b; + a -= c; + a ^= (c >> 12); + b -= c; + b -= a; + b ^= (a << 16); + c -= a; + c -= b; + c ^= (b >> 5); + a -= b; + a -= c; + a ^= (c >> 3); + b -= c; + b -= a; + b ^= (a << 10); + c -= a; + c -= b; + c ^= (b >> 15); return c; } static void TestFunction(uint32_t thread_salt, SpinLock* spinlock) { for (int i = 0; i < kIters; i++) { - SpinLockHolder h(spinlock); + SpinLockHolder h(*spinlock); for (size_t j = 0; j < kArrayLength; j++) { const size_t index = (j + thread_salt) % kArrayLength; values[index] = Hash32(values[index], thread_salt); @@ -102,7 +120,7 @@ thread.join(); } - SpinLockHolder h(spinlock); + SpinLockHolder h(*spinlock); for (size_t i = 1; i < kArrayLength; i++) { EXPECT_EQ(values[0], values[i]); } @@ -114,15 +132,13 @@ TEST(SpinLock, StackNonCooperativeDisablesScheduling) { SpinLock spinlock(base_internal::SCHEDULE_KERNEL_ONLY); - spinlock.Lock(); + SpinLockHolder l(spinlock); EXPECT_FALSE(base_internal::SchedulingGuard::ReschedulingIsAllowed()); - spinlock.Unlock(); } TEST(SpinLock, StaticNonCooperativeDisablesScheduling) { - static_noncooperative_spinlock.Lock(); + SpinLockHolder l(static_noncooperative_spinlock); EXPECT_FALSE(base_internal::SchedulingGuard::ReschedulingIsAllowed()); - static_noncooperative_spinlock.Unlock(); } TEST(SpinLock, WaitCyclesEncoding) { @@ -134,7 +150,7 @@ // We should be able to encode up to (1^kMaxCycleBits - 1) without clamping // but the lower kProfileTimestampShift will be dropped. const int kMaxCyclesShift = - 32 - kLockwordReservedShift + kProfileTimestampShift; + 32 - kLockwordReservedShift + kProfileTimestampShift; const int64_t kMaxCycles = (int64_t{1} << kMaxCyclesShift) - 1; // These bits should be zero after encoding. @@ -171,22 +187,22 @@ SpinLockTest::DecodeWaitCycles(~kLockwordReservedMask)); // Check that we cannot produce kSpinLockSleeper during encoding. - int64_t sleeper_cycles = - kSpinLockSleeper << (kProfileTimestampShift - kLockwordReservedShift); + int64_t sleeper_cycles = kSpinLockSleeper + << (kProfileTimestampShift - kLockwordReservedShift); uint32_t sleeper_value = SpinLockTest::EncodeWaitCycles(start_time, start_time + sleeper_cycles); EXPECT_NE(sleeper_value, kSpinLockSleeper); // Test clamping uint32_t max_value = - SpinLockTest::EncodeWaitCycles(start_time, start_time + kMaxCycles); + SpinLockTest::EncodeWaitCycles(start_time, start_time + kMaxCycles); 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); + uint32_t after_max_value = SpinLockTest::EncodeWaitCycles( + start_time, start_time + kMaxCycles + step); int64_t after_max_value_decoded = SpinLockTest::DecodeWaitCycles(after_max_value); EXPECT_EQ(expected_max_value_decoded, after_max_value_decoded); @@ -204,7 +220,7 @@ } TEST(SpinLockWithThreads, StackCooperativeSpinLock) { - SpinLock spinlock(base_internal::SCHEDULE_COOPERATIVE_AND_KERNEL); + SpinLock spinlock; ThreadedTest(&spinlock); } @@ -227,12 +243,12 @@ BlockingCounter* b) { locked->WaitForNotification(); // Wait for LockThenWait() to hold "s". b->DecrementCount(); - SpinLockHolder l(spinlock); + SpinLockHolder l(*spinlock); } static void LockThenWait(Notification* locked, SpinLock* spinlock, BlockingCounter* b) { - SpinLockHolder l(spinlock); + SpinLockHolder l(*spinlock); locked->Notify(); b->Wait(); } @@ -255,30 +271,31 @@ } }; - SpinLock stack_cooperative_spinlock( - base_internal::SCHEDULE_COOPERATIVE_AND_KERNEL); + int num_threads = base_internal::NumCPUs() * 2; + SpinLock stack_cooperative_spinlock; SpinLock stack_noncooperative_spinlock(base_internal::SCHEDULE_KERNEL_ONLY); - Helper::DeadlockTest(&stack_cooperative_spinlock, - base_internal::NumCPUs() * 2); - Helper::DeadlockTest(&stack_noncooperative_spinlock, - base_internal::NumCPUs() * 2); - Helper::DeadlockTest(&static_cooperative_spinlock, - base_internal::NumCPUs() * 2); - Helper::DeadlockTest(&static_noncooperative_spinlock, - base_internal::NumCPUs() * 2); + Helper::DeadlockTest(&stack_cooperative_spinlock, num_threads); + Helper::DeadlockTest(&stack_noncooperative_spinlock, num_threads); + Helper::DeadlockTest(&static_cooperative_spinlock, num_threads); + Helper::DeadlockTest(&static_noncooperative_spinlock, num_threads); } TEST(SpinLockTest, IsCooperative) { SpinLock default_constructor; EXPECT_TRUE(SpinLockTest::IsCooperative(default_constructor)); - SpinLock cooperative(base_internal::SCHEDULE_COOPERATIVE_AND_KERNEL); + SpinLock cooperative; EXPECT_TRUE(SpinLockTest::IsCooperative(cooperative)); SpinLock kernel_only(base_internal::SCHEDULE_KERNEL_ONLY); EXPECT_FALSE(SpinLockTest::IsCooperative(kernel_only)); } +TEST(SpinLockTest, ScopedLock) { + SpinLock s; + std::scoped_lock l(s); +} + } // namespace } // namespace base_internal ABSL_NAMESPACE_END
diff --git a/absl/cleanup/BUILD.bazel b/absl/cleanup/BUILD.bazel index d579781..5475439 100644 --- a/absl/cleanup/BUILD.bazel +++ b/absl/cleanup/BUILD.bazel
@@ -12,6 +12,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load( "//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS",
diff --git a/absl/cleanup/cleanup.h b/absl/cleanup/cleanup.h index 311e482..632ec6e 100644 --- a/absl/cleanup/cleanup.h +++ b/absl/cleanup/cleanup.h
@@ -19,6 +19,10 @@ // `absl::Cleanup` implements the scope guard idiom, invoking the contained // callback's `operator()() &&` on scope exit. // +// This class doesn't allocate or take any locks, and is safe to use in a signal +// handler. Of course the callback with which it is constructed also must be +// signal safe in order for this to be useful. +// // Example: // // ```
diff --git a/absl/container/BUILD.bazel b/absl/container/BUILD.bazel index 61e816f..e90aaec 100644 --- a/absl/container/BUILD.bazel +++ b/absl/container/BUILD.bazel
@@ -14,6 +14,9 @@ # limitations under the License. # +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load( "//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS", @@ -305,7 +308,7 @@ cc_test( name = "flat_hash_set_test", srcs = ["flat_hash_set_test.cc"], - copts = ABSL_TEST_COPTS + ["-DUNORDERED_SET_CXX17"], + copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, tags = ["no_test_loonix"], deps = [ @@ -385,7 +388,7 @@ cc_test( name = "node_hash_set_test", srcs = ["node_hash_set_test.cc"], - copts = ABSL_TEST_COPTS + ["-DUNORDERED_SET_CXX17"], + copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, tags = ["no_test_loonix"], deps = [ @@ -410,6 +413,7 @@ linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ "//absl/base:config", + "//absl/hash", "//absl/memory", "//absl/meta:type_traits", "//absl/utility", @@ -425,6 +429,7 @@ deps = [ ":container_memory", ":test_instance_tracker", + "//absl/base:config", "//absl/base:no_destructor", "//absl/meta:type_traits", "//absl/strings", @@ -491,6 +496,7 @@ linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":hash_policy_testing", + "//absl/base:config", "//absl/base:no_destructor", "//absl/memory", "//absl/meta:type_traits", @@ -530,6 +536,7 @@ linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":common_policy_traits", + ":container_memory", "//absl/meta:type_traits", ], ) @@ -697,6 +704,19 @@ ], ) +cc_test( + name = "hashtable_control_bytes_test", + srcs = ["internal/hashtable_control_bytes_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":hashtable_control_bytes", + "//absl/base:config", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + cc_library( name = "raw_hash_set_resize_impl", hdrs = ["internal/raw_hash_set_resize_impl.h"], @@ -735,6 +755,7 @@ ":hashtable_debug_hooks", ":hashtablez_sampler", ":raw_hash_set_resize_impl", + "//absl/base", "//absl/base:config", "//absl/base:core_headers", "//absl/base:dynamic_annotations", @@ -922,9 +943,13 @@ hdrs = ["internal/unordered_map_constructor_test.h"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/container:__pkg__", + ], deps = [ ":hash_generator_testing", ":hash_policy_testing", + "//absl/base:config", "@googletest//:gtest", ], ) @@ -935,6 +960,9 @@ hdrs = ["internal/unordered_map_lookup_test.h"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/container:__pkg__", + ], deps = [ ":hash_generator_testing", ":hash_policy_testing", @@ -948,6 +976,9 @@ hdrs = ["internal/unordered_map_modifiers_test.h"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/container:__pkg__", + ], deps = [ ":hash_generator_testing", ":hash_policy_testing", @@ -961,9 +992,13 @@ hdrs = ["internal/unordered_set_constructor_test.h"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/container:__pkg__", + ], deps = [ ":hash_generator_testing", ":hash_policy_testing", + "//absl/base:config", "//absl/meta:type_traits", "@googletest//:gtest", ], @@ -975,6 +1010,9 @@ hdrs = ["internal/unordered_set_members_test.h"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/container:__pkg__", + ], deps = [ "//absl/meta:type_traits", "@googletest//:gtest", @@ -987,6 +1025,9 @@ hdrs = ["internal/unordered_map_members_test.h"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/container:__pkg__", + ], deps = [ "//absl/meta:type_traits", "@googletest//:gtest", @@ -999,6 +1040,9 @@ hdrs = ["internal/unordered_set_lookup_test.h"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/container:__pkg__", + ], deps = [ ":hash_generator_testing", ":hash_policy_testing", @@ -1012,6 +1056,9 @@ hdrs = ["internal/unordered_set_modifiers_test.h"], copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/container:__pkg__", + ], deps = [ ":hash_generator_testing", ":hash_policy_testing", @@ -1180,3 +1227,168 @@ "@google_benchmark//:benchmark_main", ], ) + +cc_library( + name = "heterogeneous_lookup_testing", + testonly = True, + hdrs = ["internal/heterogeneous_lookup_testing.h"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl/container:__pkg__", + ], + deps = [ + "//absl/base:config", + "//absl/container:test_instance_tracker", + "@googletest//:gtest", + ], +) + +cc_library( + name = "linked_hash_set", + hdrs = ["linked_hash_set.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":common", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/container:flat_hash_set", + ], +) + +cc_test( + name = "linked_hash_set_test", + srcs = ["linked_hash_set_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":heterogeneous_lookup_testing", + ":linked_hash_set", + "//absl/base:config", + "//absl/container:hash_generator_testing", + "//absl/container:hash_policy_testing", + "//absl/container:test_instance_tracker", + "//absl/container:unordered_set_constructor_test", + "//absl/container:unordered_set_lookup_test", + "//absl/container:unordered_set_members_test", + "//absl/container:unordered_set_modifiers_test", + "//absl/strings:string_view", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_binary( + name = "linked_hash_set_benchmark", + testonly = True, + srcs = ["linked_hash_set_benchmark.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = ["benchmark"], + visibility = ["//visibility:private"], + deps = [ + ":linked_hash_set", + "//absl/functional:function_ref", + "//absl/strings:str_format", + "//absl/strings:string_view", + "@google_benchmark//:benchmark_main", + ], +) + +cc_library( + name = "linked_hash_map", + hdrs = ["linked_hash_map.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":common", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:throw_delegate", + "//absl/container:flat_hash_set", + ], +) + +cc_test( + name = "linked_hash_map_test", + srcs = ["linked_hash_map_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":heterogeneous_lookup_testing", + ":linked_hash_map", + "//absl/base:config", + "//absl/base:exception_testing", + "//absl/container:hash_generator_testing", + "//absl/container:hash_policy_testing", + "//absl/container:test_instance_tracker", + "//absl/container:unordered_map_constructor_test", + "//absl/container:unordered_map_lookup_test", + "//absl/container:unordered_map_members_test", + "//absl/container:unordered_map_modifiers_test", + "//absl/strings:string_view", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_binary( + name = "linked_hash_map_benchmark", + testonly = True, + srcs = ["linked_hash_map_benchmark.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = ["benchmark"], + visibility = ["//visibility:private"], + deps = [ + ":linked_hash_map", + "//absl/functional:function_ref", + "//absl/strings:str_format", + "//absl/strings:string_view", + "@google_benchmark//:benchmark_main", + ], +) + +cc_library( + name = "chunked_queue", + srcs = ["internal/chunked_queue.h"], + hdrs = ["chunked_queue.h"], + deps = [ + ":layout", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:iterator_traits_internal", + ], +) + +cc_test( + name = "chunked_queue_test", + size = "small", + srcs = ["chunked_queue_test.cc"], + deps = [ + ":chunked_queue", + ":test_allocator", + "//absl/base:core_headers", + "//absl/strings", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_binary( + name = "chunked_queue_benchmark", + testonly = True, + srcs = ["chunked_queue_benchmark.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + tags = ["benchmark"], + visibility = ["//visibility:private"], + deps = [ + ":chunked_queue", + "//absl/random", + "//absl/status", + "//absl/strings:cord", + "@google_benchmark//:benchmark_main", + ], +)
diff --git a/absl/container/CMakeLists.txt b/absl/container/CMakeLists.txt index d8cd7d0..365c6ea 100644 --- a/absl/container/CMakeLists.txt +++ b/absl/container/CMakeLists.txt
@@ -349,7 +349,6 @@ "flat_hash_set_test.cc" COPTS ${ABSL_TEST_COPTS} - "-DUNORDERED_SET_CXX17" DEPS absl::check absl::config @@ -432,7 +431,6 @@ "node_hash_set_test.cc" COPTS ${ABSL_TEST_COPTS} - "-DUNORDERED_SET_CXX17" DEPS absl::hash_generator_testing absl::hash_policy_testing @@ -469,6 +467,7 @@ ${ABSL_DEFAULT_COPTS} DEPS absl::config + absl::hash absl::memory absl::type_traits absl::utility @@ -483,6 +482,7 @@ COPTS ${ABSL_TEST_COPTS} DEPS + absl::config absl::container_memory absl::no_destructor absl::strings @@ -539,6 +539,7 @@ COPTS ${ABSL_TEST_COPTS} DEPS + absl::config absl::hash_policy_testing absl::memory absl::meta @@ -583,6 +584,7 @@ COPTS ${ABSL_DEFAULT_COPTS} DEPS + absl::container_memory absl::common_policy_traits absl::meta PUBLIC @@ -761,6 +763,19 @@ absl::endian ) +absl_cc_test( + NAME + hashtable_control_bytes_test + SRCS + "internal/hashtable_control_bytes_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::config + absl::hashtable_control_bytes + GTest::gmock_main +) + # Internal-only target, do not depend on directly. absl_cc_library( NAME @@ -772,6 +787,7 @@ COPTS ${ABSL_DEFAULT_COPTS} DEPS + absl::base absl::bits absl::common_policy_traits absl::compressed_tuple @@ -933,6 +949,7 @@ COPTS ${ABSL_TEST_COPTS} DEPS + absl::config absl::hash_generator_testing absl::hash_policy_testing GTest::gmock @@ -992,6 +1009,7 @@ COPTS ${ABSL_TEST_COPTS} DEPS + absl::config absl::hash_generator_testing absl::hash_policy_testing GTest::gmock @@ -1087,3 +1105,135 @@ absl::hashtablez_sampler GTest::gmock_main ) + +absl_cc_library( + NAME + heterogeneous_lookup_testing + HDRS + "internal/heterogeneous_lookup_testing.h" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::test_instance_tracker + GTest::gmock +) + +absl_cc_library( + NAME + linked_hash_set + HDRS + "linked_hash_set.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::container_common + absl::config + absl::core_headers + absl::flat_hash_set +) + +absl_cc_test( + NAME + linked_hash_set_test + SRCS + "linked_hash_set_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::linked_hash_set + absl::config + absl::heterogeneous_lookup_testing + absl::hash_generator_testing + absl::hash_policy_testing + absl::string_view + absl::test_instance_tracker + absl::unordered_set_constructor_test + absl::unordered_set_lookup_test + absl::unordered_set_members_test + absl::unordered_set_modifiers_test + GTest::gmock_main +) + +absl_cc_library( + NAME + linked_hash_map + HDRS + "linked_hash_map.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::container_common + absl::config + absl::core_headers + absl::flat_hash_set + absl::throw_delegate +) + +absl_cc_test( + NAME + linked_hash_map_test + SRCS + "linked_hash_map_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::linked_hash_map + absl::config + absl::exception_testing + absl::heterogeneous_lookup_testing + absl::hash_generator_testing + absl::hash_policy_testing + absl::string_view + absl::test_instance_tracker + absl::unordered_set_constructor_test + absl::unordered_set_lookup_test + absl::unordered_set_members_test + absl::unordered_set_modifiers_test + GTest::gmock_main +) + +absl_cc_library( + NAME + chunked_queue + HDRS + "chunked_queue.h" + "internal/chunked_queue.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::core_headers + absl::iterator_traits_internal + absl::layout +) + +absl_cc_test( + NAME + chunked_queue_test + SRCS + "chunked_queue_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::chunked_queue + absl::config + absl::core_headers + absl::strings + absl::test_allocator + GTest::gmock_main +)
diff --git a/absl/container/btree_map.h b/absl/container/btree_map.h index 32a82ef..0746f72 100644 --- a/absl/container/btree_map.h +++ b/absl/container/btree_map.h
@@ -57,18 +57,44 @@ #ifndef ABSL_CONTAINER_BTREE_MAP_H_ #define ABSL_CONTAINER_BTREE_MAP_H_ +#include <functional> +#include <memory> +#include <type_traits> +#include <utility> + #include "absl/base/attributes.h" #include "absl/container/internal/btree.h" // IWYU pragma: export #include "absl/container/internal/btree_container.h" // IWYU pragma: export +#include "absl/container/internal/common.h" +#include "absl/container/internal/container_memory.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace container_internal { +template <typename Key, typename Data, typename... Params> +struct map_params_impl; + +template <typename Key, typename Data> +struct btree_map_defaults { + using Compare = std::less<Key>; + using Alloc = std::allocator<std::pair<const Key, Data>>; + using TargetNodeSize = std::integral_constant<int, 256>; + using IsMulti = std::false_type; +}; + template <typename Key, typename Data, typename Compare, typename Alloc, int TargetNodeSize, bool IsMulti> -struct map_params; +using map_params = typename ApplyWithoutDefaultSuffix< + map_params_impl, + TypeList<void, void, typename btree_map_defaults<Key, Data>::Compare, + typename btree_map_defaults<Key, Data>::Alloc, + typename btree_map_defaults<Key, Data>::TargetNodeSize, + typename btree_map_defaults<Key, Data>::IsMulti>, + TypeList<Key, Data, Compare, Alloc, + std::integral_constant<int, TargetNodeSize>, + std::integral_constant<bool, IsMulti>>>::type; } // namespace container_internal @@ -117,8 +143,8 @@ // // * Copy assignment operator // - // absl::btree_map<int, std::string> map4; - // map4 = map3; + // absl::btree_map<int, std::string> map4; + // map4 = map3; // // * Move constructor // @@ -555,8 +581,8 @@ // // * Copy assignment operator // - // absl::btree_multimap<int, std::string> map4; - // map4 = map3; + // absl::btree_multimap<int, std::string> map4; + // map4 = map3; // // * Move constructor // @@ -855,11 +881,20 @@ // A parameters structure for holding the type parameters for a btree_map. // Compare and Alloc should be nothrow copy-constructible. -template <typename Key, typename Data, typename Compare, typename Alloc, - int TargetNodeSize, bool IsMulti> -struct map_params : common_params<Key, Compare, Alloc, TargetNodeSize, IsMulti, - /*IsMap=*/true, map_slot_policy<Key, Data>> { - using super_type = typename map_params::common_params; +template <typename Key, typename Data, typename... Params> +struct map_params_impl + : common_params< + Key, + GetFromListOr<typename btree_map_defaults<Key, Data>::Compare, 0, + Params...>, + GetFromListOr<typename btree_map_defaults<Key, Data>::Alloc, 1, + Params...>, + GetFromListOr<typename btree_map_defaults<Key, Data>::TargetNodeSize, + 2, Params...>::value, + GetFromListOr<typename btree_map_defaults<Key, Data>::IsMulti, 3, + Params...>::value, + /*IsMap=*/true, map_slot_policy<Key, Data>> { + using super_type = typename map_params_impl::common_params; using mapped_type = Data; // This type allows us to move keys when it is safe to do so. It is safe // for maps in which value_type and mutable_value_type are layout compatible. @@ -868,6 +903,21 @@ using value_type = typename super_type::value_type; using init_type = typename super_type::init_type; + static_assert( + std::is_same_v< + map_params< + Key, Data, + GetFromListOr<typename btree_map_defaults<Key, Data>::Compare, 0, + Params...>, + GetFromListOr<typename btree_map_defaults<Key, Data>::Alloc, 1, + Params...>, + GetFromListOr< + typename btree_map_defaults<Key, Data>::TargetNodeSize, 2, + Params...>::value, + GetFromListOr<typename btree_map_defaults<Key, Data>::IsMulti, 3, + Params...>::value>, + map_params_impl>); + template <typename V> static auto key(const V &value ABSL_ATTRIBUTE_LIFETIME_BOUND) -> decltype((value.first)) {
diff --git a/absl/container/btree_set.h b/absl/container/btree_set.h index 16181de..991cb89 100644 --- a/absl/container/btree_set.h +++ b/absl/container/btree_set.h
@@ -56,9 +56,15 @@ #ifndef ABSL_CONTAINER_BTREE_SET_H_ #define ABSL_CONTAINER_BTREE_SET_H_ +#include <functional> +#include <memory> +#include <type_traits> +#include <utility> + #include "absl/base/attributes.h" #include "absl/container/internal/btree.h" // IWYU pragma: export #include "absl/container/internal/btree_container.h" // IWYU pragma: export +#include "absl/container/internal/common.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -68,9 +74,27 @@ template <typename Key> struct set_slot_policy; +template <typename Key, typename...Params> +struct set_params_impl; + +template <typename Key> +struct btree_set_defaults { + using Compare = std::less<Key>; + using Alloc = std::allocator<Key>; + using TargetNodeSize = std::integral_constant<int, 256>; + using IsMulti = std::false_type; +}; + template <typename Key, typename Compare, typename Alloc, int TargetNodeSize, bool IsMulti> -struct set_params; +using set_params = typename ApplyWithoutDefaultSuffix< + set_params_impl, + TypeList<void, typename btree_set_defaults<Key>::Compare, + typename btree_set_defaults<Key>::Alloc, + typename btree_set_defaults<Key>::TargetNodeSize, + typename btree_set_defaults<Key>::IsMulti>, + TypeList<Key, Compare, Alloc, std::integral_constant<int, TargetNodeSize>, + std::integral_constant<bool, IsMulti>>>::type; } // namespace container_internal @@ -119,8 +143,8 @@ // // * Copy assignment operator // - // absl::btree_set<std::string> set4; - // set4 = set3; + // absl::btree_set<std::string> set4; + // set4 = set3; // // * Move constructor // @@ -475,8 +499,8 @@ // // * Copy assignment operator // - // absl::btree_multiset<std::string> set4; - // set4 = set3; + // absl::btree_multiset<std::string> set4; + // set4 = set3; // // * Move constructor // @@ -803,12 +827,34 @@ // A parameters structure for holding the type parameters for a btree_set. // Compare and Alloc should be nothrow copy-constructible. -template <typename Key, typename Compare, typename Alloc, int TargetNodeSize, - bool IsMulti> -struct set_params : common_params<Key, Compare, Alloc, TargetNodeSize, IsMulti, - /*IsMap=*/false, set_slot_policy<Key>> { +template <typename Key, typename... Params> +struct set_params_impl + : common_params< + Key, + GetFromListOr<typename btree_set_defaults<Key>::Compare, 0, + Params...>, + GetFromListOr<typename btree_set_defaults<Key>::Alloc, 1, Params...>, + GetFromListOr<typename btree_set_defaults<Key>::TargetNodeSize, 2, + Params...>::value, + GetFromListOr<typename btree_set_defaults<Key>::IsMulti, 3, + Params...>::value, + /*IsMap=*/false, set_slot_policy<Key>> { using value_type = Key; - using slot_type = typename set_params::common_params::slot_type; + using slot_type = typename set_params_impl::common_params::slot_type; + + static_assert( + std::is_same_v< + set_params< + Key, + GetFromListOr<typename btree_set_defaults<Key>::Compare, 0, + Params...>, + GetFromListOr<typename btree_set_defaults<Key>::Alloc, 1, + Params...>, + GetFromListOr<typename btree_set_defaults<Key>::TargetNodeSize, 2, + Params...>::value, + GetFromListOr<typename btree_set_defaults<Key>::IsMulti, 3, + Params...>::value>, + set_params_impl>); template <typename V> static const V &key(const V &value) {
diff --git a/absl/container/btree_test.cc b/absl/container/btree_test.cc index 1d2c2a6..0cf3ed3 100644 --- a/absl/container/btree_test.cc +++ b/absl/container/btree_test.cc
@@ -2048,7 +2048,10 @@ TEST(Btree, ExtractDoesntCauseExtraMoves) { #ifdef _MSC_VER - GTEST_SKIP() << "This test fails on MSVC."; + // This conditional is to avoid an unreachable code warning. + if (_MSC_VER > 0) { + GTEST_SKIP() << "This test fails on MSVC."; + } #endif using Set = absl::btree_set<MovableOnlyInstance>; @@ -3541,6 +3544,109 @@ TestBasicFunctionality(set_type()); } +// Alias whose only purpose is to have the same length as set_params for better +// alignment in the test below. +template <typename... T> +using set_p_impl = set_params_impl<T...>; + +TEST(BtreeTest, SetParamsStripsDefaults) { + using K = int; + using DA = btree_set_defaults<int>::Compare; + using DB = btree_set_defaults<int>::Alloc; + using DC = btree_set_defaults<int>::TargetNodeSize; + using DD = btree_set_defaults<int>::IsMulti; + + using XA = std::greater<int>; + struct XB {}; + using XC = std::integral_constant<int, 100>; + using XD = std::true_type; + + EXPECT_TRUE((std::is_same_v<set_params<K, XA, XB, XC{}, XD{}>, + set_p_impl<K, XA, XB, XC, XD>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, XA, XB, XC{}, DD{}>, + set_p_impl<K, XA, XB, XC>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, XA, XB, DC{}, XD{}>, + set_p_impl<K, XA, XB, DC, XD>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, XA, XB, DC{}, DD{}>, + set_p_impl<K, XA, XB>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, XA, DB, XC{}, XD{}>, + set_p_impl<K, XA, DB, XC, XD>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, XA, DB, XC{}, DD{}>, + set_p_impl<K, XA, DB, XC>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, XA, DB, DC{}, XD{}>, + set_p_impl<K, XA, DB, DC, XD>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, XA, DB, DC{}, DD{}>, + set_p_impl<K, XA>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, DA, XB, XC{}, XD{}>, + set_p_impl<K, DA, XB, XC, XD>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, DA, XB, XC{}, DD{}>, + set_p_impl<K, DA, XB, XC>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, DA, XB, DC{}, XD{}>, + set_p_impl<K, DA, XB, DC, XD>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, DA, XB, DC{}, DD{}>, + set_p_impl<K, DA, XB>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, DA, DB, XC{}, XD{}>, + set_p_impl<K, DA, DB, XC, XD>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, DA, DB, XC{}, DD{}>, + set_p_impl<K, DA, DB, XC>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, DA, DB, DC{}, XD{}>, + set_p_impl<K, DA, DB, DC, XD>>)); + EXPECT_TRUE((std::is_same_v<set_params<K, DA, DB, DC{}, DD{}>, + set_p_impl<K>>)); +} + +// Alias whose only purpose is to have the same length as map_params for better +// alignment in the test below. +template <typename... T> +using map_p_impl = map_params_impl<T...>; + +TEST(BtreeTest, MapParamsStripsDefaults) { + using K = int; + using V = double; + using DA = btree_map_defaults<int, double>::Compare; + using DB = btree_map_defaults<int, double>::Alloc; + using DC = btree_map_defaults<int, double>::TargetNodeSize; + using DD = btree_map_defaults<int, double>::IsMulti; + + using XA = std::greater<int>; + struct XB {}; + using XC = std::integral_constant<int, 100>; + using XD = std::true_type; + + EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, XB, XC{}, XD{}>, + map_p_impl<K, V, XA, XB, XC, XD>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, XB, XC{}, DD{}>, + map_p_impl<K, V, XA, XB, XC>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, XB, DC{}, XD{}>, + map_p_impl<K, V, XA, XB, DC, XD>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, XB, DC{}, DD{}>, + map_p_impl<K, V, XA, XB>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, DB, XC{}, XD{}>, + map_p_impl<K, V, XA, DB, XC, XD>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, DB, XC{}, DD{}>, + map_p_impl<K, V, XA, DB, XC>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, DB, DC{}, XD{}>, + map_p_impl<K, V, XA, DB, DC, XD>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, XA, DB, DC{}, DD{}>, + map_p_impl<K, V, XA>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, XB, XC{}, XD{}>, + map_p_impl<K, V, DA, XB, XC, XD>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, XB, XC{}, DD{}>, + map_p_impl<K, V, DA, XB, XC>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, XB, DC{}, XD{}>, + map_p_impl<K, V, DA, XB, DC, XD>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, XB, DC{}, DD{}>, + map_p_impl<K, V, DA, XB>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, DB, XC{}, XD{}>, + map_p_impl<K, V, DA, DB, XC, XD>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, DB, XC{}, DD{}>, + map_p_impl<K, V, DA, DB, XC>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, DB, DC{}, XD{}>, + map_p_impl<K, V, DA, DB, DC, XD>>)); + EXPECT_TRUE((std::is_same_v<map_params<K, V, DA, DB, DC{}, DD{}>, + map_p_impl<K, V>>)); +} + } // namespace } // namespace container_internal ABSL_NAMESPACE_END
diff --git a/absl/container/chunked_queue.h b/absl/container/chunked_queue.h new file mode 100644 index 0000000..d5b1184 --- /dev/null +++ b/absl/container/chunked_queue.h
@@ -0,0 +1,755 @@ +// Copyright 2025 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: chunked_queue.h +// ----------------------------------------------------------------------------- +// +// `std::deque` provides random access and fast push/pop back/front. It is +// implemented as an array of fixed blocks. It provides no control of block size +// and implementations differ; libstdc++ tries to allocate blocks of ~512 bytes +// and libc++ tries for blocks of ~4k bytes. +// +// `absl::chunked_queue` provides the same minus random access. It is +// implemented as a double-linked list of fixed or variable sized blocks. +// +// `absl::chunked_queue` is useful when memory usage is paramount as it provides +// finegrained and configurable block sizing. +// +// The interface supported by this class is limited to: +// +// empty() +// size() +// max_size() +// shrink_to_fit() +// resize() +// assign() +// push_back() +// emplace_back() +// pop_front() +// front() +// back() +// swap() +// clear() +// begin(), end() +// cbegin(), cend() +// +// === ADVANCED USAGE +// +// == clear() +// +// As an optimization clear() leaves the first block of the chunked_queue +// allocated (but empty). So clear will not delete all memory of the container. +// In order to do so, call shrink_to_fit() or swap the container with an empty +// one. +// +// absl::chunked_queue<int64> q = {1, 2, 3}; +// q.clear(); +// q.shrink_to_fit(); +// +// == block size customization +// +// chunked_queue allows customization of the block size for each block. By +// default the block size is set to 1 element and the size doubles for the next +// block until it reaches the default max block size, which is 128 elements. +// +// = fixed size +// +// When only the first block size parameter is specified, it sets a fixed block +// size for all blocks: +// +// chunked_queue<T, 32>: 32 elements per block +// +// The smaller the block size, the less the memory usage for small queues at the +// cost of performance. Caveat: For large queues, a smaller block size will +// increase memory usage, and reduce performance. +// +// = variable size +// +// When both block size parameters are specified, they set the min and max block +// sizes for the blocks. Initially the queue starts with the min block size and +// as it grows, the size of each block grows until it reaches the max block +// size. +// New blocks are double the size of the tail block (so they at least +// double the size of the queue). +// +// chunked_queue<T, 4, 64>: first block 4 elements, second block 8 elements, +// third block 16 elements, fourth block 32 elements, +// all other blocks 64 elements +// +// One can specify a min and max such that small queues will not waste memory +// and large queues will not have too many blocks. + +#ifndef ABSL_CONTAINER_CHUNKED_QUEUE_H_ +#define ABSL_CONTAINER_CHUNKED_QUEUE_H_ + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <initializer_list> +#include <iterator> +#include <memory> +#include <new> +#include <tuple> +#include <type_traits> +#include <utility> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/internal/iterator_traits.h" +#include "absl/base/macros.h" +#include "absl/container/internal/chunked_queue.h" +#include "absl/container/internal/layout.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +template <typename T, size_t BLo = 0, size_t BHi = BLo, + typename Allocator = std::allocator<T>> +class chunked_queue { + public: + static constexpr size_t kBlockSizeMin = (BLo == 0 && BHi == 0) ? 1 : BLo; + static constexpr size_t kBlockSizeMax = (BLo == 0 && BHi == 0) ? 128 : BHi; + + private: + static_assert(kBlockSizeMin > 0, "Min block size cannot be zero"); + static_assert(kBlockSizeMin <= kBlockSizeMax, "Invalid block size bounds"); + + using Block = container_internal::ChunkedQueueBlock<T, Allocator>; + using AllocatorTraits = std::allocator_traits<Allocator>; + + class iterator_common { + public: + friend bool operator==(const iterator_common& a, const iterator_common& b) { + return a.ptr == b.ptr; + } + + friend bool operator!=(const iterator_common& a, const iterator_common& b) { + return !(a == b); + } + + protected: + iterator_common() = default; + explicit iterator_common(Block* b) + : block(b), ptr(b->start()), limit(b->limit()) {} + + void Incr() { + // If we do not have a next block, make ptr point one past the end of this + // block. If we do have a next block, make ptr point to the first element + // of the next block. + ++ptr; + if (ptr == limit && block->next()) *this = iterator_common(block->next()); + } + + void IncrBy(size_t n) { + while (ptr + n > limit) { + n -= limit - ptr; + *this = iterator_common(block->next()); + } + ptr += n; + } + + Block* block = nullptr; + T* ptr = nullptr; + T* limit = nullptr; + }; + + // CT can be either T or const T. + template <typename CT> + class basic_iterator : public iterator_common { + public: + using iterator_category = std::forward_iterator_tag; + using value_type = typename AllocatorTraits::value_type; + using difference_type = typename AllocatorTraits::difference_type; + using pointer = + typename std::conditional<std::is_const<CT>::value, + typename AllocatorTraits::const_pointer, + typename AllocatorTraits::pointer>::type; + using reference = CT&; + + basic_iterator() = default; + + // Copy ctor if CT is T. + // Otherwise it's a conversion of iterator to const_iterator. + basic_iterator(const basic_iterator<T>& it) // NOLINT(runtime/explicit) + : iterator_common(it) {} + + basic_iterator& operator=(const basic_iterator& other) = default; + + reference operator*() const { return *this->ptr; } + pointer operator->() const { return this->ptr; } + basic_iterator& operator++() { + this->Incr(); + return *this; + } + basic_iterator operator++(int) { + basic_iterator t = *this; + ++*this; + return t; + } + + private: + explicit basic_iterator(Block* b) : iterator_common(b) {} + + friend chunked_queue; + }; + + public: + using allocator_type = typename AllocatorTraits::allocator_type; + using value_type = typename AllocatorTraits::value_type; + using size_type = typename AllocatorTraits::size_type; + using difference_type = typename AllocatorTraits::difference_type; + using reference = value_type&; + using const_reference = const value_type&; + using iterator = basic_iterator<T>; + using const_iterator = basic_iterator<const T>; + + // Constructs an empty queue. + chunked_queue() : chunked_queue(allocator_type()) {} + + // Constructs an empty queue with a custom allocator. + explicit chunked_queue(const allocator_type& alloc) + : alloc_and_size_(alloc) {} + + // Constructs a queue with `count` default-inserted elements. + explicit chunked_queue(size_type count, + const allocator_type& alloc = allocator_type()) + : alloc_and_size_(alloc) { + resize(count); + } + + // Constructs a queue with `count` copies of `value`. + chunked_queue(size_type count, const T& value, + const allocator_type& alloc = allocator_type()) + : alloc_and_size_(alloc) { + assign(count, value); + } + + // Constructs a queue with the contents of the range [first, last). + template <typename Iter, + typename = std::enable_if_t< + base_internal::IsAtLeastInputIterator<Iter>::value>> + chunked_queue(Iter first, Iter last, + const allocator_type& alloc = allocator_type()) + : alloc_and_size_(alloc) { + using Tag = typename std::iterator_traits<Iter>::iterator_category; + RangeInit(first, last, Tag()); + } + + // Constructs a queue with the contents of the initializer list `list`. + chunked_queue(std::initializer_list<T> list, + const allocator_type& alloc = allocator_type()) + : chunked_queue(list.begin(), list.end(), alloc) {} + + ~chunked_queue(); + + // Copy constructor. + chunked_queue(const chunked_queue& other) + : chunked_queue(other, + AllocatorTraits::select_on_container_copy_construction( + other.alloc_and_size_.allocator())) {} + + // Copy constructor with specific allocator. + chunked_queue(const chunked_queue& other, const allocator_type& alloc) + : alloc_and_size_(alloc) { + for (const_reference item : other) { + push_back(item); + } + } + + // Move constructor. + chunked_queue(chunked_queue&& other) noexcept + : head_(other.head_), + tail_(other.tail_), + alloc_and_size_(std::move(other.alloc_and_size_)) { + other.head_ = {}; + other.tail_ = {}; + other.alloc_and_size_.size = 0; + } + + // Replaces contents with those from initializer list `il`. + chunked_queue& operator=(std::initializer_list<T> il) { + assign(il.begin(), il.end()); + return *this; + } + + // Copy assignment operator. + chunked_queue& operator=(const chunked_queue& other) { + if (this == &other) { + return *this; + } + if (AllocatorTraits::propagate_on_container_copy_assignment::value && + (alloc_and_size_.allocator() != other.alloc_and_size_.allocator())) { + // Destroy all current elements and blocks with the current allocator, + // before switching this to use the allocator propagated from "other". + DestroyAndDeallocateAll(); + alloc_and_size_ = AllocatorAndSize(other.alloc_and_size_.allocator()); + } + assign(other.begin(), other.end()); + return *this; + } + + // Move assignment operator. + chunked_queue& operator=(chunked_queue&& other) noexcept; + + // Returns true if the queue contains no elements. + bool empty() const { return alloc_and_size_.size == 0; } + + // Returns the number of elements in the queue. + size_t size() const { return alloc_and_size_.size; } + + // Returns the maximum number of elements the queue is able to hold. + size_type max_size() const noexcept { + return AllocatorTraits::max_size(alloc_and_size_.allocator()); + } + + // Resizes the container to contain `new_size` elements. + // If `new_size > size()`, additional default-inserted elements are appended. + // If `new_size < size()`, elements are removed from the end. + void resize(size_t new_size); + + // Resizes the container to contain `new_size` elements. + // If `new_size > size()`, additional copies of `value` are appended. + // If `new_size < size()`, elements are removed from the end. + void resize(size_type new_size, const T& value) { + if (new_size > size()) { + size_t to_add = new_size - size(); + for (size_t i = 0; i < to_add; ++i) { + push_back(value); + } + } else { + resize(new_size); + } + } + + // Requests the removal of unused capacity. + void shrink_to_fit() { + // As an optimization clear() leaves the first block of the chunked_queue + // allocated (but empty). When empty, shrink_to_fit() deallocates the first + // block by swapping it a newly constructed container that has no first + // block. + if (empty()) { + chunked_queue(alloc_and_size_.allocator()).swap(*this); + } + } + + // Replaces the contents with copies of those in the range [first, last). + template <typename Iter, + typename = std::enable_if_t< + base_internal::IsAtLeastInputIterator<Iter>::value>> + void assign(Iter first, Iter last) { + auto out = begin(); + Block* prev_block = nullptr; + + // Overwrite existing elements. + for (; out != end() && first != last; ++first) { + // Track the previous block so we can correctly update tail_ if we stop + // exactly at a block boundary. + if (out.ptr + 1 == out.block->limit()) { + prev_block = out.block; + } + *out = *first; + ++out; + } + + // If we stopped exactly at the start of a block (meaning the previous block + // was full), we must ensure tail_ points to the end of the previous block, + // not the start of the current (now empty and to be deleted) block. + // This maintains the invariant required by back() which assumes tail_ + // never points to the start of a block (unless it's the only block). + if (!empty() && out.block != nullptr && out.ptr == out.block->start() && + prev_block != nullptr) { + // Delete the current block and all subsequent blocks. + // + // NOTE: Calling EraseAllFrom on an iterator that points to the limit of + // the previous block will not delete any element from the previous block. + iterator prev_block_end(prev_block); + prev_block_end.ptr = prev_block->limit(); + EraseAllFrom(prev_block_end); + + // Update tail_ to point to the end of the previous block. + tail_ = prev_block_end; + prev_block->set_next(nullptr); + } else { + // Standard erase from the current position to the end. + EraseAllFrom(out); + } + + // Append any remaining new elements. + for (; first != last; ++first) { + push_back(*first); + } + } + + // Replaces the contents with `count` copies of `value`. + void assign(size_type count, const T& value) { + clear(); + for (size_type i = 0; i < count; ++i) { + push_back(value); + } + } + + // Replaces the contents with the elements from the initializer list `il`. + void assign(std::initializer_list<T> il) { assign(il.begin(), il.end()); } + + // Appends the given element value to the end of the container. + // Invalidates `end()` iterator. References to other elements remain valid. + void push_back(const T& val) { emplace_back(val); } + void push_back(T&& val) { emplace_back(std::move(val)); } + + // Appends a new element to the end of the container. + // The element is constructed in-place with `args`. + // Returns a reference to the new element. + // Invalidates `end()` iterator. References to other elements remain valid. + template <typename... A> + T& emplace_back(A&&... args) { + T* storage = AllocateBack(); + AllocatorTraits::construct(alloc_and_size_.allocator(), storage, + std::forward<A>(args)...); + return *storage; + } + + // Removes the first element of the container. + // Invalidates iterators to the removed element. + // REQUIRES: !empty() + void pop_front(); + + // Returns a reference to the first element in the container. + // REQUIRES: !empty() + T& front() { + ABSL_HARDENING_ASSERT(!empty()); + return *head_; + } + const T& front() const { + ABSL_HARDENING_ASSERT(!empty()); + return *head_; + } + + // Returns a reference to the last element in the container. + // REQUIRES: !empty() + T& back() { + ABSL_HARDENING_ASSERT(!empty()); + return *(&*tail_ - 1); + } + const T& back() const { + ABSL_HARDENING_ASSERT(!empty()); + return *(&*tail_ - 1); + } + + // Swaps the contents of this queue with `other`. + void swap(chunked_queue& other) noexcept { + using std::swap; + swap(head_, other.head_); + swap(tail_, other.tail_); + if (AllocatorTraits::propagate_on_container_swap::value) { + swap(alloc_and_size_, other.alloc_and_size_); + } else { + // Swap only the sizes; each object keeps its allocator. + // + // (It is undefined behavior to swap between two containers with unequal + // allocators if propagate_on_container_swap is false, so we don't have to + // handle that here like we do in the move-assignment operator.) + ABSL_HARDENING_ASSERT(get_allocator() == other.get_allocator()); + swap(alloc_and_size_.size, other.alloc_and_size_.size); + } + } + + // Erases all elements from the container. + // Note: Leaves one empty block allocated as an optimization. + // To free all memory, call shrink_to_fit() after calling clear(). + void clear(); + + iterator begin() { return head_; } + iterator end() { return tail_; } + + const_iterator begin() const { return head_; } + const_iterator end() const { return tail_; } + + const_iterator cbegin() const { return head_; } + const_iterator cend() const { return tail_; } + + // Returns the allocator associated with the container. + allocator_type get_allocator() const { return alloc_and_size_.allocator(); } + + private: + // Empty base-class optimization: bundle storage for our allocator together + // with a field we had to store anyway (size), via inheriting from the + // allocator, so this allocator instance doesn't consume any storage + // when its type has no data members. + struct AllocatorAndSize : private allocator_type { + explicit AllocatorAndSize(const allocator_type& alloc) + : allocator_type(alloc) {} + const allocator_type& allocator() const { return *this; } + allocator_type& allocator() { return *this; } + size_t size = 0; + }; + + template <typename Iter> + void RangeInit(Iter first, Iter last, std::input_iterator_tag) { + while (first != last) { + AddTailBlock(); + for (; first != last && tail_.ptr != tail_.limit; + ++alloc_and_size_.size, ++tail_.ptr, ++first) { + AllocatorTraits::construct(alloc_and_size_.allocator(), tail_.ptr, + *first); + } + } + } + + void Construct(T* start, T* limit) { + ABSL_ASSERT(start <= limit); + for (; start != limit; ++start) { + AllocatorTraits::construct(alloc_and_size_.allocator(), start); + } + } + + size_t Destroy(T* start, T* limit) { + ABSL_ASSERT(start <= limit); + const size_t n = limit - start; + for (; start != limit; ++start) { + AllocatorTraits::destroy(alloc_and_size_.allocator(), start); + } + return n; + } + + T* block_begin(Block* b) const { + return b == head_.block ? head_.ptr : b->start(); + } + T* block_end(Block* b) const { + // We have the choice of !b->next or b == tail_.block to determine if b is + // the tail or not. !b->next is usually faster because the caller of + // block_end() is most likely traversing the list of blocks so b->next is + // already fetched into some register. + return !b->next() ? tail_.ptr : b->limit(); + } + + void AddTailBlock(); + size_t NewBlockSize() { + // Double the last block size and bound to [kBlockSizeMin, kBlockSizeMax]. + if (!tail_.block) return kBlockSizeMin; + return (std::min)(kBlockSizeMax, 2 * tail_.block->size()); + } + + T* AllocateBack(); + void EraseAllFrom(iterator i); + + // Destroys any contained elements and destroys all allocated storage. + // (Like clear(), except this doesn't leave any empty blocks behind.) + void DestroyAndDeallocateAll(); + + // The set of elements in the queue is the following: + // + // (1) When we have just one block: + // [head_.ptr .. tail_.ptr-1] + // (2) When we have multiple blocks: + // [head_.ptr .. head_.limit-1] + // ... concatenation of all elements from interior blocks ... + // [tail_.ptr .. tail_.limit-1] + // + // Rep invariants: + // When have just one block: + // head_.limit == tail_.limit == &head_.block->element[kBlockSize] + // Always: + // head_.ptr <= head_.limit + // tail_.ptr <= tail_.limit + + iterator head_; + iterator tail_; + AllocatorAndSize alloc_and_size_; +}; + +template <typename T, size_t BLo, size_t BHi, typename Allocator> +constexpr size_t chunked_queue<T, BLo, BHi, Allocator>::kBlockSizeMin; + +template <typename T, size_t BLo, size_t BHi, typename Allocator> +constexpr size_t chunked_queue<T, BLo, BHi, Allocator>::kBlockSizeMax; + +template <typename T, size_t BLo, size_t BHi, typename Allocator> +inline void swap(chunked_queue<T, BLo, BHi, Allocator>& a, + chunked_queue<T, BLo, BHi, Allocator>& b) noexcept { + a.swap(b); +} + +template <typename T, size_t BLo, size_t BHi, typename Allocator> +chunked_queue<T, BLo, BHi, Allocator>& +chunked_queue<T, BLo, BHi, Allocator>::operator=( + chunked_queue&& other) noexcept { + if (this == &other) { + return *this; + } + DestroyAndDeallocateAll(); + + if constexpr (AllocatorTraits::propagate_on_container_move_assignment:: + value) { + // Take over the storage of "other", along with its allocator. + head_ = other.head_; + tail_ = other.tail_; + alloc_and_size_ = std::move(other.alloc_and_size_); + other.head_ = {}; + other.tail_ = {}; + other.alloc_and_size_.size = 0; + } else if (get_allocator() == other.get_allocator()) { + // Take over the storage of "other", with which we share an allocator. + head_ = other.head_; + tail_ = other.tail_; + alloc_and_size_.size = other.alloc_and_size_.size; + other.head_ = {}; + other.tail_ = {}; + other.alloc_and_size_.size = 0; + } else { + // We cannot take over of the storage from "other", since it has a different + // allocator; we're stuck move-assigning elements individually. + for (auto& elem : other) { + push_back(std::move(elem)); + } + } + return *this; +} + +template <typename T, size_t BLo, size_t BHi, typename Allocator> +inline chunked_queue<T, BLo, BHi, Allocator>::~chunked_queue() { + Block* b = head_.block; + while (b) { + Block* next = b->next(); + Destroy(block_begin(b), block_end(b)); + Block::Delete(b, &alloc_and_size_.allocator()); + b = next; + } +} + +template <typename T, size_t BLo, size_t BHi, typename Allocator> +void chunked_queue<T, BLo, BHi, Allocator>::resize(size_t new_size) { + while (new_size > size()) { + ptrdiff_t to_add = new_size - size(); + if (tail_.ptr == tail_.limit) { + AddTailBlock(); + } + T* start = tail_.ptr; + T* limit = (std::min)(tail_.limit, start + to_add); + Construct(start, limit); + tail_.ptr = limit; + alloc_and_size_.size += limit - start; + } + if (size() == new_size) { + return; + } + ABSL_ASSERT(new_size < size()); + auto new_end = begin(); + new_end.IncrBy(new_size); + ABSL_ASSERT(new_end != end()); + EraseAllFrom(new_end); +} + +template <typename T, size_t BLo, size_t BHi, typename Allocator> +inline void chunked_queue<T, BLo, BHi, Allocator>::AddTailBlock() { + ABSL_ASSERT(tail_.ptr == tail_.limit); + auto* b = Block::New(NewBlockSize(), &alloc_and_size_.allocator()); + if (!head_.block) { + ABSL_ASSERT(!tail_.block); + head_ = iterator(b); + } else { + ABSL_ASSERT(tail_.block); + tail_.block->set_next(b); + } + tail_ = iterator(b); +} + +template <typename T, size_t BLo, size_t BHi, typename Allocator> +inline T* chunked_queue<T, BLo, BHi, Allocator>::AllocateBack() { + if (tail_.ptr == tail_.limit) { + AddTailBlock(); + } + ++alloc_and_size_.size; + return tail_.ptr++; +} + +template <typename T, size_t BLo, size_t BHi, typename Allocator> +inline void chunked_queue<T, BLo, BHi, Allocator>::EraseAllFrom(iterator i) { + if (!i.block) { + return; + } + ABSL_ASSERT(i.ptr); + ABSL_ASSERT(i.limit); + alloc_and_size_.size -= Destroy(i.ptr, block_end(i.block)); + Block* b = i.block->next(); + while (b) { + Block* next = b->next(); + alloc_and_size_.size -= Destroy(b->start(), block_end(b)); + Block::Delete(b, &alloc_and_size_.allocator()); + b = next; + } + tail_ = i; + tail_.block->set_next(nullptr); +} + +template <typename T, size_t BLo, size_t BHi, typename Allocator> +inline void chunked_queue<T, BLo, BHi, Allocator>::DestroyAndDeallocateAll() { + Block* b = head_.block; + while (b) { + Block* next = b->next(); + Destroy(block_begin(b), block_end(b)); + Block::Delete(b, &alloc_and_size_.allocator()); + b = next; + } + head_ = iterator(); + tail_ = iterator(); + alloc_and_size_.size = 0; +} + +template <typename T, size_t BLo, size_t BHi, typename Allocator> +inline void chunked_queue<T, BLo, BHi, Allocator>::pop_front() { + ABSL_HARDENING_ASSERT(!empty()); + ABSL_ASSERT(head_.block); + AllocatorTraits::destroy(alloc_and_size_.allocator(), head_.ptr); + ++head_.ptr; + --alloc_and_size_.size; + if (empty()) { + // Reset head and tail to the start of the (only) block. + ABSL_ASSERT(head_.block == tail_.block); + head_.ptr = tail_.ptr = head_.block->start(); + return; + } + if (head_.ptr == head_.limit) { + Block* n = head_.block->next(); + Block::Delete(head_.block, &alloc_and_size_.allocator()); + head_ = iterator(n); + } +} + +template <typename T, size_t BLo, size_t BHi, typename Allocator> +void chunked_queue<T, BLo, BHi, Allocator>::clear() { + // NOTE: As an optimization we leave one block allocated. + Block* b = head_.block; + if (!b) { + ABSL_ASSERT(empty()); + return; + } + while (b) { + Block* next = b->next(); + Destroy(block_begin(b), block_end(b)); + if (head_.block != b) { + Block::Delete(b, &alloc_and_size_.allocator()); + } + b = next; + } + b = head_.block; + b->set_next(nullptr); + head_ = tail_ = iterator(b); + alloc_and_size_.size = 0; +} + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CONTAINER_CHUNKED_QUEUE_H_
diff --git a/absl/container/chunked_queue_benchmark.cc b/absl/container/chunked_queue_benchmark.cc new file mode 100644 index 0000000..ee4d3c1 --- /dev/null +++ b/absl/container/chunked_queue_benchmark.cc
@@ -0,0 +1,386 @@ +// Copyright 2025 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <cstddef> +#include <cstdint> +#include <deque> +#include <forward_list> +#include <list> +#include <random> + +#include "absl/container/chunked_queue.h" +#include "absl/random/random.h" +#include "absl/status/status.h" +#include "absl/strings/cord.h" +#include "benchmark/benchmark.h" + +namespace { + +// Queue implementation using std::forward_list. Used to benchmark +// absl::chunked_queue against another plausable implementation. +template <typename T> +class forward_list_queue { + public: + using iterator = typename std::forward_list<T>::iterator; + + forward_list_queue() = default; + ~forward_list_queue() = default; + + template <typename... Args> + void emplace_back(Args&&... args) { + if (list_.empty()) { + list_.emplace_front(std::forward<Args>(args)...); + tail_ = list_.begin(); + } else { + list_.emplace_after(tail_, std::forward<Args>(args)...); + ++tail_; + } + } + + void push_back(const T& value) { emplace_back(value); } + iterator begin() { return list_.begin(); } + iterator end() { return list_.end(); } + T& front() { return list_.front(); } + const T& front() const { return list_.front(); } + void pop_front() { list_.pop_front(); } + bool empty() const { return list_.empty(); } + void clear() { list_.clear(); } + + private: + std::forward_list<T> list_; + typename std::forward_list<T>::iterator tail_; +}; + +template <class T> +using Deque = std::deque<T>; +template <class T> +using List = std::list<T>; +template <class T> +using FwdList = forward_list_queue<T>; +template <class T> +using Chunked = absl::chunked_queue<T>; +template <class T> +using ExpChunked = absl::chunked_queue<T, 2, 64>; + +class Element { + public: + Element() : Element(-1) {} + Element(int type) : type_(type) {} // NOLINT + operator int() const { return type_; } // NOLINT + + private: + int type_; + absl::Cord item_; + absl::Status status_; +}; + +template <class Q> +Q MakeQueue(int64_t num_elements) { + Q q; + for (int64_t i = 0; i < num_elements; i++) { + q.push_back(static_cast<int>(i)); + } + return q; +} + +void CustomArgs(benchmark::internal::Benchmark* b) { + b->Arg(1 << 4); + b->Arg(1 << 10); + b->Arg(1 << 17); +} + +template <class Q> +void BM_construct(benchmark::State& state) { + for (auto s : state) { + Q q; + benchmark::DoNotOptimize(q); + } +} + +BENCHMARK_TEMPLATE(BM_construct, Deque<int64_t>); +BENCHMARK_TEMPLATE(BM_construct, List<int64_t>); +BENCHMARK_TEMPLATE(BM_construct, FwdList<int64_t>); +BENCHMARK_TEMPLATE(BM_construct, Chunked<int64_t>); +BENCHMARK_TEMPLATE(BM_construct, ExpChunked<int64_t>); +BENCHMARK_TEMPLATE(BM_construct, Deque<Element>); +BENCHMARK_TEMPLATE(BM_construct, List<Element>); +BENCHMARK_TEMPLATE(BM_construct, FwdList<Element>); +BENCHMARK_TEMPLATE(BM_construct, Chunked<Element>); +BENCHMARK_TEMPLATE(BM_construct, ExpChunked<Element>); + +template <class Q> +void BM_destroy(benchmark::State& state) { + const int64_t num_elements = state.range(0); + + for (auto s : state) { + state.PauseTiming(); + { + Q q = MakeQueue<Q>(num_elements); + benchmark::DoNotOptimize(q); + state.ResumeTiming(); + } + } + state.SetItemsProcessed(state.iterations() * num_elements); +} + +BENCHMARK_TEMPLATE(BM_destroy, Deque<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_destroy, List<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_destroy, FwdList<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_destroy, Chunked<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_destroy, ExpChunked<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_destroy, Deque<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_destroy, List<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_destroy, FwdList<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_destroy, Chunked<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_destroy, ExpChunked<Element>)->Apply(CustomArgs); + +template <class Q> +void BM_push_back(benchmark::State& state) { + const int64_t num_elements = state.range(0); + + state.SetItemsProcessed(state.max_iterations * num_elements); + for (auto s : state) { + state.PauseTiming(); + Q q; + state.ResumeTiming(); + for (int j = 0; j < num_elements; j++) q.push_back(j); + benchmark::DoNotOptimize(q); + } +} + +BENCHMARK_TEMPLATE(BM_push_back, Deque<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_back, List<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_back, FwdList<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_back, Chunked<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_back, ExpChunked<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_back, Deque<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_back, List<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_back, FwdList<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_back, Chunked<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_back, ExpChunked<Element>)->Apply(CustomArgs); + +template <class Q> +void BM_pop_front(benchmark::State& state) { + const int64_t num_elements = state.range(0); + + state.SetItemsProcessed(state.max_iterations * num_elements); + for (auto s : state) { + state.PauseTiming(); + Q q = MakeQueue<Q>(num_elements); + state.ResumeTiming(); + for (int j = 0; j < num_elements; j++) q.pop_front(); + benchmark::DoNotOptimize(q); + } +} + +BENCHMARK_TEMPLATE(BM_pop_front, Deque<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_pop_front, List<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_pop_front, FwdList<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_pop_front, Chunked<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_pop_front, ExpChunked<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_pop_front, Deque<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_pop_front, List<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_pop_front, FwdList<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_pop_front, Chunked<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_pop_front, ExpChunked<Element>)->Apply(CustomArgs); + +template <class Q> +void BM_clear(benchmark::State& state) { + const int64_t num_elements = state.range(0); + + state.SetItemsProcessed(state.max_iterations * num_elements); + for (auto s : state) { + state.PauseTiming(); + Q q = MakeQueue<Q>(num_elements); + state.ResumeTiming(); + q.clear(); + benchmark::DoNotOptimize(q); + } +} + +BENCHMARK_TEMPLATE(BM_clear, Deque<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_clear, List<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_clear, FwdList<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_clear, Chunked<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_clear, ExpChunked<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_clear, Deque<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_clear, List<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_clear, FwdList<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_clear, Chunked<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_clear, ExpChunked<Element>)->Apply(CustomArgs); + +template <class Q> +void BM_iter(benchmark::State& state) { + const int64_t num_elements = state.range(0); + + state.SetItemsProcessed(state.max_iterations * num_elements); + for (auto s : state) { + state.PauseTiming(); + Q q = MakeQueue<Q>(state.max_iterations); + int sum = 0; + state.ResumeTiming(); + for (const auto& v : q) sum += v; + benchmark::DoNotOptimize(sum); + } +} + +BENCHMARK_TEMPLATE(BM_iter, Deque<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_iter, List<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_iter, FwdList<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_iter, Chunked<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_iter, ExpChunked<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_iter, Deque<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_iter, List<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_iter, FwdList<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_iter, Chunked<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_iter, ExpChunked<Element>)->Apply(CustomArgs); + +template <class Q> +void BM_resize_shrink(benchmark::State& state) { + const int64_t num_elements = state.range(0); + + state.SetItemsProcessed(state.max_iterations * num_elements); + for (auto s : state) { + state.PauseTiming(); + Q q = MakeQueue<Q>(num_elements * 2); + state.ResumeTiming(); + q.resize(num_elements); + benchmark::DoNotOptimize(q); + } +} + +// FwdList does not support resize. +BENCHMARK_TEMPLATE(BM_resize_shrink, Deque<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_resize_shrink, List<int64_t>)->Apply(CustomArgs); +// BENCHMARK_TEMPLATE(BM_resize_shrink, FwdList<int64>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_resize_shrink, Chunked<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_resize_shrink, ExpChunked<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_resize_shrink, Deque<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_resize_shrink, List<Element>)->Apply(CustomArgs); +// BENCHMARK_TEMPLATE(BM_resize_shrink, FwdList<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_resize_shrink, Chunked<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_resize_shrink, ExpChunked<Element>)->Apply(CustomArgs); + +template <class Q> +void BM_resize_grow(benchmark::State& state) { + const int64_t num_elements = state.range(0); + + state.SetItemsProcessed(state.max_iterations * num_elements); + for (auto s : state) { + state.PauseTiming(); + Q q = MakeQueue<Q>(num_elements); + state.ResumeTiming(); + q.resize(static_cast<size_t>(num_elements) * 2); + benchmark::DoNotOptimize(q); + } +} + +// FwdList does not support resize. +BENCHMARK_TEMPLATE(BM_resize_grow, Deque<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_resize_grow, List<int64_t>)->Apply(CustomArgs); +// BENCHMARK_TEMPLATE(BM_resize_grow, FwdList<int64>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_resize_grow, Chunked<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_resize_grow, ExpChunked<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_resize_grow, Deque<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_resize_grow, List<Element>)->Apply(CustomArgs); +// BENCHMARK_TEMPLATE(BM_resize_grow, FwdList<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_resize_grow, Chunked<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_resize_grow, ExpChunked<Element>)->Apply(CustomArgs); + +template <class Q> +void BM_assign_shrink(benchmark::State& state) { + const int64_t num_elements = state.range(0); + + state.SetItemsProcessed(state.max_iterations * num_elements); + for (auto s : state) { + state.PauseTiming(); + const Q src = MakeQueue<Q>(num_elements); + Q dst = MakeQueue<Q>(num_elements * 2); + state.ResumeTiming(); + dst = src; + benchmark::DoNotOptimize(dst); + } +} + +BENCHMARK_TEMPLATE(BM_assign_shrink, Deque<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_assign_shrink, List<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_assign_shrink, FwdList<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_assign_shrink, Chunked<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_assign_shrink, ExpChunked<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_assign_shrink, Deque<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_assign_shrink, List<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_assign_shrink, FwdList<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_assign_shrink, Chunked<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_assign_shrink, ExpChunked<Element>)->Apply(CustomArgs); + +template <class Q> +void BM_assign_grow(benchmark::State& state) { + const int64_t num_elements = state.range(0); + + state.SetItemsProcessed(state.max_iterations * num_elements); + for (auto s : state) { + state.PauseTiming(); + const Q src = MakeQueue<Q>(num_elements * 2); + Q dst = MakeQueue<Q>(num_elements); + state.ResumeTiming(); + dst = src; + benchmark::DoNotOptimize(dst); + } +} + +BENCHMARK_TEMPLATE(BM_assign_grow, Deque<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_assign_grow, List<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_assign_grow, FwdList<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_assign_grow, Chunked<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_assign_grow, ExpChunked<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_assign_grow, Deque<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_assign_grow, List<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_assign_grow, FwdList<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_assign_grow, Chunked<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_assign_grow, ExpChunked<Element>)->Apply(CustomArgs); + +template <class Q> +void BM_push_pop(benchmark::State& state) { + const int64_t num_elements = state.range(0); + + state.SetItemsProcessed(state.max_iterations * num_elements); + + std::mt19937 rnd; + for (auto s : state) { + state.PauseTiming(); + Q q; + state.ResumeTiming(); + for (int j = 0; j < num_elements; j++) { + if (q.empty() || absl::Bernoulli(rnd, 0.5)) { + q.push_back(state.iterations()); + } else { + q.pop_front(); + } + } + benchmark::DoNotOptimize(q); + } +} + +BENCHMARK_TEMPLATE(BM_push_pop, Deque<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_pop, List<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_pop, FwdList<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_pop, Chunked<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_pop, ExpChunked<int64_t>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_pop, Deque<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_pop, List<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_pop, FwdList<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_pop, Chunked<Element>)->Apply(CustomArgs); +BENCHMARK_TEMPLATE(BM_push_pop, ExpChunked<Element>)->Apply(CustomArgs); + +} // namespace
diff --git a/absl/container/chunked_queue_test.cc b/absl/container/chunked_queue_test.cc new file mode 100644 index 0000000..d394ec4 --- /dev/null +++ b/absl/container/chunked_queue_test.cc
@@ -0,0 +1,768 @@ +// Copyright 2025 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/chunked_queue.h" + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <deque> +#include <forward_list> +#include <iterator> +#include <list> +#include <memory> +#include <string> +#include <type_traits> +#include <utility> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/macros.h" +#include "absl/container/internal/test_allocator.h" +#include "absl/strings/str_cat.h" + +using ::testing::ElementsAre; +using ::testing::Pair; +using ::testing::Pointee; +using ::testing::SizeIs; + +// Hide in a namespace to make sure swap is found via ADL. +namespace adl_namespace { +namespace { +TEST(ChunkedQueueADLTest, Swap) { + absl::chunked_queue<int64_t> q1; + absl::chunked_queue<int64_t> q2; + q1.push_back(4); + q2.push_back(5); + q2.push_back(6); + swap(q1, q2); + EXPECT_THAT(q1, ElementsAre(5, 6)); + EXPECT_THAT(q2, ElementsAre(4)); +} +} // namespace +} // namespace adl_namespace + +namespace { + +template <class T> +using ChunkedQueueBlock = + absl::container_internal::ChunkedQueueBlock<T, std::allocator<T>>; + +TEST(Internal, elements_in_bytes) { + EXPECT_EQ(size_t{1}, ChunkedQueueBlock<int>::block_size_from_bytes(0)); + EXPECT_EQ(size_t{1}, ChunkedQueueBlock<int>::block_size_from_bytes( + sizeof(ChunkedQueueBlock<int>))); + EXPECT_EQ(size_t{1}, + ChunkedQueueBlock<int>::block_size_from_bytes(sizeof(int))); + EXPECT_EQ(size_t{2}, ChunkedQueueBlock<int>::block_size_from_bytes( + sizeof(ChunkedQueueBlock<int>) + 2 * sizeof(int))); +} + +TEST(Internal, BlockSizedDelete) { + struct Item { + int i; + char c; + }; + std::allocator<Item> allocator; + auto* block = ChunkedQueueBlock<Item>::New(3, &allocator); + ChunkedQueueBlock<Item>::Delete(block, &allocator); +} + +template <size_t elem_size> +void BlockSizeRounding() { + struct Elem { + char data[elem_size]; + }; + typedef ChunkedQueueBlock<Elem> Block; + for (size_t n = 1; n < 100; ++n) { + SCOPED_TRACE(n); + std::allocator<Elem> allocator; + Block* b = Block::New(n, &allocator); + EXPECT_GE(b->size(), n); + Block::Delete(b, &allocator); + } +} + +TEST(Internal, BlockSizeRounding1) { BlockSizeRounding<1>(); } +TEST(Internal, BlockSizeRounding17) { BlockSizeRounding<17>(); } +TEST(Internal, BlockSizeRounding101) { BlockSizeRounding<101>(); } +TEST(Internal, BlockSizeRounding528) { BlockSizeRounding<528>(); } + +TEST(ChunkedQueue, MinMaxBlockSize) { + absl::chunked_queue<int64_t, 1, 2> q = {1, 2, 3}; + EXPECT_THAT(q, ElementsAre(1, 2, 3)); +} + +TEST(ChunkedQueue, Empty) { + absl::chunked_queue<int64_t> q; + EXPECT_TRUE(q.empty()); + q.push_back(10); + EXPECT_FALSE(q.empty()); + EXPECT_EQ(q.front(), 10); + EXPECT_EQ(q.back(), 10); + q.pop_front(); + EXPECT_TRUE(q.empty()); + q.clear(); + EXPECT_TRUE(q.empty()); +} + +TEST(ChunkedQueue, CopyConstruct) { + absl::chunked_queue<int64_t> q; + q.push_back(1); + absl::chunked_queue<int64_t> r(q); + EXPECT_THAT(r, ElementsAre(1)); + EXPECT_EQ(1, r.size()); +} + +TEST(ChunkedQueue, CopyConstructMultipleChunks) { + absl::chunked_queue<int64_t, 2> q; + q.push_back(1); + q.push_back(2); + q.push_back(3); + absl::chunked_queue<int64_t, 2> r(q); + EXPECT_THAT(r, ElementsAre(1, 2, 3)); + EXPECT_EQ(3, r.size()); +} + +TEST(ChunkedQueue, BeginEndConstruct) { + std::vector<int64_t> src = {1, 2, 3, 4, 5}; + absl::chunked_queue<int64_t, 2> q(src.begin(), src.end()); + EXPECT_THAT(q, ElementsAre(1, 2, 3, 4, 5)); + EXPECT_EQ(5, q.size()); +} + +TEST(ChunkedQueue, InitializerListConstruct) { + absl::chunked_queue<int64_t, 2> q = {1, 2, 3, 4, 5}; + EXPECT_THAT(q, ElementsAre(1, 2, 3, 4, 5)); + EXPECT_EQ(5, q.size()); +} + +TEST(ChunkedQueue, CountConstruct) { + absl::chunked_queue<int64_t> q(3); + EXPECT_THAT(q, ElementsAre(0, 0, 0)); + EXPECT_EQ(3, q.size()); +} + +TEST(ChunkedQueue, CountValueConstruct) { + absl::chunked_queue<int64_t> q(3, 10); + EXPECT_THAT(q, ElementsAre(10, 10, 10)); + EXPECT_EQ(3, q.size()); +} + +TEST(ChunkedQueue, InitializerListAssign) { + absl::chunked_queue<int64_t, 2> q; + q = {1, 2, 3, 4, 5}; + EXPECT_THAT(q, ElementsAre(1, 2, 3, 4, 5)); + EXPECT_EQ(5, q.size()); +} + +TEST(ChunkedQueue, CopyAssign) { + absl::chunked_queue<int64_t> q; + q.push_back(1); + absl::chunked_queue<int64_t> r = q; + EXPECT_THAT(r, ElementsAre(1)); +} + +TEST(ChunkedQueue, CopyAssignSelf) { + absl::chunked_queue<int64_t> q; + q.push_back(1); + q = *&q; // Avoid -Wself-assign. + EXPECT_THAT(q, ElementsAre(1)); + EXPECT_EQ(1, q.size()); +} + +TEST(ChunkedQueue, CopyAssignDestinationBigger) { + absl::chunked_queue<int64_t> q; + q.push_back(1); + absl::chunked_queue<int64_t> r; + r.push_back(9); + r.push_back(9); + r.push_back(9); + r = q; + EXPECT_THAT(r, ElementsAre(1)); + EXPECT_EQ(1, r.size()); +} + +TEST(ChunkedQueue, CopyAssignSourceBiggerMultipleChunks) { + absl::chunked_queue<int64_t, 2> q; + q.push_back(1); + q.push_back(2); + q.push_back(3); + absl::chunked_queue<int64_t, 2> r; + r.push_back(9); + r = q; + EXPECT_THAT(r, ElementsAre(1, 2, 3)); + EXPECT_EQ(3, r.size()); +} + +TEST(ChunkedQueue, CopyAssignDestinationBiggerMultipleChunks) { + absl::chunked_queue<int64_t, 2> q; + q.push_back(1); + absl::chunked_queue<int64_t, 2> r; + r.push_back(9); + r.push_back(9); + r.push_back(9); + r = q; + EXPECT_THAT(r, ElementsAre(1)); + EXPECT_EQ(1, r.size()); +} + +TEST(ChunkedQueue, AssignCountValue) { + absl::chunked_queue<int64_t> q; + q.assign(3, 10); + EXPECT_THAT(q, ElementsAre(10, 10, 10)); + EXPECT_EQ(3, q.size()); + + q.assign(2, 20); + EXPECT_THAT(q, ElementsAre(20, 20)); + EXPECT_EQ(2, q.size()); +} + +TEST(ChunkedQueue, MoveConstruct) { + absl::chunked_queue<int64_t> q; + q.push_back(1); + absl::chunked_queue<int64_t> r(std::move(q)); + EXPECT_THAT(r, ElementsAre(1)); + EXPECT_EQ(1, r.size()); +} + +TEST(ChunkedQueue, MoveAssign) { + absl::chunked_queue<int64_t> q; + q.push_back(1); + absl::chunked_queue<int64_t> r; + r = std::move(q); + EXPECT_THAT(r, ElementsAre(1)); + EXPECT_EQ(1, r.size()); +} + +TEST(ChunkedQueue, MoveAssignImmovable) { + struct Immovable { + Immovable() = default; + + Immovable(const Immovable&) = delete; + Immovable& operator=(const Immovable&) = delete; + Immovable(Immovable&&) = delete; + Immovable& operator=(Immovable&&) = delete; + }; + absl::chunked_queue<Immovable> q; + q.emplace_back(); + absl::chunked_queue<Immovable> r; + r = std::move(q); + EXPECT_THAT(r, SizeIs(1)); +} + +TEST(ChunkedQueue, MoveAssignSelf) { + absl::chunked_queue<int64_t> q; + absl::chunked_queue<int64_t>& q2 = q; + q.push_back(1); + q = std::move(q2); + EXPECT_THAT(q, ElementsAre(1)); + EXPECT_EQ(1, q.size()); +} + +TEST(ChunkedQueue, MoveAssignDestinationBigger) { + absl::chunked_queue<int64_t> q; + q.push_back(1); + absl::chunked_queue<int64_t> r; + r.push_back(9); + r.push_back(9); + r.push_back(9); + r = std::move(q); + EXPECT_THAT(r, ElementsAre(1)); + EXPECT_EQ(1, r.size()); +} + +TEST(ChunkedQueue, MoveAssignDestinationBiggerMultipleChunks) { + absl::chunked_queue<int64_t, 2> q; + q.push_back(1); + absl::chunked_queue<int64_t, 2> r; + r.push_back(9); + r.push_back(9); + r.push_back(9); + r = std::move(q); + EXPECT_THAT(r, ElementsAre(1)); + EXPECT_EQ(1, r.size()); +} + +TEST(ChunkedQueue, ConstFrontBack) { + absl::chunked_queue<int64_t> q; + q.push_back(10); + EXPECT_EQ(q.front(), 10); + EXPECT_EQ(q.back(), 10); + q.front() = 12; + EXPECT_EQ(q.front(), 12); + EXPECT_EQ(q.back(), 12); + + const absl::chunked_queue<int64_t>& qref = q; + EXPECT_EQ(qref.front(), 12); + EXPECT_EQ(qref.back(), 12); + + q.pop_front(); + + // Test at block bloundary and beyond + for (int i = 0; i < 64; ++i) q.push_back(i + 10); + EXPECT_EQ(q.front(), 10); + EXPECT_EQ(q.back(), 73); + + for (int i = 64; i < 128; ++i) q.push_back(i + 10); + EXPECT_EQ(q.front(), 10); + EXPECT_EQ(q.back(), 137); + q.clear(); + EXPECT_TRUE(q.empty()); +} + +TEST(ChunkedQueue, PushAndPop) { + absl::chunked_queue<int64_t> q; + EXPECT_TRUE(q.empty()); + EXPECT_EQ(0, q.size()); + for (int i = 0; i < 10000; i++) { + q.push_back(i); + EXPECT_EQ(q.front(), 0) << ": iteration " << i; + EXPECT_FALSE(q.empty()); + EXPECT_EQ(i + 1, q.size()); + } + for (int i = 0; i < 10000; i++) { + EXPECT_FALSE(q.empty()); + EXPECT_EQ(10000 - i, q.size()); + EXPECT_EQ(q.front(), i); + q.pop_front(); + } + EXPECT_TRUE(q.empty()); + EXPECT_EQ(0, q.size()); +} + +TEST(ChunkedQueue, Swap) { + absl::chunked_queue<int64_t> q1; + absl::chunked_queue<int64_t> q2; + q1.push_back(4); + q2.push_back(5); + q2.push_back(6); + q2.swap(q1); + EXPECT_EQ(2, q1.size()); + EXPECT_EQ(5, q1.front()); + EXPECT_EQ(1, q2.size()); + EXPECT_EQ(4, q2.front()); + q1.pop_front(); + q1.swap(q2); + EXPECT_EQ(1, q1.size()); + EXPECT_EQ(4, q1.front()); + EXPECT_EQ(1, q2.size()); + EXPECT_EQ(6, q2.front()); + q1.pop_front(); + q1.swap(q2); + EXPECT_EQ(1, q1.size()); + EXPECT_EQ(6, q1.front()); + EXPECT_EQ(0, q2.size()); + q1.clear(); + EXPECT_TRUE(q1.empty()); +} + +TEST(ChunkedQueue, ShrinkToFit) { + absl::chunked_queue<int64_t> q; + q.shrink_to_fit(); // Should work on empty + EXPECT_TRUE(q.empty()); + + q.push_back(1); + q.shrink_to_fit(); // Should work on non-empty + EXPECT_THAT(q, ElementsAre(1)); + + q.clear(); + // We know clear leaves a block and shrink_to_fit should remove it. + // Hard to test internal memory state without mocks or inspection. + // But at least we verify it doesn't crash or corrupt. + q.shrink_to_fit(); + EXPECT_TRUE(q.empty()); +} + +TEST(ChunkedQueue, ResizeExtends) { + absl::chunked_queue<int64_t> q; + q.resize(2); + EXPECT_THAT(q, ElementsAre(0, 0)); + EXPECT_EQ(2, q.size()); +} + +TEST(ChunkedQueue, ResizeShrinks) { + absl::chunked_queue<int64_t> q; + q.push_back(1); + q.push_back(2); + q.resize(1); + EXPECT_THAT(q, ElementsAre(1)); + EXPECT_EQ(1, q.size()); +} + +TEST(ChunkedQueue, ResizeExtendsMultipleBlocks) { + absl::chunked_queue<int64_t, 2> q; + q.resize(3); + EXPECT_THAT(q, ElementsAre(0, 0, 0)); + EXPECT_EQ(3, q.size()); +} + +TEST(ChunkedQueue, ResizeShrinksMultipleBlocks) { + absl::chunked_queue<int64_t, 2> q; + q.push_back(1); + q.push_back(2); + q.push_back(3); + q.resize(1); + EXPECT_THAT(q, ElementsAre(1)); + EXPECT_EQ(1, q.size()); +} + +TEST(ChunkedQueue, ResizeValue) { + absl::chunked_queue<int64_t> q; + q.resize(3, 10); + EXPECT_THAT(q, ElementsAre(10, 10, 10)); + EXPECT_EQ(3, q.size()); + + q.resize(5, 20); + EXPECT_THAT(q, ElementsAre(10, 10, 10, 20, 20)); + EXPECT_EQ(5, q.size()); + + q.resize(2, 30); + EXPECT_THAT(q, ElementsAre(10, 10)); + EXPECT_EQ(2, q.size()); +} + +TEST(ChunkedQueue, MaxSize) { + absl::chunked_queue<int64_t> q; + EXPECT_GE(q.max_size(), + size_t{1} << (sizeof(size_t) * 8 - sizeof(int64_t) - 4)); +} + +TEST(ChunkedQueue, AssignExtends) { + absl::chunked_queue<int64_t, 2> q; + std::vector<int64_t> v = {1, 2, 3, 4, 5}; + q.assign(v.begin(), v.end()); + EXPECT_THAT(q, ElementsAre(1, 2, 3, 4, 5)); + EXPECT_EQ(5, q.size()); +} + +TEST(ChunkedQueue, AssignShrinks) { + absl::chunked_queue<int64_t, 2> q = {1, 2, 3, 4, 5}; + std::vector<int64_t> v = {1}; + q.assign(v.begin(), v.end()); + EXPECT_THAT(q, ElementsAre(1)); + EXPECT_EQ(1, q.size()); +} + +TEST(ChunkedQueue, AssignBoundaryCondition) { + // Create a queue with fixed block size of 4. + // 3 blocks: [1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12] + absl::chunked_queue<int, 4> q = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}; + + // Assign a range that fills exactly the first block (4 elements). + // This triggers the boundary condition where the assignment loop ends + // exactly at the limit of the first block. + std::vector<int> v = {101, 102, 103, 104}; + q.assign(v.begin(), v.end()); + + EXPECT_EQ(q.size(), 4); + EXPECT_EQ(q.front(), 101); + // Verify back() is valid. If tail_ was incorrectly pointing to the start + // of the (now deleted) second block, this might access invalid memory + // or fail assertions. + EXPECT_EQ(q.back(), 104); + + // Verify we can continue to push elements correctly. + q.push_back(105); + EXPECT_EQ(q.size(), 5); + EXPECT_EQ(q.back(), 105); +} + +TEST(ChunkedQueue, Iterator) { + absl::chunked_queue<int64_t> q; + EXPECT_TRUE(q.begin() == q.end()); + + q.push_back(1); + absl::chunked_queue<int64_t>::const_iterator iter = q.begin(); + ASSERT_FALSE(iter == q.end()); + ASSERT_EQ(*iter, 1); + ++iter; + ASSERT_TRUE(iter == q.end()); + + q.push_back(2); + iter = q.begin(); + ASSERT_EQ(*iter, 1); + ++iter; + absl::chunked_queue<int64_t>::const_iterator copy_iter = iter; + ASSERT_FALSE(copy_iter == q.end()); + ASSERT_EQ(*copy_iter, 2); + ++copy_iter; + ASSERT_TRUE(copy_iter == q.end()); + + copy_iter = iter; + ASSERT_FALSE(iter == q.end()); + ASSERT_EQ(*iter, 2); + ++iter; + ASSERT_TRUE(iter == q.end()); + + ASSERT_FALSE(copy_iter == q.end()); + ASSERT_EQ(*copy_iter, 2); + ++copy_iter; + ASSERT_TRUE(copy_iter == q.end()); +} + +TEST(ChunkedQueue, IteratorDefaultConstructor) { + using ConstIter = absl::chunked_queue<int64_t>::const_iterator; + using Iter = absl::chunked_queue<int64_t>::iterator; + ConstIter const_iter; + EXPECT_TRUE(const_iter == ConstIter()); + Iter iter; + EXPECT_TRUE(iter == Iter()); +} + +TEST(ChunkedQueue, IteratorConversion) { + using ConstIter = absl::chunked_queue<int64_t>::const_iterator; + using Iter = absl::chunked_queue<int64_t>::iterator; + EXPECT_FALSE((std::is_convertible<ConstIter, Iter>::value)); + EXPECT_TRUE((std::is_convertible<Iter, ConstIter>::value)); + absl::chunked_queue<int64_t> q; + ConstIter it1 = q.begin(); + ConstIter it2 = q.cbegin(); + Iter it3 = q.begin(); + it1 = q.end(); + it2 = q.cend(); + it3 = q.end(); + EXPECT_FALSE((std::is_assignable<Iter, ConstIter>::value)); +} + +struct TestEntry { + int x, y; +}; + +TEST(ChunkedQueue, Iterator2) { + absl::chunked_queue<TestEntry> q; + TestEntry e; + e.x = 1; + e.y = 2; + q.push_back(e); + e.x = 3; + e.y = 4; + q.push_back(e); + + absl::chunked_queue<TestEntry>::const_iterator iter = q.begin(); + EXPECT_EQ(iter->x, 1); + EXPECT_EQ(iter->y, 2); + ++iter; + EXPECT_EQ(iter->x, 3); + EXPECT_EQ(iter->y, 4); + ++iter; + EXPECT_TRUE(iter == q.end()); +} + +TEST(ChunkedQueue, Iterator_MultipleBlocks) { + absl::chunked_queue<int64_t> q; + for (int i = 0; i < 130; ++i) { + absl::chunked_queue<int64_t>::const_iterator iter = q.begin(); + for (int j = 0; j < i; ++j) { + ASSERT_FALSE(iter == q.end()); + EXPECT_EQ(*iter, j); + ++iter; + } + ASSERT_TRUE(iter == q.end()); + q.push_back(i); + } + + for (int i = 0; i < 130; ++i) { + absl::chunked_queue<int64_t>::const_iterator iter = q.begin(); + for (int j = i; j < 130; ++j) { + ASSERT_FALSE(iter == q.end()); + EXPECT_EQ(*iter, j); + ++iter; + } + q.pop_front(); + } + EXPECT_TRUE(q.empty()); + EXPECT_TRUE(q.begin() == q.end()); +} + +TEST(ChunkedQueue, Iterator_PopFrontInvalidate) { + absl::chunked_queue<int64_t> q; + for (int i = 0; i < 130; ++i) { + q.push_back(i); + } + + auto iter = q.begin(); + for (int i = 0; i < 130; ++i) { + auto prev = iter++; + ASSERT_FALSE(prev == q.end()); + EXPECT_EQ(*prev, i); + q.pop_front(); + } + ASSERT_TRUE(q.empty()); +} + +TEST(ChunkedQueue, Iterator_PushBackInvalidate) { + absl::chunked_queue<int64_t, 2> q; + q.push_back(0); + auto i = q.begin(); + EXPECT_EQ(*i, 0); + q.push_back(1); + EXPECT_EQ(*++i, 1); + q.push_back(2); + EXPECT_EQ(*++i, 2); +} + +struct MyType { + static int constructor_calls; + static int destructor_calls; + + explicit MyType(int x) : val(x) { constructor_calls++; } + MyType(const MyType& t) : val(t.val) { constructor_calls++; } + ~MyType() { destructor_calls++; } + + int val; +}; + +int MyType::constructor_calls = 0; +int MyType::destructor_calls = 0; + +TEST(ChunkedQueue, ConstructorDestructorCalls) { + for (int i = 0; i < 100; i++) { + std::vector<MyType> vals; + for (int j = 0; j < i; j++) { + vals.push_back(MyType(j)); + } + MyType::constructor_calls = 0; + MyType::destructor_calls = 0; + { + absl::chunked_queue<MyType> q; + for (int j = 0; j < i; j++) { + q.push_back(vals[j]); + } + if (i % 10 == 0) { + q.clear(); + } else { + for (int j = 0; j < i; j++) { + EXPECT_EQ(q.front().val, j); + q.pop_front(); + } + } + } + EXPECT_EQ(MyType::constructor_calls, i); + EXPECT_EQ(MyType::destructor_calls, i); + } +} + +TEST(ChunkedQueue, MoveObjects) { + absl::chunked_queue<std::unique_ptr<int>> q; + q.push_back(std::make_unique<int>(10)); + q.push_back(std::make_unique<int>(11)); + + EXPECT_EQ(10, *q.front()); + q.pop_front(); + EXPECT_EQ(11, *q.front()); + q.pop_front(); +} + +TEST(ChunkedQueue, EmplaceBack1) { + absl::chunked_queue<std::pair<int, int>> q; + auto& v = q.emplace_back(1, 2); + EXPECT_THAT(v, Pair(1, 2)); + EXPECT_THAT(q.front(), Pair(1, 2)); + EXPECT_EQ(&v, &q.back()); +} + +TEST(ChunkedQueue, EmplaceBack2) { + absl::chunked_queue<std::pair<std::unique_ptr<int>, std::string>> q; + auto& v = q.emplace_back(std::make_unique<int>(11), "val12"); + EXPECT_THAT(v, Pair(Pointee(11), "val12")); + EXPECT_THAT(q.front(), Pair(Pointee(11), "val12")); +} + +TEST(ChunkedQueue, OveralignmentEmplaceBack) { + struct alignas(64) Overaligned { + int x; + int y; + }; + absl::chunked_queue<Overaligned, 1, 8> q; + for (int i = 0; i < 10; ++i) { + auto& v = q.emplace_back(Overaligned{i, i}); + EXPECT_EQ(reinterpret_cast<uintptr_t>(&v) % 64, 0); + } +} + +TEST(ChunkedQueue, StatelessAllocatorDoesntAffectObjectSizes) { + // When a stateless allocator type is used -- such as when no explicit + // allocator type is given, and the stateless default is used -- it does not + // increase the object sizes from what they used to be before allocator + // support was added. (In practice this verifies that allocator support makes + // use of the empty base-class optimization.) + // + // These "Mock*" structs model the data members of absl::chunked_queue<> and + // its internal ChunkedQueueBlock<> type, without any extra storage for + // allocator state. (We use these to generate expected stateless-allocator + // object sizes in a portable way.) + struct MockQueue { + struct MockIterator { + void* block; + void* ptr; + void* limit; + }; + MockIterator head; + MockIterator tail; + size_t size; + }; + struct MockBlock { + void* next; + void* limit; + }; + using TestQueueType = absl::chunked_queue<int64_t, 1, 16>; + EXPECT_EQ(sizeof(TestQueueType), sizeof(MockQueue)); + EXPECT_EQ(sizeof(absl::container_internal::ChunkedQueueBlock< + TestQueueType::value_type, TestQueueType::allocator_type>), + sizeof(MockBlock)); +} + +TEST(ChunkedQueue, DoesNotRoundBlockSizesUpWithNonDefaultAllocator) { + using OneByte = uint8_t; + using CustomAllocator = absl::container_internal::CountingAllocator<OneByte>; + using Block = + absl::container_internal::ChunkedQueueBlock<OneByte, CustomAllocator>; + int64_t allocator_live_bytes = 0; + CustomAllocator allocator(&allocator_live_bytes); + // Create a Block big enough to accomodate at least 1 OneByte. + Block* b = Block::New(1, &allocator); + ASSERT_TRUE(b != nullptr); + // With a non-default allocator in play, the resulting block should have + // capacity for exactly 1 element -- the implementation should not round the + // allocation size up, which may be inappropriate for non-default allocators. + // + // (Note that we don't always round up even with the default allocator in use, + // e.g. when compiling for ASAN analysis.) + EXPECT_EQ(b->size(), 1); + Block::Delete(b, &allocator); +} + +TEST(ChunkedQueue, Hardening) { + bool hardened = false; + ABSL_HARDENING_ASSERT([&hardened]() { + hardened = true; + return true; + }()); + if (!hardened) { + GTEST_SKIP() << "Not a hardened build"; + } + + absl::chunked_queue<int> q; + EXPECT_DEATH(q.front(), ""); + EXPECT_DEATH(q.back(), ""); + EXPECT_DEATH(q.pop_front(), ""); + + const absl::chunked_queue<int> cq; + EXPECT_DEATH(cq.front(), ""); + EXPECT_DEATH(cq.back(), ""); +} + +} // namespace
diff --git a/absl/container/fixed_array.h b/absl/container/fixed_array.h index 6c238fc..d47b0e4 100644 --- a/absl/container/fixed_array.h +++ b/absl/container/fixed_array.h
@@ -84,11 +84,9 @@ static constexpr size_t kInlineBytesDefault = 256; using AllocatorTraits = std::allocator_traits<A>; - // std::iterator_traits isn't guaranteed to be SFINAE-friendly until C++17, - // but this seems to be mostly pedantic. template <typename Iterator> - using EnableIfForwardIterator = std::enable_if_t< - base_internal::IsAtLeastForwardIterator<Iterator>::value>; + using EnableIfInputIterator = + std::enable_if_t<base_internal::IsAtLeastInputIterator<Iterator>::value>; static constexpr bool NoexceptCopyable() { return std::is_nothrow_copy_constructible<StorageElement>::value && absl::allocator_is_nothrow<allocator_type>::value; @@ -161,8 +159,8 @@ // Creates an array initialized with the elements from the input // range. The array's size will always be `std::distance(first, last)`. - // REQUIRES: Iterator must be a forward_iterator or better. - template <typename Iterator, EnableIfForwardIterator<Iterator>* = nullptr> + // REQUIRES: Iterator must be a input_iterator or better. + template <typename Iterator, EnableIfInputIterator<Iterator>* = nullptr> FixedArray(Iterator first, Iterator last, const allocator_type& a = allocator_type()) : storage_(std::distance(first, last), a) { @@ -392,8 +390,7 @@ template <typename H> friend H AbslHashValue(H h, const FixedArray& v) { - return H::combine(H::combine_contiguous(std::move(h), v.data(), v.size()), - hash_internal::WeaklyMixedInteger{v.size()}); + return H::combine_contiguous(std::move(h), v.data(), v.size()); } private:
diff --git a/absl/container/flat_hash_map.h b/absl/container/flat_hash_map.h index bc86ced..7ce3353 100644 --- a/absl/container/flat_hash_map.h +++ b/absl/container/flat_hash_map.h
@@ -115,25 +115,29 @@ // absl::flat_hash_map<std::string, std::string> ducks = // {{"a", "huey"}, {"b", "dewey"}, {"c", "louie"}}; // -// // Insert a new element into the flat hash map -// ducks.insert({"d", "donald"}); +// // Insert a new element into the flat hash map +// ducks.insert({"d", "donald"}); // -// // Force a rehash of the flat hash map -// ducks.rehash(0); +// // Force a rehash of the flat hash map +// ducks.rehash(0); // -// // Find the element with the key "b" -// std::string search_key = "b"; -// auto result = ducks.find(search_key); -// if (result != ducks.end()) { -// std::cout << "Result: " << result->second << std::endl; -// } -template <class K, class V, class Hash = DefaultHashContainerHash<K>, - class Eq = DefaultHashContainerEq<K>, - class Allocator = std::allocator<std::pair<const K, V>>> +// // Find the element with the key "b" +// std::string search_key = "b"; +// auto result = ducks.find(search_key); +// if (result != ducks.end()) { +// std::cout << "Result: " << result->second << std::endl; +// } +template < + class K, class V, + class Hash = + typename container_internal::FlatHashMapPolicy<K, V>::DefaultHash, + class Eq = typename container_internal::FlatHashMapPolicy<K, V>::DefaultEq, + class Allocator = + typename container_internal::FlatHashMapPolicy<K, V>::DefaultAlloc> class ABSL_ATTRIBUTE_OWNER flat_hash_map - : public absl::container_internal::raw_hash_map< + : public absl::container_internal::InstantiateRawHashMap< absl::container_internal::FlatHashMapPolicy<K, V>, Hash, Eq, - Allocator> { + Allocator>::type { using Base = typename flat_hash_map::raw_hash_map; public: @@ -158,9 +162,9 @@ // // * Copy assignment operator // - // // Hash functor and Comparator are copied as well - // absl::flat_hash_map<int, std::string> map4; - // map4 = map3; + // // Hash functor and Comparator are copied as well + // absl::flat_hash_map<int, std::string> map4; + // map4 = map3; // // * Move constructor // @@ -462,7 +466,9 @@ // // Sets the number of slots in the `flat_hash_map` to the number needed to // accommodate at least `count` total elements without exceeding the current - // maximum load factor, and may rehash the container if needed. + // maximum load factor, and may rehash the container if needed. After this + // returns, it is guaranteed that `count - size()` elements can be inserted + // into the `flat_hash_map` without another rehash. using Base::reserve; // flat_hash_map::at() @@ -635,6 +641,10 @@ using mapped_type = V; using init_type = std::pair</*non const*/ key_type, mapped_type>; + using DefaultHash = DefaultHashContainerHash<K>; + using DefaultEq = DefaultHashContainerEq<K>; + using DefaultAlloc = std::allocator<std::pair<const K, V>>; + template <class Allocator, class... Args> static void construct(Allocator* alloc, slot_type* slot, Args&&... args) { slot_policy::construct(alloc, slot, std::forward<Args>(args)...); @@ -660,10 +670,10 @@ std::forward<Args>(args)...); } - template <class Hash> + template <class Hash, bool kIsDefault> static constexpr HashSlotFn get_hash_slot_fn() { return memory_internal::IsLayoutCompatible<K, V>::value - ? &TypeErasedApplyToSlotFn<Hash, K> + ? &TypeErasedApplyToSlotFn<Hash, K, kIsDefault> : nullptr; }
diff --git a/absl/container/flat_hash_map_test.cc b/absl/container/flat_hash_map_test.cc index 5c83c94..73f28c7 100644 --- a/absl/container/flat_hash_map_test.cc +++ b/absl/container/flat_hash_map_test.cc
@@ -39,8 +39,7 @@ ABSL_NAMESPACE_BEGIN namespace container_internal { namespace { -using ::absl::container_internal::hash_internal::Enum; -using ::absl::container_internal::hash_internal::EnumClass; + using ::testing::_; using ::testing::IsEmpty; using ::testing::Pair; @@ -116,15 +115,6 @@ TEST(FlatHashMap, Relocatability) { static_assert(absl::is_trivially_relocatable<int>::value); -#if ABSL_INTERNAL_CPLUSPLUS_LANG <= 202002L - // std::pair is not trivially copyable in C++23 in some standard - // library versions. - // See https://github.com/llvm/llvm-project/pull/95444 for instance. - // container_memory.h contains a workaround so what really matters - // is the transfer test below. - static_assert( - absl::is_trivially_relocatable<std::pair<const int, int>>::value); -#endif static_assert( std::is_same<decltype(absl::container_internal::FlatHashMapPolicy< int, int>::transfer<std::allocator<char>>(nullptr,
diff --git a/absl/container/flat_hash_set.h b/absl/container/flat_hash_set.h index bf63eb5..a469fa0 100644 --- a/absl/container/flat_hash_set.h +++ b/absl/container/flat_hash_set.h
@@ -114,22 +114,26 @@ // absl::flat_hash_set<std::string> ducks = // {"huey", "dewey", "louie"}; // -// // Insert a new element into the flat hash set -// ducks.insert("donald"); +// // Insert a new element into the flat hash set +// ducks.insert("donald"); // -// // Force a rehash of the flat hash set -// ducks.rehash(0); +// // Force a rehash of the flat hash set +// ducks.rehash(0); // -// // See if "dewey" is present -// if (ducks.contains("dewey")) { -// std::cout << "We found dewey!" << std::endl; -// } -template <class T, class Hash = DefaultHashContainerHash<T>, - class Eq = DefaultHashContainerEq<T>, - class Allocator = std::allocator<T>> +// // See if "dewey" is present +// if (ducks.contains("dewey")) { +// std::cout << "We found dewey!" << std::endl; +// } +template < + class T, + class Hash = typename container_internal::FlatHashSetPolicy<T>::DefaultHash, + class Eq = typename container_internal::FlatHashSetPolicy<T>::DefaultEq, + class Allocator = + typename container_internal::FlatHashSetPolicy<T>::DefaultAlloc> class ABSL_ATTRIBUTE_OWNER flat_hash_set - : public absl::container_internal::raw_hash_set< - absl::container_internal::FlatHashSetPolicy<T>, Hash, Eq, Allocator> { + : public absl::container_internal::InstantiateRawHashSet< + absl::container_internal::FlatHashSetPolicy<T>, Hash, Eq, + Allocator>::type { using Base = typename flat_hash_set::raw_hash_set; public: @@ -154,9 +158,9 @@ // // * Copy assignment operator // - // // Hash functor and Comparator are copied as well - // absl::flat_hash_set<std::string> set4; - // set4 = set3; + // // Hash functor and Comparator are copied as well + // absl::flat_hash_set<std::string> set4; + // set4 = set3; // // * Move constructor // @@ -396,7 +400,9 @@ // // Sets the number of slots in the `flat_hash_set` to the number needed to // accommodate at least `count` total elements without exceeding the current - // maximum load factor, and may rehash the container if needed. + // maximum load factor, and may rehash the container if needed. After this + // returns, it is guaranteed that `count - size()` elements can be inserted + // into the `flat_hash_set` without another rehash. using Base::reserve; // flat_hash_set::contains() @@ -533,6 +539,10 @@ using init_type = T; using constant_iterators = std::true_type; + using DefaultHash = DefaultHashContainerHash<T>; + using DefaultEq = DefaultHashContainerEq<T>; + using DefaultAlloc = std::allocator<T>; + template <class Allocator, class... Args> static void construct(Allocator* alloc, slot_type* slot, Args&&... args) { absl::allocator_traits<Allocator>::construct(*alloc, slot, @@ -558,9 +568,9 @@ static size_t space_used(const T*) { return 0; } - template <class Hash> + template <class Hash, bool kIsDefault> static constexpr HashSlotFn get_hash_slot_fn() { - return &TypeErasedApplyToSlotFn<Hash, T>; + return &TypeErasedApplyToSlotFn<Hash, T, kIsDefault>; } }; } // namespace container_internal
diff --git a/absl/container/flat_hash_set_test.cc b/absl/container/flat_hash_set_test.cc index bb90efa..9b6a6d1 100644 --- a/absl/container/flat_hash_set_test.cc +++ b/absl/container/flat_hash_set_test.cc
@@ -43,8 +43,6 @@ namespace container_internal { namespace { -using ::absl::container_internal::hash_internal::Enum; -using ::absl::container_internal::hash_internal::EnumClass; using ::testing::IsEmpty; using ::testing::Pointee; using ::testing::UnorderedElementsAre; @@ -383,6 +381,20 @@ EXPECT_THAT(s, UnorderedElementsAre(1, 2, 3)); } +TEST(FlatHashSet, IsDefaultHash) { + using absl::container_internal::hashtable_debug_internal:: + HashtableDebugAccess; + EXPECT_EQ(HashtableDebugAccess<flat_hash_set<int>>::kIsDefaultHash, true); + EXPECT_EQ(HashtableDebugAccess<flat_hash_set<std::string>>::kIsDefaultHash, + true); + + struct Hash { + size_t operator()(size_t i) const { return i; } + }; + EXPECT_EQ((HashtableDebugAccess<flat_hash_set<size_t, Hash>>::kIsDefaultHash), + false); +} + } // namespace } // namespace container_internal ABSL_NAMESPACE_END
diff --git a/absl/container/inlined_vector.h b/absl/container/inlined_vector.h index f871b34..6b05d92 100644 --- a/absl/container/inlined_vector.h +++ b/absl/container/inlined_vector.h
@@ -815,13 +815,11 @@ // `InlinedVector::clear()` // // Destroys all elements in the inlined vector, setting the size to `0` and - // deallocating any held memory. + // preserving capacity. void clear() noexcept { inlined_vector_internal::DestroyAdapter<A>::DestroyElements( storage_.GetAllocator(), data(), size()); - storage_.DeallocateIfAllocated(); - - storage_.SetInlinedSize(0); + storage_.SetSize(0); } // `InlinedVector::reserve(...)` @@ -1008,9 +1006,17 @@ // call this directly. template <typename H, typename T, size_t N, typename A> H AbslHashValue(H h, const absl::InlinedVector<T, N, A>& a) { - auto size = a.size(); - return H::combine(H::combine_contiguous(std::move(h), a.data(), size), - hash_internal::WeaklyMixedInteger{size}); + return H::combine_contiguous(std::move(h), a.data(), a.size()); +} + +template <typename T, size_t N, typename A, typename Predicate> +constexpr typename InlinedVector<T, N, A>::size_type erase_if( + InlinedVector<T, N, A>& v, Predicate pred) { + const auto it = std::remove_if(v.begin(), v.end(), std::move(pred)); + const auto removed = static_cast<typename InlinedVector<T, N, A>::size_type>( + std::distance(it, v.end())); + v.erase(it, v.end()); + return removed; } ABSL_NAMESPACE_END
diff --git a/absl/container/inlined_vector_test.cc b/absl/container/inlined_vector_test.cc index ff0e77b..1e3ff82 100644 --- a/absl/container/inlined_vector_test.cc +++ b/absl/container/inlined_vector_test.cc
@@ -51,6 +51,7 @@ using testing::Each; using testing::ElementsAre; using testing::ElementsAreArray; +using testing::IsEmpty; using testing::Eq; using testing::Gt; using testing::Pointee; @@ -919,7 +920,9 @@ SCOPED_TRACE(len); IntVec v; Fill(&v, len); + size_t capacity_before_clear = v.capacity(); v.clear(); + EXPECT_EQ(v.capacity(), capacity_before_clear); EXPECT_EQ(0u, v.size()); EXPECT_EQ(v.begin(), v.end()); } @@ -2254,4 +2257,22 @@ sizeof(MySpan<int>) / sizeof(int)); } +TEST(IntVec, EraseIf) { + IntVec v = {3, 1, 2, 0}; + EXPECT_EQ(absl::erase_if(v, [](int i) { return i > 1; }), 2u); + EXPECT_THAT(v, ElementsAre(1, 0)); +} + +TEST(IntVec, EraseIfMatchesNone) { + IntVec v = {1, 2, 3}; + EXPECT_EQ(absl::erase_if(v, [](int i) { return i > 10; }), 0u);; + EXPECT_THAT(v, ElementsAre(1, 2, 3)); +} + +TEST(IntVec, EraseIfMatchesAll) { + IntVec v = {1, 2, 3}; + EXPECT_EQ(absl::erase_if(v, [](int i) { return i > 0; }), 3u); + EXPECT_THAT(v, IsEmpty()); +} + } // anonymous namespace
diff --git a/absl/container/internal/btree_container.h b/absl/container/internal/btree_container.h index 21f00ae..e1649e3 100644 --- a/absl/container/internal/btree_container.h +++ b/absl/container/internal/btree_container.h
@@ -640,12 +640,12 @@ } template <class K = key_type, int = EnableIf<LifetimeBoundK<K, false>>()> mapped_type &operator[](key_arg<K> &&k) ABSL_ATTRIBUTE_LIFETIME_BOUND { - return try_emplace(std::forward<K>(k)).first->second; + return try_emplace(std::forward<key_arg<K>>(k)).first->second; } template <class K = key_type, int &..., EnableIf<LifetimeBoundK<K, true>> = 0> mapped_type &operator[](key_arg<K> &&k ABSL_INTERNAL_ATTRIBUTE_CAPTURED_BY( this)) ABSL_ATTRIBUTE_LIFETIME_BOUND { - return this->template operator[]<K, 0>(std::forward<K>(k)); + return this->template operator[]<K, 0>(std::forward<key_arg<K>>(k)); } template <typename K = key_type> @@ -672,27 +672,36 @@ std::pair<iterator, bool> insert_or_assign_impl(K &&k, M &&obj) { const std::pair<iterator, bool> ret = this->tree_.insert_unique(k, std::forward<K>(k), std::forward<M>(obj)); - if (!ret.second) ret.first->second = std::forward<M>(obj); + if (!ret.second) { + // NOLINTNEXTLINE(bugprone-use-after-move) + ret.first->second = std::forward<M>(obj); + } return ret; } template <class K, class M> iterator insert_or_assign_hint_impl(const_iterator hint, K &&k, M &&obj) { const std::pair<iterator, bool> ret = this->tree_.insert_hint_unique( iterator(hint), k, std::forward<K>(k), std::forward<M>(obj)); - if (!ret.second) ret.first->second = std::forward<M>(obj); + if (!ret.second) { + // NOLINTNEXTLINE(bugprone-use-after-move) + ret.first->second = std::forward<M>(obj); + } return ret.first; } template <class K, class... Args> std::pair<iterator, bool> try_emplace_impl(K &&k, Args &&... args) { return this->tree_.insert_unique( + // NOLINTNEXTLINE(bugprone-use-after-move) k, std::piecewise_construct, std::forward_as_tuple(std::forward<K>(k)), std::forward_as_tuple(std::forward<Args>(args)...)); } template <class K, class... Args> iterator try_emplace_hint_impl(const_iterator hint, K &&k, Args &&... args) { return this->tree_ - .insert_hint_unique(iterator(hint), k, std::piecewise_construct, + .insert_hint_unique(iterator(hint), + // NOLINTNEXTLINE(bugprone-use-after-move) + k, std::piecewise_construct, std::forward_as_tuple(std::forward<K>(k)), std::forward_as_tuple(std::forward<Args>(args)...)) .first;
diff --git a/absl/container/internal/chunked_queue.h b/absl/container/internal/chunked_queue.h new file mode 100644 index 0000000..c3718ac --- /dev/null +++ b/absl/container/internal/chunked_queue.h
@@ -0,0 +1,173 @@ +// Copyright 2025 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_CHUNKED_QUEUE_H_ +#define ABSL_CONTAINER_INTERNAL_CHUNKED_QUEUE_H_ + +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <initializer_list> +#include <iterator> +#include <memory> +#include <new> +#include <tuple> +#include <type_traits> +#include <utility> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/macros.h" +#include "absl/container/internal/layout.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace container_internal { + +// ChunkedQueueBlock defines a node in a forward list of uninitialized storage +// of size T's. The user is responsible for constructing and destroying T's in +// said storage. +// +// ChunkedQueueBlock::New(size) returns said node, with at least size_hint T's +// of uninitialized storage. +template <typename T, typename Allocator> +class ChunkedQueueBlock { + private: + using ChunkedQueueBlockAllocator = typename std::allocator_traits< + Allocator>::template rebind_alloc<ChunkedQueueBlock>; + using ByteAllocator = + typename std::allocator_traits<Allocator>::template rebind_alloc<char>; + + public: + // NB, instances of this must not be created or destroyed directly, only via + // the New() and Delete() methods. (This notionally-private constructor is + // public only to allow access from allocator types used by New().) + explicit ChunkedQueueBlock(size_t size) + : next_(nullptr), limit_(start() + size) {} + + // Must be deleted by ChunkedQueueBlock::Delete. + static ChunkedQueueBlock* New(size_t size_hint, Allocator* alloc) { // NOLINT + ABSL_ASSERT(size_hint >= size_t{1}); + size_t allocation_bytes = AllocSize(size_hint); + void* mem; + std::tie(mem, allocation_bytes) = Allocate(allocation_bytes, alloc); + const size_t element_count = + (allocation_bytes - start_offset()) / sizeof(T); + ChunkedQueueBlock* as_block = static_cast<ChunkedQueueBlock*>(mem); + ChunkedQueueBlockAllocator block_alloc(*alloc); + std::allocator_traits<ChunkedQueueBlockAllocator>::construct( + block_alloc, as_block, element_count); + return as_block; + } + + static void Delete(ChunkedQueueBlock* ptr, Allocator* alloc) { + const size_t allocation_bytes = AllocSize(ptr->size()); + ChunkedQueueBlockAllocator block_alloc(*alloc); + std::allocator_traits<ChunkedQueueBlockAllocator>::destroy(block_alloc, + ptr); + if constexpr (std::is_same_v<ByteAllocator, std::allocator<char>>) { +#ifdef __STDCPP_DEFAULT_NEW_ALIGNMENT__ + if (alignment() > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { + ::operator delete(ptr +#ifdef __cpp_sized_deallocation + , + allocation_bytes +#endif + , + std::align_val_t(alignment())); + return; + } +#endif + ::operator delete(ptr); + } else { + void* mem = ptr; + ByteAllocator byte_alloc(*alloc); + std::allocator_traits<ByteAllocator>::deallocate( + byte_alloc, static_cast<char*>(mem), allocation_bytes); + } + } + + ChunkedQueueBlock* next() const { return next_; } + void set_next(ChunkedQueueBlock* next) { next_ = next; } + T* start() { + return reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(this) + + start_offset()); + } + T* limit() { return limit_; } + size_t size() { return limit() - start(); } + + static constexpr size_t block_size_from_bytes(size_t bytes) { + return bytes <= static_cast<size_t>(start_offset()) + ? size_t{1} + : elements_in_bytes(bytes - start_offset()); + } + + private: + ChunkedQueueBlock(const ChunkedQueueBlock&) = delete; + ChunkedQueueBlock& operator=(const ChunkedQueueBlock&) = delete; + + // The byte size to allocate to ensure space for `min_element_count` elements. + static constexpr size_t AllocSize(size_t min_element_count) { + return absl::container_internal::Layout<ChunkedQueueBlock, T>( + 1, min_element_count) + .AllocSize(); + } + + static constexpr ptrdiff_t start_offset() { + return absl::container_internal::Layout<ChunkedQueueBlock, T>(1, 1) + .template Offset<1>(); + } + + static constexpr size_t alignment() { + return absl::container_internal::Layout<ChunkedQueueBlock, T>(1, 1) + .Alignment(); + } + + static constexpr size_t elements_in_bytes(size_t bytes) { + return (bytes + sizeof(T) - 1) / sizeof(T); + } + + static std::pair<void*, size_t> Allocate(size_t allocation_bytes, + Allocator* alloc) { + // If we're using the default allocator, then we can use new. + void* mem; + if constexpr (std::is_same_v<ByteAllocator, std::allocator<char>>) { + // Older GCC versions have an unused variable warning on `alloc` inside + // this constexpr branch. + static_cast<void>(alloc); +#ifdef __STDCPP_DEFAULT_NEW_ALIGNMENT__ + if (alignment() > __STDCPP_DEFAULT_NEW_ALIGNMENT__) { + // Align the allocation to respect alignof(T). + mem = ::operator new(allocation_bytes, std::align_val_t(alignment())); + return {mem, allocation_bytes}; + } +#endif + mem = ::operator new(allocation_bytes); + } else { + ByteAllocator byte_alloc(*alloc); + mem = std::allocator_traits<ByteAllocator>::allocate(byte_alloc, + allocation_bytes); + } + return {mem, allocation_bytes}; + } + + ChunkedQueueBlock* next_; + T* limit_; +}; + +} // namespace container_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CONTAINER_INTERNAL_CHUNKED_QUEUE_H_
diff --git a/absl/container/internal/common.h b/absl/container/internal/common.h index 5ef6c56..3e263a3 100644 --- a/absl/container/internal/common.h +++ b/absl/container/internal/common.h
@@ -15,7 +15,10 @@ #ifndef ABSL_CONTAINER_INTERNAL_COMMON_H_ #define ABSL_CONTAINER_INTERNAL_COMMON_H_ +#include <algorithm> #include <cassert> +#include <cstddef> +#include <tuple> #include <type_traits> #include "absl/meta/type_traits.h" @@ -243,6 +246,54 @@ NodeType node; }; +// Utilities to strip redundant template parameters from the underlying +// implementation types. +// We use a variadic pack (ie Params...) to specify required prefix of types for +// non-default types, and then we use GetFromListOr to select the provided types +// or the default ones otherwise. +// +// These default types do not contribute information for debugging and just +// bloat the binary. +// Removing the redundant tail types reduces mangled names and stringified +// function names like __PRETTY_FUNCTION__. +// +// How to use: +// 1. Define a template with `typename ...Params` +// 2. Instantiate it via `ApplyWithoutDefaultSuffix<>` to only pass the minimal +// set of types. +// 3. Inside the template use `GetFromListOr` to map back from the existing +// `Params` list to the actual types, filling the gaps when types are +// missing. + +template <typename Or, size_t N, typename... Params> +using GetFromListOr = std::tuple_element_t<(std::min)(N, sizeof...(Params)), + std::tuple<Params..., Or>>; + +template <typename... T> +struct TypeList { + template <template <typename...> class Template> + using Apply = Template<T...>; +}; + +// Evaluate to `Template<TPrefix...>` where the last type in the list (if any) +// is different from the corresponding one in the default list. +// Eg +// ApplyWithoutDefaultSuffix<Template, TypeList<a, b, c>, TypeList<a, X, c>> +// evaluates to +// Template<a, X> +template <template <typename...> class Template, typename D, typename T, + typename L = TypeList<>, typename = void> +struct ApplyWithoutDefaultSuffix { + using type = typename L::template Apply<Template>; +}; +template <template <typename...> class Template, typename D, typename... Ds, + typename T, typename... Ts, typename... L> +struct ApplyWithoutDefaultSuffix< + Template, TypeList<D, Ds...>, TypeList<T, Ts...>, TypeList<L...>, + std::enable_if_t<!std::is_same_v<TypeList<D, Ds...>, TypeList<T, Ts...>>>> + : ApplyWithoutDefaultSuffix<Template, TypeList<Ds...>, TypeList<Ts...>, + TypeList<L..., T>> {}; + } // namespace container_internal ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/container/internal/compressed_tuple.h b/absl/container/internal/compressed_tuple.h index 6db0468..2dd8d6c 100644 --- a/absl/container/internal/compressed_tuple.h +++ b/absl/container/internal/compressed_tuple.h
@@ -64,24 +64,24 @@ template <typename D, size_t I> using ElemT = typename Elem<D, I>::type; -// We can't use EBCO on other CompressedTuples because that would mean that we -// derive from multiple Storage<> instantiations with the same I parameter, -// and potentially from multiple identical Storage<> instantiations. So anytime -// we use type inheritance rather than encapsulation, we mark -// CompressedTupleImpl, to make this easy to detect. -struct uses_inheritance {}; template <typename T> constexpr bool ShouldUseBase() { return std::is_class<T>::value && std::is_empty<T>::value && - !std::is_final<T>::value && - !std::is_base_of<uses_inheritance, T>::value; + !std::is_final<T>::value; } +// Tag type used to disambiguate Storage types for different CompresseedTuples. +// Without it, CompressedTuple<T, CompressedTuple<T>> would inherit from +// Storage<T, 0> twice. +template <typename... Ts> +struct StorageTag; + // The storage class provides two specializations: // - For empty classes, it stores T as a base class. // - For everything else, it stores T as a member. -template <typename T, size_t I, bool UseBase = ShouldUseBase<T>()> +// Tag should be set to StorageTag<Ts...>. +template <typename T, size_t I, typename Tag, bool UseBase = ShouldUseBase<T>()> struct Storage { T value; constexpr Storage() = default; @@ -94,8 +94,8 @@ constexpr T&& get() && { return std::move(*this).value; } }; -template <typename T, size_t I> -struct ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC Storage<T, I, true> : T { +template <typename T, size_t I, typename Tag> +struct ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC Storage<T, I, Tag, true> : T { constexpr Storage() = default; template <typename V> @@ -111,30 +111,35 @@ struct ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC CompressedTupleImpl; template <typename... Ts, size_t... I, bool ShouldAnyUseBase> -struct ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC CompressedTupleImpl< - CompressedTuple<Ts...>, absl::index_sequence<I...>, ShouldAnyUseBase> +struct ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC + CompressedTupleImpl<CompressedTuple<Ts...>, absl::index_sequence<I...>, + ShouldAnyUseBase> // We use the dummy identity function through std::integral_constant to // convince MSVC of accepting and expanding I in that context. Without it // you would get: // error C3548: 'I': parameter pack cannot be used in this context - : uses_inheritance, - Storage<Ts, std::integral_constant<size_t, I>::value>... { + : Storage<Ts, std::integral_constant<size_t, I>::value, + StorageTag<Ts...>>... { constexpr CompressedTupleImpl() = default; template <typename... Vs> explicit constexpr CompressedTupleImpl(absl::in_place_t, Vs&&... args) - : Storage<Ts, I>(absl::in_place, std::forward<Vs>(args))... {} + : Storage<Ts, I, StorageTag<Ts...>>(absl::in_place, + std::forward<Vs>(args))... {} friend CompressedTuple<Ts...>; }; template <typename... Ts, size_t... I> -struct ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC CompressedTupleImpl< - CompressedTuple<Ts...>, absl::index_sequence<I...>, false> +struct ABSL_INTERNAL_COMPRESSED_TUPLE_DECLSPEC + CompressedTupleImpl<CompressedTuple<Ts...>, absl::index_sequence<I...>, + false> // We use the dummy identity function as above... - : Storage<Ts, std::integral_constant<size_t, I>::value, false>... { + : Storage<Ts, std::integral_constant<size_t, I>::value, StorageTag<Ts...>, + false>... { constexpr CompressedTupleImpl() = default; template <typename... Vs> explicit constexpr CompressedTupleImpl(absl::in_place_t, Vs&&... args) - : Storage<Ts, I, false>(absl::in_place, std::forward<Vs>(args))... {} + : Storage<Ts, I, StorageTag<Ts...>, false>(absl::in_place, + std::forward<Vs>(args))... {} friend CompressedTuple<Ts...>; }; @@ -183,9 +188,7 @@ // Helper class to perform the Empty Base Class Optimization. // Ts can contain classes and non-classes, empty or not. For the ones that // are empty classes, we perform the CompressedTuple. If all types in Ts are -// empty classes, then CompressedTuple<Ts...> is itself an empty class. (This -// does not apply when one or more of those empty classes is itself an empty -// CompressedTuple.) +// empty classes, then CompressedTuple<Ts...> is itself an empty class. // // To access the members, use member .get<N>() function. // @@ -208,7 +211,8 @@ using ElemT = internal_compressed_tuple::ElemT<CompressedTuple, I>; template <int I> - using StorageT = internal_compressed_tuple::Storage<ElemT<I>, I>; + using StorageT = internal_compressed_tuple::Storage< + ElemT<I>, I, internal_compressed_tuple::StorageTag<Ts...>>; public: // There seems to be a bug in MSVC dealing in which using '=default' here will
diff --git a/absl/container/internal/compressed_tuple_test.cc b/absl/container/internal/compressed_tuple_test.cc index 01b334e..662f944 100644 --- a/absl/container/internal/compressed_tuple_test.cc +++ b/absl/container/internal/compressed_tuple_test.cc
@@ -452,14 +452,15 @@ } #endif -// TODO(b/214288561): enable this test. -TEST(CompressedTupleTest, DISABLED_NestedEbo) { +TEST(CompressedTupleTest, NestedEbo) { struct Empty1 {}; struct Empty2 {}; CompressedTuple<Empty1, CompressedTuple<Empty2>, int> x; CompressedTuple<Empty1, Empty2, int> y; - // Currently fails with sizeof(x) == 8, sizeof(y) == 4. EXPECT_EQ(sizeof(x), sizeof(y)); + + using NestedEmpty = CompressedTuple<Empty1, CompressedTuple<Empty2>>; + EXPECT_TRUE(std::is_empty_v<NestedEmpty>); } } // namespace
diff --git a/absl/container/internal/container_memory.h b/absl/container/internal/container_memory.h index e7ac1db..47064a7 100644 --- a/absl/container/internal/container_memory.h +++ b/absl/container/internal/container_memory.h
@@ -26,6 +26,7 @@ #include <utility> #include "absl/base/config.h" +#include "absl/hash/hash.h" #include "absl/memory/memory.h" #include "absl/meta/type_traits.h" #include "absl/utility/utility.h" @@ -43,7 +44,12 @@ namespace container_internal { template <size_t Alignment> -struct alignas(Alignment) AlignedType {}; +struct alignas(Alignment) AlignedType { + // When alignment is sufficient for the allocated memory to store pointers, + // include a pointer member so that swisstable backing arrays end up in the + // pointer-containing partition for heap partitioning. + std::conditional_t<(Alignment < alignof(void*)), char, void*> pointer; +}; // Allocates at least n bytes aligned to the specified alignment. // Alignment must be a power of 2. It must be positive. @@ -129,6 +135,7 @@ template <class T, size_t... Is> auto TupleRefImpl(T&& t, absl::index_sequence<Is...>) -> decltype(std::forward_as_tuple(std::get<Is>(std::forward<T>(t))...)) { + // NOLINTNEXTLINE(bugprone-use-after-move) return std::forward_as_tuple(std::get<Is>(std::forward<T>(t))...); } @@ -464,24 +471,87 @@ } }; +// Suppress erroneous uninitialized memory errors on GCC. For example, GCC +// thinks that the call to slot_array() in find_or_prepare_insert() is reading +// uninitialized memory, but slot_array is only called there when the table is +// non-empty and this memory is initialized when the table is non-empty. +#if !defined(__clang__) && defined(__GNUC__) +#define ABSL_SWISSTABLE_IGNORE_UNINITIALIZED(x) \ + _Pragma("GCC diagnostic push") \ + _Pragma("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") \ + _Pragma("GCC diagnostic ignored \"-Wuninitialized\"") x; \ + _Pragma("GCC diagnostic pop") +#define ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN(x) \ + ABSL_SWISSTABLE_IGNORE_UNINITIALIZED(return x) +#else +#define ABSL_SWISSTABLE_IGNORE_UNINITIALIZED(x) x +#define ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN(x) return x +#endif + +// Variadic arguments hash function that ignore the rest of the arguments. +// Useful for usage with policy traits. +template <class Hash, bool kIsDefault> +struct HashElement { + HashElement(const Hash& h, size_t s) : hash(h), seed(s) {} + + template <class K, class... Args> + size_t operator()(const K& key, Args&&...) const { + if constexpr (kIsDefault) { + // TODO(b/384509507): resolve `no header providing + // "absl::hash_internal::SupportsHashWithSeed" is directly included`. + // Maybe we should make "internal/hash.h" be a separate library. + return absl::hash_internal::HashWithSeed().hash(hash, key, seed); + } + // NOLINTNEXTLINE(clang-diagnostic-sign-conversion) + return hash(key) ^ seed; + } + const Hash& hash; + size_t seed; +}; + +// No arguments function hash function for a specific key. +template <class Hash, class Key, bool kIsDefault> +struct HashKey { + HashKey(const Hash& h, const Key& k) : hash(h), key(k) {} + + size_t operator()(size_t seed) const { + return HashElement<Hash, kIsDefault>{hash, seed}(key); + } + const Hash& hash; + const Key& key; +}; + +// Variadic arguments equality function that ignore the rest of the arguments. +// Useful for usage with policy traits. +template <class K1, class KeyEqual> +struct EqualElement { + template <class K2, class... Args> + bool operator()(const K2& lhs, Args&&...) const { + ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN(eq(lhs, rhs)); + } + const K1& rhs; + const KeyEqual& eq; +}; + // Type erased function for computing hash of the slot. -using HashSlotFn = size_t (*)(const void* hash_fn, void* slot); +using HashSlotFn = size_t (*)(const void* hash_fn, void* slot, size_t seed); // Type erased function to apply `Fn` to data inside of the `slot`. // The data is expected to have type `T`. -template <class Fn, class T> -size_t TypeErasedApplyToSlotFn(const void* fn, void* slot) { +template <class Fn, class T, bool kIsDefault> +size_t TypeErasedApplyToSlotFn(const void* fn, void* slot, size_t seed) { const auto* f = static_cast<const Fn*>(fn); - return (*f)(*static_cast<const T*>(slot)); + return HashElement<Fn, kIsDefault>{*f, seed}(*static_cast<const T*>(slot)); } // Type erased function to apply `Fn` to data inside of the `*slot_ptr`. // The data is expected to have type `T`. -template <class Fn, class T> -size_t TypeErasedDerefAndApplyToSlotFn(const void* fn, void* slot_ptr) { +template <class Fn, class T, bool kIsDefault> +size_t TypeErasedDerefAndApplyToSlotFn(const void* fn, void* slot_ptr, + size_t seed) { const auto* f = static_cast<const Fn*>(fn); const T* slot = *static_cast<const T**>(slot_ptr); - return (*f)(*slot); + return HashElement<Fn, kIsDefault>{*f, seed}(*slot); } } // namespace container_internal
diff --git a/absl/container/internal/container_memory_test.cc b/absl/container/internal/container_memory_test.cc index 7e4357d..946d1d3 100644 --- a/absl/container/internal/container_memory_test.cc +++ b/absl/container/internal/container_memory_test.cc
@@ -25,6 +25,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/base/config.h" #include "absl/base/no_destructor.h" #include "absl/container/internal/test_instance_tracker.h" #include "absl/meta/type_traits.h" @@ -42,6 +43,16 @@ using ::testing::Gt; using ::testing::Pair; +#if ABSL_HAVE_BUILTIN(__builtin_infer_alloc_token) +TEST(Memory, AlignedTypeAllocToken) { +#if defined(__wasm__) + GTEST_SKIP() << "Fails on wasm due to lack of heap partitioning support."; +#endif + EXPECT_GT(__builtin_infer_alloc_token(sizeof(AlignedType<alignof(void*)>)), + __builtin_infer_alloc_token(sizeof(int))); +} +#endif + TEST(Memory, AlignmentLargerThanBase) { std::allocator<int8_t> alloc; void* mem = Allocate<2>(&alloc, 3); @@ -300,16 +311,46 @@ TEST(ApplyTest, TypeErasedApplyToSlotFn) { size_t x = 7; + size_t seed = 100; auto fn = [](size_t v) { return v * 2; }; - EXPECT_EQ((TypeErasedApplyToSlotFn<decltype(fn), size_t>(&fn, &x)), 14); + EXPECT_EQ( + (TypeErasedApplyToSlotFn<decltype(fn), size_t, /*kIsDefault=*/false>( + &fn, &x, seed)), + (HashElement<decltype(fn), /*kIsDefault=*/false>(fn, seed)(x))); } TEST(ApplyTest, TypeErasedDerefAndApplyToSlotFn) { size_t x = 7; + size_t seed = 100; auto fn = [](size_t v) { return v * 2; }; size_t* x_ptr = &x; + EXPECT_EQ((TypeErasedDerefAndApplyToSlotFn<decltype(fn), size_t, + /*kIsDefault=*/false>(&fn, &x_ptr, + seed)), + (HashElement<decltype(fn), /*kIsDefault=*/false>(fn, seed)(x))); +} + +TEST(HashElement, DefaultHash) { + size_t x = 7; + size_t seed = 100; + struct HashWithSeed { + size_t operator()(size_t v) const { return v * 2; } + size_t hash_with_seed(size_t v, size_t seed) const { + return v * 2 + seed * 3; + } + } hash; + EXPECT_EQ((HashElement<HashWithSeed, /*kIsDefault=*/true>(hash, seed)(x)), + hash.hash_with_seed(x, seed)); +} + +TEST(HashElement, NonDefaultHash) { + size_t x = 7; + size_t seed = 100; + auto fn = [](size_t v) { return v * 2; }; EXPECT_EQ( - (TypeErasedDerefAndApplyToSlotFn<decltype(fn), size_t>(&fn, &x_ptr)), 14); + (HashElement<decltype(fn), /*kIsDefault=*/false>( + fn, seed)(x)), + fn(x) ^ seed); } } // namespace
diff --git a/absl/container/internal/hash_function_defaults.h b/absl/container/internal/hash_function_defaults.h index c2a757b..eefecab 100644 --- a/absl/container/internal/hash_function_defaults.h +++ b/absl/container/internal/hash_function_defaults.h
@@ -79,6 +79,18 @@ size_t operator()(const absl::Cord& v) const { return absl::Hash<absl::Cord>{}(v); } + + private: + friend struct absl::hash_internal::HashWithSeed; + + size_t hash_with_seed(absl::string_view v, size_t seed) const { + return absl::hash_internal::HashWithSeed().hash( + absl::Hash<absl::string_view>{}, v, seed); + } + size_t hash_with_seed(const absl::Cord& v, size_t seed) const { + return absl::hash_internal::HashWithSeed().hash(absl::Hash<absl::Cord>{}, v, + seed); + } }; struct StringEq {
diff --git a/absl/container/internal/hash_generator_testing.cc b/absl/container/internal/hash_generator_testing.cc index be20e21..4ae58da 100644 --- a/absl/container/internal/hash_generator_testing.cc +++ b/absl/container/internal/hash_generator_testing.cc
@@ -26,7 +26,6 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace container_internal { -namespace hash_internal { std::string Generator<std::string>::operator()() const { absl::InsecureBitGen gen; @@ -50,7 +49,6 @@ return res; } -} // namespace hash_internal } // namespace container_internal ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/container/internal/hash_generator_testing.h b/absl/container/internal/hash_generator_testing.h index 14c878e..4c5d87b 100644 --- a/absl/container/internal/hash_generator_testing.h +++ b/absl/container/internal/hash_generator_testing.h
@@ -31,6 +31,7 @@ #include <utility> #include <vector> +#include "absl/base/config.h" #include "absl/container/internal/hash_policy_testing.h" #include "absl/memory/memory.h" #include "absl/meta/type_traits.h" @@ -40,7 +41,6 @@ namespace absl { ABSL_NAMESPACE_BEGIN namespace container_internal { -namespace hash_internal { namespace generator_internal { template <class Container, class = void> @@ -165,7 +165,6 @@ } }; -} // namespace hash_internal } // namespace container_internal ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/container/internal/hash_policy_testing.h b/absl/container/internal/hash_policy_testing.h index e9f5757..86ea96a 100644 --- a/absl/container/internal/hash_policy_testing.h +++ b/absl/container/internal/hash_policy_testing.h
@@ -170,18 +170,4 @@ ABSL_NAMESPACE_END } // namespace absl -// ABSL_UNORDERED_SUPPORTS_ALLOC_CTORS is false for glibcxx versions -// where the unordered containers are missing certain constructors that -// take allocator arguments. This test is defined ad-hoc for the platforms -// we care about (notably Crosstool 17) because libstdcxx's useless -// versioning scheme precludes a more principled solution. -// From GCC-4.9 Changelog: (src: https://gcc.gnu.org/gcc-4.9/changes.html) -// "the unordered associative containers in <unordered_map> and <unordered_set> -// meet the allocator-aware container requirements;" -#if defined(__GLIBCXX__) && __GLIBCXX__ <= 20140425 -#define ABSL_UNORDERED_SUPPORTS_ALLOC_CTORS 0 -#else -#define ABSL_UNORDERED_SUPPORTS_ALLOC_CTORS 1 -#endif - #endif // ABSL_CONTAINER_INTERNAL_HASH_POLICY_TESTING_H_
diff --git a/absl/container/internal/hash_policy_traits.h b/absl/container/internal/hash_policy_traits.h index cd6b42f..82eed2a 100644 --- a/absl/container/internal/hash_policy_traits.h +++ b/absl/container/internal/hash_policy_traits.h
@@ -22,6 +22,7 @@ #include <utility> #include "absl/container/internal/common_policy_traits.h" +#include "absl/container/internal/container_memory.h" #include "absl/meta/type_traits.h" namespace absl { @@ -145,9 +146,7 @@ return P::value(elem); } - using HashSlotFn = size_t (*)(const void* hash_fn, void* slot); - - template <class Hash> + template <class Hash, bool kIsDefault> static constexpr HashSlotFn get_hash_slot_fn() { // get_hash_slot_fn may return nullptr to signal that non type erased function // should be used. GCC warns against comparing function address with nullptr. @@ -156,9 +155,9 @@ // silent error: the address of * will never be NULL [-Werror=address] #pragma GCC diagnostic ignored "-Waddress" #endif - return Policy::template get_hash_slot_fn<Hash>() == nullptr - ? &hash_slot_fn_non_type_erased<Hash> - : Policy::template get_hash_slot_fn<Hash>(); + return Policy::template get_hash_slot_fn<Hash, kIsDefault>() == nullptr + ? &hash_slot_fn_non_type_erased<Hash, kIsDefault> + : Policy::template get_hash_slot_fn<Hash, kIsDefault>(); #if defined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic pop #endif @@ -168,19 +167,12 @@ static constexpr bool soo_enabled() { return soo_enabled_impl(Rank1{}); } private: - template <class Hash> - struct HashElement { - template <class K, class... Args> - size_t operator()(const K& key, Args&&...) const { - return h(key); - } - const Hash& h; - }; - - template <class Hash> - static size_t hash_slot_fn_non_type_erased(const void* hash_fn, void* slot) { - return Policy::apply(HashElement<Hash>{*static_cast<const Hash*>(hash_fn)}, - Policy::element(static_cast<slot_type*>(slot))); + template <class Hash, bool kIsDefault> + static size_t hash_slot_fn_non_type_erased(const void* hash_fn, void* slot, + size_t seed) { + return Policy::apply( + HashElement<Hash, kIsDefault>{*static_cast<const Hash*>(hash_fn), seed}, + Policy::element(static_cast<slot_type*>(slot))); } // Use go/ranked-overloads for dispatching. Rank1 is preferred.
diff --git a/absl/container/internal/hash_policy_traits_test.cc b/absl/container/internal/hash_policy_traits_test.cc index 2d2c7c2..03de132 100644 --- a/absl/container/internal/hash_policy_traits_test.cc +++ b/absl/container/internal/hash_policy_traits_test.cc
@@ -45,7 +45,7 @@ static std::function<int(int)> apply_impl; static std::function<Slot&(Slot*)> value; - template <class Hash> + template <class Hash, bool kIsDefault> static constexpr HashSlotFn get_hash_slot_fn() { return nullptr; } @@ -99,7 +99,7 @@ return fn(v); } - template <class Hash> + template <class Hash, bool kIsDefault> static constexpr HashSlotFn get_hash_slot_fn() { return nullptr; } @@ -108,9 +108,9 @@ size_t* PolicyNoHashFn::apply_called_count; struct PolicyCustomHashFn : PolicyNoHashFn { - template <class Hash> + template <class Hash, bool kIsDefault> static constexpr HashSlotFn get_hash_slot_fn() { - return &TypeErasedApplyToSlotFn<Hash, int>; + return &TypeErasedApplyToSlotFn<Hash, int, kIsDefault>; } }; @@ -120,9 +120,11 @@ Hash hasher; Slot value = 7; - auto* fn = hash_policy_traits<PolicyNoHashFn>::get_hash_slot_fn<Hash>(); + auto* fn = hash_policy_traits<PolicyNoHashFn>::get_hash_slot_fn< + Hash, /*kIsDefault=*/false>(); EXPECT_NE(fn, nullptr); - EXPECT_EQ(fn(&hasher, &value), hasher(value)); + EXPECT_EQ(fn(&hasher, &value, 100), + (HashElement<Hash, /*kIsDefault=*/false>(hasher, 100)(value))); EXPECT_EQ(apply_called_count, 1); } @@ -132,9 +134,12 @@ Hash hasher; Slot value = 7; - auto* fn = hash_policy_traits<PolicyCustomHashFn>::get_hash_slot_fn<Hash>(); - EXPECT_EQ(fn, PolicyCustomHashFn::get_hash_slot_fn<Hash>()); - EXPECT_EQ(fn(&hasher, &value), hasher(value)); + auto* fn = hash_policy_traits<PolicyCustomHashFn>::get_hash_slot_fn< + Hash, /*kIsDefault=*/false>(); + EXPECT_EQ( + fn, (PolicyCustomHashFn::get_hash_slot_fn<Hash, /*kIsDefault=*/false>())); + EXPECT_EQ(fn(&hasher, &value, 100), + (HashElement<Hash, /*kIsDefault=*/false>(hasher, 100)(value))); EXPECT_EQ(apply_called_count, 0); }
diff --git a/absl/container/internal/hashtable_control_bytes.h b/absl/container/internal/hashtable_control_bytes.h index abaadc3..f0fd354 100644 --- a/absl/container/internal/hashtable_control_bytes.h +++ b/absl/container/internal/hashtable_control_bytes.h
@@ -91,11 +91,6 @@ return container_internal::TrailingZeros(mask_) >> Shift; } - // Returns the index of the highest *abstract* bit set in `self`. - uint32_t HighestBitSet() const { - return static_cast<uint32_t>((bit_width(mask_) - 1) >> Shift); - } - // Returns the number of trailing zero *abstract* bits. uint32_t TrailingZeros() const { return container_internal::TrailingZeros(mask_) >> Shift; @@ -217,6 +212,9 @@ static_assert(ctrl_t::kDeleted == static_cast<ctrl_t>(-2), "ctrl_t::kDeleted must be -2 to make the implementation of " "ConvertSpecialToEmptyAndFullToDeleted efficient"); +static_assert(ctrl_t::kEmpty == static_cast<ctrl_t>(-128), + "ctrl_t::kEmpty must be -128 to use saturated subtraction in" + " ConvertSpecialToEmptyAndFullToDeleted"); // Helpers for checking the state of a control byte. inline bool IsEmpty(ctrl_t c) { return c == ctrl_t::kEmpty; } @@ -324,23 +322,25 @@ _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<char>(ctrl_t::kSentinel)); - return TrailingZeros(static_cast<uint32_t>( - _mm_movemask_epi8(_mm_cmpgt_epi8_fixed(special, ctrl)) + 1)); + // Returns a bitmask representing the positions of full or sentinel slots. + // Note: for `is_small()` tables group may contain the "same" slot twice: + // original and mirrored. + NonIterableBitMaskType MaskFullOrSentinel() const { + auto special = _mm_set1_epi8(static_cast<char>(ctrl_t::kSentinel) - 1); + return NonIterableBitMaskType(static_cast<uint16_t>( + _mm_movemask_epi8(_mm_cmpgt_epi8_fixed(ctrl, special)))); } void ConvertSpecialToEmptyAndFullToDeleted(ctrl_t* dst) const { + // Take advantage of the fact that kEmpty is already the smallest signed + // char value, and using a saturated subtraction will not affect it. + // All special values have the MSB set, so after an AND with MSBS, we + // are left with -128 for special values and 0 for full. After applying + // subs 2, we arrive at the result of -128(kEmpty) for special and + // -2(kDeleted) for full. auto msbs = _mm_set1_epi8(static_cast<char>(-128)); - auto x126 = _mm_set1_epi8(126); -#ifdef ABSL_INTERNAL_HAVE_SSSE3 - auto res = _mm_or_si128(_mm_shuffle_epi8(x126, ctrl), msbs); -#else - auto zero = _mm_setzero_si128(); - auto special_mask = _mm_cmpgt_epi8_fixed(zero, ctrl); - auto res = _mm_or_si128(msbs, _mm_andnot_si128(special_mask, x126)); -#endif + auto twos = _mm_set1_epi8(static_cast<char>(2)); + auto res = _mm_subs_epi8(_mm_and_si128(msbs, ctrl), twos); _mm_storeu_si128(reinterpret_cast<__m128i*>(dst), res); } @@ -406,17 +406,13 @@ return NonIterableBitMaskType(mask); } - uint32_t CountLeadingEmptyOrDeleted() const { - 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. - return static_cast<uint32_t>(countr_zero(mask)) >> 3; + NonIterableBitMaskType MaskFullOrSentinel() const { + uint64_t mask = vget_lane_u64( + vreinterpret_u64_u8( + vcgt_s8(vreinterpret_s8_u8(ctrl), + vdup_n_s8(static_cast<int8_t>(ctrl_t::kSentinel) - 1))), + 0); + return NonIterableBitMaskType(mask); } void ConvertSpecialToEmptyAndFullToDeleted(ctrl_t* dst) const { @@ -481,12 +477,8 @@ return NonIterableBitMaskType((ctrl & ~(ctrl << 7)) & kMsbs8Bytes); } - uint32_t CountLeadingEmptyOrDeleted() const { - // 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 static_cast<uint32_t>(countr_zero((ctrl | ~(ctrl >> 7)) & bits) >> - 3); + auto MaskFullOrSentinel() const { + return NonIterableBitMaskType((~ctrl | (ctrl << 7)) & kMsbs8Bytes); } void ConvertSpecialToEmptyAndFullToDeleted(ctrl_t* dst) const {
diff --git a/absl/container/internal/hashtable_control_bytes_test.cc b/absl/container/internal/hashtable_control_bytes_test.cc new file mode 100644 index 0000000..a4aa3a9 --- /dev/null +++ b/absl/container/internal/hashtable_control_bytes_test.cc
@@ -0,0 +1,259 @@ +// Copyright 2025 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/hashtable_control_bytes.h" + +#include <array> +#include <cstddef> +#include <cstdint> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace container_internal { +namespace { + +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; + +// Convenience function to static cast to ctrl_t. +ctrl_t CtrlT(int i) { return static_cast<ctrl_t>(i); } + +TEST(BitMask, Smoke) { + EXPECT_FALSE((BitMask<uint8_t, 8>(0))); + EXPECT_TRUE((BitMask<uint8_t, 8>(5))); + + EXPECT_THAT((BitMask<uint8_t, 8>(0)), ElementsAre()); + EXPECT_THAT((BitMask<uint8_t, 8>(0x1)), ElementsAre(0)); + EXPECT_THAT((BitMask<uint8_t, 8>(0x2)), ElementsAre(1)); + EXPECT_THAT((BitMask<uint8_t, 8>(0x3)), ElementsAre(0, 1)); + EXPECT_THAT((BitMask<uint8_t, 8>(0x4)), ElementsAre(2)); + EXPECT_THAT((BitMask<uint8_t, 8>(0x5)), ElementsAre(0, 2)); + EXPECT_THAT((BitMask<uint8_t, 8>(0x55)), ElementsAre(0, 2, 4, 6)); + EXPECT_THAT((BitMask<uint8_t, 8>(0xAA)), ElementsAre(1, 3, 5, 7)); +} + +TEST(BitMask, WithShift_MatchPortable) { + // See the non-SSE version of Group for details on what this math is for. + uint64_t ctrl = 0x1716151413121110; + uint64_t hash = 0x12; + constexpr uint64_t lsbs = 0x0101010101010101ULL; + auto x = ctrl ^ (lsbs * hash); + uint64_t mask = (x - lsbs) & ~x & kMsbs8Bytes; + EXPECT_EQ(0x0000000080800000, mask); + + BitMask<uint64_t, 8, 3> b(mask); + EXPECT_EQ(*b, 2); +} + +constexpr uint64_t kSome8BytesMask = /* */ 0x8000808080008000ULL; +constexpr uint64_t kSome8BytesMaskAllOnes = 0xff00ffffff00ff00ULL; +constexpr auto kSome8BytesMaskBits = std::array<int, 5>{1, 3, 4, 5, 7}; + +TEST(BitMask, WithShift_FullMask) { + EXPECT_THAT((BitMask<uint64_t, 8, 3>(kMsbs8Bytes)), + ElementsAre(0, 1, 2, 3, 4, 5, 6, 7)); + EXPECT_THAT( + (BitMask<uint64_t, 8, 3, /*NullifyBitsOnIteration=*/true>(kMsbs8Bytes)), + ElementsAre(0, 1, 2, 3, 4, 5, 6, 7)); + EXPECT_THAT( + (BitMask<uint64_t, 8, 3, /*NullifyBitsOnIteration=*/true>(~uint64_t{0})), + ElementsAre(0, 1, 2, 3, 4, 5, 6, 7)); +} + +TEST(BitMask, WithShift_EmptyMask) { + EXPECT_THAT((BitMask<uint64_t, 8, 3>(0)), ElementsAre()); + EXPECT_THAT((BitMask<uint64_t, 8, 3, /*NullifyBitsOnIteration=*/true>(0)), + ElementsAre()); +} + +TEST(BitMask, WithShift_SomeMask) { + EXPECT_THAT((BitMask<uint64_t, 8, 3>(kSome8BytesMask)), + ElementsAreArray(kSome8BytesMaskBits)); + EXPECT_THAT((BitMask<uint64_t, 8, 3, /*NullifyBitsOnIteration=*/true>( + kSome8BytesMask)), + ElementsAreArray(kSome8BytesMaskBits)); + EXPECT_THAT((BitMask<uint64_t, 8, 3, /*NullifyBitsOnIteration=*/true>( + kSome8BytesMaskAllOnes)), + ElementsAreArray(kSome8BytesMaskBits)); +} + +TEST(BitMask, WithShift_SomeMaskExtraBitsForNullify) { + // Verify that adding extra bits into non zero bytes is fine. + uint64_t extra_bits = 77; + for (int i = 0; i < 100; ++i) { + // Add extra bits, but keep zero bytes untouched. + uint64_t extra_mask = extra_bits & kSome8BytesMaskAllOnes; + EXPECT_THAT((BitMask<uint64_t, 8, 3, /*NullifyBitsOnIteration=*/true>( + kSome8BytesMask | extra_mask)), + ElementsAreArray(kSome8BytesMaskBits)) + << i << " " << extra_mask; + extra_bits = (extra_bits + 1) * 3; + } +} + +TEST(BitMask, LeadingTrailing) { + EXPECT_EQ((BitMask<uint32_t, 16>(0x00001a40).LeadingZeros()), 3); + EXPECT_EQ((BitMask<uint32_t, 16>(0x00001a40).TrailingZeros()), 6); + + EXPECT_EQ((BitMask<uint32_t, 16>(0x00000001).LeadingZeros()), 15); + EXPECT_EQ((BitMask<uint32_t, 16>(0x00000001).TrailingZeros()), 0); + + EXPECT_EQ((BitMask<uint32_t, 16>(0x00008000).LeadingZeros()), 0); + EXPECT_EQ((BitMask<uint32_t, 16>(0x00008000).TrailingZeros()), 15); + + EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x0000008080808000).LeadingZeros()), 3); + EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x0000008080808000).TrailingZeros()), 1); + + EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x0000000000000080).LeadingZeros()), 7); + EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x0000000000000080).TrailingZeros()), 0); + + EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x8000000000000000).LeadingZeros()), 0); + EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x8000000000000000).TrailingZeros()), 7); +} + +template <class GroupTypeParam> +class GroupTest : public testing::Test {}; +using GroupTypes = + ::testing::Types<Group, GroupPortableImpl, GroupFullEmptyOrDeleted>; +TYPED_TEST_SUITE(GroupTest, GroupTypes); + +TYPED_TEST(GroupTest, Match) { + using GroupType = TypeParam; + if (GroupType::kWidth == 16) { + ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), ctrl_t::kDeleted, CtrlT(3), + ctrl_t::kEmpty, CtrlT(5), ctrl_t::kSentinel, CtrlT(7), + CtrlT(7), CtrlT(5), CtrlT(3), CtrlT(1), + CtrlT(1), CtrlT(1), CtrlT(1), CtrlT(1)}; + EXPECT_THAT(GroupType{group}.Match(0), ElementsAre()); + EXPECT_THAT(GroupType{group}.Match(1), ElementsAre(1, 11, 12, 13, 14, 15)); + EXPECT_THAT(GroupType{group}.Match(3), ElementsAre(3, 10)); + EXPECT_THAT(GroupType{group}.Match(5), ElementsAre(5, 9)); + EXPECT_THAT(GroupType{group}.Match(7), ElementsAre(7, 8)); + } else if (GroupType::kWidth == 8) { + ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), CtrlT(2), + ctrl_t::kDeleted, CtrlT(2), CtrlT(1), + ctrl_t::kSentinel, CtrlT(1)}; + EXPECT_THAT(GroupType{group}.Match(0), ElementsAre()); + EXPECT_THAT(GroupType{group}.Match(1), ElementsAre(1, 5, 7)); + EXPECT_THAT(GroupType{group}.Match(2), ElementsAre(2, 4)); + } else { + FAIL() << "No test coverage for Group::kWidth==" << GroupType::kWidth; + } +} + +TYPED_TEST(GroupTest, MaskEmpty) { + using GroupType = TypeParam; + if (GroupType::kWidth == 16) { + ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), ctrl_t::kDeleted, CtrlT(3), + ctrl_t::kEmpty, CtrlT(5), ctrl_t::kSentinel, CtrlT(7), + CtrlT(7), CtrlT(5), CtrlT(3), CtrlT(1), + CtrlT(1), CtrlT(1), CtrlT(1), CtrlT(1)}; + EXPECT_THAT(GroupType{group}.MaskEmpty().LowestBitSet(), 0); + } else if (GroupType::kWidth == 8) { + ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), CtrlT(2), + ctrl_t::kDeleted, CtrlT(2), CtrlT(1), + ctrl_t::kSentinel, CtrlT(1)}; + EXPECT_THAT(GroupType{group}.MaskEmpty().LowestBitSet(), 0); + } else { + FAIL() << "No test coverage for Group::kWidth==" << GroupType::kWidth; + } +} + +TYPED_TEST(GroupTest, MaskFull) { + using GroupType = TypeParam; + if (GroupType::kWidth == 16) { + ctrl_t group[] = { + ctrl_t::kEmpty, CtrlT(1), ctrl_t::kDeleted, CtrlT(3), + ctrl_t::kEmpty, CtrlT(5), ctrl_t::kSentinel, CtrlT(7), + CtrlT(7), CtrlT(5), ctrl_t::kDeleted, CtrlT(1), + CtrlT(1), ctrl_t::kSentinel, ctrl_t::kEmpty, CtrlT(1)}; + EXPECT_THAT(GroupType{group}.MaskFull(), + ElementsAre(1, 3, 5, 7, 8, 9, 11, 12, 15)); + } else if (GroupType::kWidth == 8) { + ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), ctrl_t::kEmpty, + ctrl_t::kDeleted, CtrlT(2), ctrl_t::kSentinel, + ctrl_t::kSentinel, CtrlT(1)}; + EXPECT_THAT(GroupType{group}.MaskFull(), ElementsAre(1, 4, 7)); + } else { + FAIL() << "No test coverage for Group::kWidth==" << GroupType::kWidth; + } +} + +TYPED_TEST(GroupTest, MaskNonFull) { + using GroupType = TypeParam; + if (GroupType::kWidth == 16) { + ctrl_t group[] = { + ctrl_t::kEmpty, CtrlT(1), ctrl_t::kDeleted, CtrlT(3), + ctrl_t::kEmpty, CtrlT(5), ctrl_t::kSentinel, CtrlT(7), + CtrlT(7), CtrlT(5), ctrl_t::kDeleted, CtrlT(1), + CtrlT(1), ctrl_t::kSentinel, ctrl_t::kEmpty, CtrlT(1)}; + EXPECT_THAT(GroupType{group}.MaskNonFull(), + ElementsAre(0, 2, 4, 6, 10, 13, 14)); + } else if (GroupType::kWidth == 8) { + ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), ctrl_t::kEmpty, + ctrl_t::kDeleted, CtrlT(2), ctrl_t::kSentinel, + ctrl_t::kSentinel, CtrlT(1)}; + EXPECT_THAT(GroupType{group}.MaskNonFull(), ElementsAre(0, 2, 3, 5, 6)); + } else { + FAIL() << "No test coverage for Group::kWidth==" << GroupType::kWidth; + } +} + +TYPED_TEST(GroupTest, MaskEmptyOrDeleted) { + using GroupType = TypeParam; + if (GroupType::kWidth == 16) { + ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), ctrl_t::kEmpty, CtrlT(3), + ctrl_t::kDeleted, CtrlT(5), ctrl_t::kSentinel, CtrlT(7), + CtrlT(7), CtrlT(5), CtrlT(3), CtrlT(1), + CtrlT(1), CtrlT(1), CtrlT(1), CtrlT(1)}; + EXPECT_THAT(GroupType{group}.MaskEmptyOrDeleted().LowestBitSet(), 0); + } else if (GroupType::kWidth == 8) { + ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), CtrlT(2), + ctrl_t::kDeleted, CtrlT(2), CtrlT(1), + ctrl_t::kSentinel, CtrlT(1)}; + EXPECT_THAT(GroupType{group}.MaskEmptyOrDeleted().LowestBitSet(), 0); + } else { + FAIL() << "No test coverage for Group::kWidth==" << GroupType::kWidth; + } +} + +TYPED_TEST(GroupTest, MaskFullOrSentinel) { + using GroupType = TypeParam; + if (GroupType::kWidth == 16) { + ctrl_t group[] = { + ctrl_t::kEmpty, ctrl_t::kDeleted, ctrl_t::kEmpty, CtrlT(3), + ctrl_t::kDeleted, CtrlT(5), ctrl_t::kSentinel, ctrl_t::kEmpty, + ctrl_t::kEmpty, ctrl_t::kDeleted, ctrl_t::kDeleted, ctrl_t::kDeleted, + ctrl_t::kEmpty, ctrl_t::kDeleted, ctrl_t::kDeleted, ctrl_t::kDeleted, + }; + EXPECT_THAT(GroupType{group}.MaskFullOrSentinel().LowestBitSet(), 3); + } else if (GroupType::kWidth == 8) { + ctrl_t group[] = {ctrl_t::kEmpty, ctrl_t::kDeleted, CtrlT(2), + ctrl_t::kDeleted, CtrlT(2), ctrl_t::kSentinel, + ctrl_t::kDeleted, ctrl_t::kEmpty}; + EXPECT_THAT(GroupType{group}.MaskFullOrSentinel().LowestBitSet(), 2); + } else { + FAIL() << "No test coverage for Group::kWidth==" << GroupType::kWidth; + } +} + +} // namespace +} // namespace container_internal +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/container/internal/hashtablez_sampler.cc b/absl/container/internal/hashtablez_sampler.cc index c0fce87..4f6e946 100644 --- a/absl/container/internal/hashtablez_sampler.cc +++ b/absl/container/internal/hashtablez_sampler.cc
@@ -81,12 +81,12 @@ capacity.store(0, std::memory_order_relaxed); size.store(0, std::memory_order_relaxed); num_erases.store(0, std::memory_order_relaxed); + num_insert_hits.store(0, std::memory_order_relaxed); num_rehashes.store(0, std::memory_order_relaxed); max_probe_length.store(0, std::memory_order_relaxed); total_probe_length.store(0, std::memory_order_relaxed); hashes_bitwise_or.store(0, std::memory_order_relaxed); hashes_bitwise_and.store(~size_t{}, std::memory_order_relaxed); - hashes_bitwise_xor.store(0, std::memory_order_relaxed); max_reserve.store(0, std::memory_order_relaxed); create_time = absl::Now(); @@ -94,8 +94,9 @@ // The inliner makes hardcoded skip_count difficult (especially when combined // with LTO). We use the ability to exclude stacks by regex when encoding // instead. - depth = absl::GetStackTrace(stack, HashtablezInfo::kMaxStackDepth, - /* skip_count= */ 0); + depth = static_cast<unsigned>( + absl::GetStackTrace(stack, HashtablezInfo::kMaxStackDepth, + /* skip_count= */ 0)); inline_element_size = inline_element_size_value; key_size = key_size_value; value_size = value_size_value; @@ -229,8 +230,8 @@ } } -void RecordInsertSlow(HashtablezInfo* info, size_t hash, - size_t distance_from_desired) { +void RecordInsertMissSlow(HashtablezInfo* info, size_t hash, + size_t distance_from_desired) { // SwissTables probe in groups of 16, so scale this to count items probes and // not offset from desired. size_t probe_length = distance_from_desired; @@ -242,7 +243,6 @@ info->hashes_bitwise_and.fetch_and(hash, std::memory_order_relaxed); info->hashes_bitwise_or.fetch_or(hash, std::memory_order_relaxed); - info->hashes_bitwise_xor.fetch_xor(hash, std::memory_order_relaxed); info->max_probe_length.store( std::max(info->max_probe_length.load(std::memory_order_relaxed), probe_length),
diff --git a/absl/container/internal/hashtablez_sampler.h b/absl/container/internal/hashtablez_sampler.h index 305dc85..6c20dc3 100644 --- a/absl/container/internal/hashtablez_sampler.h +++ b/absl/container/internal/hashtablez_sampler.h
@@ -82,12 +82,12 @@ std::atomic<size_t> capacity; std::atomic<size_t> size; std::atomic<size_t> num_erases; + std::atomic<size_t> num_insert_hits; std::atomic<size_t> num_rehashes; std::atomic<size_t> max_probe_length; std::atomic<size_t> total_probe_length; std::atomic<size_t> hashes_bitwise_or; std::atomic<size_t> hashes_bitwise_and; - std::atomic<size_t> hashes_bitwise_xor; std::atomic<size_t> max_reserve; // All of the fields below are set by `PrepareForSampling`, they must not be @@ -97,7 +97,7 @@ // the lock. static constexpr int kMaxStackDepth = 64; absl::Time create_time; - int32_t depth; + uint32_t depth; // The SOO capacity for this table in elements (not bytes). Note that sampled // tables are never SOO because we need to store the infoz handle on the heap. // Tables that would be SOO if not sampled should have: soo_capacity > 0 && @@ -111,6 +111,16 @@ void RecordRehashSlow(HashtablezInfo* info, size_t total_probe_length); +// This is inline to avoid calling convention overhead for an otherwise +// lightweight operation. +inline void RecordInsertHitSlow(HashtablezInfo* info) { + // We avoid fetch_add since no other thread should be mutating the table + // simultaneously without synchronization. + info->num_insert_hits.store( + info->num_insert_hits.load(std::memory_order_relaxed) + 1, + std::memory_order_relaxed); +} + void RecordReservationSlow(HashtablezInfo* info, size_t target_capacity); void RecordClearedReservationSlow(HashtablezInfo* info); @@ -118,8 +128,8 @@ void RecordStorageChangedSlow(HashtablezInfo* info, size_t size, size_t capacity); -void RecordInsertSlow(HashtablezInfo* info, size_t hash, - size_t distance_from_desired); +void RecordInsertMissSlow(HashtablezInfo* info, size_t hash, + size_t distance_from_desired); void RecordEraseSlow(HashtablezInfo* info); @@ -174,9 +184,9 @@ RecordClearedReservationSlow(info_); } - inline void RecordInsert(size_t hash, size_t distance_from_desired) { + inline void RecordInsertMiss(size_t hash, size_t distance_from_desired) { if (ABSL_PREDICT_TRUE(info_ == nullptr)) return; - RecordInsertSlow(info_, hash, distance_from_desired); + RecordInsertMissSlow(info_, hash, distance_from_desired); } inline void RecordErase() { @@ -184,6 +194,11 @@ RecordEraseSlow(info_); } + inline void RecordInsertHit() { + if (ABSL_PREDICT_TRUE(info_ == nullptr)) return; + RecordInsertHitSlow(info_); + } + friend inline void swap(HashtablezInfoHandle& lhs, HashtablezInfoHandle& rhs) { std::swap(lhs.info_, rhs.info_); @@ -207,8 +222,10 @@ inline void RecordRehash(size_t /*total_probe_length*/) {} inline void RecordReservation(size_t /*target_capacity*/) {} inline void RecordClearedReservation() {} - inline void RecordInsert(size_t /*hash*/, size_t /*distance_from_desired*/) {} + inline void RecordInsertMiss(size_t /*hash*/, + size_t /*distance_from_desired*/) {} inline void RecordErase() {} + inline void RecordInsertHit() {} friend inline void swap(HashtablezInfoHandle& /*lhs*/, HashtablezInfoHandle& /*rhs*/) {}
diff --git a/absl/container/internal/hashtablez_sampler_test.cc b/absl/container/internal/hashtablez_sampler_test.cc index 0de1e29..9391e94 100644 --- a/absl/container/internal/hashtablez_sampler_test.cc +++ b/absl/container/internal/hashtablez_sampler_test.cc
@@ -90,7 +90,7 @@ const size_t test_value_size = 13; HashtablezInfo info; - absl::MutexLock l(&info.init_mu); + absl::MutexLock l(info.init_mu); info.PrepareForSampling(test_stride, test_element_size, /*key_size=*/test_key_size, /*value_size=*/test_value_size, @@ -99,12 +99,12 @@ EXPECT_EQ(info.capacity.load(), 0); EXPECT_EQ(info.size.load(), 0); EXPECT_EQ(info.num_erases.load(), 0); + EXPECT_EQ(info.num_insert_hits.load(), 0); EXPECT_EQ(info.num_rehashes.load(), 0); EXPECT_EQ(info.max_probe_length.load(), 0); EXPECT_EQ(info.total_probe_length.load(), 0); EXPECT_EQ(info.hashes_bitwise_or.load(), 0); EXPECT_EQ(info.hashes_bitwise_and.load(), ~size_t{}); - EXPECT_EQ(info.hashes_bitwise_xor.load(), 0); EXPECT_EQ(info.max_reserve.load(), 0); EXPECT_GE(info.create_time, test_start); EXPECT_EQ(info.weight, test_stride); @@ -116,11 +116,11 @@ info.capacity.store(1, std::memory_order_relaxed); info.size.store(1, std::memory_order_relaxed); info.num_erases.store(1, std::memory_order_relaxed); + info.num_insert_hits.store(1, std::memory_order_relaxed); info.max_probe_length.store(1, std::memory_order_relaxed); info.total_probe_length.store(1, std::memory_order_relaxed); info.hashes_bitwise_or.store(1, std::memory_order_relaxed); info.hashes_bitwise_and.store(1, std::memory_order_relaxed); - info.hashes_bitwise_xor.store(1, std::memory_order_relaxed); info.max_reserve.store(1, std::memory_order_relaxed); info.create_time = test_start - absl::Hours(20); @@ -131,12 +131,12 @@ EXPECT_EQ(info.capacity.load(), 0); EXPECT_EQ(info.size.load(), 0); EXPECT_EQ(info.num_erases.load(), 0); + EXPECT_EQ(info.num_insert_hits.load(), 0); EXPECT_EQ(info.num_rehashes.load(), 0); EXPECT_EQ(info.max_probe_length.load(), 0); EXPECT_EQ(info.total_probe_length.load(), 0); EXPECT_EQ(info.hashes_bitwise_or.load(), 0); EXPECT_EQ(info.hashes_bitwise_and.load(), ~size_t{}); - EXPECT_EQ(info.hashes_bitwise_xor.load(), 0); EXPECT_EQ(info.max_reserve.load(), 0); EXPECT_EQ(info.weight, 2 * test_stride); EXPECT_EQ(info.inline_element_size, test_element_size); @@ -148,7 +148,7 @@ TEST(HashtablezInfoTest, RecordStorageChanged) { HashtablezInfo info; - absl::MutexLock l(&info.init_mu); + absl::MutexLock l(info.init_mu); const int64_t test_stride = 21; const size_t test_element_size = 19; const size_t test_key_size = 17; @@ -166,9 +166,9 @@ EXPECT_EQ(info.capacity.load(), 20); } -TEST(HashtablezInfoTest, RecordInsert) { +TEST(HashtablezInfoTest, RecordInsertMiss) { HashtablezInfo info; - absl::MutexLock l(&info.init_mu); + absl::MutexLock l(info.init_mu); const int64_t test_stride = 25; const size_t test_element_size = 23; const size_t test_key_size = 21; @@ -179,21 +179,18 @@ /*value_size=*/test_value_size, /*soo_capacity_value=*/0); EXPECT_EQ(info.max_probe_length.load(), 0); - RecordInsertSlow(&info, 0x0000FF00, 6 * kProbeLength); + RecordInsertMissSlow(&info, 0x0000FF00, 6 * kProbeLength); EXPECT_EQ(info.max_probe_length.load(), 6); EXPECT_EQ(info.hashes_bitwise_and.load(), 0x0000FF00); EXPECT_EQ(info.hashes_bitwise_or.load(), 0x0000FF00); - EXPECT_EQ(info.hashes_bitwise_xor.load(), 0x0000FF00); - RecordInsertSlow(&info, 0x000FF000, 4 * kProbeLength); + RecordInsertMissSlow(&info, 0x000FF000, 4 * kProbeLength); EXPECT_EQ(info.max_probe_length.load(), 6); EXPECT_EQ(info.hashes_bitwise_and.load(), 0x0000F000); EXPECT_EQ(info.hashes_bitwise_or.load(), 0x000FFF00); - EXPECT_EQ(info.hashes_bitwise_xor.load(), 0x000F0F00); - RecordInsertSlow(&info, 0x00FF0000, 12 * kProbeLength); + RecordInsertMissSlow(&info, 0x00FF0000, 12 * kProbeLength); EXPECT_EQ(info.max_probe_length.load(), 12); EXPECT_EQ(info.hashes_bitwise_and.load(), 0x00000000); EXPECT_EQ(info.hashes_bitwise_or.load(), 0x00FFFF00); - EXPECT_EQ(info.hashes_bitwise_xor.load(), 0x00F00F00); } TEST(HashtablezInfoTest, RecordErase) { @@ -203,14 +200,14 @@ const size_t test_value_size = 25; HashtablezInfo info; - absl::MutexLock l(&info.init_mu); + absl::MutexLock l(info.init_mu); info.PrepareForSampling(test_stride, test_element_size, /*key_size=*/test_key_size, /*value_size=*/test_value_size, /*soo_capacity_value=*/1); EXPECT_EQ(info.num_erases.load(), 0); EXPECT_EQ(info.size.load(), 0); - RecordInsertSlow(&info, 0x0000FF00, 6 * kProbeLength); + RecordInsertMissSlow(&info, 0x0000FF00, 6 * kProbeLength); EXPECT_EQ(info.size.load(), 1); RecordEraseSlow(&info); EXPECT_EQ(info.size.load(), 0); @@ -221,22 +218,41 @@ EXPECT_EQ(info.soo_capacity, 1); } +TEST(HashtablezInfoTest, RecordInsertHit) { + const int64_t test_stride = 31; + const size_t test_element_size = 29; + const size_t test_key_size = 27; + const size_t test_value_size = 25; + + HashtablezInfo info; + absl::MutexLock l(info.init_mu); + info.PrepareForSampling(test_stride, test_element_size, + /*key_size=*/test_key_size, + /*value_size=*/test_value_size, + /*soo_capacity_value=*/1); + EXPECT_EQ(info.num_insert_hits.load(), 0); + RecordInsertHitSlow(&info); + EXPECT_EQ(info.num_insert_hits.load(), 1); + RecordInsertHitSlow(&info); + EXPECT_EQ(info.num_insert_hits.load(), 2); +} + TEST(HashtablezInfoTest, RecordRehash) { const int64_t test_stride = 33; const size_t test_element_size = 31; const size_t test_key_size = 29; const size_t test_value_size = 27; HashtablezInfo info; - absl::MutexLock l(&info.init_mu); + absl::MutexLock l(info.init_mu); info.PrepareForSampling(test_stride, test_element_size, /*key_size=*/test_key_size, /*value_size=*/test_value_size, /*soo_capacity_value=*/0); - RecordInsertSlow(&info, 0x1, 0); - RecordInsertSlow(&info, 0x2, kProbeLength); - RecordInsertSlow(&info, 0x4, kProbeLength); - RecordInsertSlow(&info, 0x8, 2 * kProbeLength); + RecordInsertMissSlow(&info, 0x1, 0); + RecordInsertMissSlow(&info, 0x2, kProbeLength); + RecordInsertMissSlow(&info, 0x4, kProbeLength); + RecordInsertMissSlow(&info, 0x8, 2 * kProbeLength); EXPECT_EQ(info.size.load(), 4); EXPECT_EQ(info.total_probe_length.load(), 4); @@ -259,7 +275,7 @@ TEST(HashtablezInfoTest, RecordReservation) { HashtablezInfo info; - absl::MutexLock l(&info.init_mu); + absl::MutexLock l(info.init_mu); const int64_t test_stride = 35; const size_t test_element_size = 33; const size_t test_key_size = 31;
diff --git a/absl/container/internal/heterogeneous_lookup_testing.h b/absl/container/internal/heterogeneous_lookup_testing.h new file mode 100644 index 0000000..b8cfae3 --- /dev/null +++ b/absl/container/internal/heterogeneous_lookup_testing.h
@@ -0,0 +1,80 @@ +// Copyright 2025 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_HETEROGENEOUS_LOOKUP_TESTING_H_ +#define ABSL_CONTAINER_INTERNAL_HETEROGENEOUS_LOOKUP_TESTING_H_ + +#include <cstddef> +#include <ostream> + +#include "gmock/gmock.h" +#include "absl/base/config.h" +#include "absl/container/internal/test_instance_tracker.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace container_internal { + +// An expensive class that is convertible to CheapType to demonstrate +// heterogeneous lookups. +class ExpensiveType : public absl::test_internal::CopyableMovableInstance { + public: + explicit ExpensiveType(int value) + : absl::test_internal::CopyableMovableInstance(value) {} + + friend std::ostream& operator<<(std::ostream& os, const ExpensiveType& a) { + return os << a.value(); + } +}; + +class CheapType { + public: + explicit CheapType(const int value) : value_(value) {} + + explicit operator ExpensiveType() const { return ExpensiveType(value_); } + + int value() const { return value_; } + + private: + int value_; +}; + +struct HeterogeneousHash { + using is_transparent = void; + size_t operator()(const ExpensiveType& a) const { return a.value(); } + size_t operator()(const CheapType& a) const { return a.value(); } +}; + +struct HeterogeneousEqual { + using is_transparent = void; + bool operator()(const ExpensiveType& a, const ExpensiveType& b) const { + return a.value() == b.value(); + } + bool operator()(const ExpensiveType& a, const CheapType& b) const { + return a.value() == b.value(); + } + bool operator()(const CheapType& a, const ExpensiveType& b) const { + return a.value() == b.value(); + } +}; + +MATCHER_P(HasExpensiveValue, n, "") { + return ::testing::ExplainMatchResult(n, arg.value(), result_listener); +} + +} // namespace container_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CONTAINER_INTERNAL_HETEROGENEOUS_LOOKUP_TESTING_H_
diff --git a/absl/container/internal/inlined_vector.h b/absl/container/internal/inlined_vector.h index b0d3f07..c7b709f 100644 --- a/absl/container/internal/inlined_vector.h +++ b/absl/container/internal/inlined_vector.h
@@ -27,7 +27,6 @@ #include "absl/base/attributes.h" #include "absl/base/config.h" -#include "absl/base/internal/identity.h" #include "absl/base/macros.h" #include "absl/container/internal/compressed_tuple.h" #include "absl/memory/memory.h" @@ -127,7 +126,7 @@ }; template <typename A, typename ValueAdapter> -void ConstructElements(absl::internal::type_identity_t<A>& allocator, +void ConstructElements(absl::type_identity_t<A>& allocator, Pointer<A> construct_first, ValueAdapter& values, SizeType<A> construct_size) { for (SizeType<A> i = 0; i < construct_size; ++i) { @@ -796,16 +795,9 @@ move_construction_values, move_construction.size()); - for (Pointer<A> - destination = move_assignment.data() + move_assignment.size(), - last_destination = move_assignment.data(), - source = move_assignment_values + move_assignment.size(); - ;) { - --destination; - --source; - if (destination < last_destination) break; - *destination = std::move(*source); - } + std::move_backward(move_assignment_values, + move_assignment_values + move_assignment.size(), + move_assignment.data() + move_assignment.size()); AssignElements<A>(insert_assignment.data(), values, insert_assignment.size());
diff --git a/absl/container/internal/raw_hash_map.h b/absl/container/internal/raw_hash_map.h index b42a4f2..9338f16 100644 --- a/absl/container/internal/raw_hash_map.h +++ b/absl/container/internal/raw_hash_map.h
@@ -31,8 +31,20 @@ ABSL_NAMESPACE_BEGIN namespace container_internal { -template <class Policy, class Hash, class Eq, class Alloc> -class raw_hash_map : public raw_hash_set<Policy, Hash, Eq, Alloc> { +template <class Policy, class... Params> +class raw_hash_map; + +template <typename Policy, typename Hash, typename Eq, typename Alloc> +struct InstantiateRawHashMap { + using type = typename ApplyWithoutDefaultSuffix< + raw_hash_map, + TypeList<int, typename Policy::DefaultHash, typename Policy::DefaultEq, + typename Policy::DefaultAlloc>, + TypeList<Policy, Hash, Eq, Alloc>>::type; +}; + +template <class Policy, class... Params> +class raw_hash_map : public raw_hash_set<Policy, Params...> { // P is Policy. It's passed as a template argument to support maps that have // incomplete types as values, as in unordered_map<K, IncompleteType>. // MappedReference<> may be a non-reference type. @@ -45,6 +57,10 @@ using MappedConstReference = decltype(P::value( std::addressof(std::declval<typename raw_hash_map::const_reference>()))); + using Hash = typename raw_hash_map::raw_hash_set::hasher; + using Eq = typename raw_hash_map::raw_hash_set::key_equal; + using Alloc = typename raw_hash_map::raw_hash_set::allocator_type; + template <class K> using key_arg = typename KeyArg<IsTransparent<Eq>::value && IsTransparent<Hash>::value>:: @@ -205,7 +221,8 @@ !std::is_convertible<K, const_iterator>::value, int>::type = 0> std::pair<iterator, bool> try_emplace(key_arg<K> &&k, Args &&...args) ABSL_ATTRIBUTE_LIFETIME_BOUND { - return try_emplace_impl(std::forward<K>(k), std::forward<Args>(args)...); + return try_emplace_impl(std::forward<key_arg<K>>(k), + std::forward<Args>(args)...); } template <class K = key_type, class... Args, @@ -215,7 +232,7 @@ std::pair<iterator, bool> try_emplace( key_arg<K> &&k ABSL_INTERNAL_ATTRIBUTE_CAPTURED_BY(this), Args &&...args) ABSL_ATTRIBUTE_LIFETIME_BOUND { - return this->template try_emplace<K, 0>(std::forward<K>(k), + return this->template try_emplace<K, 0>(std::forward<key_arg<K>>(k), std::forward<Args>(args)...); } @@ -241,14 +258,15 @@ class... Args> iterator try_emplace(const_iterator, key_arg<K> &&k, Args &&...args) ABSL_ATTRIBUTE_LIFETIME_BOUND { - return try_emplace(std::forward<K>(k), std::forward<Args>(args)...).first; + return try_emplace(std::forward<key_arg<K>>(k), std::forward<Args>(args)...) + .first; } template <class K = key_type, class... Args, EnableIf<LifetimeBoundK<K, true, K *>> = 0> iterator try_emplace(const_iterator hint, key_arg<K> &&k ABSL_INTERNAL_ATTRIBUTE_CAPTURED_BY(this), Args &&...args) ABSL_ATTRIBUTE_LIFETIME_BOUND { - return this->template try_emplace<K, 0>(hint, std::forward<K>(k), + return this->template try_emplace<K, 0>(hint, std::forward<key_arg<K>>(k), std::forward<Args>(args)...); } @@ -264,7 +282,7 @@ const key_arg<K> &k ABSL_INTERNAL_ATTRIBUTE_CAPTURED_BY(this), Args &&...args) ABSL_ATTRIBUTE_LIFETIME_BOUND { - return this->template try_emplace<K, 0>(hint, std::forward<K>(k), + return this->template try_emplace<K, 0>(hint, k, std::forward<Args>(args)...); } @@ -296,15 +314,15 @@ // It is safe to use unchecked_deref here because try_emplace // will always return an iterator pointing to a valid item in the table, // since it inserts if nothing is found for the given key. - return Policy::value( - &this->unchecked_deref(try_emplace(std::forward<K>(key)).first)); + return Policy::value(&this->unchecked_deref( + try_emplace(std::forward<key_arg<K>>(key)).first)); } template <class K = key_type, class P = Policy, int &..., EnableIf<LifetimeBoundK<K, true, K *>> = 0> MappedReference<P> operator[]( key_arg<K> &&key ABSL_INTERNAL_ATTRIBUTE_CAPTURED_BY(this)) ABSL_ATTRIBUTE_LIFETIME_BOUND { - return this->template operator[]<K, P, 0>(std::forward<K>(key)); + return this->template operator[]<K, P, 0>(std::forward<key_arg<K>>(key)); } template <class K = key_type, class P = Policy, @@ -325,6 +343,13 @@ } private: + static_assert( + std::is_same_v< + typename InstantiateRawHashMap<Policy, Hash, Eq, Alloc>::type, + raw_hash_map>, + "Redundant template parameters were passed. Use InstantiateRawHashMap<> " + "instead"); + template <class K, class V> std::pair<iterator, bool> insert_or_assign_impl(K&& k, V&& v) ABSL_ATTRIBUTE_LIFETIME_BOUND {
diff --git a/absl/container/internal/raw_hash_set.cc b/absl/container/internal/raw_hash_set.cc index 339e662..9955029 100644 --- a/absl/container/internal/raw_hash_set.cc +++ b/absl/container/internal/raw_hash_set.cc
@@ -19,6 +19,9 @@ #include <cstddef> #include <cstdint> #include <cstring> +#include <memory> +#include <tuple> +#include <utility> #include "absl/base/attributes.h" #include "absl/base/config.h" @@ -40,32 +43,13 @@ // Represents a control byte corresponding to a full slot with arbitrary hash. constexpr ctrl_t ZeroCtrlT() { return static_cast<ctrl_t>(0); } -// We have space for `growth_info` before a single block of control bytes. A -// single block of empty control bytes for tables without any slots allocated. -// This enables removing a branch in the hot path of find(). In order to ensure -// that the control bytes are aligned to 16, we have 16 bytes before the control -// bytes even though growth_info only needs 8. -alignas(16) ABSL_CONST_INIT ABSL_DLL const ctrl_t kEmptyGroup[32] = { - ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), - ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), - ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), - ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), ZeroCtrlT(), - ctrl_t::kSentinel, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, - ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, - ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, - ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty, ctrl_t::kEmpty}; +// A single control byte for default-constructed iterators. We leave it +// uninitialized because reading this memory is a bug. +ABSL_DLL ctrl_t kDefaultIterControl; -// We need one full byte followed by a sentinel byte for iterator::operator++ to -// work. We have a full group after kSentinel to be safe (in case operator++ is -// changed to read a full group). -ABSL_CONST_INIT ABSL_DLL const ctrl_t kSooControl[17] = { - ZeroCtrlT(), ctrl_t::kSentinel, ZeroCtrlT(), 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_assert(NumControlBytes(SooCapacity()) <= 17, - "kSooControl capacity too small"); +// We need one full byte followed by a sentinel byte for iterator::operator++. +ABSL_CONST_INIT ABSL_DLL const ctrl_t kSooControl[2] = {ZeroCtrlT(), + ctrl_t::kSentinel}; namespace { @@ -100,13 +84,13 @@ return value ^ static_cast<size_t>(reinterpret_cast<uintptr_t>(&counter)); } -bool ShouldRehashForBugDetection(PerTableSeed seed, size_t capacity) { +bool ShouldRehashForBugDetection(size_t capacity) { // Note: we can't use the abseil-random library because abseil-random // depends on swisstable. We want to return true with probability // `min(1, RehashProbabilityConstant() / capacity())`. In order to do this, // we probe based on a random hash and see if the offset is less than // RehashProbabilityConstant(). - return probe(seed, capacity, absl::HashOf(RandomSeed())).offset() < + return probe(capacity, absl::HashOf(RandomSeed())).offset() < RehashProbabilityConstant(); } @@ -118,6 +102,21 @@ return hash ^ seed.seed(); } +// Returns the offset of the new element after resize from capacity 1 to 3. +size_t Resize1To3NewOffset(size_t hash, PerTableSeed seed) { + // After resize from capacity 1 to 3, we always have exactly the slot with + // index 1 occupied, so we need to insert either at index 0 or index 2. + static_assert(SooSlotIndex() == 1); + return SingleGroupTableH1(hash, seed) & 2; +} + +// Returns the address of the ith slot in slots where each slot occupies +// slot_size. +inline void* SlotAddress(void* slot_array, size_t slot, size_t slot_size) { + return static_cast<void*>(static_cast<char*>(slot_array) + + (slot * slot_size)); +} + // Returns the address of the slot `i` iterations after `slot` assuming each // slot has the specified size. inline void* NextSlot(void* slot, size_t slot_size, size_t i = 1) { @@ -133,6 +132,16 @@ } // namespace +// Must be defined out-of-line to avoid MSVC error C2482 on some platforms, +// which is caused by non-constexpr initialization. +uint16_t HashtableSize::NextSeed() { + static_assert(PerTableSeed::kBitCount == 16); + thread_local uint16_t seed = + static_cast<uint16_t>(reinterpret_cast<uintptr_t>(&seed)); + seed += uint16_t{0xad53}; + return seed; +} + GenerationType* EmptyGeneration() { if (SwisstableGenerationsEnabled()) { constexpr size_t kNumEmptyGenerations = 1024; @@ -144,66 +153,98 @@ } bool CommonFieldsGenerationInfoEnabled:: - should_rehash_for_bug_detection_on_insert(PerTableSeed seed, - size_t capacity) const { + should_rehash_for_bug_detection_on_insert(size_t capacity) const { if (reserved_growth_ == kReservedGrowthJustRanOut) return true; if (reserved_growth_ > 0) return false; - return ShouldRehashForBugDetection(seed, capacity); + return ShouldRehashForBugDetection(capacity); } bool CommonFieldsGenerationInfoEnabled::should_rehash_for_bug_detection_on_move( - PerTableSeed seed, size_t capacity) const { - return ShouldRehashForBugDetection(seed, capacity); + size_t capacity) const { + return ShouldRehashForBugDetection(capacity); } namespace { -FindInfo find_first_non_full_from_h1(const ctrl_t* ctrl, size_t h1, - size_t capacity) { - auto seq = probe(h1, capacity); - if (IsEmptyOrDeleted(ctrl[seq.offset()])) { - return {seq.offset(), /*probe_length=*/0}; - } +// Probes an array of control bits using a probe sequence, +// and returns the mask corresponding to the first group with a deleted or empty +// slot. +inline Group::NonIterableBitMaskType probe_till_first_non_full_group( + const ctrl_t* ctrl, probe_seq<Group::kWidth>& seq, + [[maybe_unused]] size_t capacity) { while (true) { GroupFullEmptyOrDeleted g{ctrl + seq.offset()}; auto mask = g.MaskEmptyOrDeleted(); if (mask) { - return {seq.offset(mask.LowestBitSet()), seq.index()}; + return mask; } seq.next(); ABSL_SWISSTABLE_ASSERT(seq.index() <= capacity && "full table!"); } } -// Whether a table is "small". A small table fits entirely into a probing -// group, i.e., has a capacity < `Group::kWidth`. +FindInfo find_first_non_full_from_h1(const ctrl_t* ctrl, size_t h1, + size_t capacity) { + auto seq = probe_h1(capacity, h1); + if (IsEmptyOrDeleted(ctrl[seq.offset()])) { + return {seq.offset(), /*probe_length=*/0}; + } + auto mask = probe_till_first_non_full_group(ctrl, seq, capacity); + return {seq.offset(mask.LowestBitSet()), seq.index()}; +} + +// Probes an array of control bits using a probe sequence derived from `hash`, +// and returns the offset corresponding to the first deleted or empty slot. // -// In small mode we are able to use the whole capacity. The extra control +// Behavior when the entire table is full is undefined. +// +// NOTE: this function must work with tables having both empty and deleted +// slots in the same group. Such tables appear during `erase()`. +FindInfo find_first_non_full(const CommonFields& common, size_t hash) { + return find_first_non_full_from_h1(common.control(), H1(hash), + common.capacity()); +} + +// Same as `find_first_non_full`, but returns the mask corresponding to the +// first group with a deleted or empty slot. +std::pair<FindInfo, Group::NonIterableBitMaskType> find_first_non_full_group( + const CommonFields& common, size_t hash) { + auto seq = probe(common, hash); + auto mask = + probe_till_first_non_full_group(common.control(), seq, common.capacity()); + return {{seq.offset(), seq.index()}, mask}; +} + +// Whether a table fits in half a group. A half-group table fits entirely into a +// probing group, i.e., has a capacity < `Group::kWidth`. +// +// In half-group mode we are able to use the whole capacity. The extra control // bytes give us at least one "empty" control byte to stop the iteration. // This is important to make 1 a valid capacity. // -// In small mode only the first `capacity` control bytes after the sentinel +// In half-group mode only the first `capacity` control bytes after the sentinel // are valid. The rest contain dummy ctrl_t::kEmpty values that do not // represent a real slot. -constexpr bool is_small(size_t capacity) { +constexpr bool is_half_group(size_t capacity) { return capacity < Group::kWidth - 1; } template <class Fn> void IterateOverFullSlotsImpl(const CommonFields& c, size_t slot_size, Fn cb) { const size_t cap = c.capacity(); + ABSL_SWISSTABLE_ASSERT(!IsSmallCapacity(cap)); const ctrl_t* ctrl = c.control(); void* slot = c.slot_array(); - if (is_small(cap)) { - // Mirrored/cloned control bytes in small table are also located in the + if (is_half_group(cap)) { + // Mirrored/cloned control bytes in half-group table are also located in the // first group (starting from position 0). We are taking group from position // `capacity` in order to avoid duplicates. - // Small tables capacity fits into portable group, where + // Half-group tables capacity fits into portable group, where // GroupPortableImpl::MaskFull is more efficient for the // capacity <= GroupPortableImpl::kWidth. ABSL_SWISSTABLE_ASSERT(cap <= GroupPortableImpl::kWidth && - "unexpectedly large small capacity"); + "unexpectedly large half-group capacity"); static_assert(Group::kWidth >= GroupPortableImpl::kWidth, "unexpected group width"); // Group starts from kSentinel slot, so indices in the mask will @@ -250,11 +291,6 @@ ctrl[capacity] = ctrl_t::kSentinel; } -FindInfo find_first_non_full(const CommonFields& common, size_t hash) { - return find_first_non_full_from_h1(common.control(), H1(hash, common.seed()), - common.capacity()); -} - void IterateOverFullSlots(const CommonFields& c, size_t slot_size, absl::FunctionRef<void(const ctrl_t*, void*)> cb) { IterateOverFullSlotsImpl(c, slot_size, cb); @@ -300,6 +336,69 @@ common.maybe_increment_generation_on_insert(); } +// Sets sanitizer poisoning for slot corresponding to control byte being set. +inline void DoSanitizeOnSetCtrl(const CommonFields& c, size_t i, ctrl_t h, + size_t slot_size) { + ABSL_SWISSTABLE_ASSERT(i < c.capacity()); + auto* slot_i = static_cast<const char*>(c.slot_array()) + i * slot_size; + if (IsFull(h)) { + SanitizerUnpoisonMemoryRegion(slot_i, slot_size); + } else { + SanitizerPoisonMemoryRegion(slot_i, slot_size); + } +} + +// 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(const CommonFields& c, size_t i, ctrl_t h, + size_t slot_size) { + ABSL_SWISSTABLE_ASSERT(!c.is_small()); + DoSanitizeOnSetCtrl(c, i, h, slot_size); + ctrl_t* ctrl = c.control(); + ctrl[i] = h; + ctrl[((i - NumClonedBytes()) & c.capacity()) + + (NumClonedBytes() & c.capacity())] = h; +} +// Overload for setting to an occupied `h2_t` rather than a special `ctrl_t`. +inline void SetCtrl(const CommonFields& c, size_t i, h2_t h, size_t slot_size) { + SetCtrl(c, i, static_cast<ctrl_t>(h), slot_size); +} + +// Like SetCtrl, but in a single group table, we can save some operations when +// setting the cloned control byte. +inline void SetCtrlInSingleGroupTable(const CommonFields& c, size_t i, ctrl_t h, + size_t slot_size) { + ABSL_SWISSTABLE_ASSERT(!c.is_small()); + ABSL_SWISSTABLE_ASSERT(is_single_group(c.capacity())); + DoSanitizeOnSetCtrl(c, i, h, slot_size); + ctrl_t* ctrl = c.control(); + ctrl[i] = h; + ctrl[i + c.capacity() + 1] = h; +} +// Overload for setting to an occupied `h2_t` rather than a special `ctrl_t`. +inline void SetCtrlInSingleGroupTable(const CommonFields& c, size_t i, h2_t h, + size_t slot_size) { + SetCtrlInSingleGroupTable(c, i, static_cast<ctrl_t>(h), slot_size); +} + +// Like SetCtrl, but in a table with capacity >= Group::kWidth - 1, +// we can save some operations when setting the cloned control byte. +inline void SetCtrlInLargeTable(const CommonFields& c, size_t i, ctrl_t h, + size_t slot_size) { + ABSL_SWISSTABLE_ASSERT(c.capacity() >= Group::kWidth - 1); + DoSanitizeOnSetCtrl(c, i, h, slot_size); + ctrl_t* ctrl = c.control(); + ctrl[i] = h; + ctrl[((i - NumClonedBytes()) & c.capacity()) + NumClonedBytes()] = h; +} +// Overload for setting to an occupied `h2_t` rather than a special `ctrl_t`. +inline void SetCtrlInLargeTable(const CommonFields& c, size_t i, h2_t h, + size_t slot_size) { + SetCtrlInLargeTable(c, i, static_cast<ctrl_t>(h), slot_size); +} + size_t DropDeletesWithoutResizeAndPrepareInsert( CommonFields& common, const PolicyFunctions& __restrict policy, size_t new_hash) { @@ -347,7 +446,7 @@ continue; } if (!IsDeleted(ctrl[i])) continue; - const size_t hash = (*hasher)(hash_fn, slot_ptr); + const size_t hash = (*hasher)(hash_fn, slot_ptr, common.seed().seed()); const FindInfo target = find_first_non_full(common, hash); const size_t new_i = target.offset; total_probe_length += target.probe_length; @@ -405,12 +504,12 @@ ResetGrowthLeft(common); FindInfo find_info = find_first_non_full(common, new_hash); SetCtrlInLargeTable(common, find_info.offset, H2(new_hash), slot_size); - common.infoz().RecordInsert(new_hash, find_info.probe_length); + common.infoz().RecordInsertMiss(new_hash, find_info.probe_length); common.infoz().RecordRehash(total_probe_length); return find_info.offset; } -static bool WasNeverFull(CommonFields& c, size_t index) { +bool WasNeverFull(CommonFields& c, size_t index) { if (is_single_group(c.capacity())) { return true; } @@ -434,6 +533,7 @@ ctrl_t* ctrl = common.control(); static constexpr size_t kTwoGroupCapacity = 2 * Group::kWidth - 1; if (ABSL_PREDICT_TRUE(capacity <= kTwoGroupCapacity)) { + if (IsSmallCapacity(capacity)) return; std::memset(ctrl, static_cast<int8_t>(ctrl_t::kEmpty), Group::kWidth); std::memset(ctrl + capacity, static_cast<int8_t>(ctrl_t::kEmpty), Group::kWidth); @@ -449,38 +549,11 @@ SanitizerPoisonMemoryRegion(common.slot_array(), slot_size * capacity); } -// Initializes control bytes for single element table. -// Capacity of the table must be 1. -ABSL_ATTRIBUTE_ALWAYS_INLINE inline void InitializeSingleElementControlBytes( - uint64_t h2, ctrl_t* new_ctrl) { - static constexpr uint64_t kEmptyXorSentinel = - static_cast<uint8_t>(ctrl_t::kEmpty) ^ - static_cast<uint8_t>(ctrl_t::kSentinel); - static constexpr uint64_t kEmpty64 = static_cast<uint8_t>(ctrl_t::kEmpty); - // The first 8 bytes, where present slot positions are replaced with 0. - static constexpr uint64_t kFirstCtrlBytesWithZeroes = - k8EmptyBytes ^ kEmpty64 ^ (kEmptyXorSentinel << 8) ^ (kEmpty64 << 16); - - // Fill the original 0th and mirrored 2nd bytes with the hash. - // Result will look like: - // HSHEEEEE - // Where H = h2, E = kEmpty, S = kSentinel. - const uint64_t first_ctrl_bytes = - (h2 | kFirstCtrlBytesWithZeroes) | (h2 << 16); - // Fill last bytes with kEmpty. - std::memset(new_ctrl + 1, static_cast<int8_t>(ctrl_t::kEmpty), Group::kWidth); - // Overwrite the first 3 bytes with HSH. Other bytes will not be changed. - absl::little_endian::Store64(new_ctrl, first_ctrl_bytes); -} - -// Initializes control bytes for growing after SOO to the next capacity. -// `soo_ctrl` is placed in the position `SooSlotIndex()`. -// `new_hash` is placed in the position `new_offset`. -// The table must be non-empty SOO. -ABSL_ATTRIBUTE_ALWAYS_INLINE inline void -InitializeThreeElementsControlBytesAfterSoo(ctrl_t soo_ctrl, size_t new_hash, - size_t new_offset, - ctrl_t* new_ctrl) { +// Initializes control bytes for growing from capacity 1 to 3. +// `orig_h2` is placed in the position `SooSlotIndex()`. +// `new_h2` is placed in the position `new_offset`. +ABSL_ATTRIBUTE_ALWAYS_INLINE inline void InitializeThreeElementsControlBytes( + h2_t orig_h2, h2_t new_h2, size_t new_offset, ctrl_t* new_ctrl) { static constexpr size_t kNewCapacity = NextCapacity(SooCapacity()); static_assert(kNewCapacity == 3); static_assert(is_single_group(kNewCapacity)); @@ -501,9 +574,9 @@ (kEmptyXorSentinel << (8 * kNewCapacity)) ^ (kEmpty64 << (8 * kMirroredSooSlotIndex)); - const uint64_t soo_h2 = static_cast<uint64_t>(soo_ctrl); - const uint64_t new_h2_xor_empty = static_cast<uint64_t>( - H2(new_hash) ^ static_cast<uint8_t>(ctrl_t::kEmpty)); + const uint64_t soo_h2 = static_cast<uint64_t>(orig_h2); + const uint64_t new_h2_xor_empty = + static_cast<uint64_t>(new_h2 ^ static_cast<uint8_t>(ctrl_t::kEmpty)); // Fill the original and mirrored bytes for SOO slot. // Result will look like: // EHESEHEE @@ -544,11 +617,24 @@ } // namespace -void EraseMetaOnly(CommonFields& c, size_t index, size_t slot_size) { - ABSL_SWISSTABLE_ASSERT(IsFull(c.control()[index]) && - "erasing a dangling iterator"); +void EraseMetaOnlySmall(CommonFields& c, bool soo_enabled, size_t slot_size) { + ABSL_SWISSTABLE_ASSERT(c.is_small()); + if (soo_enabled) { + c.set_empty_soo(); + return; + } c.decrement_size(); c.infoz().RecordErase(); + SanitizerPoisonMemoryRegion(c.slot_array(), slot_size); +} + +void EraseMetaOnlyLarge(CommonFields& c, const ctrl_t* ctrl, size_t slot_size) { + ABSL_SWISSTABLE_ASSERT(!c.is_small()); + ABSL_SWISSTABLE_ASSERT(IsFull(*ctrl) && "erasing a dangling iterator"); + c.decrement_size(); + c.infoz().RecordErase(); + + size_t index = static_cast<size_t>(ctrl - c.control()); if (WasNeverFull(c, index)) { SetCtrl(c, index, ctrl_t::kEmpty, slot_size); @@ -599,15 +685,25 @@ void* new_slots = common.slot_array(); const void* hash_fn = policy.hash_fn(common); const size_t slot_size = policy.slot_size; + const size_t seed = common.seed().seed(); const auto insert_slot = [&](void* slot) { - size_t hash = policy.hash_slot(hash_fn, slot); - auto target = find_first_non_full(common, hash); - SetCtrl(common, target.offset, H2(hash), slot_size); + size_t hash = policy.hash_slot(hash_fn, slot, seed); + FindInfo target; + if (common.is_small()) { + target = FindInfo{0, 0}; + } else { + target = find_first_non_full(common, hash); + SetCtrl(common, target.offset, H2(hash), slot_size); + } policy.transfer_n(&common, SlotAddress(new_slots, target.offset, slot_size), slot, 1); return target.probe_length; }; + if (IsSmallCapacity(old_capacity)) { + if (common.size() == 1) insert_slot(old_slots); + return 0; + } size_t total_probe_length = 0; for (size_t i = 0; i < old_capacity; ++i) { if (IsFull(old_ctrl[i])) { @@ -618,6 +714,75 @@ return total_probe_length; } +void ReportGrowthToInfozImpl(CommonFields& common, HashtablezInfoHandle infoz, + size_t hash, size_t total_probe_length, + size_t distance_from_desired) { + ABSL_SWISSTABLE_ASSERT(infoz.IsSampled()); + infoz.RecordStorageChanged(common.size() - 1, common.capacity()); + infoz.RecordRehash(total_probe_length); + infoz.RecordInsertMiss(hash, distance_from_desired); + common.set_has_infoz(); + // TODO(b/413062340): we could potentially store infoz in place of the + // control pointer for the capacity 1 case. + common.set_infoz(infoz); +} + +// Specialization to avoid passing two 0s from hot function. +ABSL_ATTRIBUTE_NOINLINE void ReportSingleGroupTableGrowthToInfoz( + CommonFields& common, HashtablezInfoHandle infoz, size_t hash) { + ReportGrowthToInfozImpl(common, infoz, hash, /*total_probe_length=*/0, + /*distance_from_desired=*/0); +} + +ABSL_ATTRIBUTE_NOINLINE void ReportGrowthToInfoz(CommonFields& common, + HashtablezInfoHandle infoz, + size_t hash, + size_t total_probe_length, + size_t distance_from_desired) { + ReportGrowthToInfozImpl(common, infoz, hash, total_probe_length, + distance_from_desired); +} + +ABSL_ATTRIBUTE_NOINLINE void ReportResizeToInfoz(CommonFields& common, + HashtablezInfoHandle infoz, + size_t total_probe_length) { + ABSL_SWISSTABLE_ASSERT(infoz.IsSampled()); + infoz.RecordStorageChanged(common.size(), common.capacity()); + infoz.RecordRehash(total_probe_length); + common.set_has_infoz(); + common.set_infoz(infoz); +} + +struct BackingArrayPtrs { + ctrl_t* ctrl; + void* slots; +}; + +BackingArrayPtrs AllocBackingArray(CommonFields& common, + const PolicyFunctions& __restrict policy, + size_t new_capacity, bool has_infoz, + void* alloc) { + RawHashSetLayout layout(new_capacity, policy.slot_size, policy.slot_align, + has_infoz); + // Perform a direct call in the common case to allow for profile-guided + // heap optimization (PGHO) to understand which allocation function is used. + constexpr size_t kDefaultAlignment = BackingArrayAlignment(alignof(size_t)); + char* mem = static_cast<char*>( + ABSL_PREDICT_TRUE( + policy.alloc == + (&AllocateBackingArray<kDefaultAlignment, std::allocator<char>>)) + ? AllocateBackingArray<kDefaultAlignment, std::allocator<char>>( + alloc, layout.alloc_size()) + : policy.alloc(alloc, layout.alloc_size())); + const GenerationType old_generation = common.generation(); + common.set_generation_ptr( + reinterpret_cast<GenerationType*>(mem + layout.generation_offset())); + common.set_generation(NextGeneration(old_generation)); + + return {reinterpret_cast<ctrl_t*>(mem + layout.control_offset()), + mem + layout.slot_offset()}; +} + template <ResizeNonSooMode kMode> void ResizeNonSooImpl(CommonFields& common, const PolicyFunctions& __restrict policy, @@ -625,26 +790,25 @@ ABSL_SWISSTABLE_ASSERT(IsValidCapacity(new_capacity)); ABSL_SWISSTABLE_ASSERT(new_capacity > policy.soo_capacity()); - const size_t old_capacity = common.capacity(); - [[maybe_unused]] ctrl_t* old_ctrl = common.control(); - [[maybe_unused]] void* old_slots = common.slot_array(); + [[maybe_unused]] const size_t old_capacity = common.capacity(); + [[maybe_unused]] ctrl_t* old_ctrl; + [[maybe_unused]] void* old_slots; + if constexpr (kMode == ResizeNonSooMode::kGuaranteedAllocated) { + old_ctrl = common.control(); + old_slots = common.slot_array(); + } const size_t slot_size = policy.slot_size; - const size_t slot_align = policy.slot_align; + [[maybe_unused]] const size_t slot_align = policy.slot_align; const bool has_infoz = infoz.IsSampled(); + void* alloc = policy.get_char_alloc(common); common.set_capacity(new_capacity); - RawHashSetLayout layout(new_capacity, slot_size, slot_align, has_infoz); - void* alloc = policy.get_char_alloc(common); - char* mem = static_cast<char*>(policy.alloc(alloc, layout.alloc_size())); - const GenerationType old_generation = common.generation(); - common.set_generation_ptr( - reinterpret_cast<GenerationType*>(mem + layout.generation_offset())); - common.set_generation(NextGeneration(old_generation)); - - ctrl_t* new_ctrl = reinterpret_cast<ctrl_t*>(mem + layout.control_offset()); - common.set_control</*kGenerateSeed=*/true>(new_ctrl); - common.set_slots(mem + layout.slot_offset()); + const auto [new_ctrl, new_slots] = + AllocBackingArray(common, policy, new_capacity, has_infoz, alloc); + common.set_control(new_ctrl); + common.set_slots(new_slots); + common.generate_new_seed(has_infoz); size_t total_probe_length = 0; ResetCtrl(common, slot_size); @@ -664,11 +828,8 @@ CapacityToGrowth(new_capacity)); } - if (has_infoz) { - common.set_has_infoz(); - infoz.RecordStorageChanged(common.size(), new_capacity); - infoz.RecordRehash(total_probe_length); - common.set_infoz(infoz); + if (ABSL_PREDICT_FALSE(has_infoz)) { + ReportResizeToInfoz(common, infoz, total_probe_length); } } @@ -697,23 +858,26 @@ // It is rare to resize an SOO table with one element to a large size. // Requires: `c` contains SOO data. void InsertOldSooSlotAndInitializeControlBytes( - CommonFields& c, const PolicyFunctions& __restrict policy, size_t hash, - ctrl_t* new_ctrl, void* new_slots) { + CommonFields& c, const PolicyFunctions& __restrict policy, ctrl_t* new_ctrl, + void* new_slots, bool has_infoz) { ABSL_SWISSTABLE_ASSERT(c.size() == policy.soo_capacity()); ABSL_SWISSTABLE_ASSERT(policy.soo_enabled); size_t new_capacity = c.capacity(); - c.generate_new_seed(); - size_t offset = probe(c.seed(), new_capacity, hash).offset(); + c.generate_new_seed(has_infoz); + + const size_t soo_slot_hash = + policy.hash_slot(policy.hash_fn(c), c.soo_data(), c.seed().seed()); + size_t offset = probe(new_capacity, soo_slot_hash).offset(); offset = offset == new_capacity ? 0 : offset; SanitizerPoisonMemoryRegion(new_slots, policy.slot_size * new_capacity); void* target_slot = SlotAddress(new_slots, offset, policy.slot_size); SanitizerUnpoisonMemoryRegion(target_slot, policy.slot_size); policy.transfer_n(&c, target_slot, c.soo_data(), 1); - c.set_control</*kGenerateSeed=*/false>(new_ctrl); + c.set_control(new_ctrl); c.set_slots(new_slots); ResetCtrl(c, policy.slot_size); - SetCtrl(c, offset, H2(hash), policy.slot_size); + SetCtrl(c, offset, H2(soo_slot_hash), policy.slot_size); } enum class ResizeFullSooTableSamplingMode { @@ -739,9 +903,10 @@ ResizeFullSooTableSamplingMode sampling_mode) { AssertFullSoo(common, policy); const size_t slot_size = policy.slot_size; - const size_t slot_align = policy.slot_align; + void* alloc = policy.get_char_alloc(common); HashtablezInfoHandle infoz; + bool has_infoz = false; if (sampling_mode == ResizeFullSooTableSamplingMode::kForceSampleNoResizeIfUnsampled) { if (ABSL_PREDICT_FALSE(policy.is_hashtablez_eligible)) { @@ -749,33 +914,19 @@ policy.soo_capacity()); } - if (!infoz.IsSampled()) { - return; - } + if (!infoz.IsSampled()) return; + has_infoz = true; } - const bool has_infoz = infoz.IsSampled(); - common.set_capacity(new_capacity); - RawHashSetLayout layout(new_capacity, slot_size, slot_align, has_infoz); - void* alloc = policy.get_char_alloc(common); - char* mem = static_cast<char*>(policy.alloc(alloc, layout.alloc_size())); - const GenerationType old_generation = common.generation(); - common.set_generation_ptr( - reinterpret_cast<GenerationType*>(mem + layout.generation_offset())); - common.set_generation(NextGeneration(old_generation)); - // We do not set control and slots in CommonFields yet to avoid overriding // SOO data. - ctrl_t* new_ctrl = reinterpret_cast<ctrl_t*>(mem + layout.control_offset()); - void* new_slots = mem + layout.slot_offset(); + const auto [new_ctrl, new_slots] = + AllocBackingArray(common, policy, new_capacity, has_infoz, alloc); - const size_t soo_slot_hash = - policy.hash_slot(policy.hash_fn(common), common.soo_data()); - - InsertOldSooSlotAndInitializeControlBytes(common, policy, soo_slot_hash, - new_ctrl, new_slots); + InsertOldSooSlotAndInitializeControlBytes(common, policy, new_ctrl, new_slots, + has_infoz); ResetGrowthLeft(common); if (has_infoz) { common.set_has_infoz(); @@ -853,7 +1004,7 @@ return; } - ABSL_SWISSTABLE_ASSERT(Group::kWidth == 16); + ABSL_SWISSTABLE_ASSERT(Group::kWidth == 16); // NOLINT(misc-static-assert) // Fill the second half of the main control bytes with kEmpty. // For small capacity that may write into mirrored control bytes. @@ -965,12 +1116,13 @@ const void* hash_fn = policy.hash_fn(c); auto hash_slot = policy.hash_slot; auto transfer_n = policy.transfer_n; + const size_t seed = c.seed().seed(); for (size_t old_index = start; old_index < old_capacity; ++old_index) { if (old_ctrl[old_index] != ctrl_t::kSentinel) { continue; } void* src_slot = SlotAddress(old_slots, old_index, slot_size); - const size_t hash = hash_slot(hash_fn, src_slot); + const size_t hash = hash_slot(hash_fn, src_slot, seed); const FindInfo target = find_first_non_full(c, hash); total_probe_length += target.probe_length; const size_t new_i = target.offset; @@ -1224,6 +1376,79 @@ } } +void IncrementSmallSizeNonSoo(CommonFields& common, + const PolicyFunctions& __restrict policy) { + ABSL_SWISSTABLE_ASSERT(common.is_small()); + common.increment_size(); + SanitizerUnpoisonMemoryRegion(common.slot_array(), policy.slot_size); +} + +void IncrementSmallSize(CommonFields& common, + const PolicyFunctions& __restrict policy) { + ABSL_SWISSTABLE_ASSERT(common.is_small()); + if (policy.soo_enabled) { + common.set_full_soo(); + } else { + IncrementSmallSizeNonSoo(common, policy); + } +} + +std::pair<ctrl_t*, void*> Grow1To3AndPrepareInsert( + CommonFields& common, const PolicyFunctions& __restrict policy, + absl::FunctionRef<size_t(size_t)> get_hash) { + // TODO(b/413062340): Refactor to reuse more code with + // GrowSooTableToNextCapacityAndPrepareInsert. + ABSL_SWISSTABLE_ASSERT(common.capacity() == 1); + ABSL_SWISSTABLE_ASSERT(!common.empty()); + ABSL_SWISSTABLE_ASSERT(!policy.soo_enabled); + constexpr size_t kOldCapacity = 1; + constexpr size_t kNewCapacity = NextCapacity(kOldCapacity); + ctrl_t* old_ctrl = common.control(); + void* old_slots = common.slot_array(); + + const size_t slot_size = policy.slot_size; + const size_t slot_align = policy.slot_align; + void* alloc = policy.get_char_alloc(common); + HashtablezInfoHandle infoz = common.infoz(); + const bool has_infoz = infoz.IsSampled(); + common.set_capacity(kNewCapacity); + + const auto [new_ctrl, new_slots] = + AllocBackingArray(common, policy, kNewCapacity, has_infoz, alloc); + common.set_control(new_ctrl); + common.set_slots(new_slots); + SanitizerPoisonMemoryRegion(new_slots, kNewCapacity * slot_size); + + if (ABSL_PREDICT_TRUE(!has_infoz)) { + // When we're sampled, we already have a seed. + common.generate_new_seed(/*has_infoz=*/false); + } + const size_t new_hash = get_hash(common.seed().seed()); + h2_t new_h2 = H2(new_hash); + size_t orig_hash = + policy.hash_slot(policy.hash_fn(common), old_slots, common.seed().seed()); + size_t offset = Resize1To3NewOffset(new_hash, common.seed()); + InitializeThreeElementsControlBytes(H2(orig_hash), new_h2, offset, new_ctrl); + + void* old_element_target = NextSlot(new_slots, slot_size); + SanitizerUnpoisonMemoryRegion(old_element_target, slot_size); + policy.transfer_n(&common, old_element_target, old_slots, 1); + + void* new_element_target_slot = SlotAddress(new_slots, offset, slot_size); + SanitizerUnpoisonMemoryRegion(new_element_target_slot, slot_size); + + policy.dealloc(alloc, kOldCapacity, old_ctrl, slot_size, slot_align, + has_infoz); + PrepareInsertCommon(common); + ABSL_SWISSTABLE_ASSERT(common.size() == 2); + GetGrowthInfoFromControl(new_ctrl).InitGrowthLeftNoDeleted(kNewCapacity - 2); + + if (ABSL_PREDICT_FALSE(has_infoz)) { + ReportSingleGroupTableGrowthToInfoz(common, infoz, new_hash); + } + return {new_ctrl + offset, new_element_target_slot}; +} + // Grows to next capacity and prepares insert for the given new_hash. // Returns the offset of the new element. size_t GrowToNextCapacityAndPrepareInsert( @@ -1231,98 +1456,116 @@ size_t new_hash) { ABSL_SWISSTABLE_ASSERT(common.growth_left() == 0); const size_t old_capacity = common.capacity(); - ABSL_SWISSTABLE_ASSERT(old_capacity == 0 || - old_capacity > policy.soo_capacity()); + ABSL_SWISSTABLE_ASSERT(old_capacity > policy.soo_capacity()); + ABSL_SWISSTABLE_ASSERT(!IsSmallCapacity(old_capacity)); const size_t new_capacity = NextCapacity(old_capacity); - ABSL_SWISSTABLE_ASSERT(IsValidCapacity(new_capacity)); - ABSL_SWISSTABLE_ASSERT(new_capacity > policy.soo_capacity()); - ctrl_t* old_ctrl = common.control(); void* old_slots = common.slot_array(); common.set_capacity(new_capacity); const size_t slot_size = policy.slot_size; const size_t slot_align = policy.slot_align; - HashtablezInfoHandle infoz; - if (old_capacity > 0) { - infoz = common.infoz(); - } else { - const bool should_sample = - policy.is_hashtablez_eligible && ShouldSampleNextTable(); - if (ABSL_PREDICT_FALSE(should_sample)) { - infoz = ForcedTrySample(slot_size, policy.key_size, policy.value_size, - policy.soo_capacity()); - } - } + void* alloc = policy.get_char_alloc(common); + HashtablezInfoHandle infoz = common.infoz(); const bool has_infoz = infoz.IsSampled(); - RawHashSetLayout layout(new_capacity, slot_size, slot_align, has_infoz); - void* alloc = policy.get_char_alloc(common); - char* mem = static_cast<char*>(policy.alloc(alloc, layout.alloc_size())); - const GenerationType old_generation = common.generation(); - common.set_generation_ptr( - reinterpret_cast<GenerationType*>(mem + layout.generation_offset())); - common.set_generation(NextGeneration(old_generation)); - - ctrl_t* new_ctrl = reinterpret_cast<ctrl_t*>(mem + layout.control_offset()); - void* new_slots = mem + layout.slot_offset(); - common.set_control</*kGenerateSeed=*/false>(new_ctrl); + const auto [new_ctrl, new_slots] = + AllocBackingArray(common, policy, new_capacity, has_infoz, alloc); + common.set_control(new_ctrl); common.set_slots(new_slots); SanitizerPoisonMemoryRegion(new_slots, new_capacity * slot_size); h2_t new_h2 = H2(new_hash); size_t total_probe_length = 0; FindInfo find_info; - if (old_capacity == 0) { - static_assert(NextCapacity(0) == 1); - InitializeSingleElementControlBytes(new_h2, new_ctrl); - common.generate_new_seed(); - find_info = FindInfo{0, 0}; - SanitizerUnpoisonMemoryRegion(new_slots, slot_size); - } else { - if (ABSL_PREDICT_TRUE(is_single_group(new_capacity))) { - GrowIntoSingleGroupShuffleControlBytes(old_ctrl, old_capacity, new_ctrl, - new_capacity); - // Single group tables have all slots full on resize. So we can transfer - // all slots without checking the control bytes. - ABSL_SWISSTABLE_ASSERT(common.size() == old_capacity); - auto* target = NextSlot(new_slots, slot_size); - SanitizerUnpoisonMemoryRegion(target, old_capacity * slot_size); - policy.transfer_n(&common, target, old_slots, old_capacity); - // We put the new element either at the beginning or at the end of the - // table with approximately equal probability. - size_t offset = SingleGroupTableH1(new_hash, common.seed()) & 1 - ? 0 - : new_capacity - 1; + if (ABSL_PREDICT_TRUE(is_single_group(new_capacity))) { + size_t offset; + GrowIntoSingleGroupShuffleControlBytes(old_ctrl, old_capacity, new_ctrl, + new_capacity); + // We put the new element either at the beginning or at the end of the + // table with approximately equal probability. + offset = + SingleGroupTableH1(new_hash, common.seed()) & 1 ? 0 : new_capacity - 1; - ABSL_SWISSTABLE_ASSERT(IsEmpty(new_ctrl[offset])); - SetCtrlInSingleGroupTable(common, offset, new_h2, policy.slot_size); - find_info = FindInfo{offset, 0}; - } else { - total_probe_length = - GrowToNextCapacityDispatch(common, policy, old_ctrl, old_slots); - find_info = find_first_non_full(common, new_hash); - SetCtrlInLargeTable(common, find_info.offset, new_h2, policy.slot_size); - } - ABSL_SWISSTABLE_ASSERT(old_capacity > policy.soo_capacity()); - (*policy.dealloc)(alloc, old_capacity, old_ctrl, slot_size, slot_align, - has_infoz); + ABSL_SWISSTABLE_ASSERT(IsEmpty(new_ctrl[offset])); + SetCtrlInSingleGroupTable(common, offset, new_h2, policy.slot_size); + find_info = FindInfo{offset, 0}; + // Single group tables have all slots full on resize. So we can transfer + // all slots without checking the control bytes. + ABSL_SWISSTABLE_ASSERT(common.size() == old_capacity); + void* target = NextSlot(new_slots, slot_size); + SanitizerUnpoisonMemoryRegion(target, old_capacity * slot_size); + policy.transfer_n(&common, target, old_slots, old_capacity); + } else { + total_probe_length = + GrowToNextCapacityDispatch(common, policy, old_ctrl, old_slots); + find_info = find_first_non_full(common, new_hash); + SetCtrlInLargeTable(common, find_info.offset, new_h2, policy.slot_size); } + ABSL_SWISSTABLE_ASSERT(old_capacity > policy.soo_capacity()); + (*policy.dealloc)(alloc, old_capacity, old_ctrl, slot_size, slot_align, + has_infoz); PrepareInsertCommon(common); ResetGrowthLeft(GetGrowthInfoFromControl(new_ctrl), new_capacity, common.size()); if (ABSL_PREDICT_FALSE(has_infoz)) { - common.set_has_infoz(); - infoz.RecordStorageChanged(common.size() - 1, new_capacity); - infoz.RecordRehash(total_probe_length); - infoz.RecordInsert(new_hash, find_info.probe_length); - common.set_infoz(infoz); + ReportGrowthToInfoz(common, infoz, new_hash, total_probe_length, + find_info.probe_length); } return find_info.offset; } +} // namespace + +std::pair<ctrl_t*, void*> PrepareInsertSmallNonSoo( + CommonFields& common, const PolicyFunctions& __restrict policy, + absl::FunctionRef<size_t(size_t)> get_hash) { + ABSL_SWISSTABLE_ASSERT(common.is_small()); + ABSL_SWISSTABLE_ASSERT(!policy.soo_enabled); + if (common.capacity() == 1) { + if (common.empty()) { + IncrementSmallSizeNonSoo(common, policy); + return {SooControl(), common.slot_array()}; + } else { + return Grow1To3AndPrepareInsert(common, policy, get_hash); + } + } + + // Growing from 0 to 1 capacity. + ABSL_SWISSTABLE_ASSERT(common.capacity() == 0); + constexpr size_t kNewCapacity = 1; + + common.set_capacity(kNewCapacity); + HashtablezInfoHandle infoz; + const bool should_sample = + policy.is_hashtablez_eligible && ShouldSampleNextTable(); + if (ABSL_PREDICT_FALSE(should_sample)) { + infoz = ForcedTrySample(policy.slot_size, policy.key_size, + policy.value_size, policy.soo_capacity()); + } + const bool has_infoz = infoz.IsSampled(); + void* alloc = policy.get_char_alloc(common); + + const auto [new_ctrl, new_slots] = + AllocBackingArray(common, policy, kNewCapacity, has_infoz, alloc); + common.set_control(new_ctrl); + common.set_slots(new_slots); + + static_assert(NextCapacity(0) == 1); + PrepareInsertCommon(common); + + if (ABSL_PREDICT_FALSE(has_infoz)) { + common.generate_new_seed(/*has_infoz=*/true); + ReportSingleGroupTableGrowthToInfoz(common, infoz, + get_hash(common.seed().seed())); + } + return {SooControl(), new_slots}; +} + +namespace { + // Called whenever the table needs to vacate empty slots either by removing // tombstones via rehash or growth to next capacity. ABSL_ATTRIBUTE_NOINLINE @@ -1382,11 +1625,11 @@ } } -// Slow path for PrepareInsertNonSoo that is called when the table has deleted +// Slow path for PrepareInsertLarge that is called when the table has deleted // slots or need to be resized or rehashed. -size_t PrepareInsertNonSooSlow(CommonFields& common, - const PolicyFunctions& __restrict policy, - size_t hash) { +size_t PrepareInsertLargeSlow(CommonFields& common, + const PolicyFunctions& __restrict policy, + size_t hash) { const GrowthInfo growth_info = common.growth_info(); ABSL_SWISSTABLE_ASSERT(!growth_info.HasNoDeletedAndGrowthLeft()); if (ABSL_PREDICT_TRUE(growth_info.HasNoGrowthLeftAndNoDeleted())) { @@ -1404,7 +1647,7 @@ PrepareInsertCommon(common); common.growth_info().OverwriteControlAsFull(common.control()[target.offset]); SetCtrlInLargeTable(common, target.offset, H2(hash), policy.slot_size); - common.infoz().RecordInsert(hash, target.probe_length); + common.infoz().RecordInsertMiss(hash, target.probe_length); return target.offset; } @@ -1417,14 +1660,15 @@ ABSL_ATTRIBUTE_NOINLINE size_t GrowEmptySooTableToNextCapacityForceSamplingAndPrepareInsert( CommonFields& common, const PolicyFunctions& __restrict policy, - size_t new_hash) { + absl::FunctionRef<size_t(size_t)> get_hash) { ResizeEmptyNonAllocatedTableImpl(common, policy, NextCapacity(SooCapacity()), /*force_infoz=*/true); PrepareInsertCommon(common); common.growth_info().OverwriteEmptyAsFull(); + const size_t new_hash = get_hash(common.seed().seed()); SetCtrlInSingleGroupTable(common, SooSlotIndex(), H2(new_hash), policy.slot_size); - common.infoz().RecordInsert(new_hash, /*distance_from_desired=*/0); + common.infoz().RecordInsertMiss(new_hash, /*distance_from_desired=*/0); return SooSlotIndex(); } @@ -1471,6 +1715,17 @@ common.infoz().RecordReservation(new_size); } +// As `ResizeFullSooTableToNextCapacity`, except that we also force the SOO +// table to be sampled. SOO tables need to switch from SOO to heap in order to +// store the infoz. No-op if sampling is disabled or not possible. +void GrowFullSooTableToNextCapacityForceSampling( + CommonFields& common, const PolicyFunctions& __restrict policy) { + AssertFullSoo(common, policy); + ResizeFullSooTable( + common, policy, NextCapacity(SooCapacity()), + ResizeFullSooTableSamplingMode::kForceSampleNoResizeIfUnsampled); +} + } // namespace void* GetRefForEmptyClass(CommonFields& common) { @@ -1501,46 +1756,38 @@ template <size_t SooSlotMemcpySize, bool TransferUsesMemcpy> size_t GrowSooTableToNextCapacityAndPrepareInsert( CommonFields& common, const PolicyFunctions& __restrict policy, - size_t new_hash, ctrl_t soo_slot_ctrl) { + absl::FunctionRef<size_t(size_t)> get_hash, bool force_sampling) { AssertSoo(common, policy); - if (ABSL_PREDICT_FALSE(soo_slot_ctrl == ctrl_t::kEmpty)) { + if (ABSL_PREDICT_FALSE(force_sampling)) { // The table is empty, it is only used for forced sampling of SOO tables. return GrowEmptySooTableToNextCapacityForceSamplingAndPrepareInsert( - common, policy, new_hash); + common, policy, get_hash); } ABSL_SWISSTABLE_ASSERT(common.size() == policy.soo_capacity()); static constexpr size_t kNewCapacity = NextCapacity(SooCapacity()); const size_t slot_size = policy.slot_size; - const size_t slot_align = policy.slot_align; + void* alloc = policy.get_char_alloc(common); common.set_capacity(kNewCapacity); // Since the table is not empty, it will not be sampled. // The decision to sample was already made during the first insertion. - RawHashSetLayout layout(kNewCapacity, slot_size, slot_align, - /*has_infoz=*/false); - void* alloc = policy.get_char_alloc(common); - char* mem = static_cast<char*>(policy.alloc(alloc, layout.alloc_size())); - const GenerationType old_generation = common.generation(); - common.set_generation_ptr( - reinterpret_cast<GenerationType*>(mem + layout.generation_offset())); - common.set_generation(NextGeneration(old_generation)); - + // // We do not set control and slots in CommonFields yet to avoid overriding // SOO data. - ctrl_t* new_ctrl = reinterpret_cast<ctrl_t*>(mem + layout.control_offset()); - void* new_slots = mem + layout.slot_offset(); + const auto [new_ctrl, new_slots] = AllocBackingArray( + common, policy, kNewCapacity, /*has_infoz=*/false, alloc); PrepareInsertCommon(common); ABSL_SWISSTABLE_ASSERT(common.size() == 2); GetGrowthInfoFromControl(new_ctrl).InitGrowthLeftNoDeleted(kNewCapacity - 2); - common.generate_new_seed(); + common.generate_new_seed(/*has_infoz=*/false); + const h2_t soo_slot_h2 = H2(policy.hash_slot( + policy.hash_fn(common), common.soo_data(), common.seed().seed())); + const size_t new_hash = get_hash(common.seed().seed()); - // After resize from capacity 1 to 3, we always have exactly the slot with - // index 1 occupied, so we need to insert either at index 0 or index 2. - static_assert(SooSlotIndex() == 1); - const size_t offset = SingleGroupTableH1(new_hash, common.seed()) & 2; - InitializeThreeElementsControlBytesAfterSoo(soo_slot_ctrl, new_hash, offset, - new_ctrl); + const size_t offset = Resize1To3NewOffset(new_hash, common.seed()); + InitializeThreeElementsControlBytes(soo_slot_h2, H2(new_hash), offset, + new_ctrl); SanitizerPoisonMemoryRegion(new_slots, slot_size * kNewCapacity); void* target_slot = SlotAddress(new_slots, SooSlotIndex(), slot_size); @@ -1562,24 +1809,17 @@ static_assert(SooSlotMemcpySize == 0); policy.transfer_n(&common, target_slot, common.soo_data(), 1); } - // Seed was already generated above. - common.set_control</*kGenerateSeed=*/false>(new_ctrl); + common.set_control(new_ctrl); common.set_slots(new_slots); - common.infoz().RecordInsert(new_hash, /*distance_from_desired=*/0); + // Full SOO table couldn't be sampled. If SOO table is sampled, it would + // have been resized to the next capacity. + ABSL_SWISSTABLE_ASSERT(!common.infoz().IsSampled()); SanitizerUnpoisonMemoryRegion(SlotAddress(new_slots, offset, slot_size), slot_size); return offset; } -void GrowFullSooTableToNextCapacityForceSampling( - CommonFields& common, const PolicyFunctions& __restrict policy) { - AssertFullSoo(common, policy); - ResizeFullSooTable( - common, policy, NextCapacity(SooCapacity()), - ResizeFullSooTableSamplingMode::kForceSampleNoResizeIfUnsampled); -} - void Rehash(CommonFields& common, const PolicyFunctions& __restrict policy, size_t n) { const size_t cap = common.capacity(); @@ -1612,7 +1852,7 @@ } ABSL_SWISSTABLE_ASSERT(slot_size <= sizeof(HeapOrSoo)); ABSL_SWISSTABLE_ASSERT(policy.slot_align <= alignof(HeapOrSoo)); - HeapOrSoo tmp_slot(uninitialized_tag_t{}); + HeapOrSoo tmp_slot; size_t begin_offset = FindFirstFullSlot(0, cap, common.control()); policy.transfer_n( &common, &tmp_slot, @@ -1655,19 +1895,22 @@ ABSL_SWISSTABLE_ASSERT(size > 0); const size_t soo_capacity = policy.soo_capacity(); const size_t slot_size = policy.slot_size; - if (size <= soo_capacity) { - ABSL_SWISSTABLE_ASSERT(size == 1); - common.set_full_soo(); + const bool soo_enabled = policy.soo_enabled; + if (size == 1) { + if (!soo_enabled) ReserveTableToFitNewSize(common, policy, 1); + IncrementSmallSize(common, policy); + const size_t other_capacity = other.capacity(); const void* other_slot = - other.capacity() <= soo_capacity - ? other.soo_data() - : SlotAddress( - other.slot_array(), - FindFirstFullSlot(0, other.capacity(), other.control()), - slot_size); - copy_fn(common.soo_data(), other_slot); + other_capacity <= soo_capacity ? other.soo_data() + : other.is_small() + ? other.slot_array() + : SlotAddress(other.slot_array(), + FindFirstFullSlot(0, other_capacity, other.control()), + slot_size); + copy_fn(soo_enabled ? common.soo_data() : common.slot_array(), other_slot); - if (policy.is_hashtablez_eligible && ShouldSampleNextTable()) { + if (soo_enabled && policy.is_hashtablez_eligible && + ShouldSampleNextTable()) { GrowFullSooTableToNextCapacityForceSampling(common, policy); } return; @@ -1678,47 +1921,22 @@ ABSL_SWISSTABLE_ASSERT(other.capacity() > soo_capacity); const size_t cap = common.capacity(); ABSL_SWISSTABLE_ASSERT(cap > soo_capacity); - // Note about single group tables: - // 1. It is correct to have any order of elements. - // 2. Order has to be non deterministic. - // 3. We are assigning elements with arbitrary `shift` starting from - // `capacity + shift` position. - // 4. `shift` must be coprime with `capacity + 1` in order to be able to use - // modular arithmetic to traverse all positions, instead of cycling - // through a subset of positions. Odd numbers are coprime with any - // `capacity + 1` (2^N). size_t offset = cap; - const size_t shift = is_single_group(cap) ? (common.seed().seed() | 1) : 0; const void* hash_fn = policy.hash_fn(common); auto hasher = policy.hash_slot; + const size_t seed = common.seed().seed(); IterateOverFullSlotsImpl( - other, slot_size, [&](const ctrl_t* that_ctrl, void* that_slot) { - if (shift == 0) { - // Big tables case. Position must be searched via probing. - // The table is guaranteed to be empty, so we can do faster than - // a full `insert`. - const size_t hash = (*hasher)(hash_fn, that_slot); - FindInfo target = find_first_non_full(common, hash); - infoz.RecordInsert(hash, target.probe_length); - offset = target.offset; - } else { - // Small tables case. Next position is computed via shift. - offset = (offset + shift) & cap; - } - const h2_t h2 = static_cast<h2_t>(*that_ctrl); - // We rely on the hash not changing for small tables. - ABSL_SWISSTABLE_ASSERT( - H2((*hasher)(hash_fn, that_slot)) == h2 && - "hash function value changed unexpectedly during the copy"); - SetCtrl(common, offset, h2, slot_size); + other, slot_size, [&](const ctrl_t*, void* that_slot) { + // The table is guaranteed to be empty, so we can do faster than + // a full `insert`. + const size_t hash = (*hasher)(hash_fn, that_slot, seed); + FindInfo target = find_first_non_full(common, hash); + infoz.RecordInsertMiss(hash, target.probe_length); + offset = target.offset; + SetCtrl(common, offset, H2(hash), slot_size); copy_fn(SlotAddress(common.slot_array(), offset, slot_size), that_slot); common.maybe_increment_generation_on_insert(); }); - if (shift != 0) { - // On small table copy we do not record individual inserts. - // RecordInsert requires hash, but it is unknown for small tables. - infoz.RecordStorageChanged(size, cap); - } common.increment_size(size); common.growth_info().OverwriteManyEmptyAsFull(size); } @@ -1737,41 +1955,62 @@ ABSL_SWISSTABLE_ASSERT(!common.empty() || cap > policy.soo_capacity()); ABSL_SWISSTABLE_ASSERT(cap > 0); const size_t max_size_before_growth = - cap <= policy.soo_capacity() ? policy.soo_capacity() - : common.size() + common.growth_left(); + IsSmallCapacity(cap) ? cap : common.size() + common.growth_left(); if (new_size <= max_size_before_growth) { return; } ReserveAllocatedTable(common, policy, new_size); } -size_t PrepareInsertNonSoo(CommonFields& common, - const PolicyFunctions& __restrict policy, - size_t hash, FindInfo target) { - const bool rehash_for_bug_detection = - common.should_rehash_for_bug_detection_on_insert() && - // Required to allow use of ResizeAllocatedTable. - common.capacity() > 0; - if (rehash_for_bug_detection) { - // Move to a different heap allocation in order to detect bugs. - const size_t cap = common.capacity(); - ResizeAllocatedTableWithSeedChange( - common, policy, common.growth_left() > 0 ? cap : NextCapacity(cap)); - target = find_first_non_full(common, hash); - } - +namespace { +size_t PrepareInsertLargeImpl(CommonFields& common, + const PolicyFunctions& __restrict policy, + size_t hash, + Group::NonIterableBitMaskType mask_empty, + FindInfo target_group) { + ABSL_SWISSTABLE_ASSERT(!common.is_small()); const GrowthInfo growth_info = common.growth_info(); // When there are no deleted slots in the table // and growth_left is positive, we can insert at the first // empty slot in the probe sequence (target). if (ABSL_PREDICT_FALSE(!growth_info.HasNoDeletedAndGrowthLeft())) { - return PrepareInsertNonSooSlow(common, policy, hash); + return PrepareInsertLargeSlow(common, policy, hash); } PrepareInsertCommon(common); common.growth_info().OverwriteEmptyAsFull(); - SetCtrl(common, target.offset, H2(hash), policy.slot_size); - common.infoz().RecordInsert(hash, target.probe_length); - return target.offset; + target_group.offset += mask_empty.LowestBitSet(); + target_group.offset &= common.capacity(); + SetCtrl(common, target_group.offset, H2(hash), policy.slot_size); + common.infoz().RecordInsertMiss(hash, target_group.probe_length); + return target_group.offset; +} +} // namespace + +size_t PrepareInsertLarge(CommonFields& common, + const PolicyFunctions& __restrict policy, size_t hash, + Group::NonIterableBitMaskType mask_empty, + FindInfo target_group) { + // NOLINTNEXTLINE(misc-static-assert) + ABSL_SWISSTABLE_ASSERT(!SwisstableGenerationsEnabled()); + return PrepareInsertLargeImpl(common, policy, hash, mask_empty, target_group); +} + +size_t PrepareInsertLargeGenerationsEnabled( + CommonFields& common, const PolicyFunctions& policy, size_t hash, + Group::NonIterableBitMaskType mask_empty, FindInfo target_group, + absl::FunctionRef<size_t(size_t)> recompute_hash) { + // NOLINTNEXTLINE(misc-static-assert) + ABSL_SWISSTABLE_ASSERT(SwisstableGenerationsEnabled()); + if (common.should_rehash_for_bug_detection_on_insert()) { + // Move to a different heap allocation in order to detect bugs. + const size_t cap = common.capacity(); + ResizeAllocatedTableWithSeedChange( + common, policy, common.growth_left() > 0 ? cap : NextCapacity(cap)); + hash = recompute_hash(common.seed().seed()); + std::tie(target_group, mask_empty) = + find_first_non_full_group(common, hash); + } + return PrepareInsertLargeImpl(common, policy, hash, mask_empty, target_group); } namespace { @@ -1808,35 +2047,44 @@ // We need to instantiate ALL possible template combinations because we define // the function in the cc file. template size_t GrowSooTableToNextCapacityAndPrepareInsert<0, false>( - CommonFields&, const PolicyFunctions&, size_t, ctrl_t); + CommonFields&, const PolicyFunctions&, absl::FunctionRef<size_t(size_t)>, + bool); template size_t GrowSooTableToNextCapacityAndPrepareInsert< - OptimalMemcpySizeForSooSlotTransfer(1), true>(CommonFields&, - const PolicyFunctions&, - size_t, ctrl_t); + OptimalMemcpySizeForSooSlotTransfer(1), true>( + CommonFields&, const PolicyFunctions&, absl::FunctionRef<size_t(size_t)>, + bool); static_assert(VerifyOptimalMemcpySizeForSooSlotTransferRange(2, 3)); template size_t GrowSooTableToNextCapacityAndPrepareInsert< - OptimalMemcpySizeForSooSlotTransfer(3), true>(CommonFields&, - const PolicyFunctions&, - size_t, ctrl_t); + OptimalMemcpySizeForSooSlotTransfer(3), true>( + CommonFields&, const PolicyFunctions&, absl::FunctionRef<size_t(size_t)>, + bool); static_assert(VerifyOptimalMemcpySizeForSooSlotTransferRange(4, 8)); template size_t GrowSooTableToNextCapacityAndPrepareInsert< - OptimalMemcpySizeForSooSlotTransfer(8), true>(CommonFields&, - const PolicyFunctions&, - size_t, ctrl_t); + OptimalMemcpySizeForSooSlotTransfer(8), true>( + CommonFields&, const PolicyFunctions&, absl::FunctionRef<size_t(size_t)>, + bool); #if UINTPTR_MAX == UINT32_MAX static_assert(MaxSooSlotSize() == 8); #else static_assert(VerifyOptimalMemcpySizeForSooSlotTransferRange(9, 16)); template size_t GrowSooTableToNextCapacityAndPrepareInsert< - OptimalMemcpySizeForSooSlotTransfer(16), true>(CommonFields&, - const PolicyFunctions&, - size_t, ctrl_t); + OptimalMemcpySizeForSooSlotTransfer(16), true>( + CommonFields&, const PolicyFunctions&, absl::FunctionRef<size_t(size_t)>, + bool); static_assert(MaxSooSlotSize() == 16); #endif +template void* AllocateBackingArray<BackingArrayAlignment(alignof(size_t)), + std::allocator<char>>(void* alloc, + size_t n); +template void DeallocateBackingArray<BackingArrayAlignment(alignof(size_t)), + std::allocator<char>>( + void* alloc, size_t capacity, ctrl_t* ctrl, size_t slot_size, + size_t slot_align, bool had_infoz); + } // namespace container_internal ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/container/internal/raw_hash_set.h b/absl/container/internal/raw_hash_set.h index f5e3fdf..d307e2d 100644 --- a/absl/container/internal/raw_hash_set.h +++ b/absl/container/internal/raw_hash_set.h
@@ -150,11 +150,11 @@ // To `insert`, we compose `unchecked_insert` with `find`. We compute `h(x)` and // perform a `find` to see if it's already present; if it is, we're done. If // it's not, we may decide the table is getting overcrowded (i.e. the load -// factor is greater than 7/8 for big tables; `is_small()` tables use a max load -// factor of 1); in this case, we allocate a bigger array, `unchecked_insert` -// each element of the table into the new array (we know that no insertion here -// will insert an already-present value), and discard the old backing array. At -// this point, we may `unchecked_insert` the value `x`. +// factor is greater than 7/8 for big tables; tables smaller than one probing +// group use a max load factor of 1); in this case, we allocate a bigger array, +// `unchecked_insert` each element of the table into the new array (we know that +// no insertion here will insert an already-present value), and discard the old +// backing array. At this point, we may `unchecked_insert` the value `x`. // // Below, `unchecked_insert` is partly implemented by `prepare_insert`, which // presents a viable, initialized slot pointee to the caller. @@ -194,6 +194,7 @@ #include <utility> #include "absl/base/attributes.h" +#include "absl/base/casts.h" #include "absl/base/config.h" #include "absl/base/internal/endian.h" #include "absl/base/internal/iterator_traits.h" @@ -282,10 +283,8 @@ swap(lhs, rhs); } template <typename AllocType> -void SwapAlloc(AllocType& lhs, AllocType& rhs, +void SwapAlloc([[maybe_unused]] AllocType& lhs, [[maybe_unused]] AllocType& rhs, std::false_type /* propagate_on_container_swap */) { - (void)lhs; - (void)rhs; assert(lhs == rhs && "It's UB to call swap with unequal non-propagating allocators."); } @@ -369,19 +368,7 @@ std::declval<Ts>()...))>, Policy, Hash, Eq, Ts...> : std::true_type {}; -// TODO(alkis): Switch to std::is_nothrow_swappable when gcc/clang supports it. -template <class T> -constexpr bool IsNoThrowSwappable(std::true_type = {} /* is_swappable */) { - using std::swap; - return noexcept(swap(std::declval<T&>(), std::declval<T&>())); -} -template <class T> -constexpr bool IsNoThrowSwappable(std::false_type /* is_swappable */) { - return false; -} - -// See definition comment for why this is size 32. -ABSL_DLL extern const ctrl_t kEmptyGroup[32]; +ABSL_DLL extern ctrl_t kDefaultIterControl; // We use these sentinel capacity values in debug mode to indicate different // classes of bugs. @@ -395,17 +382,14 @@ kSelfMovedFrom, }; -// Returns a pointer to a control byte group that can be used by empty tables. -inline ctrl_t* EmptyGroup() { - // Const must be cast away here; no uses of this function will actually write - // to it because it is only used for empty tables. - return const_cast<ctrl_t*>(kEmptyGroup + 16); -} +// Returns a pointer to a control byte that can be used by default-constructed +// iterators. We don't expect this pointer to be dereferenced. +inline ctrl_t* DefaultIterControl() { return &kDefaultIterControl; } // For use in SOO iterators. // TODO(b/289225379): we could potentially get rid of this by adding an is_soo // bit in iterators. This would add branches but reduce cache misses. -ABSL_DLL extern const ctrl_t kSooControl[17]; +ABSL_DLL extern const ctrl_t kSooControl[2]; // Returns a pointer to a full byte followed by a sentinel byte. inline ctrl_t* SooControl() { @@ -452,29 +436,31 @@ // The number of bits in the seed. // It is big enough to ensure non-determinism of iteration order. // We store the seed inside a uint64_t together with size and other metadata. - // Using 16 bits allows us to save one `and` instruction in H1 (we use movzwl - // instead of movq+and). + // Using 16 bits allows us to save one `and` instruction in H1 (we use + // sign-extended move instead of mov+and). static constexpr size_t kBitCount = 16; + static constexpr size_t kSignBit = uint64_t{1} << (kBitCount - 1); - // Returns the seed for the table. Only the lowest kBitCount are non zero. - size_t seed() const { return seed_; } + // Returns the seed for the table. + size_t seed() const { + // We use a sign-extended load to ensure high bits are non-zero. + int16_t seed_signed = absl::bit_cast<int16_t>(seed_); + auto seed_sign_extended = + static_cast<std::make_signed_t<size_t>>(seed_signed); + return absl::bit_cast<size_t>(seed_sign_extended); + } private: friend class HashtableSize; - explicit PerTableSeed(size_t seed) : seed_(seed) {} + explicit PerTableSeed(uint16_t seed) : seed_(seed) { + ABSL_SWISSTABLE_ASSERT((seed & kSignBit) != 0 || seed == 0); + } - const size_t seed_; + // The most significant bit of the seed is always 1 when there is a non-zero + // seed. This way, when sign-extended the seed has non-zero high bits. + const uint16_t seed_; }; -// Returns next per-table seed. -inline uint16_t NextSeed() { - static_assert(PerTableSeed::kBitCount == 16); - thread_local uint16_t seed = - static_cast<uint16_t>(reinterpret_cast<uintptr_t>(&seed)); - seed += uint16_t{0xad53}; - return seed; -} - // The size and also has additionally // 1) one bit that stores whether we have infoz. // 2) PerTableSeed::kBitCount bits for the seed. @@ -502,8 +488,14 @@ return PerTableSeed(static_cast<size_t>(data_) & kSeedMask); } - void generate_new_seed() { - data_ = (data_ & ~kSeedMask) ^ uint64_t{NextSeed()}; + void generate_new_seed() { set_seed(NextSeed()); } + + // We need to use a constant seed when the table is sampled so that sampled + // hashes use the same seed and can e.g. identify stuck bits accurately. + void set_sampled_seed() { set_seed(PerTableSeed::kSignBit); } + + bool is_sampled_seed() const { + return (data_ & kSeedMask) == PerTableSeed::kSignBit; } // Returns true if the table has infoz. @@ -516,7 +508,13 @@ void set_no_seed_for_testing() { data_ &= ~kSeedMask; } + // Returns next per-table seed. + static uint16_t NextSeed(); + private: + void set_seed(uint16_t seed) { + data_ = (data_ & ~kSeedMask) | (seed | PerTableSeed::kSignBit); + } static constexpr size_t kSizeShift = 64 - kSizeBitCount; static constexpr uint64_t kSizeOneNoMetadata = uint64_t{1} << kSizeShift; static constexpr uint64_t kMetadataMask = kSizeOneNoMetadata - 1; @@ -527,15 +525,13 @@ uint64_t data_; }; -// Extracts the H1 portion of a hash: 57 bits mixed with a per-table seed. -inline size_t H1(size_t hash, PerTableSeed seed) { - return (hash >> 7) ^ seed.seed(); -} +// H1 is just the low bits of the hash. +inline size_t H1(size_t hash) { return hash; } -// Extracts the H2 portion of a hash: the 7 bits not used for H1. +// Extracts the H2 portion of a hash: the 7 most significant bits. // // These are used as an occupied control byte. -inline h2_t H2(size_t hash) { return hash & 0x7F; } +inline h2_t H2(size_t hash) { return hash >> (sizeof(size_t) * 8 - 7); } // When there is an insertion with no reserved growth, we rehash with // probability `min(1, RehashProbabilityConstant() / capacity())`. Using a @@ -571,11 +567,9 @@ // references. We rehash on the first insertion after reserved_growth_ reaches // 0 after a call to reserve. We also do a rehash with low probability // whenever reserved_growth_ is zero. - bool should_rehash_for_bug_detection_on_insert(PerTableSeed seed, - size_t capacity) const; + bool should_rehash_for_bug_detection_on_insert(size_t capacity) const; // Similar to above, except that we don't depend on reserved_growth_. - bool should_rehash_for_bug_detection_on_move(PerTableSeed seed, - size_t capacity) const; + bool should_rehash_for_bug_detection_on_move(size_t capacity) const; void maybe_increment_generation_on_insert() { if (reserved_growth_ == kReservedGrowthJustRanOut) reserved_growth_ = 0; @@ -628,12 +622,8 @@ CommonFieldsGenerationInfoDisabled& operator=( CommonFieldsGenerationInfoDisabled&&) = default; - bool should_rehash_for_bug_detection_on_insert(PerTableSeed, size_t) const { - return false; - } - bool should_rehash_for_bug_detection_on_move(PerTableSeed, size_t) const { - return false; - } + bool should_rehash_for_bug_detection_on_insert(size_t) const { return false; } + bool should_rehash_for_bug_detection_on_move(size_t) const { return false; } void maybe_increment_generation_on_insert() {} void increment_generation() {} void reset_reserved_growth(size_t, size_t) {} @@ -781,6 +771,9 @@ // A valid capacity is a non-zero integer `2^m - 1`. constexpr bool IsValidCapacity(size_t n) { return ((n + 1) & n) == 0 && n > 0; } +// Whether a table is small enough that we don't need to hash any keys. +constexpr bool IsSmallCapacity(size_t capacity) { return capacity <= 1; } + // Returns the number of "cloned control bytes". // // This is the number of control bytes that are present both at the beginning @@ -790,7 +783,7 @@ // Returns the number of control bytes including cloned. constexpr size_t NumControlBytes(size_t capacity) { - return capacity + 1 + NumClonedBytes(); + return IsSmallCapacity(capacity) ? 0 : capacity + 1 + NumClonedBytes(); } // Computes the offset from the start of the backing allocation of control. @@ -808,6 +801,9 @@ // Helper class for computing offsets and allocation size of hash set fields. class RawHashSetLayout { public: + // TODO(b/413062340): maybe don't allocate growth info for capacity 1 tables. + // Doing so may require additional branches/complexity so it might not be + // worth it. explicit RawHashSetLayout(size_t capacity, size_t slot_size, size_t slot_align, bool has_infoz) : control_offset_(ControlOffset(has_infoz)), @@ -846,50 +842,30 @@ struct HashtableFreeFunctionsAccess; -// Suppress erroneous uninitialized memory errors on GCC. For example, GCC -// thinks that the call to slot_array() in find_or_prepare_insert() is reading -// uninitialized memory, but slot_array is only called there when the table is -// non-empty and this memory is initialized when the table is non-empty. -#if !defined(__clang__) && defined(__GNUC__) -#define ABSL_SWISSTABLE_IGNORE_UNINITIALIZED(x) \ - _Pragma("GCC diagnostic push") \ - _Pragma("GCC diagnostic ignored \"-Wmaybe-uninitialized\"") \ - _Pragma("GCC diagnostic ignored \"-Wuninitialized\"") x; \ - _Pragma("GCC diagnostic pop") -#define ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN(x) \ - ABSL_SWISSTABLE_IGNORE_UNINITIALIZED(return x) -#else -#define ABSL_SWISSTABLE_IGNORE_UNINITIALIZED(x) x -#define ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN(x) return x -#endif - // This allows us to work around an uninitialized memory warning when // constructing begin() iterators in empty hashtables. +template <typename T> union MaybeInitializedPtr { - void* get() const { ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN(p); } - void set(void* ptr) { p = ptr; } + T* get() const { ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN(p); } + void set(T* ptr) { p = ptr; } - void* p; + T* p; }; struct HeapPtrs { - explicit HeapPtrs(uninitialized_tag_t) {} - explicit HeapPtrs(ctrl_t* c) : control(c) {} - // The control bytes (and, also, a pointer near to the base of the backing // array). // - // This contains `capacity + 1 + NumClonedBytes()` entries, even - // when the table is empty (hence EmptyGroup). + // This contains `capacity + 1 + NumClonedBytes()` entries. // // Note that growth_info is stored immediately before this pointer. - // May be uninitialized for SOO tables. - ctrl_t* control; + // May be uninitialized for small tables. + MaybeInitializedPtr<ctrl_t> control; // The beginning of the slots, located at `SlotOffset()` bytes after // `control`. May be uninitialized for empty tables. // Note: we can't use `slots` because Qt defines "slots" as a macro. - MaybeInitializedPtr slot_array; + MaybeInitializedPtr<void> slot_array; }; // Returns the maximum size of the SOO slot. @@ -898,19 +874,16 @@ // Manages the backing array pointers or the SOO slot. When raw_hash_set::is_soo // is true, the SOO slot is stored in `soo_data`. Otherwise, we use `heap`. union HeapOrSoo { - explicit HeapOrSoo(uninitialized_tag_t) : heap(uninitialized_tag_t{}) {} - explicit HeapOrSoo(ctrl_t* c) : heap(c) {} - - ctrl_t*& control() { + MaybeInitializedPtr<ctrl_t>& control() { ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN(heap.control); } - ctrl_t* control() const { + MaybeInitializedPtr<ctrl_t> control() const { ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN(heap.control); } - MaybeInitializedPtr& slot_array() { + MaybeInitializedPtr<void>& slot_array() { ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN(heap.slot_array); } - MaybeInitializedPtr slot_array() const { + MaybeInitializedPtr<void> slot_array() const { ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN(heap.slot_array); } void* get_soo_data() { @@ -939,20 +912,13 @@ class CommonFields : public CommonFieldsGenerationInfo { public: explicit CommonFields(soo_tag_t) - : capacity_(SooCapacity()), - size_(no_seed_empty_tag_t{}), - heap_or_soo_(uninitialized_tag_t{}) {} + : capacity_(SooCapacity()), size_(no_seed_empty_tag_t{}) {} explicit CommonFields(full_soo_tag_t) - : capacity_(SooCapacity()), - size_(full_soo_tag_t{}), - heap_or_soo_(uninitialized_tag_t{}) {} + : capacity_(SooCapacity()), size_(full_soo_tag_t{}) {} explicit CommonFields(non_soo_tag_t) - : capacity_(0), - size_(no_seed_empty_tag_t{}), - heap_or_soo_(EmptyGroup()) {} + : capacity_(0), size_(no_seed_empty_tag_t{}) {} // For use in swapping. - explicit CommonFields(uninitialized_tag_t) - : size_(uninitialized_tag_t{}), heap_or_soo_(uninitialized_tag_t{}) {} + explicit CommonFields(uninitialized_tag_t) : size_(uninitialized_tag_t{}) {} // Not copyable CommonFields(const CommonFields&) = delete; @@ -979,31 +945,25 @@ const void* soo_data() const { return heap_or_soo_.get_soo_data(); } void* soo_data() { return heap_or_soo_.get_soo_data(); } - ctrl_t* control() const { return heap_or_soo_.control(); } + ctrl_t* control() const { + ABSL_SWISSTABLE_ASSERT(capacity() > 0); + // Assume that the control bytes don't alias `this`. + ctrl_t* ctrl = heap_or_soo_.control().get(); + [[maybe_unused]] size_t num_control_bytes = NumControlBytes(capacity()); + ABSL_ASSUME(reinterpret_cast<uintptr_t>(ctrl + num_control_bytes) <= + reinterpret_cast<uintptr_t>(this) || + reinterpret_cast<uintptr_t>(this + 1) <= + reinterpret_cast<uintptr_t>(ctrl)); + ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN(ctrl); + } - // When we set the control bytes, we also often want to generate a new seed. - // So we bundle these two operations together to make sure we don't forget to - // generate a new seed. - // The table will be invalidated if - // `kGenerateSeed && !empty() && !is_single_group(capacity())` because H1 is - // being changed. In such cases, we will need to rehash the table. - template <bool kGenerateSeed> - void set_control(ctrl_t* c) { - heap_or_soo_.control() = c; - if constexpr (kGenerateSeed) { - generate_new_seed(); - } - } - void* backing_array_start() const { - // growth_info (and maybe infoz) is stored before control bytes. - ABSL_SWISSTABLE_ASSERT( - reinterpret_cast<uintptr_t>(control()) % alignof(size_t) == 0); - return control() - ControlOffset(has_infoz()); - } + void set_control(ctrl_t* c) { heap_or_soo_.control().set(c); } // Note: we can't use slots() because Qt defines "slots" as a macro. void* slot_array() const { return heap_or_soo_.slot_array().get(); } - MaybeInitializedPtr slots_union() const { return heap_or_soo_.slot_array(); } + MaybeInitializedPtr<void> slots_union() const { + return heap_or_soo_.slot_array(); + } void set_slots(void* s) { heap_or_soo_.slot_array().set(s); } // The number of filled slots. @@ -1032,13 +992,20 @@ } bool empty() const { return size_.empty(); } - // The seed used for the H1 part of the hash function. + // The seed used for the hash function. PerTableSeed seed() const { return size_.seed(); } - // Generates a new seed for the H1 part of the hash function. - // The table will be invalidated if - // `kGenerateSeed && !empty() && !is_single_group(capacity())` because H1 is - // being changed. In such cases, we will need to rehash the table. - void generate_new_seed() { size_.generate_new_seed(); } + // Generates a new seed the hash function. + // The table will be invalidated if `!empty()` because hash is being changed. + // In such cases, we will need to rehash the table. + void generate_new_seed(bool has_infoz) { + // Note: we can't use has_infoz() here because we set has_infoz later than + // we generate the seed. + if (ABSL_PREDICT_FALSE(has_infoz)) { + size_.set_sampled_seed(); + return; + } + size_.generate_new_seed(); + } void set_no_seed_for_testing() { size_.set_no_seed_for_testing(); } // The total number of available slots. @@ -1049,6 +1016,7 @@ c > kAboveMaxValidCapacity); capacity_ = c; } + bool is_small() const { return IsSmallCapacity(capacity_); } // The number of slots we can still fill without needing to rehash. // This is stored in the heap allocation before the control bytes. @@ -1057,6 +1025,7 @@ size_t growth_left() const { return growth_info().GetGrowthLeft(); } GrowthInfo& growth_info() { + ABSL_SWISSTABLE_ASSERT(!is_small()); return GetGrowthInfoFromControl(control()); } GrowthInfo growth_info() const { @@ -1064,16 +1033,26 @@ } bool has_infoz() const { return size_.has_infoz(); } - void set_has_infoz() { size_.set_has_infoz(); } + void set_has_infoz() { + ABSL_SWISSTABLE_ASSERT(size_.is_sampled_seed()); + size_.set_has_infoz(); + } + + HashtablezInfoHandle* infoz_ptr() const { + // growth_info is stored before control bytes. + ABSL_SWISSTABLE_ASSERT( + reinterpret_cast<uintptr_t>(control()) % alignof(size_t) == 0); + ABSL_SWISSTABLE_ASSERT(has_infoz()); + return reinterpret_cast<HashtablezInfoHandle*>( + control() - ControlOffset(/*has_infoz=*/true)); + } HashtablezInfoHandle infoz() { - return has_infoz() - ? *reinterpret_cast<HashtablezInfoHandle*>(backing_array_start()) - : HashtablezInfoHandle(); + return has_infoz() ? *infoz_ptr() : HashtablezInfoHandle(); } void set_infoz(HashtablezInfoHandle infoz) { ABSL_SWISSTABLE_ASSERT(has_infoz()); - *reinterpret_cast<HashtablezInfoHandle*>(backing_array_start()) = infoz; + *infoz_ptr() = infoz; } bool should_rehash_for_bug_detection_on_insert() const { @@ -1084,11 +1063,11 @@ // will end up rehashing anyways. if (growth_left() == 0) return false; return CommonFieldsGenerationInfo:: - should_rehash_for_bug_detection_on_insert(seed(), capacity()); + should_rehash_for_bug_detection_on_insert(capacity()); } bool should_rehash_for_bug_detection_on_move() const { return CommonFieldsGenerationInfo::should_rehash_for_bug_detection_on_move( - seed(), capacity()); + capacity()); } void reset_reserved_growth(size_t reservation) { CommonFieldsGenerationInfo::reset_reserved_growth(reservation, size()); @@ -1159,7 +1138,7 @@ HeapOrSoo heap_or_soo_; }; -template <class Policy, class Hash, class Eq, class Alloc> +template <class Policy, class... Params> class raw_hash_set; // Returns the next valid capacity after `n`. @@ -1223,6 +1202,10 @@ // NormalizeCapacity(size). int leading_zeros = absl::countl_zero(size); constexpr size_t kLast3Bits = size_t{7} << (sizeof(size_t) * 8 - 3); + // max_size_for_next_capacity = max_load_factor * next_capacity + // = (7/8) * (~size_t{} >> leading_zeros) + // = (7/8*~size_t{}) >> leading_zeros + // = kLast3Bits >> leading_zeros size_t max_size_for_next_capacity = kLast3Bits >> leading_zeros; // Decrease shift if size is too big for the minimum capacity. leading_zeros -= static_cast<int>(size > max_size_for_next_capacity); @@ -1267,7 +1250,7 @@ if (ABSL_PREDICT_FALSE(ctrl == nullptr)) { ABSL_RAW_LOG(FATAL, "%s called on end() iterator.", operation); } - if (ABSL_PREDICT_FALSE(ctrl == EmptyGroup())) { + if (ABSL_PREDICT_FALSE(ctrl == DefaultIterControl())) { ABSL_RAW_LOG(FATAL, "%s called on default-constructed iterator.", operation); } @@ -1302,7 +1285,7 @@ const GenerationType* generation_ptr) { if (!SwisstableDebugEnabled()) return; const bool ctrl_is_valid_for_comparison = - ctrl == nullptr || ctrl == EmptyGroup() || IsFull(*ctrl); + ctrl == nullptr || ctrl == DefaultIterControl() || IsFull(*ctrl); if (SwisstableGenerationsEnabled()) { if (ABSL_PREDICT_FALSE(generation != *generation_ptr)) { ABSL_RAW_LOG(FATAL, @@ -1368,8 +1351,8 @@ } }; - const bool a_is_default = ctrl_a == EmptyGroup(); - const bool b_is_default = ctrl_b == EmptyGroup(); + const bool a_is_default = ctrl_a == DefaultIterControl(); + const bool b_is_default = ctrl_b == DefaultIterControl(); if (a_is_default && b_is_default) return; fail_if(a_is_default != b_is_default, "Comparing default-constructed hashtable iterator with a " @@ -1377,13 +1360,6 @@ if (SwisstableGenerationsEnabled()) { if (ABSL_PREDICT_TRUE(generation_ptr_a == generation_ptr_b)) return; - // Users don't need to know whether the tables are SOO so don't mention SOO - // in the debug message. - const bool a_is_soo = IsSooControl(ctrl_a); - const bool b_is_soo = IsSooControl(ctrl_b); - fail_if(a_is_soo != b_is_soo || (a_is_soo && b_is_soo), - "Comparing iterators from different hashtables."); - const bool a_is_empty = IsEmptyGeneration(generation_ptr_a); const bool b_is_empty = IsEmptyGeneration(generation_ptr_b); fail_if(a_is_empty != b_is_empty, @@ -1419,26 +1395,16 @@ } // Begins a probing operation on `common.control`, using `hash`. -inline probe_seq<Group::kWidth> probe(size_t h1, size_t capacity) { +inline probe_seq<Group::kWidth> probe_h1(size_t capacity, size_t h1) { return probe_seq<Group::kWidth>(h1, capacity); } -inline probe_seq<Group::kWidth> probe(PerTableSeed seed, size_t capacity, - size_t hash) { - return probe(H1(hash, seed), capacity); +inline probe_seq<Group::kWidth> probe(size_t capacity, size_t hash) { + return probe_h1(capacity, H1(hash)); } inline probe_seq<Group::kWidth> probe(const CommonFields& common, size_t hash) { - return probe(common.seed(), common.capacity(), hash); + return probe(common.capacity(), hash); } -// Probes an array of control bits using a probe sequence derived from `hash`, -// and returns the offset corresponding to the first deleted or empty slot. -// -// Behavior when the entire table is full is undefined. -// -// NOTE: this function must work with tables having both empty and deleted -// slots in the same group. Such tables appear during `erase()`. -FindInfo find_first_non_full(const CommonFields& common, size_t hash); - constexpr size_t kProbedElementIndexSentinel = ~size_t{}; // Implementation detail of transfer_unprobed_elements_to_next_capacity_fn. @@ -1502,79 +1468,11 @@ ctrl_t* new_ctrl, size_t new_capacity); -// Sets sanitizer poisoning for slot corresponding to control byte being set. -inline void DoSanitizeOnSetCtrl(const CommonFields& c, size_t i, ctrl_t h, - size_t slot_size) { - ABSL_SWISSTABLE_ASSERT(i < c.capacity()); - auto* slot_i = static_cast<const char*>(c.slot_array()) + i * slot_size; - if (IsFull(h)) { - SanitizerUnpoisonMemoryRegion(slot_i, slot_size); - } else { - SanitizerPoisonMemoryRegion(slot_i, slot_size); - } -} - -// 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(const CommonFields& c, size_t i, ctrl_t h, - size_t slot_size) { - DoSanitizeOnSetCtrl(c, i, h, slot_size); - ctrl_t* ctrl = c.control(); - ctrl[i] = h; - ctrl[((i - NumClonedBytes()) & c.capacity()) + - (NumClonedBytes() & c.capacity())] = h; -} -// Overload for setting to an occupied `h2_t` rather than a special `ctrl_t`. -inline void SetCtrl(const CommonFields& c, size_t i, h2_t h, size_t slot_size) { - SetCtrl(c, i, static_cast<ctrl_t>(h), slot_size); -} - -// Like SetCtrl, but in a single group table, we can save some operations when -// setting the cloned control byte. -inline void SetCtrlInSingleGroupTable(const CommonFields& c, size_t i, ctrl_t h, - size_t slot_size) { - ABSL_SWISSTABLE_ASSERT(is_single_group(c.capacity())); - DoSanitizeOnSetCtrl(c, i, h, slot_size); - ctrl_t* ctrl = c.control(); - ctrl[i] = h; - ctrl[i + c.capacity() + 1] = h; -} -// Overload for setting to an occupied `h2_t` rather than a special `ctrl_t`. -inline void SetCtrlInSingleGroupTable(const CommonFields& c, size_t i, h2_t h, - size_t slot_size) { - SetCtrlInSingleGroupTable(c, i, static_cast<ctrl_t>(h), slot_size); -} - -// Like SetCtrl, but in a table with capacity >= Group::kWidth - 1, -// we can save some operations when setting the cloned control byte. -inline void SetCtrlInLargeTable(const CommonFields& c, size_t i, ctrl_t h, - size_t slot_size) { - ABSL_SWISSTABLE_ASSERT(c.capacity() >= Group::kWidth - 1); - DoSanitizeOnSetCtrl(c, i, h, slot_size); - ctrl_t* ctrl = c.control(); - ctrl[i] = h; - ctrl[((i - NumClonedBytes()) & c.capacity()) + NumClonedBytes()] = h; -} -// Overload for setting to an occupied `h2_t` rather than a special `ctrl_t`. -inline void SetCtrlInLargeTable(const CommonFields& c, size_t i, h2_t h, - size_t slot_size) { - SetCtrlInLargeTable(c, i, static_cast<ctrl_t>(h), slot_size); -} - // growth_info (which is a size_t) is stored with the backing array. constexpr size_t BackingArrayAlignment(size_t align_of_slot) { return (std::max)(align_of_slot, alignof(GrowthInfo)); } -// Returns the address of the ith slot in slots where each slot occupies -// slot_size. -inline void* SlotAddress(void* slot_array, size_t slot, size_t slot_size) { - return static_cast<void*>(static_cast<char*>(slot_array) + - (slot * slot_size)); -} - // Iterates over all full slots and calls `cb(const ctrl_t*, void*)`. // No insertion to the table is allowed during `cb` call. // Erasure is allowed only for the element passed to the callback. @@ -1592,33 +1490,14 @@ return std::is_same_v<CharAlloc, std::allocator<char>>; } -template <bool kSooEnabled> -bool ShouldSampleHashtablezInfoOnResize(bool force_sampling, - bool is_hashtablez_eligible, - size_t old_capacity, CommonFields& c) { - if (!is_hashtablez_eligible) return false; - // Force sampling is only allowed for SOO tables. - ABSL_SWISSTABLE_ASSERT(kSooEnabled || !force_sampling); - if (kSooEnabled && force_sampling) { - return true; - } - // In SOO, we sample on the first insertion so if this is an empty SOO case - // (e.g. when reserve is called), then we still need to sample. - if (kSooEnabled && old_capacity == SooCapacity() && c.empty()) { - return ShouldSampleNextTable(); - } - if (!kSooEnabled && old_capacity == 0) { - return ShouldSampleNextTable(); - } - return false; -} - // Allocates `n` bytes for a backing array. template <size_t AlignOfBackingArray, typename Alloc> -ABSL_ATTRIBUTE_NOINLINE void* AllocateBackingArray(void* alloc, size_t n) { +void* AllocateBackingArray(void* alloc, size_t n) { return Allocate<AlignOfBackingArray>(static_cast<Alloc*>(alloc), n); } +// Note: we mark this function as ABSL_ATTRIBUTE_NOINLINE because we don't want +// it to be inlined into e.g. the destructor to save code size. template <size_t AlignOfBackingArray, typename Alloc> ABSL_ATTRIBUTE_NOINLINE void DeallocateBackingArray( void* alloc, size_t capacity, ctrl_t* ctrl, size_t slot_size, @@ -1647,7 +1526,7 @@ void* (*hash_fn)(CommonFields& common); // Returns the hash of the pointed-to slot. - size_t (*hash_slot)(const void* hash_fn, void* slot); + HashSlotFn hash_slot; // Transfers the contents of `count` slots from src_slot to dst_slot. // We use ability to transfer several slots in single group table growth. @@ -1802,23 +1681,20 @@ // Resizes SOO table to the NextCapacity(SooCapacity()) and prepares insert for // the given new_hash. Returns the offset of the new element. -// `soo_slot_ctrl` is the control byte of the SOO slot. -// If soo_slot_ctrl is kEmpty -// 1. The table must be empty. -// 2. Table will be forced to be sampled. // All possible template combinations are defined in cc file to improve // compilation time. template <size_t SooSlotMemcpySize, bool TransferUsesMemcpy> -size_t GrowSooTableToNextCapacityAndPrepareInsert(CommonFields& common, - const PolicyFunctions& policy, - size_t new_hash, - ctrl_t soo_slot_ctrl); +size_t GrowSooTableToNextCapacityAndPrepareInsert( + CommonFields& common, const PolicyFunctions& policy, + absl::FunctionRef<size_t(size_t)> get_hash, bool force_sampling); -// As `ResizeFullSooTableToNextCapacity`, except that we also force the SOO -// table to be sampled. SOO tables need to switch from SOO to heap in order to -// store the infoz. No-op if sampling is disabled or not possible. -void GrowFullSooTableToNextCapacityForceSampling(CommonFields& common, - const PolicyFunctions& policy); +// PrepareInsert for small tables (is_small()==true). +// Returns the new control and the new slot. +// Hash is only computed if the table is sampled or grew to large size +// (is_small()==false). +std::pair<ctrl_t*, void*> PrepareInsertSmallNonSoo( + CommonFields& common, const PolicyFunctions& policy, + absl::FunctionRef<size_t(size_t)> get_hash); // Resizes table with allocated slots and change the table seed. // Tables with SOO enabled must have capacity > policy.soo_capacity. @@ -1833,8 +1709,9 @@ void ClearBackingArray(CommonFields& c, const PolicyFunctions& policy, void* alloc, bool reuse, bool soo_enabled); -// Type-erased version of raw_hash_set::erase_meta_only. -void EraseMetaOnly(CommonFields& c, size_t index, size_t slot_size); +// Type-erased versions of raw_hash_set::erase_meta_only_{small,large}. +void EraseMetaOnlySmall(CommonFields& c, bool soo_enabled, size_t slot_size); +void EraseMetaOnlyLarge(CommonFields& c, const ctrl_t* ctrl, size_t slot_size); // 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 @@ -1853,23 +1730,44 @@ // empty class cases. void* GetRefForEmptyClass(CommonFields& common); -// Given the hash of a value not currently in the table and the first empty -// slot in the probe sequence, finds a viable slot index to insert it at. +// Given the hash of a value not currently in the table and the first group with +// an empty slot in the probe sequence, finds a viable slot index to insert it +// at. // // In case there's no space left, the table can be resized or rehashed // (for tables with deleted slots, see FindInsertPositionWithGrowthOrRehash). // // In the case of absence of deleted slots and positive growth_left, the element -// can be inserted in the provided `target` position. +// can be inserted in one of the empty slots in the provided `target_group`. // // When the table has deleted slots (according to GrowthInfo), the target // position will be searched one more time using `find_first_non_full`. // -// REQUIRES: Table is not SOO. +// REQUIRES: `!common.is_small()`. // REQUIRES: At least one non-full slot available. -// REQUIRES: `target` is a valid empty position to insert. -size_t PrepareInsertNonSoo(CommonFields& common, const PolicyFunctions& policy, - size_t hash, FindInfo target); +// REQUIRES: `mask_empty` is a mask containing empty slots for the +// `target_group`. +// REQUIRES: `target_group` is a starting position for the group that has +// at least one empty slot. +size_t PrepareInsertLarge(CommonFields& common, const PolicyFunctions& policy, + size_t hash, Group::NonIterableBitMaskType mask_empty, + FindInfo target_group); + +// Same as above, but with generations enabled, we may end up changing the seed, +// which means we need to be able to recompute the hash. +size_t PrepareInsertLargeGenerationsEnabled( + CommonFields& common, const PolicyFunctions& policy, size_t hash, + Group::NonIterableBitMaskType mask_empty, FindInfo target_group, + absl::FunctionRef<size_t(size_t)> recompute_hash); + +template <typename Policy, typename Hash, typename Eq, typename Alloc> +struct InstantiateRawHashSet { + using type = typename ApplyWithoutDefaultSuffix< + raw_hash_set, + TypeList<void, typename Policy::DefaultHash, typename Policy::DefaultEq, + typename Policy::DefaultAlloc>, + TypeList<Policy, Hash, Eq, Alloc>>::type; +}; // A SwissTable. // @@ -1877,26 +1775,47 @@ // the slots of the hashtable (see hash_policy_traits.h for the full interface // of policy). // +// Params...: a variadic list of parameters that allows us to omit default +// types. This reduces the mangled name of the class and the size of +// debug strings like __PRETTY_FUNCTION__. Default types do not give +// any new information. +// // Hash: a (possibly polymorphic) functor that hashes keys of the hashtable. The // functor should accept a key and return size_t as hash. For best performance // it is important that the hash function provides high entropy across all bits // of the hash. +// This is the first element in `Params...` if it exists, or Policy::DefaultHash +// otherwise. // // Eq: a (possibly polymorphic) functor that compares two keys for equality. It // should accept two (of possibly different type) keys and return a bool: true // if they are equal, false if they are not. If two keys compare equal, then // their hash values as defined by Hash MUST be equal. +// This is the second element in `Params...` if it exists, or Policy::DefaultEq +// otherwise. // // Allocator: an Allocator // [https://en.cppreference.com/w/cpp/named_req/Allocator] with which // the storage of the hashtable will be allocated and the elements will be // constructed and destroyed. -template <class Policy, class Hash, class Eq, class Alloc> +// This is the third element in `Params...` if it exists, or +// Policy::DefaultAlloc otherwise. +template <class Policy, class... Params> class raw_hash_set { using PolicyTraits = hash_policy_traits<Policy>; + using Hash = GetFromListOr<typename Policy::DefaultHash, 0, Params...>; + using Eq = GetFromListOr<typename Policy::DefaultEq, 1, Params...>; + using Alloc = GetFromListOr<typename Policy::DefaultAlloc, 2, Params...>; using KeyArgImpl = KeyArg<IsTransparent<Eq>::value && IsTransparent<Hash>::value>; + static_assert( + std::is_same_v< + typename InstantiateRawHashSet<Policy, Hash, Eq, Alloc>::type, + raw_hash_set>, + "Redundant template parameters were passed. Use InstantiateRawHashSet<> " + "instead"); + public: using init_type = typename PolicyTraits::init_type; using key_type = typename PolicyTraits::key_type; @@ -1924,6 +1843,10 @@ using slot_type = typename PolicyTraits::slot_type; + constexpr static bool kIsDefaultHash = + std::is_same_v<hasher, absl::Hash<key_type>> || + std::is_same_v<hasher, absl::container_internal::StringHash>; + // TODO(b/289225379): we could add extra SOO space inside raw_hash_set // after CommonFields to allow inlining larger slot_types (e.g. std::string), // but it's a bit complicated if we want to support incomplete mapped_type in @@ -1948,10 +1871,19 @@ bool is_soo() const { return fits_in_soo(capacity()); } bool is_full_soo() const { return is_soo() && !empty(); } + bool is_small() const { return common().is_small(); } + // Give an early error when key_type is not hashable/eq. auto KeyTypeCanBeHashed(const Hash& h, const key_type& k) -> decltype(h(k)); auto KeyTypeCanBeEq(const Eq& eq, const key_type& k) -> decltype(eq(k, k)); + // Try to be helpful when the hasher returns an unreasonable type. + using key_hash_result = + absl::remove_cvref_t<decltype(std::declval<const Hash&>()( + std::declval<const key_type&>()))>; + static_assert(sizeof(key_hash_result) >= sizeof(size_t), + "`Hash::operator()` should return a `size_t`"); + using AllocTraits = absl::allocator_traits<allocator_type>; using SlotAlloc = typename absl::allocator_traits< allocator_type>::template rebind_alloc<slot_type>; @@ -2018,19 +1950,19 @@ // PRECONDITION: not an end() iterator. reference operator*() const { - AssertIsFull(ctrl_, generation(), generation_ptr(), "operator*()"); + assert_is_full("operator*()"); return unchecked_deref(); } // PRECONDITION: not an end() iterator. pointer operator->() const { - AssertIsFull(ctrl_, generation(), generation_ptr(), "operator->"); + assert_is_full("operator->"); return &operator*(); } // PRECONDITION: not an end() iterator. iterator& operator++() { - AssertIsFull(ctrl_, generation(), generation_ptr(), "operator++"); + assert_is_full("operator++"); ++ctrl_; ++slot_; skip_empty_or_deleted(); @@ -2068,7 +2000,7 @@ // This constructor is used in begin() to avoid an MSan // use-of-uninitialized-value error. Delegating from this constructor to // the previous one doesn't avoid the error. - iterator(ctrl_t* ctrl, MaybeInitializedPtr slot, + iterator(ctrl_t* ctrl, MaybeInitializedPtr<void> slot, const GenerationType* generation_ptr) : HashSetIteratorGenerationInfo(generation_ptr), ctrl_(ctrl), @@ -2081,38 +2013,42 @@ explicit iterator(const GenerationType* generation_ptr) : HashSetIteratorGenerationInfo(generation_ptr), ctrl_(nullptr) {} + void assert_is_full(const char* operation) const { + AssertIsFull(ctrl_, generation(), generation_ptr(), operation); + } + // Fixes up `ctrl_` to point to a full or sentinel by advancing `ctrl_` and // `slot_` until they reach one. void skip_empty_or_deleted() { while (IsEmptyOrDeleted(*ctrl_)) { - uint32_t shift = - GroupFullEmptyOrDeleted{ctrl_}.CountLeadingEmptyOrDeleted(); - ctrl_ += shift; - slot_ += shift; + ++ctrl_; + ++slot_; } } - ctrl_t* control() const { return ctrl_; } - slot_type* slot() const { return slot_; } - - // We use EmptyGroup() for default-constructed iterators so that they can - // be distinguished from end iterators, which have nullptr ctrl_. - ctrl_t* ctrl_ = EmptyGroup(); - // To avoid uninitialized member warnings, put slot_ in an anonymous union. - // The member is not initialized on singleton and end iterators. - union { - slot_type* slot_; - }; - // An equality check which skips ABSL Hardening iterator invalidation // checks. // Should be used when the lifetimes of the iterators are well-enough // understood to prove that they cannot be invalid. - bool unchecked_equals(const iterator& b) { return ctrl_ == b.control(); } + bool unchecked_equals(const iterator& b) const { + return ctrl_ == b.control(); + } // Dereferences the iterator without ABSL Hardening iterator invalidation // checks. reference unchecked_deref() const { return PolicyTraits::element(slot_); } + + ctrl_t* control() const { return ctrl_; } + slot_type* slot() const { return slot_; } + + // We use DefaultIterControl() for default-constructed iterators so that + // they can be distinguished from end iterators, which have nullptr ctrl_. + ctrl_t* ctrl_ = DefaultIterControl(); + // To avoid uninitialized member warnings, put slot_ in an anonymous union. + // The member is not initialized on singleton and end iterators. + union { + slot_type* slot_; + }; }; class const_iterator { @@ -2153,14 +2089,13 @@ const GenerationType* gen) : inner_(const_cast<ctrl_t*>(ctrl), const_cast<slot_type*>(slot), gen) { } + bool unchecked_equals(const const_iterator& b) const { + return inner_.unchecked_equals(b.inner_); + } ctrl_t* control() const { return inner_.control(); } slot_type* slot() const { return inner_.slot(); } iterator inner_; - - bool unchecked_equals(const const_iterator& b) { - return inner_.unchecked_equals(b.inner_); - } }; using node_type = node_handle<Policy, hash_policy_traits<Policy>, Alloc>; @@ -2345,7 +2280,7 @@ } raw_hash_set& operator=(raw_hash_set&& that) noexcept( - absl::allocator_traits<allocator_type>::is_always_equal::value && + AllocTraits::is_always_equal::value && std::is_nothrow_move_assignable<hasher>::value && std::is_nothrow_move_assignable<key_equal>::value) { // TODO(sbenza): We should only use the operations from the noexcept clause @@ -2365,7 +2300,7 @@ iterator begin() ABSL_ATTRIBUTE_LIFETIME_BOUND { if (ABSL_PREDICT_FALSE(empty())) return end(); - if (capacity() == 1) return single_iterator(); + if (is_small()) return single_iterator(); iterator it = {control(), common().slots_union(), common().generation_ptr()}; it.skip_empty_or_deleted(); @@ -2419,9 +2354,11 @@ const size_t cap = capacity(); if (cap == 0) { // Already guaranteed to be empty; so nothing to do. - } else if (is_soo()) { - if (!empty()) destroy(soo_slot()); - common().set_empty_soo(); + } else if (is_small()) { + if (!empty()) { + destroy(single_slot()); + decrement_small_size(); + } } else { destroy_slots(); clear_backing_array(/*reuse=*/cap < 128); @@ -2491,13 +2428,13 @@ // s.insert({"abc", 42}); std::pair<iterator, bool> insert(init_type&& value) ABSL_ATTRIBUTE_LIFETIME_BOUND -#if __cplusplus >= 202002L +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L requires(!IsLifetimeBoundAssignmentFrom<init_type>::value) #endif { return emplace(std::move(value)); } -#if __cplusplus >= 202002L +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L std::pair<iterator, bool> insert( init_type&& value ABSL_INTERNAL_ATTRIBUTE_CAPTURED_BY(this)) ABSL_ATTRIBUTE_LIFETIME_BOUND @@ -2540,17 +2477,17 @@ template <class InputIt> void insert(InputIt first, InputIt last) { - for (; first != last; ++first) emplace(*first); + insert_range(first, last); } template <class T, RequiresNotInit<T> = 0, std::enable_if_t<Insertable<const T&>::value, int> = 0> void insert(std::initializer_list<T> ilist) { - insert(ilist.begin(), ilist.end()); + insert_range(ilist.begin(), ilist.end()); } void insert(std::initializer_list<init_type> ilist) { - insert(ilist.begin(), ilist.end()); + insert_range(ilist.begin(), ilist.end()); } insert_return_type insert(node_type&& node) ABSL_ATTRIBUTE_LIFETIME_BOUND { @@ -2697,14 +2634,11 @@ // 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_SWISSTABLE_ASSERT(capacity() > 0); AssertNotDebugCapacity(); - AssertIsFull(it.control(), it.generation(), it.generation_ptr(), "erase()"); + it.assert_is_full("erase()"); destroy(it.slot()); - if (is_soo()) { - common().set_empty_soo(); - } else { - erase_meta_only(it); - } + erase_meta_only(it); } iterator erase(const_iterator first, @@ -2714,9 +2648,9 @@ // capacity() > 0 as a precondition. if (empty()) return end(); if (first == last) return last.inner_; - if (is_soo()) { - destroy(soo_slot()); - common().set_empty_soo(); + if (is_small()) { + destroy(single_slot()); + erase_meta_only_small(); return end(); } if (first == begin() && last == end()) { @@ -2736,8 +2670,11 @@ // Moves elements from `src` into `this`. // If the element already exists in `this`, it is left unmodified in `src`. - template <typename H, typename E> - void merge(raw_hash_set<Policy, H, E, Alloc>& src) { // NOLINT + template < + typename... Params2, + typename = std::enable_if_t<std::is_same_v< + Alloc, typename raw_hash_set<Policy, Params2...>::allocator_type>>> + void merge(raw_hash_set<Policy, Params2...>& src) { // NOLINT AssertNotDebugCapacity(); src.AssertNotDebugCapacity(); assert(this != &src); @@ -2748,34 +2685,33 @@ .second; }; - if (src.is_soo()) { + if (src.is_small()) { if (src.empty()) return; - if (insert_slot(src.soo_slot())) src.common().set_empty_soo(); + if (insert_slot(src.single_slot())) + src.erase_meta_only_small(); return; } for (auto it = src.begin(), e = src.end(); it != e;) { auto next = std::next(it); - if (insert_slot(it.slot())) src.erase_meta_only(it); + if (insert_slot(it.slot())) src.erase_meta_only_large(it); it = next; } } - template <typename H, typename E> - void merge(raw_hash_set<Policy, H, E, Alloc>&& src) { + template < + typename... Params2, + typename = std::enable_if_t<std::is_same_v< + Alloc, typename raw_hash_set<Policy, Params2...>::allocator_type>>> + void merge(raw_hash_set<Policy, Params2...>&& src) { // NOLINT merge(src); } node_type extract(const_iterator position) { AssertNotDebugCapacity(); - AssertIsFull(position.control(), position.inner_.generation(), - position.inner_.generation_ptr(), "extract()"); + position.inner_.assert_is_full("extract()"); allocator_type alloc(char_alloc_ref()); auto node = CommonAccess::Transfer<node_type>(alloc, position.slot()); - if (is_soo()) { - common().set_empty_soo(); - } else { - erase_meta_only(position); - } + erase_meta_only(position); return node; } @@ -2787,9 +2723,9 @@ } void swap(raw_hash_set& that) noexcept( - IsNoThrowSwappable<hasher>() && IsNoThrowSwappable<key_equal>() && - IsNoThrowSwappable<allocator_type>( - typename AllocTraits::propagate_on_container_swap{})) { + AllocTraits::is_always_equal::value && + std::is_nothrow_swappable<hasher>::value && + std::is_nothrow_swappable<key_equal>::value) { AssertNotDebugCapacity(); that.AssertNotDebugCapacity(); using std::swap; @@ -2828,12 +2764,12 @@ // NOTE: This is a very low level operation and should not be used without // specific benchmarks indicating its importance. template <class K = key_type> - void prefetch(const key_arg<K>& key) const { + void prefetch([[maybe_unused]] const key_arg<K>& key) const { if (capacity() == DefaultCapacity()) return; - (void)key; // Avoid probing if we won't be able to prefetch the addresses received. #ifdef ABSL_HAVE_PREFETCH prefetch_heap_block(); + if (is_small()) return; auto seq = probe(common(), hash_of(key)); PrefetchToLocalCache(control() + seq.offset()); PrefetchToLocalCache(slot_array() + seq.offset()); @@ -2851,7 +2787,7 @@ template <class K = key_type> iterator find(const key_arg<K>& key) ABSL_ATTRIBUTE_LIFETIME_BOUND { AssertOnFind(key); - if (capacity() <= 1) return find_small(key); + if (is_small()) return find_small(key); prefetch_heap_block(); return find_large(key, hash_of(key)); } @@ -2960,24 +2896,6 @@ const raw_hash_set& s; }; - struct HashElement { - template <class K, class... Args> - size_t operator()(const K& key, Args&&...) const { - return h(key); - } - const hasher& h; - }; - - template <class K1> - struct EqualElement { - template <class K2, class... Args> - bool operator()(const K2& lhs, Args&&...) const { - ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN(eq(lhs, rhs)); - } - const K1& rhs; - const key_equal& eq; - }; - struct EmplaceDecomposable { template <class K, class... Args> std::pair<iterator, bool> operator()(const K& key, Args&&... args) const { @@ -3031,18 +2949,13 @@ // SOO functionality. template <class K = key_type> iterator find_small(const key_arg<K>& key) { - ABSL_SWISSTABLE_ASSERT(capacity() <= 1); - return empty() || !PolicyTraits::apply( - EqualElement<K>{key, eq_ref()}, - PolicyTraits::element(single_slot())) - ? end() - : single_iterator(); + ABSL_SWISSTABLE_ASSERT(is_small()); + return empty() || !equal_to(key, single_slot()) ? end() : single_iterator(); } template <class K = key_type> iterator find_large(const key_arg<K>& key, size_t hash) { - ABSL_SWISSTABLE_ASSERT(capacity() > 1); - ABSL_SWISSTABLE_ASSERT(!is_soo()); + ABSL_SWISSTABLE_ASSERT(!is_small()); auto seq = probe(common(), hash); const h2_t h2 = H2(hash); const ctrl_t* ctrl = control(); @@ -3052,9 +2965,7 @@ #endif Group g{ctrl + seq.offset()}; for (uint32_t i : g.Match(h2)) { - if (ABSL_PREDICT_TRUE(PolicyTraits::apply( - EqualElement<K>{key, eq_ref()}, - PolicyTraits::element(slot_array() + seq.offset(i))))) + if (ABSL_PREDICT_TRUE(equal_to(key, slot_array() + seq.offset(i)))) return iterator_at(seq.offset(i)); } if (ABSL_PREDICT_TRUE(g.MaskEmpty())) return end(); @@ -3079,7 +2990,7 @@ } void destroy_slots() { - ABSL_SWISSTABLE_ASSERT(!is_soo()); + ABSL_SWISSTABLE_ASSERT(!is_small()); if (PolicyTraits::template destroy_is_trivial<Alloc>()) return; auto destroy_slot = [&](const ctrl_t*, void* slot) { this->destroy(static_cast<slot_type*>(slot)); @@ -3111,13 +3022,14 @@ return; } if (capacity() == 0) return; - if (is_soo()) { + if (is_small()) { if (!empty()) { - ABSL_SWISSTABLE_IGNORE_UNINITIALIZED(destroy(soo_slot())); + ABSL_SWISSTABLE_IGNORE_UNINITIALIZED(destroy(single_slot())); } - return; + if constexpr (SooEnabled()) return; + } else { + destroy_slots(); } - destroy_slots(); dealloc(); } @@ -3126,23 +3038,41 @@ // 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) { - ABSL_SWISSTABLE_ASSERT(!is_soo()); - EraseMetaOnly(common(), static_cast<size_t>(it.control() - control()), - sizeof(slot_type)); + if (is_small()) { + erase_meta_only_small(); + return; + } + erase_meta_only_large(it); + } + void erase_meta_only_small() { + EraseMetaOnlySmall(common(), SooEnabled(), sizeof(slot_type)); + } + void erase_meta_only_large(const_iterator it) { + EraseMetaOnlyLarge(common(), it.control(), sizeof(slot_type)); } template <class K> - size_t hash_of(const K& key) const { - return hash_ref()(key); - } - size_t hash_of(slot_type* slot) const { - return PolicyTraits::apply(HashElement{hash_ref()}, + ABSL_ATTRIBUTE_ALWAYS_INLINE bool equal_to(const K& key, + slot_type* slot) const { + return PolicyTraits::apply(EqualElement<K, key_equal>{key, eq_ref()}, PolicyTraits::element(slot)); } + template <class K> + ABSL_ATTRIBUTE_ALWAYS_INLINE size_t hash_of(const K& key) const { + return HashElement<hasher, kIsDefaultHash>{hash_ref(), + common().seed().seed()}(key); + } + ABSL_ATTRIBUTE_ALWAYS_INLINE size_t hash_of(slot_type* slot) const { + return PolicyTraits::apply( + HashElement<hasher, kIsDefaultHash>{hash_ref(), common().seed().seed()}, + PolicyTraits::element(slot)); + } // Casting directly from e.g. char* to slot_type* can cause compilation errors // on objective-C. This function converts to void* first, avoiding the issue. - static slot_type* to_slot(void* buf) { return static_cast<slot_type*>(buf); } + static ABSL_ATTRIBUTE_ALWAYS_INLINE slot_type* to_slot(void* buf) { + return static_cast<slot_type*>(buf); + } // Requires that lhs does not have a full SOO slot. static void move_common(bool rhs_is_full_soo, CharAlloc& rhs_alloc, @@ -3178,8 +3108,7 @@ std::move(tmp)); } - void annotate_for_bug_detection_on_move( - [[maybe_unused]] raw_hash_set& that) { + void annotate_for_bug_detection_on_move([[maybe_unused]] raw_hash_set& that) { // We only enable moved-from validation when generations are enabled (rather // than using NDEBUG) to avoid issues in which NDEBUG is enabled in some // translation units but not in others. @@ -3250,58 +3179,98 @@ template <class K> std::pair<iterator, bool> find_or_prepare_insert_soo(const K& key) { - ctrl_t soo_slot_ctrl; + ABSL_SWISSTABLE_ASSERT(is_soo()); + bool force_sampling; if (empty()) { if (!should_sample_soo()) { common().set_full_soo(); - return {soo_iterator(), true}; + return {single_iterator(), true}; } - soo_slot_ctrl = ctrl_t::kEmpty; - } else if (PolicyTraits::apply(EqualElement<K>{key, eq_ref()}, - PolicyTraits::element(soo_slot()))) { - return {soo_iterator(), false}; + force_sampling = true; + } else if (equal_to(key, single_slot())) { + return {single_iterator(), false}; } else { - soo_slot_ctrl = static_cast<ctrl_t>(H2(hash_of(soo_slot()))); + force_sampling = false; } + ABSL_SWISSTABLE_ASSERT(capacity() == 1); constexpr bool kUseMemcpy = PolicyTraits::transfer_uses_memcpy() && SooEnabled(); size_t index = GrowSooTableToNextCapacityAndPrepareInsert< kUseMemcpy ? OptimalMemcpySizeForSooSlotTransfer(sizeof(slot_type)) : 0, - kUseMemcpy>(common(), GetPolicyFunctions(), hash_of(key), - soo_slot_ctrl); + kUseMemcpy>(common(), GetPolicyFunctions(), + HashKey<hasher, K, kIsDefaultHash>{hash_ref(), key}, + force_sampling); return {iterator_at(index), true}; } template <class K> - std::pair<iterator, bool> find_or_prepare_insert_non_soo(const K& key) { + std::pair<iterator, bool> find_or_prepare_insert_small(const K& key) { + ABSL_SWISSTABLE_ASSERT(is_small()); + if constexpr (SooEnabled()) { + return find_or_prepare_insert_soo(key); + } + if (!empty()) { + if (equal_to(key, single_slot())) { + common().infoz().RecordInsertHit(); + return {single_iterator(), false}; + } + } + return {iterator_at_ptr(PrepareInsertSmallNonSoo( + common(), GetPolicyFunctions(), + HashKey<hasher, K, kIsDefaultHash>{hash_ref(), key})), + true}; + } + + template <class K> + std::pair<iterator, bool> find_or_prepare_insert_large(const K& key) { ABSL_SWISSTABLE_ASSERT(!is_soo()); prefetch_heap_block(); const size_t hash = hash_of(key); auto seq = probe(common(), hash); const h2_t h2 = H2(hash); const ctrl_t* ctrl = control(); - while (true) { + size_t index; + bool inserted; + // We use a lambda function to be able to exit from the nested loop without + // duplicating generated code for the return statement (e.g. iterator_at). + [&]() ABSL_ATTRIBUTE_ALWAYS_INLINE { + while (true) { #ifndef ABSL_HAVE_MEMORY_SANITIZER - absl::PrefetchToLocalCache(slot_array() + seq.offset()); + absl::PrefetchToLocalCache(slot_array() + seq.offset()); #endif - Group g{ctrl + seq.offset()}; - for (uint32_t i : g.Match(h2)) { - if (ABSL_PREDICT_TRUE(PolicyTraits::apply( - EqualElement<K>{key, eq_ref()}, - PolicyTraits::element(slot_array() + seq.offset(i))))) - return {iterator_at(seq.offset(i)), false}; + Group g{ctrl + seq.offset()}; + for (uint32_t i : g.Match(h2)) { + if (ABSL_PREDICT_TRUE(equal_to(key, slot_array() + seq.offset(i)))) { + index = seq.offset(i); + inserted = false; + common().infoz().RecordInsertHit(); + return; + } + } + auto mask_empty = g.MaskEmpty(); + if (ABSL_PREDICT_TRUE(mask_empty)) { + size_t target_group_offset = seq.offset(); + index = SwisstableGenerationsEnabled() + ? PrepareInsertLargeGenerationsEnabled( + common(), GetPolicyFunctions(), hash, mask_empty, + FindInfo{target_group_offset, seq.index()}, + HashKey<hasher, K, kIsDefaultHash>{hash_ref(), key}) + : PrepareInsertLarge( + common(), GetPolicyFunctions(), hash, mask_empty, + FindInfo{target_group_offset, seq.index()}); + inserted = true; + return; + } + seq.next(); + ABSL_SWISSTABLE_ASSERT(seq.index() <= capacity() && "full table!"); } - auto mask_empty = g.MaskEmpty(); - if (ABSL_PREDICT_TRUE(mask_empty)) { - size_t target = seq.offset(mask_empty.LowestBitSet()); - return {iterator_at(PrepareInsertNonSoo(common(), GetPolicyFunctions(), - hash, - FindInfo{target, seq.index()})), - true}; - } - seq.next(); - ABSL_SWISSTABLE_ASSERT(seq.index() <= capacity() && "full table!"); - } + }(); + return {iterator_at(index), inserted}; + } + + template <class InputIt> + void insert_range(InputIt first, InputIt last) { + for (; first != last; ++first) emplace(*first); } protected: @@ -3363,23 +3332,18 @@ const size_t hash_of_arg = hash_of(key); const auto assert_consistent = [&](const ctrl_t*, void* slot) { - const value_type& element = - PolicyTraits::element(static_cast<slot_type*>(slot)); - const bool is_key_equal = - PolicyTraits::apply(EqualElement<K>{key, eq_ref()}, element); + const bool is_key_equal = equal_to(key, to_slot(slot)); if (!is_key_equal) return; - const size_t hash_of_slot = - PolicyTraits::apply(HashElement{hash_ref()}, element); [[maybe_unused]] const bool is_hash_equal = - hash_of_arg == hash_of_slot; + hash_of_arg == hash_of(to_slot(slot)); assert((!is_key_equal || is_hash_equal) && "eq(k1, k2) must imply that hash(k1) == hash(k2). " "hash/eq functors are inconsistent."); }; - if (is_soo()) { - assert_consistent(/*unused*/ nullptr, soo_slot()); + if (is_small()) { + assert_consistent(/*unused*/ nullptr, single_slot()); return; } // We only do validation for small tables so that it's constant time. @@ -3393,8 +3357,8 @@ template <class K> std::pair<iterator, bool> find_or_prepare_insert(const K& key) { AssertOnFind(key); - if (is_soo()) return find_or_prepare_insert_soo(key); - return find_or_prepare_insert_non_soo(key); + if (is_small()) return find_or_prepare_insert_small(key); + return find_or_prepare_insert_large(key); } // Constructs the value in the space pointed by the iterator. This only works @@ -3409,9 +3373,9 @@ void emplace_at(iterator iter, Args&&... args) { construct(iter.slot(), std::forward<Args>(args)...); - // When capacity is 1, find calls find_small and if size is 0, then it will + // When is_small, find calls find_small and if size is 0, then it will // return an end iterator. This can happen in the raw_hash_set copy ctor. - assert((capacity() == 1 || + assert((is_small() || PolicyTraits::apply(FindElement{*this}, *iter) == iter) && "constructed value does not match the lookup key"); } @@ -3422,6 +3386,10 @@ const_iterator iterator_at(size_t i) const ABSL_ATTRIBUTE_LIFETIME_BOUND { return const_cast<raw_hash_set*>(this)->iterator_at(i); } + iterator iterator_at_ptr(std::pair<ctrl_t*, void*> ptrs) + ABSL_ATTRIBUTE_LIFETIME_BOUND { + return {ptrs.first, to_slot(ptrs.second), common().generation_ptr()}; + } reference unchecked_deref(iterator it) { return it.unchecked_deref(); } @@ -3439,16 +3407,13 @@ // // See `CapacityToGrowth()`. size_t growth_left() const { - ABSL_SWISSTABLE_ASSERT(!is_soo()); return common().growth_left(); } GrowthInfo& growth_info() { - ABSL_SWISSTABLE_ASSERT(!is_soo()); return common().growth_info(); } GrowthInfo growth_info() const { - ABSL_SWISSTABLE_ASSERT(!is_soo()); return common().growth_info(); } @@ -3482,22 +3447,22 @@ ABSL_SWISSTABLE_IGNORE_UNINITIALIZED_RETURN( const_cast<raw_hash_set*>(this)->soo_slot()); } - iterator soo_iterator() { - return {SooControl(), soo_slot(), common().generation_ptr()}; - } - const_iterator soo_iterator() const { - return const_cast<raw_hash_set*>(this)->soo_iterator(); - } slot_type* single_slot() { - ABSL_SWISSTABLE_ASSERT(capacity() <= 1); + ABSL_SWISSTABLE_ASSERT(is_small()); return SooEnabled() ? soo_slot() : slot_array(); } const slot_type* single_slot() const { return const_cast<raw_hash_set*>(this)->single_slot(); } + void decrement_small_size() { + ABSL_SWISSTABLE_ASSERT(is_small()); + SooEnabled() ? common().set_empty_soo() : common().decrement_size(); + if (!SooEnabled()) { + SanitizerPoisonObject(single_slot()); + } + } iterator single_iterator() { - return {SooEnabled() ? SooControl() : control(), single_slot(), - common().generation_ptr()}; + return {SooControl(), single_slot(), common().generation_ptr()}; } const_iterator single_iterator() const { return const_cast<raw_hash_set*>(this)->single_iterator(); @@ -3554,8 +3519,6 @@ ctrl_t* new_ctrl = common.control(); slot_type* new_slots = set->slot_array(); - const PerTableSeed seed = common.seed(); - for (size_t group_index = 0; group_index < old_capacity; group_index += Group::kWidth) { GroupFullEmptyOrDeleted old_g(old_ctrl + group_index); @@ -3571,7 +3534,7 @@ // TODO(b/382423690): try to avoid entire hash calculation since we need // only one new bit of h1. size_t hash = set->hash_of(old_slot); - size_t h1 = H1(hash, seed); + size_t h1 = H1(hash); h2_t h2 = H2(hash); size_t new_index = TryFindNewIndexWithoutProbing( h1, old_index, old_capacity, new_ctrl, new_capacity); @@ -3614,7 +3577,7 @@ // for standard layout and alignof(Hash) <= alignof(CommonFields). std::is_empty_v<hasher> ? &GetRefForEmptyClass : &raw_hash_set::get_hash_ref_fn, - PolicyTraits::template get_hash_slot_fn<hasher>(), + PolicyTraits::template get_hash_slot_fn<hasher, kIsDefaultHash>(), PolicyTraits::transfer_uses_memcpy() ? TransferNRelocatable<sizeof(slot_type)> : &raw_hash_set::transfer_n_slots_fn, @@ -3642,15 +3605,15 @@ if (c->empty()) { return 0; } - if (c->is_soo()) { - auto it = c->soo_iterator(); + if (c->is_small()) { + auto it = c->single_iterator(); if (!pred(*it)) { ABSL_SWISSTABLE_ASSERT(c->size() == 1 && "hash table was modified unexpectedly"); return 0; } c->destroy(it.slot()); - c->common().set_empty_soo(); + c->erase_meta_only_small(); return 1; } [[maybe_unused]] const size_t original_size_for_assert = c->size(); @@ -3662,8 +3625,7 @@ auto* slot = static_cast<SlotType*>(slot_void); if (pred(Set::PolicyTraits::element(slot))) { c->destroy(slot); - EraseMetaOnly(c->common(), static_cast<size_t>(ctrl - c->control()), - sizeof(*slot)); + EraseMetaOnlyLarge(c->common(), ctrl, sizeof(*slot)); ++num_deleted; } }); @@ -3680,8 +3642,8 @@ if (c->empty()) { return; } - if (c->is_soo()) { - cb(*c->soo_iterator()); + if (c->is_small()) { + cb(*c->single_iterator()); return; } using SlotType = typename Set::slot_type; @@ -3696,19 +3658,19 @@ }; // Erases all elements that satisfy the predicate `pred` from the container `c`. -template <typename P, typename H, typename E, typename A, typename Predicate> -typename raw_hash_set<P, H, E, A>::size_type EraseIf( - Predicate& pred, raw_hash_set<P, H, E, A>* c) { +template <typename P, typename... Params, typename Predicate> +typename raw_hash_set<P, Params...>::size_type EraseIf( + Predicate& pred, raw_hash_set<P, Params...>* c) { return HashtableFreeFunctionsAccess::EraseIf(pred, c); } // Calls `cb` for all elements in the container `c`. -template <typename P, typename H, typename E, typename A, typename Callback> -void ForEach(Callback& cb, raw_hash_set<P, H, E, A>* c) { +template <typename P, typename... Params, typename Callback> +void ForEach(Callback& cb, raw_hash_set<P, Params...>* c) { return HashtableFreeFunctionsAccess::ForEach(cb, c); } -template <typename P, typename H, typename E, typename A, typename Callback> -void ForEach(Callback& cb, const raw_hash_set<P, H, E, A>* c) { +template <typename P, typename... Params, typename Callback> +void ForEach(Callback& cb, const raw_hash_set<P, Params...>* c) { return HashtableFreeFunctionsAccess::ForEach(cb, c); } @@ -3718,9 +3680,11 @@ using Traits = typename Set::PolicyTraits; using Slot = typename Traits::slot_type; + constexpr static bool kIsDefaultHash = Set::kIsDefaultHash; + static size_t GetNumProbes(const Set& set, const typename Set::key_type& key) { - if (set.is_soo()) return 0; + if (set.is_small()) return 0; size_t num_probes = 0; const size_t hash = set.hash_of(key); auto seq = probe(set.common(), hash); @@ -3729,10 +3693,7 @@ while (true) { container_internal::Group g{ctrl + seq.offset()}; for (uint32_t i : g.Match(h2)) { - if (Traits::apply( - typename Set::template EqualElement<typename Set::key_type>{ - key, set.eq_ref()}, - Traits::element(set.slot_array() + seq.offset(i)))) + if (set.equal_to(key, set.slot_array() + seq.offset(i))) return num_probes; ++num_probes; } @@ -3765,18 +3726,31 @@ // Extern template instantiations reduce binary size and linker input size. // Function definition is in raw_hash_set.cc. extern template size_t GrowSooTableToNextCapacityAndPrepareInsert<0, false>( - CommonFields&, const PolicyFunctions&, size_t, ctrl_t); + CommonFields&, const PolicyFunctions&, absl::FunctionRef<size_t(size_t)>, + bool); extern template size_t GrowSooTableToNextCapacityAndPrepareInsert<1, true>( - CommonFields&, const PolicyFunctions&, size_t, ctrl_t); + CommonFields&, const PolicyFunctions&, absl::FunctionRef<size_t(size_t)>, + bool); extern template size_t GrowSooTableToNextCapacityAndPrepareInsert<4, true>( - CommonFields&, const PolicyFunctions&, size_t, ctrl_t); + CommonFields&, const PolicyFunctions&, absl::FunctionRef<size_t(size_t)>, + bool); extern template size_t GrowSooTableToNextCapacityAndPrepareInsert<8, true>( - CommonFields&, const PolicyFunctions&, size_t, ctrl_t); + CommonFields&, const PolicyFunctions&, absl::FunctionRef<size_t(size_t)>, + bool); #if UINTPTR_MAX == UINT64_MAX extern template size_t GrowSooTableToNextCapacityAndPrepareInsert<16, true>( - CommonFields&, const PolicyFunctions&, size_t, ctrl_t); + CommonFields&, const PolicyFunctions&, absl::FunctionRef<size_t(size_t)>, + bool); #endif +extern template void* AllocateBackingArray< + BackingArrayAlignment(alignof(size_t)), std::allocator<char>>(void* alloc, + size_t n); +extern template void DeallocateBackingArray< + BackingArrayAlignment(alignof(size_t)), std::allocator<char>>( + void* alloc, size_t capacity, ctrl_t* ctrl, size_t slot_size, + size_t slot_align, bool had_infoz); + } // namespace container_internal ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/container/internal/raw_hash_set_allocator_test.cc b/absl/container/internal/raw_hash_set_allocator_test.cc index 7e7a506..c4bff60 100644 --- a/absl/container/internal/raw_hash_set_allocator_test.cc +++ b/absl/container/internal/raw_hash_set_allocator_test.cc
@@ -142,6 +142,10 @@ using init_type = Tracked<int32_t>; using key_type = int32_t; + using DefaultHash = void; + using DefaultEq = void; + using DefaultAlloc = void; + template <class allocator_type, class... Args> static void construct(allocator_type* alloc, slot_type* slot, Args&&... args) { @@ -180,7 +184,7 @@ static slot_type& element(slot_type* slot) { return *slot; } - template <class Hash> + template <class Hash, bool kIsDefault> static constexpr HashSlotFn get_hash_slot_fn() { return nullptr; } @@ -436,13 +440,14 @@ } TEST_F(PropagateOnAll, Swap) { - auto it = t1.insert(0).first; + t1.insert(0); Table u(0, a2); u.swap(t1); EXPECT_EQ(a1, u.get_allocator()); EXPECT_EQ(a2, t1.get_allocator()); EXPECT_EQ(1, a1.num_allocs()); EXPECT_EQ(0, a2.num_allocs()); + auto it = u.begin(); EXPECT_EQ(0, it->num_moves()); EXPECT_EQ(0, it->num_copies()); }
diff --git a/absl/container/internal/raw_hash_set_benchmark.cc b/absl/container/internal/raw_hash_set_benchmark.cc index ac94877..589f4f5 100644 --- a/absl/container/internal/raw_hash_set_benchmark.cc +++ b/absl/container/internal/raw_hash_set_benchmark.cc
@@ -51,6 +51,10 @@ using key_type = int64_t; using init_type = int64_t; + using DefaultHash = void; + using DefaultEq = void; + using DefaultAlloc = void; + static void construct(void*, int64_t* slot, int64_t v) { *slot = v; } static void destroy(void*, int64_t*) {} static void transfer(void*, int64_t* new_slot, int64_t* old_slot) { @@ -64,7 +68,7 @@ return std::forward<F>(f)(x, x); } - template <class Hash> + template <class Hash, bool kIsDefault> static constexpr HashSlotFn get_hash_slot_fn() { return nullptr; } @@ -97,6 +101,10 @@ using key_type = std::string; using init_type = std::pair<std::string, std::string>; + using DefaultHash = void; + using DefaultEq = void; + using DefaultAlloc = void; + template <class allocator_type, class... Args> static void construct(allocator_type* alloc, slot_type* slot, Args... args) { std::allocator_traits<allocator_type>::construct( @@ -127,7 +135,7 @@ PairArgs(std::forward<Args>(args)...)); } - template <class Hash> + template <class Hash, bool kIsDefault> static constexpr HashSlotFn get_hash_slot_fn() { return nullptr; } @@ -519,17 +527,6 @@ } BENCHMARK(BM_Group_MaskNonFull); -void BM_Group_CountLeadingEmptyOrDeleted(benchmark::State& state) { - std::array<ctrl_t, Group::kWidth> group; - Iota(group.begin(), group.end(), -2); - Group g{group.data()}; - for (auto _ : state) { - ::benchmark::DoNotOptimize(g); - ::benchmark::DoNotOptimize(g.CountLeadingEmptyOrDeleted()); - } -} -BENCHMARK(BM_Group_CountLeadingEmptyOrDeleted); - void BM_Group_MatchFirstEmptyOrDeleted(benchmark::State& state) { std::array<ctrl_t, Group::kWidth> group; Iota(group.begin(), group.end(), -2);
diff --git a/absl/container/internal/raw_hash_set_probe_benchmark.cc b/absl/container/internal/raw_hash_set_probe_benchmark.cc index e56648f..7165558 100644 --- a/absl/container/internal/raw_hash_set_probe_benchmark.cc +++ b/absl/container/internal/raw_hash_set_probe_benchmark.cc
@@ -15,8 +15,14 @@ // Generates probe length statistics for many combinations of key types and key // distributions, all using the default hash function for swisstable. +#include <algorithm> +#include <cstddef> +#include <cstdint> +#include <limits> #include <memory> #include <regex> // NOLINT +#include <string> +#include <utility> #include <vector> #include "absl/base/no_destructor.h" @@ -52,6 +58,10 @@ using key_type = T; using init_type = T; + using DefaultHash = void; + using DefaultEq = void; + using DefaultAlloc = void; + template <class allocator_type, class Arg> static void construct(allocator_type* alloc, slot_type* slot, const Arg& arg) { @@ -71,7 +81,7 @@ return std::forward<F>(f)(arg, arg); } - template <class Hash> + template <class Hash, bool kIsDefault> static constexpr auto get_hash_slot_fn() { return nullptr; } @@ -227,24 +237,6 @@ return reinterpret_cast<Ptr<Align>*>(v); } -struct IntIdentity { - uint64_t i; - friend bool operator==(IntIdentity a, IntIdentity b) { return a.i == b.i; } - IntIdentity operator++(int) { return IntIdentity{i++}; } -}; - -template <int Align> -struct PtrIdentity { - explicit PtrIdentity(uintptr_t val = PointerForAlignment<Align>()) : i(val) {} - uintptr_t i; - friend bool operator==(PtrIdentity a, PtrIdentity b) { return a.i == b.i; } - PtrIdentity operator++(int) { - PtrIdentity p(i); - i += Align; - return p; - } -}; - enum class StringSize { kSmall, kMedium, kLarge, kExtraLarge }; constexpr char kStringFormat[] = "%s/name-%07d-of-9999999.txt"; @@ -270,20 +262,6 @@ } }; -template <> -struct DefaultHash<IntIdentity> { - struct type { - size_t operator()(IntIdentity t) const { return t.i; } - }; -}; - -template <int Align> -struct DefaultHash<PtrIdentity<Align>> { - struct type { - size_t operator()(PtrIdentity<Align> t) const { return t.i; } - }; -}; - template <class T> struct Sequential { T operator()() const { return current++; } @@ -389,20 +367,6 @@ } }; -template <class Dist> -struct Random<IntIdentity, Dist> { - IntIdentity operator()() const { - return IntIdentity{Random<uint64_t, Dist>{}()}; - } -}; - -template <class Dist, int Align> -struct Random<PtrIdentity<Align>, Dist> { - PtrIdentity<Align> operator()() const { - return PtrIdentity<Align>{Random<uintptr_t, Dist>{}() * Align}; - } -}; - template <class Dist, StringSize size> struct Random<String<size>, Dist> { std::string operator()() const { @@ -423,18 +387,12 @@ std::string Name(uint32_t*) { return "u32"; } std::string Name(uint64_t*) { return "u64"; } -std::string Name(IntIdentity*) { return "IntIdentity"; } template <int Align> std::string Name(Ptr<Align>**) { return absl::StrCat("Ptr", Align); } -template <int Align> -std::string Name(PtrIdentity<Align>*) { - return absl::StrCat("PtrIdentity", Align); -} - template <StringSize size> std::string Name(String<size>*) { switch (size) { @@ -558,15 +516,10 @@ std::vector<Result> results; RunForType<uint64_t>(results); - RunForType<IntIdentity>(results); RunForType<Ptr<8>*>(results); RunForType<Ptr<16>*>(results); RunForType<Ptr<32>*>(results); RunForType<Ptr<64>*>(results); - RunForType<PtrIdentity<8>>(results); - RunForType<PtrIdentity<16>>(results); - RunForType<PtrIdentity<32>>(results); - RunForType<PtrIdentity<64>>(results); RunForType<std::pair<uint32_t, uint32_t>>(results); RunForType<String<StringSize::kSmall>>(results); RunForType<String<StringSize::kMedium>>(results); @@ -603,9 +556,11 @@ // Check the regex again. We might had have enabled only one of the // stats for the benchmark. if (!CanRunBenchmark(name)) return; + // Report at least 1, because benchy drops results with zero. + double reported_value = std::max(1e9 * result.ratios.*val, 1.0); absl::PrintF(" %s{\n", comma); - absl::PrintF(" \"cpu_time\": %f,\n", 1e9 * result.ratios.*val); - absl::PrintF(" \"real_time\": %f,\n", 1e9 * result.ratios.*val); + absl::PrintF(" \"cpu_time\": %f,\n", reported_value); + absl::PrintF(" \"real_time\": %f,\n", reported_value); absl::PrintF(" \"iterations\": 1,\n"); absl::PrintF(" \"name\": \"%s\",\n", name); absl::PrintF(" \"time_unit\": \"ns\"\n");
diff --git a/absl/container/internal/raw_hash_set_resize_impl.h b/absl/container/internal/raw_hash_set_resize_impl.h index 149d9e8..ed48d96 100644 --- a/absl/container/internal/raw_hash_set_resize_impl.h +++ b/absl/container/internal/raw_hash_set_resize_impl.h
@@ -52,7 +52,6 @@ static constexpr IntType kMaxNewBits = kMaxOldBits + 1; static constexpr IntType kMaxNewCapacity = (IntType{1} << kMaxNewBits) - 1; - static constexpr IntType kH2Shift = (kTotalBits - kH2Bits); static_assert(kMaxNewBits + kMaxOldBits + kH2Bits == kTotalBits); ProbedItemImpl() = default;
diff --git a/absl/container/internal/raw_hash_set_test.cc b/absl/container/internal/raw_hash_set_test.cc index 9a323c4..0b5ad8f 100644 --- a/absl/container/internal/raw_hash_set_test.cc +++ b/absl/container/internal/raw_hash_set_test.cc
@@ -54,8 +54,6 @@ #include "absl/container/internal/hash_function_defaults.h" #include "absl/container/internal/hash_policy_testing.h" #include "absl/random/random.h" -// TODO(b/382423690): Separate tests that depend only on -// hashtable_control_bytes. #include "absl/container/internal/hashtable_control_bytes.h" #include "absl/container/internal/hashtable_debug.h" #include "absl/container/internal/hashtablez_sampler.h" @@ -96,7 +94,6 @@ namespace { using ::testing::ElementsAre; -using ::testing::ElementsAreArray; using ::testing::Eq; using ::testing::Ge; using ::testing::Lt; @@ -279,28 +276,53 @@ EXPECT_EQ(15 * 2 + 1, NormalizeCapacity(15 + 2)); } -TEST(Util, GrowthAndCapacity) { - // Verify that GrowthToCapacity gives the minimum capacity that has enough - // growth. +TEST(Util, SizeToCapacitySmallValues) { EXPECT_EQ(SizeToCapacity(0), 0); EXPECT_EQ(SizeToCapacity(1), 1); EXPECT_EQ(SizeToCapacity(2), 3); EXPECT_EQ(SizeToCapacity(3), 3); + EXPECT_EQ(SizeToCapacity(4), 7); + EXPECT_EQ(SizeToCapacity(5), 7); + EXPECT_EQ(SizeToCapacity(6), 7); + if (Group::kWidth == 16) { + EXPECT_EQ(SizeToCapacity(7), 7); + EXPECT_EQ(SizeToCapacity(14), 15); + } else { + EXPECT_EQ(SizeToCapacity(7), 15); + } +} + +TEST(Util, CapacityToGrowthSmallValues) { + EXPECT_EQ(CapacityToGrowth(1), 1); + EXPECT_EQ(CapacityToGrowth(3), 3); + if (Group::kWidth == 16) { + EXPECT_EQ(CapacityToGrowth(7), 7); + } else { + EXPECT_EQ(CapacityToGrowth(7), 6); + } + EXPECT_EQ(CapacityToGrowth(15), 14); + EXPECT_EQ(CapacityToGrowth(31), 28); + EXPECT_EQ(CapacityToGrowth(63), 56); +} + +TEST(Util, GrowthAndCapacity) { + // Verify that GrowthToCapacity gives the minimum capacity that has enough + // growth. for (size_t growth = 1; growth < 10000; ++growth) { SCOPED_TRACE(growth); size_t capacity = SizeToCapacity(growth); ASSERT_TRUE(IsValidCapacity(capacity)); // The capacity is large enough for `growth`. - EXPECT_THAT(CapacityToGrowth(capacity), Ge(growth)); + ASSERT_THAT(CapacityToGrowth(capacity), Ge(growth)); // For (capacity+1) < kWidth, growth should equal capacity. if (capacity + 1 < Group::kWidth) { - EXPECT_THAT(CapacityToGrowth(capacity), Eq(capacity)); + ASSERT_THAT(CapacityToGrowth(capacity), Eq(capacity)); } else { - EXPECT_THAT(CapacityToGrowth(capacity), Lt(capacity)); + ASSERT_THAT(CapacityToGrowth(capacity), Lt(capacity)); } if (growth != 0 && capacity > 1) { // There is no smaller capacity that works. - EXPECT_THAT(CapacityToGrowth(capacity / 2), Lt(growth)); + ASSERT_THAT(CapacityToGrowth(capacity / 2), Lt(growth)) << capacity; } } @@ -308,9 +330,9 @@ capacity = 2 * capacity + 1) { SCOPED_TRACE(capacity); size_t growth = CapacityToGrowth(capacity); - EXPECT_THAT(growth, Lt(capacity)); - EXPECT_EQ(SizeToCapacity(growth), capacity); - EXPECT_EQ(NormalizeCapacity(SizeToCapacity(growth)), capacity); + ASSERT_THAT(growth, Lt(capacity)); + ASSERT_EQ(SizeToCapacity(growth), capacity); + ASSERT_EQ(NormalizeCapacity(SizeToCapacity(growth)), capacity); } } @@ -329,203 +351,6 @@ EXPECT_THAT(offsets, ElementsAre(0, 16, 48, 96, 32, 112, 80, 64)); } -TEST(BitMask, Smoke) { - EXPECT_FALSE((BitMask<uint8_t, 8>(0))); - EXPECT_TRUE((BitMask<uint8_t, 8>(5))); - - EXPECT_THAT((BitMask<uint8_t, 8>(0)), ElementsAre()); - EXPECT_THAT((BitMask<uint8_t, 8>(0x1)), ElementsAre(0)); - EXPECT_THAT((BitMask<uint8_t, 8>(0x2)), ElementsAre(1)); - EXPECT_THAT((BitMask<uint8_t, 8>(0x3)), ElementsAre(0, 1)); - EXPECT_THAT((BitMask<uint8_t, 8>(0x4)), ElementsAre(2)); - EXPECT_THAT((BitMask<uint8_t, 8>(0x5)), ElementsAre(0, 2)); - EXPECT_THAT((BitMask<uint8_t, 8>(0x55)), ElementsAre(0, 2, 4, 6)); - EXPECT_THAT((BitMask<uint8_t, 8>(0xAA)), ElementsAre(1, 3, 5, 7)); -} - -TEST(BitMask, WithShift_MatchPortable) { - // See the non-SSE version of Group for details on what this math is for. - uint64_t ctrl = 0x1716151413121110; - uint64_t hash = 0x12; - constexpr uint64_t lsbs = 0x0101010101010101ULL; - auto x = ctrl ^ (lsbs * hash); - uint64_t mask = (x - lsbs) & ~x & kMsbs8Bytes; - EXPECT_EQ(0x0000000080800000, mask); - - BitMask<uint64_t, 8, 3> b(mask); - EXPECT_EQ(*b, 2); -} - -constexpr uint64_t kSome8BytesMask = /* */ 0x8000808080008000ULL; -constexpr uint64_t kSome8BytesMaskAllOnes = 0xff00ffffff00ff00ULL; -constexpr auto kSome8BytesMaskBits = std::array<int, 5>{1, 3, 4, 5, 7}; - - -TEST(BitMask, WithShift_FullMask) { - EXPECT_THAT((BitMask<uint64_t, 8, 3>(kMsbs8Bytes)), - ElementsAre(0, 1, 2, 3, 4, 5, 6, 7)); - EXPECT_THAT( - (BitMask<uint64_t, 8, 3, /*NullifyBitsOnIteration=*/true>(kMsbs8Bytes)), - ElementsAre(0, 1, 2, 3, 4, 5, 6, 7)); - EXPECT_THAT( - (BitMask<uint64_t, 8, 3, /*NullifyBitsOnIteration=*/true>(~uint64_t{0})), - ElementsAre(0, 1, 2, 3, 4, 5, 6, 7)); -} - -TEST(BitMask, WithShift_EmptyMask) { - EXPECT_THAT((BitMask<uint64_t, 8, 3>(0)), ElementsAre()); - EXPECT_THAT((BitMask<uint64_t, 8, 3, /*NullifyBitsOnIteration=*/true>(0)), - ElementsAre()); -} - -TEST(BitMask, WithShift_SomeMask) { - EXPECT_THAT((BitMask<uint64_t, 8, 3>(kSome8BytesMask)), - ElementsAreArray(kSome8BytesMaskBits)); - EXPECT_THAT((BitMask<uint64_t, 8, 3, /*NullifyBitsOnIteration=*/true>( - kSome8BytesMask)), - ElementsAreArray(kSome8BytesMaskBits)); - EXPECT_THAT((BitMask<uint64_t, 8, 3, /*NullifyBitsOnIteration=*/true>( - kSome8BytesMaskAllOnes)), - ElementsAreArray(kSome8BytesMaskBits)); -} - -TEST(BitMask, WithShift_SomeMaskExtraBitsForNullify) { - // Verify that adding extra bits into non zero bytes is fine. - uint64_t extra_bits = 77; - for (int i = 0; i < 100; ++i) { - // Add extra bits, but keep zero bytes untouched. - uint64_t extra_mask = extra_bits & kSome8BytesMaskAllOnes; - EXPECT_THAT((BitMask<uint64_t, 8, 3, /*NullifyBitsOnIteration=*/true>( - kSome8BytesMask | extra_mask)), - ElementsAreArray(kSome8BytesMaskBits)) - << i << " " << extra_mask; - extra_bits = (extra_bits + 1) * 3; - } -} - -TEST(BitMask, LeadingTrailing) { - EXPECT_EQ((BitMask<uint32_t, 16>(0x00001a40).LeadingZeros()), 3); - EXPECT_EQ((BitMask<uint32_t, 16>(0x00001a40).TrailingZeros()), 6); - - EXPECT_EQ((BitMask<uint32_t, 16>(0x00000001).LeadingZeros()), 15); - EXPECT_EQ((BitMask<uint32_t, 16>(0x00000001).TrailingZeros()), 0); - - EXPECT_EQ((BitMask<uint32_t, 16>(0x00008000).LeadingZeros()), 0); - EXPECT_EQ((BitMask<uint32_t, 16>(0x00008000).TrailingZeros()), 15); - - EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x0000008080808000).LeadingZeros()), 3); - EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x0000008080808000).TrailingZeros()), 1); - - EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x0000000000000080).LeadingZeros()), 7); - EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x0000000000000080).TrailingZeros()), 0); - - EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x8000000000000000).LeadingZeros()), 0); - EXPECT_EQ((BitMask<uint64_t, 8, 3>(0x8000000000000000).TrailingZeros()), 7); -} - -TEST(Group, EmptyGroup) { - for (h2_t h = 0; h != 128; ++h) EXPECT_FALSE(Group{EmptyGroup()}.Match(h)); -} - -TEST(Group, Match) { - if (Group::kWidth == 16) { - ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), ctrl_t::kDeleted, CtrlT(3), - ctrl_t::kEmpty, CtrlT(5), ctrl_t::kSentinel, CtrlT(7), - CtrlT(7), CtrlT(5), CtrlT(3), CtrlT(1), - CtrlT(1), CtrlT(1), CtrlT(1), CtrlT(1)}; - EXPECT_THAT(Group{group}.Match(0), ElementsAre()); - EXPECT_THAT(Group{group}.Match(1), ElementsAre(1, 11, 12, 13, 14, 15)); - EXPECT_THAT(Group{group}.Match(3), ElementsAre(3, 10)); - EXPECT_THAT(Group{group}.Match(5), ElementsAre(5, 9)); - EXPECT_THAT(Group{group}.Match(7), ElementsAre(7, 8)); - } else if (Group::kWidth == 8) { - ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), CtrlT(2), - ctrl_t::kDeleted, CtrlT(2), CtrlT(1), - ctrl_t::kSentinel, CtrlT(1)}; - EXPECT_THAT(Group{group}.Match(0), ElementsAre()); - EXPECT_THAT(Group{group}.Match(1), ElementsAre(1, 5, 7)); - EXPECT_THAT(Group{group}.Match(2), ElementsAre(2, 4)); - } else { - FAIL() << "No test coverage for Group::kWidth==" << Group::kWidth; - } -} - -TEST(Group, MaskEmpty) { - if (Group::kWidth == 16) { - ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), ctrl_t::kDeleted, CtrlT(3), - ctrl_t::kEmpty, CtrlT(5), ctrl_t::kSentinel, CtrlT(7), - CtrlT(7), CtrlT(5), CtrlT(3), CtrlT(1), - CtrlT(1), CtrlT(1), CtrlT(1), CtrlT(1)}; - EXPECT_THAT(Group{group}.MaskEmpty().LowestBitSet(), 0); - EXPECT_THAT(Group{group}.MaskEmpty().HighestBitSet(), 4); - } else if (Group::kWidth == 8) { - ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), CtrlT(2), - ctrl_t::kDeleted, CtrlT(2), CtrlT(1), - ctrl_t::kSentinel, CtrlT(1)}; - EXPECT_THAT(Group{group}.MaskEmpty().LowestBitSet(), 0); - EXPECT_THAT(Group{group}.MaskEmpty().HighestBitSet(), 0); - } else { - FAIL() << "No test coverage for Group::kWidth==" << Group::kWidth; - } -} - -TEST(Group, MaskFull) { - if (Group::kWidth == 16) { - ctrl_t group[] = { - ctrl_t::kEmpty, CtrlT(1), ctrl_t::kDeleted, CtrlT(3), - ctrl_t::kEmpty, CtrlT(5), ctrl_t::kSentinel, CtrlT(7), - CtrlT(7), CtrlT(5), ctrl_t::kDeleted, CtrlT(1), - CtrlT(1), ctrl_t::kSentinel, ctrl_t::kEmpty, CtrlT(1)}; - EXPECT_THAT(Group{group}.MaskFull(), - ElementsAre(1, 3, 5, 7, 8, 9, 11, 12, 15)); - } else if (Group::kWidth == 8) { - ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), ctrl_t::kEmpty, - ctrl_t::kDeleted, CtrlT(2), ctrl_t::kSentinel, - ctrl_t::kSentinel, CtrlT(1)}; - EXPECT_THAT(Group{group}.MaskFull(), ElementsAre(1, 4, 7)); - } else { - FAIL() << "No test coverage for Group::kWidth==" << Group::kWidth; - } -} - -TEST(Group, MaskNonFull) { - if (Group::kWidth == 16) { - ctrl_t group[] = { - ctrl_t::kEmpty, CtrlT(1), ctrl_t::kDeleted, CtrlT(3), - ctrl_t::kEmpty, CtrlT(5), ctrl_t::kSentinel, CtrlT(7), - CtrlT(7), CtrlT(5), ctrl_t::kDeleted, CtrlT(1), - CtrlT(1), ctrl_t::kSentinel, ctrl_t::kEmpty, CtrlT(1)}; - EXPECT_THAT(Group{group}.MaskNonFull(), - ElementsAre(0, 2, 4, 6, 10, 13, 14)); - } else if (Group::kWidth == 8) { - ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), ctrl_t::kEmpty, - ctrl_t::kDeleted, CtrlT(2), ctrl_t::kSentinel, - ctrl_t::kSentinel, CtrlT(1)}; - EXPECT_THAT(Group{group}.MaskNonFull(), ElementsAre(0, 2, 3, 5, 6)); - } else { - FAIL() << "No test coverage for Group::kWidth==" << Group::kWidth; - } -} - -TEST(Group, MaskEmptyOrDeleted) { - if (Group::kWidth == 16) { - ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), ctrl_t::kEmpty, CtrlT(3), - ctrl_t::kDeleted, CtrlT(5), ctrl_t::kSentinel, CtrlT(7), - CtrlT(7), CtrlT(5), CtrlT(3), CtrlT(1), - CtrlT(1), CtrlT(1), CtrlT(1), CtrlT(1)}; - EXPECT_THAT(Group{group}.MaskEmptyOrDeleted().LowestBitSet(), 0); - EXPECT_THAT(Group{group}.MaskEmptyOrDeleted().HighestBitSet(), 4); - } else if (Group::kWidth == 8) { - ctrl_t group[] = {ctrl_t::kEmpty, CtrlT(1), CtrlT(2), - ctrl_t::kDeleted, CtrlT(2), CtrlT(1), - ctrl_t::kSentinel, CtrlT(1)}; - EXPECT_THAT(Group{group}.MaskEmptyOrDeleted().LowestBitSet(), 0); - EXPECT_THAT(Group{group}.MaskEmptyOrDeleted().HighestBitSet(), 3); - } else { - FAIL() << "No test coverage for Group::kWidth==" << Group::kWidth; - } -} - TEST(Batch, DropDeletes) { constexpr size_t kCapacity = 63; constexpr size_t kGroupWidth = container_internal::Group::kWidth; @@ -551,36 +376,16 @@ } } -TEST(Group, CountLeadingEmptyOrDeleted) { - const std::vector<ctrl_t> empty_examples = {ctrl_t::kEmpty, ctrl_t::kDeleted}; - const std::vector<ctrl_t> full_examples = { - CtrlT(0), CtrlT(1), CtrlT(2), CtrlT(3), - CtrlT(5), CtrlT(9), CtrlT(127), ctrl_t::kSentinel}; - - for (ctrl_t empty : empty_examples) { - std::vector<ctrl_t> e(Group::kWidth, empty); - EXPECT_EQ(Group::kWidth, Group{e.data()}.CountLeadingEmptyOrDeleted()); - for (ctrl_t full : full_examples) { - for (size_t i = 0; i != Group::kWidth; ++i) { - std::vector<ctrl_t> f(Group::kWidth, empty); - f[i] = full; - EXPECT_EQ(i, Group{f.data()}.CountLeadingEmptyOrDeleted()); - } - std::vector<ctrl_t> f(Group::kWidth, empty); - f[Group::kWidth * 2 / 3] = full; - f[Group::kWidth / 2] = full; - EXPECT_EQ(Group::kWidth / 2, - Group{f.data()}.CountLeadingEmptyOrDeleted()); - } - } -} - template <class T, bool kTransferable = false, bool kSoo = false> struct ValuePolicy { using slot_type = T; using key_type = T; using init_type = T; + using DefaultHash = hash_default_hash<T>; + using DefaultEq = std::equal_to<T>; + using DefaultAlloc = std::allocator<T>; + template <class Allocator, class... Args> static void construct(Allocator* alloc, slot_type* slot, Args&&... args) { absl::allocator_traits<Allocator>::construct(*alloc, slot, @@ -610,7 +415,7 @@ std::forward<F>(f), std::forward<Args>(args)...); } - template <class Hash> + template <class Hash, bool kIsDefault> static constexpr HashSlotFn get_hash_slot_fn() { return nullptr; } @@ -728,6 +533,10 @@ using key_type = std::string; using init_type = std::pair<std::string, std::string>; + using DefaultHash = void; + using DefaultEq = void; + using DefaultAlloc = void; + template <class allocator_type, class... Args> static void construct(allocator_type* alloc, slot_type* slot, Args... args) { std::allocator_traits<allocator_type>::construct( @@ -758,7 +567,7 @@ PairArgs(std::forward<Args>(args)...)); } - template <class Hash> + template <class Hash, bool kIsDefault> static constexpr HashSlotFn get_hash_slot_fn() { return nullptr; } @@ -780,9 +589,9 @@ template <typename T, bool kTransferable = false, bool kSoo = false, class Alloc = std::allocator<T>> -struct ValueTable - : raw_hash_set<ValuePolicy<T, kTransferable, kSoo>, hash_default_hash<T>, - std::equal_to<T>, Alloc> { +struct ValueTable : InstantiateRawHashSet<ValuePolicy<T, kTransferable, kSoo>, + hash_default_hash<T>, + std::equal_to<T>, Alloc>::type { using Base = typename ValueTable::raw_hash_set; using Base::Base; }; @@ -981,6 +790,34 @@ std::equal_to<absl::string_view>, std::allocator<int>>)); } +TEST(InstantiateRawHashSetTest, VerifyTypes) { + using P = ValuePolicy<int>; + using DA = typename P::DefaultHash; + using DB = typename P::DefaultEq; + using DC = typename P::DefaultAlloc; + + struct A {}; + struct B {}; + struct C {}; + + EXPECT_TRUE((std::is_same_v<InstantiateRawHashSet<P, A, B, C>::type, + raw_hash_set<P, A, B, C>>)); + EXPECT_TRUE((std::is_same_v<InstantiateRawHashSet<P, A, B, DC>::type, + raw_hash_set<P, A, B>>)); + EXPECT_TRUE((std::is_same_v<InstantiateRawHashSet<P, A, DB, C>::type, + raw_hash_set<P, A, DB, C>>)); + EXPECT_TRUE((std::is_same_v<InstantiateRawHashSet<P, A, DB, DC>::type, + raw_hash_set<P, A>>)); + EXPECT_TRUE((std::is_same_v<InstantiateRawHashSet<P, DA, B, C>::type, + raw_hash_set<P, DA, B, C>>)); + EXPECT_TRUE((std::is_same_v<InstantiateRawHashSet<P, DA, B, DC>::type, + raw_hash_set<P, DA, B>>)); + EXPECT_TRUE((std::is_same_v<InstantiateRawHashSet<P, DA, DB, C>::type, + raw_hash_set<P, DA, DB, C>>)); + EXPECT_TRUE((std::is_same_v<InstantiateRawHashSet<P, DA, DB, DC>::type, + raw_hash_set<P>>)); +} + template <class TableType> class SooTest : public testing::Test {}; @@ -1184,7 +1021,7 @@ t.insert(i); ASSERT_EQ(t.size(), i + 1); for (int j = 0; j < i + 1; ++j) { - EXPECT_TRUE(t.find(j) != t.end()); + ASSERT_TRUE(t.find(j) != t.end()); EXPECT_EQ(*t.find(j), j); } } @@ -1207,7 +1044,7 @@ t.reserve(target_size); } for (size_t i = 0; i < source_size; ++i) { - EXPECT_TRUE(t.find(static_cast<int>(i)) != t.end()); + ASSERT_TRUE(t.find(static_cast<int>(i)) != t.end()); EXPECT_EQ(*t.find(static_cast<int>(i)), static_cast<int>(i)); } } @@ -1232,7 +1069,7 @@ << "rehash(0) must resize to the minimum capacity"; } for (size_t i = 0; i < inserted_count; ++i) { - EXPECT_TRUE(t.find(static_cast<int>(i)) != t.end()); + ASSERT_TRUE(t.find(static_cast<int>(i)) != t.end()); EXPECT_EQ(*t.find(static_cast<int>(i)), static_cast<int>(i)); } } @@ -1283,6 +1120,9 @@ t.clear(); EXPECT_FALSE(t.contains(0)); + + EXPECT_TRUE(t.insert(0).second); + EXPECT_TRUE(t.contains(0)); } int decompose_constructed; @@ -1340,6 +1180,10 @@ using key_type = DecomposeType; using init_type = DecomposeType; + using DefaultHash = void; + using DefaultEq = void; + using DefaultAlloc = void; + template <typename T> static void construct(void*, DecomposeType* slot, T&& v) { ::new (slot) DecomposeType(std::forward<T>(v)); @@ -1352,7 +1196,7 @@ return std::forward<F>(f)(x, x); } - template <class Hash> + template <class Hash, bool kIsDefault> static constexpr HashSlotFn get_hash_slot_fn() { return nullptr; } @@ -2083,8 +1927,6 @@ TEST(Table, GrowthInfoDeletedBit) { BadTable t; - EXPECT_TRUE( - RawHashSetTestOnlyAccess::GetCommon(t).growth_info().HasNoDeleted()); int64_t init_count = static_cast<int64_t>( CapacityToGrowth(NormalizeCapacity(Group::kWidth + 1))); for (int64_t i = 0; i < init_count; ++i) { @@ -2470,12 +2312,10 @@ } TEST(Table, NoThrowSwappable) { - ASSERT_TRUE( - container_internal::IsNoThrowSwappable<absl::Hash<absl::string_view>>()); - ASSERT_TRUE(container_internal::IsNoThrowSwappable< - std::equal_to<absl::string_view>>()); - ASSERT_TRUE(container_internal::IsNoThrowSwappable<std::allocator<int>>()); - EXPECT_TRUE(container_internal::IsNoThrowSwappable<StringTable>()); + ASSERT_TRUE(std::is_nothrow_swappable<absl::Hash<absl::string_view>>()); + ASSERT_TRUE(std::is_nothrow_swappable<std::equal_to<absl::string_view>>()); + ASSERT_TRUE(std::is_nothrow_swappable<std::allocator<int>>()); + EXPECT_TRUE(std::is_nothrow_swappable<StringTable>()); } TEST(Table, HeterogeneousLookup) { @@ -2604,6 +2444,19 @@ EXPECT_THAT(t2, UnorderedElementsAre(Pair("0", "~0"))); } +TEST(Table, MergeSmall) { + StringTable t1, t2; + t1.emplace("1", "1"); + t2.emplace("2", "2"); + + EXPECT_THAT(t1, UnorderedElementsAre(Pair("1", "1"))); + EXPECT_THAT(t2, UnorderedElementsAre(Pair("2", "2"))); + + t2.merge(t1); + EXPECT_EQ(t1.size(), 0); + EXPECT_THAT(t2, UnorderedElementsAre(Pair("1", "1"), Pair("2", "2"))); +} + TEST(Table, IteratorEmplaceConstructibleRequirement) { struct Value { explicit Value(absl::string_view view) : value(view) {} @@ -2617,8 +2470,9 @@ } }; - struct Table : raw_hash_set<ValuePolicy<Value>, H, std::equal_to<Value>, - std::allocator<Value>> { + struct Table + : InstantiateRawHashSet<ValuePolicy<Value>, H, std::equal_to<Value>, + std::allocator<Value>>::type { using Base = typename Table::raw_hash_set; using Base::Base; }; @@ -2690,6 +2544,24 @@ EXPECT_FALSE(node); // NOLINT(bugprone-use-after-move) } +TEST(Nodes, ExtractInsertSmall) { + constexpr char k0[] = "Very long string zero."; + StringTable t = {{k0, ""}}; + EXPECT_THAT(t, UnorderedElementsAre(Pair(k0, ""))); + + auto node = t.extract(k0); + EXPECT_EQ(t.size(), 0); + EXPECT_TRUE(node); + EXPECT_FALSE(node.empty()); + + StringTable t2; + StringTable::insert_return_type res = t2.insert(std::move(node)); + EXPECT_TRUE(res.inserted); + EXPECT_THAT(*res.position, Pair(k0, "")); + EXPECT_FALSE(res.node); + EXPECT_THAT(t2, UnorderedElementsAre(Pair(k0, ""))); +} + TYPED_TEST(SooTest, HintInsert) { TypeParam t = {1, 2, 3}; auto node = t.extract(1); @@ -2729,7 +2601,7 @@ // in seed. void GenerateIrrelevantSeeds(int cnt) { for (int i = cnt % 17; i > 0; --i) { - NextSeed(); + HashtableSize::NextSeed(); } } @@ -2828,12 +2700,12 @@ NonSooIntTable t; // Extra simple "regexp" as regexp support is highly varied across platforms. - EXPECT_DEATH_IF_SUPPORTED(t.erase(t.end()), - "erase.* called on end.. iterator."); + EXPECT_DEATH_IF_SUPPORTED(++t.end(), "operator.* called on end.. iterator."); typename NonSooIntTable::iterator iter; EXPECT_DEATH_IF_SUPPORTED( ++iter, "operator.* called on default-constructed iterator."); t.insert(0); + t.insert(1); iter = t.begin(); t.erase(iter); const char* const kErasedDeathMessage = @@ -2959,6 +2831,7 @@ absl::flat_hash_set<const HashtablezInfo*> preexisting_info(10); absl::flat_hash_map<size_t, int> observed_checksums(10); absl::flat_hash_map<ssize_t, int> reservations(10); + absl::flat_hash_map<std::pair<size_t, size_t>, int> hit_misses(10); start_size += sampler.Iterate([&](const HashtablezInfo& info) { preexisting_info.insert(&info); @@ -2971,6 +2844,8 @@ const bool do_reserve = (i % 10 > 5); const bool do_rehash = !do_reserve && (i % 10 > 0); + const bool do_first_insert_hit = i % 2 == 0; + const bool do_second_insert_hit = i % 4 == 0; if (do_reserve) { // Don't reserve on all tables. @@ -2978,7 +2853,14 @@ } tables.back().insert(1); + if (do_first_insert_hit) { + tables.back().insert(1); + tables.back().insert(1); + } tables.back().insert(i % 5); + if (do_second_insert_hit) { + tables.back().insert(i % 5); + } if (do_rehash) { // Rehash some other tables. @@ -2989,9 +2871,11 @@ end_size += sampler.Iterate([&](const HashtablezInfo& info) { ++end_size; if (preexisting_info.contains(&info)) return; - observed_checksums[info.hashes_bitwise_xor.load( - std::memory_order_relaxed)]++; reservations[info.max_reserve.load(std::memory_order_relaxed)]++; + hit_misses[std::make_pair( + info.num_insert_hits.load(std::memory_order_relaxed), + info.size.load(std::memory_order_relaxed))]++; + EXPECT_EQ(info.inline_element_size, sizeof(typename TypeParam::value_type)); EXPECT_EQ(info.key_size, sizeof(typename TypeParam::key_type)); EXPECT_EQ(info.value_size, sizeof(typename TypeParam::value_type)); @@ -3006,12 +2890,8 @@ // Expect that we sampled at the requested sampling rate of ~1%. EXPECT_NEAR((end_size - start_size) / static_cast<double>(tables.size()), 0.01, 0.005); - EXPECT_EQ(observed_checksums.size(), 5); - for (const auto& [_, count] : observed_checksums) { - EXPECT_NEAR((100 * count) / static_cast<double>(tables.size()), 0.2, 0.05); - } - EXPECT_EQ(reservations.size(), 10); + ASSERT_EQ(reservations.size(), 10); for (const auto& [reservation, count] : reservations) { EXPECT_GE(reservation, 0); EXPECT_LT(reservation, 100); @@ -3019,6 +2899,21 @@ EXPECT_NEAR((100 * count) / static_cast<double>(tables.size()), 0.1, 0.05) << reservation; } + + EXPECT_THAT(hit_misses, testing::SizeIs(6)); + const double sampled_tables = end_size - start_size; + // i % 20: { 1, 11 } + EXPECT_NEAR((hit_misses[{1, 1}] / sampled_tables), 0.10, 0.02); + // i % 20: { 6 } + EXPECT_NEAR((hit_misses[{3, 1}] / sampled_tables), 0.05, 0.02); + // i % 20: { 0, 4, 8, 12 } + EXPECT_NEAR((hit_misses[{3, 2}] / sampled_tables), 0.20, 0.02); + // i % 20: { 2, 10, 14, 18 } + EXPECT_NEAR((hit_misses[{2, 2}] / sampled_tables), 0.20, 0.02); + // i % 20: { 16 } + EXPECT_NEAR((hit_misses[{4, 1}] / sampled_tables), 0.05, 0.02); + // i % 20: { 3, 5, 7, 9, 13, 15, 17, 19 } + EXPECT_NEAR((hit_misses[{0, 2}] / sampled_tables), 0.40, 0.02); } std::vector<const HashtablezInfo*> SampleSooMutation( @@ -3644,11 +3539,13 @@ EXPECT_DEATH_IF_SUPPORTED(void(t1.end() == default_constructed_iter), "Invalid iterator comparison.*default-constructed"); t1.insert(0); + t1.insert(1); EXPECT_DEATH_IF_SUPPORTED(void(t1.begin() == t2.end()), "Invalid iterator comparison.*empty hashtable"); EXPECT_DEATH_IF_SUPPORTED(void(t1.begin() == default_constructed_iter), "Invalid iterator comparison.*default-constructed"); t2.insert(0); + t2.insert(1); EXPECT_DEATH_IF_SUPPORTED(void(t1.begin() == t2.end()), "Invalid iterator comparison.*end.. iterator"); EXPECT_DEATH_IF_SUPPORTED(void(t1.begin() == t2.begin()), @@ -3687,40 +3584,47 @@ GTEST_SKIP() << "Only run under NDEBUG: `assert` statements may cause " "redundant hashing."; } + // When the table is sampled, we need to hash on the first insertion. + DisableSampling(); using Table = CountedHashIntTable; auto HashCount = [](const Table& t) { return t.hash_function().count; }; { Table t; + t.find(0); EXPECT_EQ(HashCount(t), 0); } { Table t; t.insert(1); - EXPECT_EQ(HashCount(t), 1); + t.find(1); + EXPECT_EQ(HashCount(t), 0); t.erase(1); - EXPECT_LE(HashCount(t), 2); + EXPECT_EQ(HashCount(t), 0); + t.insert(1); + t.insert(2); + EXPECT_EQ(HashCount(t), 2); } { Table t; t.insert(3); - EXPECT_EQ(HashCount(t), 1); + EXPECT_EQ(HashCount(t), 0); auto node = t.extract(3); - EXPECT_LE(HashCount(t), 2); + EXPECT_EQ(HashCount(t), 0); t.insert(std::move(node)); - EXPECT_LE(HashCount(t), 3); + EXPECT_EQ(HashCount(t), 0); } { Table t; t.emplace(5); - EXPECT_EQ(HashCount(t), 1); + EXPECT_EQ(HashCount(t), 0); } { Table src; src.insert(7); Table dst; dst.merge(src); - EXPECT_EQ(HashCount(dst), 1); + EXPECT_EQ(HashCount(dst), 0); } } @@ -3731,9 +3635,7 @@ auto fail_if_any = [](const ctrl_t*, void* i) { FAIL() << "expected no slots " << **static_cast<SlotType*>(i); }; - container_internal::IterateOverFullSlots( - RawHashSetTestOnlyAccess::GetCommon(t), sizeof(SlotType), fail_if_any); - for (size_t i = 0; i < 256; ++i) { + for (size_t i = 2; i < 256; ++i) { t.reserve(i); container_internal::IterateOverFullSlots( RawHashSetTestOnlyAccess::GetCommon(t), sizeof(SlotType), fail_if_any); @@ -3745,7 +3647,9 @@ using SlotType = NonSooIntTableSlotType; std::vector<int64_t> expected_slots; - for (int64_t idx = 0; idx < 128; ++idx) { + t.insert(0); + expected_slots.push_back(0); + for (int64_t idx = 1; idx < 128; ++idx) { t.insert(idx); expected_slots.push_back(idx); @@ -4226,8 +4130,8 @@ // 5. Finally we will catch up and go to overflow codepath. TEST(Table, GrowExtremelyLargeTable) { constexpr size_t kTargetCapacity = -#if defined(__wasm__) || defined(__asmjs__) - NextCapacity(ProbedItem4Bytes::kMaxNewCapacity); // OOMs on WASM. +#if defined(__wasm__) || defined(__asmjs__) || defined(__i386__) + NextCapacity(ProbedItem4Bytes::kMaxNewCapacity); // OOMs on WASM, 32-bit. #else NextCapacity(ProbedItem8Bytes::kMaxNewCapacity); #endif @@ -4240,7 +4144,7 @@ CommonFields& common = RawHashSetTestOnlyAccess::GetCommon(t); // Set 0 seed so that H1 is always 0. common.set_no_seed_for_testing(); - ASSERT_EQ(H1(t.hash_function()(75), common.seed()), 0); + ASSERT_EQ(H1(t.hash_function()(75)), 0); uint8_t inserted_till = 210; for (uint8_t i = 0; i < inserted_till; ++i) { t.insert(i); @@ -4264,6 +4168,16 @@ EXPECT_EQ(t.capacity(), kTargetCapacity); } +// Test that after calling generate_new_seed(), the high bits of the returned +// seed are non-zero. +TEST(PerTableSeed, HighBitsAreNonZero) { + HashtableSize hs(no_seed_empty_tag_t{}); + for (int i = 0; i < 100; ++i) { + hs.generate_new_seed(); + ASSERT_GT(hs.seed().seed() >> 16, 0); + } +} + } // namespace } // namespace container_internal ABSL_NAMESPACE_END
diff --git a/absl/container/internal/unordered_map_constructor_test.h b/absl/container/internal/unordered_map_constructor_test.h index 7e84dc2..1076aea 100644 --- a/absl/container/internal/unordered_map_constructor_test.h +++ b/absl/container/internal/unordered_map_constructor_test.h
@@ -21,6 +21,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/base/config.h" #include "absl/container/internal/hash_generator_testing.h" #include "absl/container/internal/hash_policy_testing.h" @@ -85,28 +86,8 @@ EXPECT_GE(m.bucket_count(), 123); } -template <typename T> -struct is_std_unordered_map : std::false_type {}; - -template <typename... T> -struct is_std_unordered_map<std::unordered_map<T...>> : std::true_type {}; - -#if defined(UNORDERED_MAP_CXX14) || defined(UNORDERED_MAP_CXX17) -using has_cxx14_std_apis = std::true_type; -#else -using has_cxx14_std_apis = std::false_type; -#endif - -template <typename T> -using expect_cxx14_apis = - absl::disjunction<absl::negation<is_std_unordered_map<T>>, - has_cxx14_std_apis>; - template <typename TypeParam> -void BucketCountAllocTest(std::false_type) {} - -template <typename TypeParam> -void BucketCountAllocTest(std::true_type) { +void BucketCountAllocTest() { using A = typename TypeParam::allocator_type; A alloc(0); TypeParam m(123, alloc); @@ -117,14 +98,11 @@ } TYPED_TEST_P(ConstructorTest, BucketCountAlloc) { - BucketCountAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + BucketCountAllocTest<TypeParam>(); } template <typename TypeParam> -void BucketCountHashAllocTest(std::false_type) {} - -template <typename TypeParam> -void BucketCountHashAllocTest(std::true_type) { +void BucketCountHashAllocTest() { using H = typename TypeParam::hasher; using A = typename TypeParam::allocator_type; H hasher; @@ -138,25 +116,11 @@ } TYPED_TEST_P(ConstructorTest, BucketCountHashAlloc) { - BucketCountHashAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + BucketCountHashAllocTest<TypeParam>(); } -#if ABSL_UNORDERED_SUPPORTS_ALLOC_CTORS -using has_alloc_std_constructors = std::true_type; -#else -using has_alloc_std_constructors = std::false_type; -#endif - -template <typename T> -using expect_alloc_constructors = - absl::disjunction<absl::negation<is_std_unordered_map<T>>, - has_alloc_std_constructors>; - template <typename TypeParam> -void AllocTest(std::false_type) {} - -template <typename TypeParam> -void AllocTest(std::true_type) { +void AllocTest() { using A = typename TypeParam::allocator_type; A alloc(0); TypeParam m(alloc); @@ -165,12 +129,10 @@ EXPECT_THAT(m, ::testing::UnorderedElementsAre()); } -TYPED_TEST_P(ConstructorTest, Alloc) { - AllocTest<TypeParam>(expect_alloc_constructors<TypeParam>()); -} +TYPED_TEST_P(ConstructorTest, Alloc) { AllocTest<TypeParam>(); } TYPED_TEST_P(ConstructorTest, InputIteratorBucketHashEqualAlloc) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; @@ -178,8 +140,7 @@ E equal; A alloc(0); std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::UniqueGenerator<T>()); + std::generate_n(std::back_inserter(values), 10, UniqueGenerator<T>()); TypeParam m(values.begin(), values.end(), 123, hasher, equal, alloc); EXPECT_EQ(m.hash_function(), hasher); EXPECT_EQ(m.key_eq(), equal); @@ -189,16 +150,12 @@ } template <typename TypeParam> -void InputIteratorBucketAllocTest(std::false_type) {} - -template <typename TypeParam> -void InputIteratorBucketAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void InputIteratorBucketAllocTest() { + using T = GeneratedType<TypeParam>; using A = typename TypeParam::allocator_type; A alloc(0); std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::UniqueGenerator<T>()); + std::generate_n(std::back_inserter(values), 10, UniqueGenerator<T>()); TypeParam m(values.begin(), values.end(), 123, alloc); EXPECT_EQ(m.get_allocator(), alloc); EXPECT_THAT(items(m), ::testing::UnorderedElementsAreArray(values)); @@ -206,22 +163,18 @@ } TYPED_TEST_P(ConstructorTest, InputIteratorBucketAlloc) { - InputIteratorBucketAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + InputIteratorBucketAllocTest<TypeParam>(); } template <typename TypeParam> -void InputIteratorBucketHashAllocTest(std::false_type) {} - -template <typename TypeParam> -void InputIteratorBucketHashAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void InputIteratorBucketHashAllocTest() { + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using A = typename TypeParam::allocator_type; H hasher; A alloc(0); std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::UniqueGenerator<T>()); + std::generate_n(std::back_inserter(values), 10, UniqueGenerator<T>()); TypeParam m(values.begin(), values.end(), 123, hasher, alloc); EXPECT_EQ(m.hash_function(), hasher); EXPECT_EQ(m.get_allocator(), alloc); @@ -230,18 +183,18 @@ } TYPED_TEST_P(ConstructorTest, InputIteratorBucketHashAlloc) { - InputIteratorBucketHashAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + InputIteratorBucketHashAllocTest<TypeParam>(); } TYPED_TEST_P(ConstructorTest, CopyConstructor) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; H hasher; E equal; A alloc(0); - hash_internal::UniqueGenerator<T> gen; + UniqueGenerator<T> gen; TypeParam m(123, hasher, equal, alloc); for (size_t i = 0; i != 10; ++i) m.insert(gen()); TypeParam n(m); @@ -252,18 +205,15 @@ } template <typename TypeParam> -void CopyConstructorAllocTest(std::false_type) {} - -template <typename TypeParam> -void CopyConstructorAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void CopyConstructorAllocTest() { + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; H hasher; E equal; A alloc(0); - hash_internal::UniqueGenerator<T> gen; + UniqueGenerator<T> gen; TypeParam m(123, hasher, equal, alloc); for (size_t i = 0; i != 10; ++i) m.insert(gen()); TypeParam n(m, A(11)); @@ -274,20 +224,20 @@ } TYPED_TEST_P(ConstructorTest, CopyConstructorAlloc) { - CopyConstructorAllocTest<TypeParam>(expect_alloc_constructors<TypeParam>()); + CopyConstructorAllocTest<TypeParam>(); } // TODO(alkis): Test non-propagating allocators on copy constructors. TYPED_TEST_P(ConstructorTest, MoveConstructor) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; H hasher; E equal; A alloc(0); - hash_internal::UniqueGenerator<T> gen; + UniqueGenerator<T> gen; TypeParam m(123, hasher, equal, alloc); for (size_t i = 0; i != 10; ++i) m.insert(gen()); TypeParam t(m); @@ -299,18 +249,15 @@ } template <typename TypeParam> -void MoveConstructorAllocTest(std::false_type) {} - -template <typename TypeParam> -void MoveConstructorAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void MoveConstructorAllocTest() { + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; H hasher; E equal; A alloc(0); - hash_internal::UniqueGenerator<T> gen; + UniqueGenerator<T> gen; TypeParam m(123, hasher, equal, alloc); for (size_t i = 0; i != 10; ++i) m.insert(gen()); TypeParam t(m); @@ -322,14 +269,14 @@ } TYPED_TEST_P(ConstructorTest, MoveConstructorAlloc) { - MoveConstructorAllocTest<TypeParam>(expect_alloc_constructors<TypeParam>()); + MoveConstructorAllocTest<TypeParam>(); } // TODO(alkis): Test non-propagating allocators on move constructors. TYPED_TEST_P(ConstructorTest, InitializerListBucketHashEqualAlloc) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::UniqueGenerator<T> gen; + using T = GeneratedType<TypeParam>; + UniqueGenerator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; @@ -346,13 +293,10 @@ } template <typename TypeParam> -void InitializerListBucketAllocTest(std::false_type) {} - -template <typename TypeParam> -void InitializerListBucketAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void InitializerListBucketAllocTest() { + using T = GeneratedType<TypeParam>; using A = typename TypeParam::allocator_type; - hash_internal::UniqueGenerator<T> gen; + UniqueGenerator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; A alloc(0); TypeParam m(values, 123, alloc); @@ -362,20 +306,17 @@ } TYPED_TEST_P(ConstructorTest, InitializerListBucketAlloc) { - InitializerListBucketAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + InitializerListBucketAllocTest<TypeParam>(); } template <typename TypeParam> -void InitializerListBucketHashAllocTest(std::false_type) {} - -template <typename TypeParam> -void InitializerListBucketHashAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void InitializerListBucketHashAllocTest() { + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using A = typename TypeParam::allocator_type; H hasher; A alloc(0); - hash_internal::UniqueGenerator<T> gen; + UniqueGenerator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; TypeParam m(values, 123, hasher, alloc); EXPECT_EQ(m.hash_function(), hasher); @@ -385,18 +326,18 @@ } TYPED_TEST_P(ConstructorTest, InitializerListBucketHashAlloc) { - InitializerListBucketHashAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + InitializerListBucketHashAllocTest<TypeParam>(); } TYPED_TEST_P(ConstructorTest, Assignment) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; H hasher; E equal; A alloc(0); - hash_internal::UniqueGenerator<T> gen; + UniqueGenerator<T> gen; TypeParam m({gen(), gen(), gen()}, 123, hasher, equal, alloc); TypeParam n; n = m; @@ -409,14 +350,14 @@ // (it depends on traits). TYPED_TEST_P(ConstructorTest, MoveAssignment) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; H hasher; E equal; A alloc(0); - hash_internal::UniqueGenerator<T> gen; + UniqueGenerator<T> gen; TypeParam m({gen(), gen(), gen()}, 123, hasher, equal, alloc); TypeParam t(m); TypeParam n; @@ -427,8 +368,8 @@ } TYPED_TEST_P(ConstructorTest, AssignmentFromInitializerList) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::UniqueGenerator<T> gen; + using T = GeneratedType<TypeParam>; + UniqueGenerator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; TypeParam m; m = values; @@ -436,8 +377,8 @@ } TYPED_TEST_P(ConstructorTest, AssignmentOverwritesExisting) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::UniqueGenerator<T> gen; + using T = GeneratedType<TypeParam>; + UniqueGenerator<T> gen; TypeParam m({gen(), gen(), gen()}); TypeParam n({gen()}); n = m; @@ -445,8 +386,8 @@ } TYPED_TEST_P(ConstructorTest, MoveAssignmentOverwritesExisting) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::UniqueGenerator<T> gen; + using T = GeneratedType<TypeParam>; + UniqueGenerator<T> gen; TypeParam m({gen(), gen(), gen()}); TypeParam t(m); TypeParam n({gen()}); @@ -455,8 +396,8 @@ } TYPED_TEST_P(ConstructorTest, AssignmentFromInitializerListOverwritesExisting) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::UniqueGenerator<T> gen; + using T = GeneratedType<TypeParam>; + UniqueGenerator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; TypeParam m; m = values; @@ -464,8 +405,8 @@ } TYPED_TEST_P(ConstructorTest, AssignmentOnSelf) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::UniqueGenerator<T> gen; + using T = GeneratedType<TypeParam>; + UniqueGenerator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; TypeParam m(values); m = *&m; // Avoid -Wself-assign
diff --git a/absl/container/internal/unordered_map_lookup_test.h b/absl/container/internal/unordered_map_lookup_test.h index 3713cd9..ba037c0 100644 --- a/absl/container/internal/unordered_map_lookup_test.h +++ b/absl/container/internal/unordered_map_lookup_test.h
@@ -30,10 +30,9 @@ TYPED_TEST_SUITE_P(LookupTest); TYPED_TEST_P(LookupTest, At) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m(values.begin(), values.end()); for (const auto& p : values) { const auto& val = m.at(p.first); @@ -42,11 +41,10 @@ } TYPED_TEST_P(LookupTest, OperatorBracket) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using V = typename TypeParam::mapped_type; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m; for (const auto& p : values) { auto& val = m[p.first]; @@ -58,10 +56,9 @@ } TYPED_TEST_P(LookupTest, Count) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m; for (const auto& p : values) EXPECT_EQ(0, m.count(p.first)) << ::testing::PrintToString(p.first); @@ -72,10 +69,9 @@ TYPED_TEST_P(LookupTest, Find) { using std::get; - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m; for (const auto& p : values) EXPECT_TRUE(m.end() == m.find(p.first)) @@ -90,10 +86,9 @@ TYPED_TEST_P(LookupTest, EqualRange) { using std::get; - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m; for (const auto& p : values) { auto r = m.equal_range(p.first);
diff --git a/absl/container/internal/unordered_map_members_test.h b/absl/container/internal/unordered_map_members_test.h index 7d48cdb..e9f4979 100644 --- a/absl/container/internal/unordered_map_members_test.h +++ b/absl/container/internal/unordered_map_members_test.h
@@ -36,10 +36,10 @@ EXPECT_TRUE((std::is_same<std::pair<const typename TypeParam::key_type, typename TypeParam::mapped_type>, typename TypeParam::value_type>())); - EXPECT_TRUE((absl::conjunction< - absl::negation<std::is_signed<typename TypeParam::size_type>>, + EXPECT_TRUE((std::conjunction< + std::negation<std::is_signed<typename TypeParam::size_type>>, std::is_integral<typename TypeParam::size_type>>())); - EXPECT_TRUE((absl::conjunction< + EXPECT_TRUE((std::conjunction< std::is_signed<typename TypeParam::difference_type>, std::is_integral<typename TypeParam::difference_type>>())); EXPECT_TRUE((std::is_convertible<
diff --git a/absl/container/internal/unordered_map_modifiers_test.h b/absl/container/internal/unordered_map_modifiers_test.h index 4d9ab30..a0b01a0 100644 --- a/absl/container/internal/unordered_map_modifiers_test.h +++ b/absl/container/internal/unordered_map_modifiers_test.h
@@ -32,10 +32,9 @@ TYPED_TEST_SUITE_P(ModifiersTest); TYPED_TEST_P(ModifiersTest, Clear) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m(values.begin(), values.end()); ASSERT_THAT(items(m), ::testing::UnorderedElementsAreArray(values)); m.clear(); @@ -44,63 +43,61 @@ } TYPED_TEST_P(ModifiersTest, Insert) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using V = typename TypeParam::mapped_type; - T val = hash_internal::Generator<T>()(); + T val = Generator<T>()(); TypeParam m; auto p = m.insert(val); EXPECT_TRUE(p.second); EXPECT_EQ(val, *p.first); - T val2 = {val.first, hash_internal::Generator<V>()()}; + T val2 = {val.first, Generator<V>()()}; p = m.insert(val2); EXPECT_FALSE(p.second); EXPECT_EQ(val, *p.first); } TYPED_TEST_P(ModifiersTest, InsertHint) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using V = typename TypeParam::mapped_type; - T val = hash_internal::Generator<T>()(); + T val = Generator<T>()(); TypeParam m; auto it = m.insert(m.end(), val); EXPECT_TRUE(it != m.end()); EXPECT_EQ(val, *it); - T val2 = {val.first, hash_internal::Generator<V>()()}; + T val2 = {val.first, Generator<V>()()}; it = m.insert(it, val2); EXPECT_TRUE(it != m.end()); EXPECT_EQ(val, *it); } TYPED_TEST_P(ModifiersTest, InsertRange) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m; m.insert(values.begin(), values.end()); ASSERT_THAT(items(m), ::testing::UnorderedElementsAreArray(values)); } TYPED_TEST_P(ModifiersTest, InsertWithinCapacity) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using V = typename TypeParam::mapped_type; - T val = hash_internal::Generator<T>()(); + T val = Generator<T>()(); TypeParam m; m.reserve(10); const size_t original_capacity = m.bucket_count(); m.insert(val); EXPECT_EQ(m.bucket_count(), original_capacity); - T val2 = {val.first, hash_internal::Generator<V>()()}; + T val2 = {val.first, Generator<V>()()}; m.insert(val2); EXPECT_EQ(m.bucket_count(), original_capacity); } TYPED_TEST_P(ModifiersTest, InsertRangeWithinCapacity) { #if !defined(__GLIBCXX__) - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> base_values; - std::generate_n(std::back_inserter(base_values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(base_values), 10, Generator<T>()); std::vector<T> values; while (values.size() != 100) { std::copy_n(base_values.begin(), 10, std::back_inserter(values)); @@ -114,106 +111,98 @@ } TYPED_TEST_P(ModifiersTest, InsertOrAssign) { -#ifdef UNORDERED_MAP_CXX17 using std::get; using K = typename TypeParam::key_type; using V = typename TypeParam::mapped_type; - K k = hash_internal::Generator<K>()(); - V val = hash_internal::Generator<V>()(); + K k = Generator<K>()(); + V val = Generator<V>()(); TypeParam m; auto p = m.insert_or_assign(k, val); EXPECT_TRUE(p.second); EXPECT_EQ(k, get<0>(*p.first)); EXPECT_EQ(val, get<1>(*p.first)); - V val2 = hash_internal::Generator<V>()(); + V val2 = Generator<V>()(); p = m.insert_or_assign(k, val2); EXPECT_FALSE(p.second); EXPECT_EQ(k, get<0>(*p.first)); EXPECT_EQ(val2, get<1>(*p.first)); -#endif } TYPED_TEST_P(ModifiersTest, InsertOrAssignHint) { -#ifdef UNORDERED_MAP_CXX17 using std::get; using K = typename TypeParam::key_type; using V = typename TypeParam::mapped_type; - K k = hash_internal::Generator<K>()(); - V val = hash_internal::Generator<V>()(); + K k = Generator<K>()(); + V val = Generator<V>()(); TypeParam m; auto it = m.insert_or_assign(m.end(), k, val); EXPECT_TRUE(it != m.end()); EXPECT_EQ(k, get<0>(*it)); EXPECT_EQ(val, get<1>(*it)); - V val2 = hash_internal::Generator<V>()(); + V val2 = Generator<V>()(); it = m.insert_or_assign(it, k, val2); EXPECT_EQ(k, get<0>(*it)); EXPECT_EQ(val2, get<1>(*it)); -#endif } TYPED_TEST_P(ModifiersTest, Emplace) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using V = typename TypeParam::mapped_type; - T val = hash_internal::Generator<T>()(); + T val = Generator<T>()(); TypeParam m; // TODO(alkis): We need a way to run emplace in a more meaningful way. Perhaps // with test traits/policy. auto p = m.emplace(val); EXPECT_TRUE(p.second); EXPECT_EQ(val, *p.first); - T val2 = {val.first, hash_internal::Generator<V>()()}; + T val2 = {val.first, Generator<V>()()}; p = m.emplace(val2); EXPECT_FALSE(p.second); EXPECT_EQ(val, *p.first); } TYPED_TEST_P(ModifiersTest, EmplaceHint) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using V = typename TypeParam::mapped_type; - T val = hash_internal::Generator<T>()(); + T val = Generator<T>()(); TypeParam m; // TODO(alkis): We need a way to run emplace in a more meaningful way. Perhaps // with test traits/policy. auto it = m.emplace_hint(m.end(), val); EXPECT_EQ(val, *it); - T val2 = {val.first, hash_internal::Generator<V>()()}; + T val2 = {val.first, Generator<V>()()}; it = m.emplace_hint(it, val2); EXPECT_EQ(val, *it); } TYPED_TEST_P(ModifiersTest, TryEmplace) { -#ifdef UNORDERED_MAP_CXX17 - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using V = typename TypeParam::mapped_type; - T val = hash_internal::Generator<T>()(); + T val = Generator<T>()(); TypeParam m; // TODO(alkis): We need a way to run emplace in a more meaningful way. Perhaps // with test traits/policy. auto p = m.try_emplace(val.first, val.second); EXPECT_TRUE(p.second); EXPECT_EQ(val, *p.first); - T val2 = {val.first, hash_internal::Generator<V>()()}; + T val2 = {val.first, Generator<V>()()}; p = m.try_emplace(val2.first, val2.second); EXPECT_FALSE(p.second); EXPECT_EQ(val, *p.first); -#endif } TYPED_TEST_P(ModifiersTest, TryEmplaceHint) { -#ifdef UNORDERED_MAP_CXX17 - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using V = typename TypeParam::mapped_type; - T val = hash_internal::Generator<T>()(); + T val = Generator<T>()(); TypeParam m; // TODO(alkis): We need a way to run emplace in a more meaningful way. Perhaps // with test traits/policy. auto it = m.try_emplace(m.end(), val.first, val.second); EXPECT_EQ(val, *it); - T val2 = {val.first, hash_internal::Generator<V>()()}; + T val2 = {val.first, Generator<V>()()}; it = m.try_emplace(it, val2.first, val2.second); EXPECT_EQ(val, *it); -#endif } template <class V> @@ -236,11 +225,10 @@ }; TYPED_TEST_P(ModifiersTest, Erase) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using std::get; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m(values.begin(), values.end()); ASSERT_THAT(items(m), ::testing::UnorderedElementsAreArray(values)); auto& first = *m.begin(); @@ -255,10 +243,9 @@ } TYPED_TEST_P(ModifiersTest, EraseRange) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m(values.begin(), values.end()); ASSERT_THAT(items(m), ::testing::UnorderedElementsAreArray(values)); auto it = m.erase(m.begin(), m.end()); @@ -267,10 +254,9 @@ } TYPED_TEST_P(ModifiersTest, EraseKey) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m(values.begin(), values.end()); ASSERT_THAT(items(m), ::testing::UnorderedElementsAreArray(values)); EXPECT_EQ(1, m.erase(values[0].first)); @@ -280,11 +266,11 @@ } TYPED_TEST_P(ModifiersTest, Swap) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> v1; std::vector<T> v2; - std::generate_n(std::back_inserter(v1), 5, hash_internal::Generator<T>()); - std::generate_n(std::back_inserter(v2), 5, hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(v1), 5, Generator<T>()); + std::generate_n(std::back_inserter(v2), 5, Generator<T>()); TypeParam m1(v1.begin(), v1.end()); TypeParam m2(v2.begin(), v2.end()); EXPECT_THAT(items(m1), ::testing::UnorderedElementsAreArray(v1)); @@ -327,20 +313,18 @@ // Test that we do not move from rvalue arguments if an insertion does not // happen. TYPED_TEST_P(UniquePtrModifiersTest, TryEmplace) { -#ifdef UNORDERED_MAP_CXX17 - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using V = typename TypeParam::mapped_type; - T val = hash_internal::Generator<T>()(); + T val = Generator<T>()(); TypeParam m; auto p = m.try_emplace(val.first, std::move(val.second)); EXPECT_TRUE(p.second); // A moved from std::unique_ptr is guaranteed to be nullptr. EXPECT_EQ(val.second, nullptr); - T val2 = {val.first, hash_internal::Generator<V>()()}; + T val2 = {val.first, Generator<V>()()}; p = m.try_emplace(val2.first, std::move(val2.second)); EXPECT_FALSE(p.second); EXPECT_NE(val2.second, nullptr); -#endif } REGISTER_TYPED_TEST_SUITE_P(UniquePtrModifiersTest, TryEmplace);
diff --git a/absl/container/internal/unordered_set_constructor_test.h b/absl/container/internal/unordered_set_constructor_test.h index af1116e..7038a0c 100644 --- a/absl/container/internal/unordered_set_constructor_test.h +++ b/absl/container/internal/unordered_set_constructor_test.h
@@ -21,6 +21,7 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/base/config.h" #include "absl/container/internal/hash_generator_testing.h" #include "absl/container/internal/hash_policy_testing.h" #include "absl/meta/type_traits.h" @@ -94,28 +95,8 @@ EXPECT_GE(cm.bucket_count(), 123); } -template <typename T> -struct is_std_unordered_set : std::false_type {}; - -template <typename... T> -struct is_std_unordered_set<std::unordered_set<T...>> : std::true_type {}; - -#if defined(UNORDERED_SET_CXX14) || defined(UNORDERED_SET_CXX17) -using has_cxx14_std_apis = std::true_type; -#else -using has_cxx14_std_apis = std::false_type; -#endif - -template <typename T> -using expect_cxx14_apis = - absl::disjunction<absl::negation<is_std_unordered_set<T>>, - has_cxx14_std_apis>; - template <typename TypeParam> -void BucketCountAllocTest(std::false_type) {} - -template <typename TypeParam> -void BucketCountAllocTest(std::true_type) { +void BucketCountAllocTest() { using A = typename TypeParam::allocator_type; A alloc(0); TypeParam m(123, alloc); @@ -126,14 +107,11 @@ } TYPED_TEST_P(ConstructorTest, BucketCountAlloc) { - BucketCountAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + BucketCountAllocTest<TypeParam>(); } template <typename TypeParam> -void BucketCountHashAllocTest(std::false_type) {} - -template <typename TypeParam> -void BucketCountHashAllocTest(std::true_type) { +void BucketCountHashAllocTest() { using H = typename TypeParam::hasher; using A = typename TypeParam::allocator_type; H hasher; @@ -147,25 +125,11 @@ } TYPED_TEST_P(ConstructorTest, BucketCountHashAlloc) { - BucketCountHashAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + BucketCountHashAllocTest<TypeParam>(); } -#if ABSL_UNORDERED_SUPPORTS_ALLOC_CTORS -using has_alloc_std_constructors = std::true_type; -#else -using has_alloc_std_constructors = std::false_type; -#endif - -template <typename T> -using expect_alloc_constructors = - absl::disjunction<absl::negation<is_std_unordered_set<T>>, - has_alloc_std_constructors>; - template <typename TypeParam> -void AllocTest(std::false_type) {} - -template <typename TypeParam> -void AllocTest(std::true_type) { +void AllocTest() { using A = typename TypeParam::allocator_type; A alloc(0); TypeParam m(alloc); @@ -174,12 +138,10 @@ EXPECT_THAT(keys(m), ::testing::UnorderedElementsAre()); } -TYPED_TEST_P(ConstructorTest, Alloc) { - AllocTest<TypeParam>(expect_alloc_constructors<TypeParam>()); -} +TYPED_TEST_P(ConstructorTest, Alloc) { AllocTest<TypeParam>(); } TYPED_TEST_P(ConstructorTest, InputIteratorBucketHashEqualAlloc) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; @@ -187,8 +149,7 @@ E equal; A alloc(0); std::vector<T> values; - for (size_t i = 0; i != 10; ++i) - values.push_back(hash_internal::Generator<T>()()); + for (size_t i = 0; i != 10; ++i) values.push_back(Generator<T>()()); TypeParam m(values.begin(), values.end(), 123, hasher, equal, alloc); EXPECT_EQ(m.hash_function(), hasher); EXPECT_EQ(m.key_eq(), equal); @@ -198,16 +159,12 @@ } template <typename TypeParam> -void InputIteratorBucketAllocTest(std::false_type) {} - -template <typename TypeParam> -void InputIteratorBucketAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void InputIteratorBucketAllocTest() { + using T = GeneratedType<TypeParam>; using A = typename TypeParam::allocator_type; A alloc(0); std::vector<T> values; - for (size_t i = 0; i != 10; ++i) - values.push_back(hash_internal::Generator<T>()()); + for (size_t i = 0; i != 10; ++i) values.push_back(Generator<T>()()); TypeParam m(values.begin(), values.end(), 123, alloc); EXPECT_EQ(m.get_allocator(), alloc); EXPECT_THAT(keys(m), ::testing::UnorderedElementsAreArray(values)); @@ -215,22 +172,18 @@ } TYPED_TEST_P(ConstructorTest, InputIteratorBucketAlloc) { - InputIteratorBucketAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + InputIteratorBucketAllocTest<TypeParam>(); } template <typename TypeParam> -void InputIteratorBucketHashAllocTest(std::false_type) {} - -template <typename TypeParam> -void InputIteratorBucketHashAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void InputIteratorBucketHashAllocTest() { + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using A = typename TypeParam::allocator_type; H hasher; A alloc(0); std::vector<T> values; - for (size_t i = 0; i != 10; ++i) - values.push_back(hash_internal::Generator<T>()()); + for (size_t i = 0; i != 10; ++i) values.push_back(Generator<T>()()); TypeParam m(values.begin(), values.end(), 123, hasher, alloc); EXPECT_EQ(m.hash_function(), hasher); EXPECT_EQ(m.get_allocator(), alloc); @@ -239,11 +192,11 @@ } TYPED_TEST_P(ConstructorTest, InputIteratorBucketHashAlloc) { - InputIteratorBucketHashAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + InputIteratorBucketHashAllocTest<TypeParam>(); } TYPED_TEST_P(ConstructorTest, CopyConstructor) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; @@ -251,7 +204,7 @@ E equal; A alloc(0); TypeParam m(123, hasher, equal, alloc); - for (size_t i = 0; i != 10; ++i) m.insert(hash_internal::Generator<T>()()); + for (size_t i = 0; i != 10; ++i) m.insert(Generator<T>()()); TypeParam n(m); EXPECT_EQ(m.hash_function(), n.hash_function()); EXPECT_EQ(m.key_eq(), n.key_eq()); @@ -261,11 +214,8 @@ } template <typename TypeParam> -void CopyConstructorAllocTest(std::false_type) {} - -template <typename TypeParam> -void CopyConstructorAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void CopyConstructorAllocTest() { + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; @@ -273,7 +223,7 @@ E equal; A alloc(0); TypeParam m(123, hasher, equal, alloc); - for (size_t i = 0; i != 10; ++i) m.insert(hash_internal::Generator<T>()()); + for (size_t i = 0; i != 10; ++i) m.insert(Generator<T>()()); TypeParam n(m, A(11)); EXPECT_EQ(m.hash_function(), n.hash_function()); EXPECT_EQ(m.key_eq(), n.key_eq()); @@ -282,13 +232,13 @@ } TYPED_TEST_P(ConstructorTest, CopyConstructorAlloc) { - CopyConstructorAllocTest<TypeParam>(expect_alloc_constructors<TypeParam>()); + CopyConstructorAllocTest<TypeParam>(); } // TODO(alkis): Test non-propagating allocators on copy constructors. TYPED_TEST_P(ConstructorTest, MoveConstructor) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; @@ -296,7 +246,7 @@ E equal; A alloc(0); TypeParam m(123, hasher, equal, alloc); - for (size_t i = 0; i != 10; ++i) m.insert(hash_internal::Generator<T>()()); + for (size_t i = 0; i != 10; ++i) m.insert(Generator<T>()()); TypeParam t(m); TypeParam n(std::move(t)); EXPECT_EQ(m.hash_function(), n.hash_function()); @@ -306,11 +256,8 @@ } template <typename TypeParam> -void MoveConstructorAllocTest(std::false_type) {} - -template <typename TypeParam> -void MoveConstructorAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void MoveConstructorAllocTest() { + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; @@ -318,7 +265,7 @@ E equal; A alloc(0); TypeParam m(123, hasher, equal, alloc); - for (size_t i = 0; i != 10; ++i) m.insert(hash_internal::Generator<T>()()); + for (size_t i = 0; i != 10; ++i) m.insert(Generator<T>()()); TypeParam t(m); TypeParam n(std::move(t), A(1)); EXPECT_EQ(m.hash_function(), n.hash_function()); @@ -328,14 +275,14 @@ } TYPED_TEST_P(ConstructorTest, MoveConstructorAlloc) { - MoveConstructorAllocTest<TypeParam>(expect_alloc_constructors<TypeParam>()); + MoveConstructorAllocTest<TypeParam>(); } // TODO(alkis): Test non-propagating allocators on move constructors. TYPED_TEST_P(ConstructorTest, InitializerListBucketHashEqualAlloc) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::Generator<T> gen; + using T = GeneratedType<TypeParam>; + Generator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; @@ -352,13 +299,10 @@ } template <typename TypeParam> -void InitializerListBucketAllocTest(std::false_type) {} - -template <typename TypeParam> -void InitializerListBucketAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void InitializerListBucketAllocTest() { + using T = GeneratedType<TypeParam>; using A = typename TypeParam::allocator_type; - hash_internal::Generator<T> gen; + Generator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; A alloc(0); TypeParam m(values, 123, alloc); @@ -368,20 +312,17 @@ } TYPED_TEST_P(ConstructorTest, InitializerListBucketAlloc) { - InitializerListBucketAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + InitializerListBucketAllocTest<TypeParam>(); } template <typename TypeParam> -void InitializerListBucketHashAllocTest(std::false_type) {} - -template <typename TypeParam> -void InitializerListBucketHashAllocTest(std::true_type) { - using T = hash_internal::GeneratedType<TypeParam>; +void InitializerListBucketHashAllocTest() { + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using A = typename TypeParam::allocator_type; H hasher; A alloc(0); - hash_internal::Generator<T> gen; + Generator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; TypeParam m(values, 123, hasher, alloc); EXPECT_EQ(m.hash_function(), hasher); @@ -391,18 +332,18 @@ } TYPED_TEST_P(ConstructorTest, InitializerListBucketHashAlloc) { - InitializerListBucketHashAllocTest<TypeParam>(expect_cxx14_apis<TypeParam>()); + InitializerListBucketHashAllocTest<TypeParam>(); } TYPED_TEST_P(ConstructorTest, CopyAssignment) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; H hasher; E equal; A alloc(0); - hash_internal::Generator<T> gen; + Generator<T> gen; TypeParam m({gen(), gen(), gen()}, 123, hasher, equal, alloc); TypeParam n; n = m; @@ -415,14 +356,14 @@ // (it depends on traits). TYPED_TEST_P(ConstructorTest, MoveAssignment) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; using H = typename TypeParam::hasher; using E = typename TypeParam::key_equal; using A = typename TypeParam::allocator_type; H hasher; E equal; A alloc(0); - hash_internal::Generator<T> gen; + Generator<T> gen; TypeParam m({gen(), gen(), gen()}, 123, hasher, equal, alloc); TypeParam t(m); TypeParam n; @@ -433,8 +374,8 @@ } TYPED_TEST_P(ConstructorTest, AssignmentFromInitializerList) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::Generator<T> gen; + using T = GeneratedType<TypeParam>; + Generator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; TypeParam m; m = values; @@ -442,8 +383,8 @@ } TYPED_TEST_P(ConstructorTest, AssignmentOverwritesExisting) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::Generator<T> gen; + using T = GeneratedType<TypeParam>; + Generator<T> gen; TypeParam m({gen(), gen(), gen()}); TypeParam n({gen()}); n = m; @@ -451,8 +392,8 @@ } TYPED_TEST_P(ConstructorTest, MoveAssignmentOverwritesExisting) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::Generator<T> gen; + using T = GeneratedType<TypeParam>; + Generator<T> gen; TypeParam m({gen(), gen(), gen()}); TypeParam t(m); TypeParam n({gen()}); @@ -461,8 +402,8 @@ } TYPED_TEST_P(ConstructorTest, AssignmentFromInitializerListOverwritesExisting) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::Generator<T> gen; + using T = GeneratedType<TypeParam>; + Generator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; TypeParam m; m = values; @@ -470,8 +411,8 @@ } TYPED_TEST_P(ConstructorTest, AssignmentOnSelf) { - using T = hash_internal::GeneratedType<TypeParam>; - hash_internal::Generator<T> gen; + using T = GeneratedType<TypeParam>; + Generator<T> gen; std::initializer_list<T> values = {gen(), gen(), gen(), gen(), gen()}; TypeParam m(values); m = *&m; // Avoid -Wself-assign.
diff --git a/absl/container/internal/unordered_set_lookup_test.h b/absl/container/internal/unordered_set_lookup_test.h index b35f766..dc63118 100644 --- a/absl/container/internal/unordered_set_lookup_test.h +++ b/absl/container/internal/unordered_set_lookup_test.h
@@ -30,10 +30,9 @@ TYPED_TEST_SUITE_P(LookupTest); TYPED_TEST_P(LookupTest, Count) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m; for (const auto& v : values) EXPECT_EQ(0, m.count(v)) << ::testing::PrintToString(v); @@ -43,10 +42,9 @@ } TYPED_TEST_P(LookupTest, Find) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m; for (const auto& v : values) EXPECT_TRUE(m.end() == m.find(v)) << ::testing::PrintToString(v); @@ -65,10 +63,9 @@ } TYPED_TEST_P(LookupTest, EqualRange) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m; for (const auto& v : values) { auto r = m.equal_range(v);
diff --git a/absl/container/internal/unordered_set_members_test.h b/absl/container/internal/unordered_set_members_test.h index 4c5e104..b416fef 100644 --- a/absl/container/internal/unordered_set_members_test.h +++ b/absl/container/internal/unordered_set_members_test.h
@@ -35,10 +35,10 @@ TYPED_TEST_P(MembersTest, Typedefs) { EXPECT_TRUE((std::is_same<typename TypeParam::key_type, typename TypeParam::value_type>())); - EXPECT_TRUE((absl::conjunction< - absl::negation<std::is_signed<typename TypeParam::size_type>>, + EXPECT_TRUE((std::conjunction< + std::negation<std::is_signed<typename TypeParam::size_type>>, std::is_integral<typename TypeParam::size_type>>())); - EXPECT_TRUE((absl::conjunction< + EXPECT_TRUE((std::conjunction< std::is_signed<typename TypeParam::difference_type>, std::is_integral<typename TypeParam::difference_type>>())); EXPECT_TRUE((std::is_convertible<
diff --git a/absl/container/internal/unordered_set_modifiers_test.h b/absl/container/internal/unordered_set_modifiers_test.h index d8864bb..b647642 100644 --- a/absl/container/internal/unordered_set_modifiers_test.h +++ b/absl/container/internal/unordered_set_modifiers_test.h
@@ -30,10 +30,9 @@ TYPED_TEST_SUITE_P(ModifiersTest); TYPED_TEST_P(ModifiersTest, Clear) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m(values.begin(), values.end()); ASSERT_THAT(keys(m), ::testing::UnorderedElementsAreArray(values)); m.clear(); @@ -42,8 +41,8 @@ } TYPED_TEST_P(ModifiersTest, Insert) { - using T = hash_internal::GeneratedType<TypeParam>; - T val = hash_internal::Generator<T>()(); + using T = GeneratedType<TypeParam>; + T val = Generator<T>()(); TypeParam m; auto p = m.insert(val); EXPECT_TRUE(p.second); @@ -53,8 +52,8 @@ } TYPED_TEST_P(ModifiersTest, InsertHint) { - using T = hash_internal::GeneratedType<TypeParam>; - T val = hash_internal::Generator<T>()(); + using T = GeneratedType<TypeParam>; + T val = Generator<T>()(); TypeParam m; auto it = m.insert(m.end(), val); EXPECT_TRUE(it != m.end()); @@ -65,18 +64,17 @@ } TYPED_TEST_P(ModifiersTest, InsertRange) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m; m.insert(values.begin(), values.end()); ASSERT_THAT(keys(m), ::testing::UnorderedElementsAreArray(values)); } TYPED_TEST_P(ModifiersTest, InsertWithinCapacity) { - using T = hash_internal::GeneratedType<TypeParam>; - T val = hash_internal::Generator<T>()(); + using T = GeneratedType<TypeParam>; + T val = Generator<T>()(); TypeParam m; m.reserve(10); const size_t original_capacity = m.bucket_count(); @@ -88,10 +86,9 @@ TYPED_TEST_P(ModifiersTest, InsertRangeWithinCapacity) { #if !defined(__GLIBCXX__) - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> base_values; - std::generate_n(std::back_inserter(base_values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(base_values), 10, Generator<T>()); std::vector<T> values; while (values.size() != 100) { values.insert(values.end(), base_values.begin(), base_values.end()); @@ -105,8 +102,8 @@ } TYPED_TEST_P(ModifiersTest, Emplace) { - using T = hash_internal::GeneratedType<TypeParam>; - T val = hash_internal::Generator<T>()(); + using T = GeneratedType<TypeParam>; + T val = Generator<T>()(); TypeParam m; // TODO(alkis): We need a way to run emplace in a more meaningful way. Perhaps // with test traits/policy. @@ -119,8 +116,8 @@ } TYPED_TEST_P(ModifiersTest, EmplaceHint) { - using T = hash_internal::GeneratedType<TypeParam>; - T val = hash_internal::Generator<T>()(); + using T = GeneratedType<TypeParam>; + T val = Generator<T>()(); TypeParam m; // TODO(alkis): We need a way to run emplace in a more meaningful way. Perhaps // with test traits/policy. @@ -150,10 +147,9 @@ }; TYPED_TEST_P(ModifiersTest, Erase) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m(values.begin(), values.end()); ASSERT_THAT(keys(m), ::testing::UnorderedElementsAreArray(values)); std::vector<T> values2; @@ -167,10 +163,9 @@ } TYPED_TEST_P(ModifiersTest, EraseRange) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m(values.begin(), values.end()); ASSERT_THAT(keys(m), ::testing::UnorderedElementsAreArray(values)); auto it = m.erase(m.begin(), m.end()); @@ -179,10 +174,9 @@ } TYPED_TEST_P(ModifiersTest, EraseKey) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> values; - std::generate_n(std::back_inserter(values), 10, - hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(values), 10, Generator<T>()); TypeParam m(values.begin(), values.end()); ASSERT_THAT(keys(m), ::testing::UnorderedElementsAreArray(values)); EXPECT_EQ(1, m.erase(values[0])); @@ -192,11 +186,11 @@ } TYPED_TEST_P(ModifiersTest, Swap) { - using T = hash_internal::GeneratedType<TypeParam>; + using T = GeneratedType<TypeParam>; std::vector<T> v1; std::vector<T> v2; - std::generate_n(std::back_inserter(v1), 5, hash_internal::Generator<T>()); - std::generate_n(std::back_inserter(v2), 5, hash_internal::Generator<T>()); + std::generate_n(std::back_inserter(v1), 5, Generator<T>()); + std::generate_n(std::back_inserter(v2), 5, Generator<T>()); TypeParam m1(v1.begin(), v1.end()); TypeParam m2(v2.begin(), v2.end()); EXPECT_THAT(keys(m1), ::testing::UnorderedElementsAreArray(v1));
diff --git a/absl/container/linked_hash_map.h b/absl/container/linked_hash_map.h new file mode 100644 index 0000000..e42a1f7 --- /dev/null +++ b/absl/container/linked_hash_map.h
@@ -0,0 +1,666 @@ +// Copyright 2025 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: linked_hash_map.h +// ----------------------------------------------------------------------------- +// +// This is a simple insertion-ordered map. It provides O(1) amortized +// insertions and lookups, as well as iteration over the map in the insertion +// order. +// +// This class is thread-compatible. +// +// Iterators point into the list and should be stable in the face of +// mutations, except for an iterator pointing to an element that was just +// deleted. +// +// This class supports heterogeneous lookups. + +#ifndef ABSL_CONTAINER_LINKED_HASH_MAP_H_ +#define ABSL_CONTAINER_LINKED_HASH_MAP_H_ + +#include <cassert> +#include <cstddef> +#include <initializer_list> +#include <list> +#include <memory> +#include <string> +#include <tuple> +#include <type_traits> +#include <utility> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/internal/throw_delegate.h" +#include "absl/base/optimization.h" +#include "absl/container/flat_hash_set.h" +#include "absl/container/internal/common.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +template <typename Key, typename Value, + typename KeyHash = typename absl::flat_hash_set<Key>::hasher, + typename KeyEq = + typename absl::flat_hash_set<Key, KeyHash>::key_equal, + typename Alloc = std::allocator<std::pair<const Key, Value>>> +class linked_hash_map { + using KeyArgImpl = absl::container_internal::KeyArg< + absl::container_internal::IsTransparent<KeyEq>::value && + absl::container_internal::IsTransparent<KeyHash>::value>; + + public: + using key_type = Key; + using mapped_type = Value; + using hasher = KeyHash; + using key_equal = KeyEq; + using value_type = std::pair<const key_type, mapped_type>; + using allocator_type = Alloc; + using difference_type = ptrdiff_t; + + private: + template <class K> + using key_arg = typename KeyArgImpl::template type<K, key_type>; + + using ListType = std::list<value_type, Alloc>; + + template <class Fn> + class Wrapped { + template <typename K> + static const K& ToKey(const K& k) { + return k; + } + static const key_type& ToKey(typename ListType::const_iterator it) { + return it->first; + } + static const key_type& ToKey(typename ListType::iterator it) { + return it->first; + } + + Fn fn_; + + friend linked_hash_map; + + public: + using is_transparent = void; + + Wrapped() = default; + explicit Wrapped(Fn fn) : fn_(std::move(fn)) {} + + template <class... Args> + auto operator()(Args&&... args) const + -> decltype(this->fn_(ToKey(args)...)) { + return fn_(ToKey(args)...); + } + }; + using SetType = + absl::flat_hash_set<typename ListType::iterator, Wrapped<hasher>, + Wrapped<key_equal>, Alloc>; + + class NodeHandle { + public: + using key_type = linked_hash_map::key_type; + using mapped_type = linked_hash_map::mapped_type; + using allocator_type = linked_hash_map::allocator_type; + + constexpr NodeHandle() noexcept = default; + NodeHandle(NodeHandle&& nh) noexcept = default; + ~NodeHandle() = default; + NodeHandle& operator=(NodeHandle&& node) noexcept = default; + bool empty() const noexcept { return list_.empty(); } + explicit operator bool() const noexcept { return !empty(); } + allocator_type get_allocator() const { return list_.get_allocator(); } + const key_type& key() const { return list_.front().first; } + mapped_type& mapped() { return list_.front().second; } + void swap(NodeHandle& nh) noexcept { list_.swap(nh.list_); } + + private: + friend linked_hash_map; + + explicit NodeHandle(ListType list) : list_(std::move(list)) {} + ListType list_; + }; + + template <class Iterator, class NodeType> + struct InsertReturnType { + Iterator position; + bool inserted; + NodeType node; + }; + + public: + using iterator = typename ListType::iterator; + using const_iterator = typename ListType::const_iterator; + using reverse_iterator = typename ListType::reverse_iterator; + using const_reverse_iterator = typename ListType::const_reverse_iterator; + using reference = typename ListType::reference; + using const_reference = typename ListType::const_reference; + using size_type = typename ListType::size_type; + using pointer = typename std::allocator_traits<allocator_type>::pointer; + using const_pointer = + typename std::allocator_traits<allocator_type>::const_pointer; + using node_type = NodeHandle; + using insert_return_type = InsertReturnType<iterator, node_type>; + + linked_hash_map() {} + + explicit linked_hash_map(size_t bucket_count, const hasher& hash = hasher(), + const key_equal& eq = key_equal(), + const allocator_type& alloc = allocator_type()) + : set_(bucket_count, Wrapped<hasher>(hash), Wrapped<key_equal>(eq), + alloc), + list_(alloc) {} + + linked_hash_map(size_t bucket_count, const hasher& hash, + const allocator_type& alloc) + : linked_hash_map(bucket_count, hash, key_equal(), alloc) {} + + linked_hash_map(size_t bucket_count, const allocator_type& alloc) + : linked_hash_map(bucket_count, hasher(), key_equal(), alloc) {} + + explicit linked_hash_map(const allocator_type& alloc) + : linked_hash_map(0, hasher(), key_equal(), alloc) {} + + template <class InputIt> + linked_hash_map(InputIt first, InputIt last, size_t bucket_count = 0, + const hasher& hash = hasher(), + const key_equal& eq = key_equal(), + const allocator_type& alloc = allocator_type()) + : linked_hash_map(bucket_count, hash, eq, alloc) { + insert(first, last); + } + + template <class InputIt> + linked_hash_map(InputIt first, InputIt last, size_t bucket_count, + const hasher& hash, const allocator_type& alloc) + : linked_hash_map(first, last, bucket_count, hash, key_equal(), alloc) {} + + template <class InputIt> + linked_hash_map(InputIt first, InputIt last, size_t bucket_count, + const allocator_type& alloc) + : linked_hash_map(first, last, bucket_count, hasher(), key_equal(), + alloc) {} + + template <class InputIt> + linked_hash_map(InputIt first, InputIt last, const allocator_type& alloc) + : linked_hash_map(first, last, /*bucket_count=*/0, hasher(), key_equal(), + alloc) {} + + linked_hash_map(std::initializer_list<value_type> init, + size_t bucket_count = 0, const hasher& hash = hasher(), + const key_equal& eq = key_equal(), + const allocator_type& alloc = allocator_type()) + : linked_hash_map(init.begin(), init.end(), bucket_count, hash, eq, + alloc) {} + + linked_hash_map(std::initializer_list<value_type> init, size_t bucket_count, + const hasher& hash, const allocator_type& alloc) + : linked_hash_map(init, bucket_count, hash, key_equal(), alloc) {} + + linked_hash_map(std::initializer_list<value_type> init, size_t bucket_count, + const allocator_type& alloc) + : linked_hash_map(init, bucket_count, hasher(), key_equal(), alloc) {} + + linked_hash_map(std::initializer_list<value_type> init, + const allocator_type& alloc) + : linked_hash_map(init, /*bucket_count=*/0, hasher(), key_equal(), + alloc) {} + + linked_hash_map(const linked_hash_map& other) + : linked_hash_map(other.bucket_count(), other.hash_function(), + other.key_eq(), other.get_allocator()) { + CopyFrom(other); + } + + linked_hash_map(const linked_hash_map& other, const allocator_type& alloc) + : linked_hash_map(other.bucket_count(), other.hash_function(), + other.key_eq(), alloc) { + CopyFrom(other); + } + + linked_hash_map(linked_hash_map&& other) noexcept + : set_(std::move(other.set_)), list_(std::move(other.list_)) { + // Since the list and set must agree for other to end up "valid", + // explicitly clear them. + other.set_.clear(); + other.list_.clear(); + } + + linked_hash_map(linked_hash_map&& other, const allocator_type& alloc) + : linked_hash_map(0, other.hash_function(), other.key_eq(), alloc) { + if (get_allocator() == other.get_allocator()) { + *this = std::move(other); + } else { + CopyFrom(std::move(other)); + } + } + + linked_hash_map& operator=(const linked_hash_map& other) { + if (this != &other) { + // Make a new set, with other's hash/eq/alloc. + set_ = SetType(other.bucket_count(), other.set_.hash_function(), + other.set_.key_eq(), other.get_allocator()); + // Copy the list, with other's allocator. + list_ = ListType(other.get_allocator()); + CopyFrom(other); + } + return *this; + } + + linked_hash_map& operator=(linked_hash_map&& other) noexcept { + if (this != &other) { + // underlying containers will handle progagate_on_container_move details + set_ = std::move(other.set_); + list_ = std::move(other.list_); + other.set_.clear(); + other.list_.clear(); + } + return *this; + } + + linked_hash_map& operator=(std::initializer_list<value_type> values) { + clear(); + insert(values.begin(), values.end()); + return *this; + } + + // Derive size_ from set_, as list::size might be O(N). + size_type size() const { return set_.size(); } + size_type max_size() const noexcept { return ~size_type{}; } + bool empty() const { return set_.empty(); } + + // Iteration is list-like, in insertion order. + // These are all forwarded. + iterator begin() { return list_.begin(); } + iterator end() { return list_.end(); } + const_iterator begin() const { return list_.begin(); } + const_iterator end() const { return list_.end(); } + const_iterator cbegin() const { return list_.cbegin(); } + const_iterator cend() const { return list_.cend(); } + reverse_iterator rbegin() { return list_.rbegin(); } + reverse_iterator rend() { return list_.rend(); } + const_reverse_iterator rbegin() const { return list_.rbegin(); } + const_reverse_iterator rend() const { return list_.rend(); } + const_reverse_iterator crbegin() const { return list_.crbegin(); } + const_reverse_iterator crend() const { return list_.crend(); } + reference front() { return list_.front(); } + reference back() { return list_.back(); } + const_reference front() const { return list_.front(); } + const_reference back() const { return list_.back(); } + + void pop_front() { erase(begin()); } + void pop_back() { erase(std::prev(end())); } + + ABSL_ATTRIBUTE_REINITIALIZES void clear() { + set_.clear(); + list_.clear(); + } + + void reserve(size_t n) { set_.reserve(n); } + size_t capacity() const { return set_.capacity(); } + size_t bucket_count() const { return set_.bucket_count(); } + float load_factor() const { return set_.load_factor(); } + + hasher hash_function() const { return set_.hash_function().fn_; } + key_equal key_eq() const { return set_.key_eq().fn_; } + allocator_type get_allocator() const { return list_.get_allocator(); } + + template <class K = key_type> + size_type erase(const key_arg<K>& key) { + auto found = set_.find(key); + if (found == set_.end()) return 0; + auto list_it = *found; + // Erase set entry first since it refers to the list element. + set_.erase(found); + list_.erase(list_it); + return 1; + } + + iterator erase(const_iterator position) { + auto found = set_.find(position); + assert(*found == position); + set_.erase(found); + return list_.erase(position); + } + + iterator erase(iterator position) { + return erase(static_cast<const_iterator>(position)); + } + + iterator erase(iterator first, iterator last) { + while (first != last) first = erase(first); + return first; + } + + iterator erase(const_iterator first, const_iterator last) { + while (first != last) first = erase(first); + if (first == end()) return end(); + return *set_.find(first); + } + + template <class K = key_type> + iterator find(const key_arg<K>& key) { + auto found = set_.find(key); + if (found == set_.end()) return end(); + return *found; + } + + template <class K = key_type> + const_iterator find(const key_arg<K>& key) const { + auto found = set_.find(key); + if (found == set_.end()) return end(); + return *found; + } + + template <class K = key_type> + size_type count(const key_arg<K>& key) const { + return contains(key) ? 1 : 0; + } + template <class K = key_type> + bool contains(const key_arg<K>& key) const { + return set_.contains(key); + } + + template <class K = key_type> + mapped_type& at(const key_arg<K>& key) { + auto it = find(key); + if (ABSL_PREDICT_FALSE(it == end())) { + absl::base_internal::ThrowStdOutOfRange("absl::linked_hash_map::at"); + } + return it->second; + } + + template <class K = key_type> + const mapped_type& at(const key_arg<K>& key) const { + return const_cast<linked_hash_map*>(this)->at(key); + } + + template <class K = key_type> + std::pair<iterator, iterator> equal_range(const key_arg<K>& key) { + auto iter = set_.find(key); + if (iter == set_.end()) return {end(), end()}; + return {*iter, std::next(*iter)}; + } + + template <class K = key_type> + std::pair<const_iterator, const_iterator> equal_range( + const key_arg<K>& key) const { + auto iter = set_.find(key); + if (iter == set_.end()) return {end(), end()}; + return {*iter, std::next(*iter)}; + } + + template <class K = key_type> + mapped_type& operator[](const key_arg<K>& key) { + return LazyEmplaceInternal(key).first->second; + } + + template <class K = key_type, K* = nullptr> + mapped_type& operator[](key_arg<K>&& key) { + return LazyEmplaceInternal(std::forward<key_arg<K>>(key)).first->second; + } + + std::pair<iterator, bool> insert(const value_type& v) { + return InsertInternal(v); + } + std::pair<iterator, bool> insert(value_type&& v) { + return InsertInternal(std::move(v)); + } + + iterator insert(const_iterator, const value_type& v) { + return insert(v).first; + } + iterator insert(const_iterator, value_type&& v) { + return insert(std::move(v)).first; + } + + void insert(std::initializer_list<value_type> ilist) { + insert(ilist.begin(), ilist.end()); + } + + template <class InputIt> + void insert(InputIt first, InputIt last) { + for (; first != last; ++first) insert(*first); + } + + insert_return_type insert(node_type&& node) { + if (node.empty()) return {end(), false, node_type()}; + if (auto [set_itr, inserted] = set_.emplace(node.list_.begin()); inserted) { + list_.splice(list_.end(), node.list_); + return {*set_itr, true, node_type()}; + } else { + return {*set_itr, false, std::move(node)}; + } + } + + iterator insert(const_iterator, node_type&& node) { + return insert(std::move(node)).first; + } + + // The last two template parameters ensure that both arguments are rvalues + // (lvalue arguments are handled by the overloads below). This is necessary + // for supporting bitfield arguments. + // + // union { int n : 1; }; + // linked_hash_map<int, int> m; + // m.insert_or_assign(n, n); + template <class K = key_type, class V = mapped_type, K* = nullptr, + V* = nullptr> + std::pair<iterator, bool> insert_or_assign(key_arg<K>&& k, V&& v) { + return InsertOrAssignInternal(std::forward<key_arg<K>>(k), + std::forward<V>(v)); + } + + template <class K = key_type, class V = mapped_type, K* = nullptr> + std::pair<iterator, bool> insert_or_assign(key_arg<K>&& k, const V& v) { + return InsertOrAssignInternal(std::forward<key_arg<K>>(k), v); + } + + template <class K = key_type, class V = mapped_type, V* = nullptr> + std::pair<iterator, bool> insert_or_assign(const key_arg<K>& k, V&& v) { + return InsertOrAssignInternal(k, std::forward<V>(v)); + } + + template <class K = key_type, class V = mapped_type> + std::pair<iterator, bool> insert_or_assign(const key_arg<K>& k, const V& v) { + return InsertOrAssignInternal(k, v); + } + + template <class K = key_type, class V = mapped_type, K* = nullptr, + V* = nullptr> + iterator insert_or_assign(const_iterator, key_arg<K>&& k, V&& v) { + return insert_or_assign(std::forward<key_arg<K>>(k), std::forward<V>(v)) + .first; + } + + template <class K = key_type, class V = mapped_type, K* = nullptr> + iterator insert_or_assign(const_iterator, key_arg<K>&& k, const V& v) { + return insert_or_assign(std::forward<key_arg<K>>(k), v).first; + } + + template <class K = key_type, class V = mapped_type, V* = nullptr> + iterator insert_or_assign(const_iterator, const key_arg<K>& k, V&& v) { + return insert_or_assign(k, std::forward<V>(v)).first; + } + + template <class K = key_type, class V = mapped_type> + iterator insert_or_assign(const_iterator, const key_arg<K>& k, const V& v) { + return insert_or_assign(k, v).first; + } + + template <typename... Args> + std::pair<iterator, bool> emplace(Args&&... args) { + ListType node_donor; + auto list_iter = + node_donor.emplace(node_donor.end(), std::forward<Args>(args)...); + auto ins = set_.insert(list_iter); + if (!ins.second) return {*ins.first, false}; + list_.splice(list_.end(), node_donor, list_iter); + return {list_iter, true}; + } + + template <class K = key_type, class... Args, K* = nullptr> + iterator try_emplace(const_iterator, key_arg<K>&& k, Args&&... args) { + return try_emplace(std::forward<key_arg<K>>(k), std::forward<Args>(args)...) + .first; + } + + template <typename... Args> + iterator emplace_hint(const_iterator, Args&&... args) { + return emplace(std::forward<Args>(args)...).first; + } + + template <class K = key_type, typename... Args, K* = nullptr> + std::pair<iterator, bool> try_emplace(key_arg<K>&& key, Args&&... args) { + return LazyEmplaceInternal(std::forward<key_arg<K>>(key), + std::forward<Args>(args)...); + } + + template <typename H, typename E> + void merge(linked_hash_map<Key, Value, H, E, Alloc>& src) { + auto itr = src.list_.begin(); + while (itr != src.list_.end()) { + if (contains(itr->first)) { + ++itr; + } else { + insert(src.extract(itr++)); + } + } + } + + template <typename H, typename E> + void merge(linked_hash_map<Key, Value, H, E, Alloc>&& src) { + merge(src); + } + + node_type extract(const_iterator position) { + set_.erase(position->first); + ListType extracted_node_list; + extracted_node_list.splice(extracted_node_list.end(), list_, position); + return node_type(std::move(extracted_node_list)); + } + + template <class K = key_type, + std::enable_if_t<!std::is_same_v<K, iterator>, int> = 0> + node_type extract(const key_arg<K>& key) { + auto node = set_.extract(key); + if (node.empty()) return node_type(); + ListType extracted_node_list; + extracted_node_list.splice(extracted_node_list.end(), list_, node.value()); + return node_type(std::move(extracted_node_list)); + } + + template <typename H, typename E> + void splice(const_iterator, linked_hash_map<Key, Value, H, E, Alloc>& list, + const_iterator it) { + if (&list == this) { + list_.splice(list_.end(), list.list_, it); + } else { + insert(list.extract(it)); + } + } + + template <class K = key_type, typename... Args> + std::pair<iterator, bool> try_emplace(const key_arg<K>& key, Args&&... args) { + return LazyEmplaceInternal(key, std::forward<Args>(args)...); + } + + template <class K = key_type, typename... Args> + iterator try_emplace(const_iterator, const key_arg<K>& key, Args&&... args) { + return LazyEmplaceInternal(key, std::forward<Args>(args)...).first; + } + + void swap(linked_hash_map& other) noexcept { + using std::swap; + swap(set_, other.set_); + swap(list_, other.list_); + } + + friend bool operator==(const linked_hash_map& a, const linked_hash_map& b) { + if (a.size() != b.size()) return false; + const linked_hash_map* outer = &a; + const linked_hash_map* inner = &b; + if (outer->capacity() > inner->capacity()) std::swap(outer, inner); + for (const value_type& elem : *outer) { + auto it = inner->find(elem.first); + if (it == inner->end()) return false; + if (it->second != elem.second) return false; + } + + return true; + } + + friend bool operator!=(const linked_hash_map& a, const linked_hash_map& b) { + return !(a == b); + } + + void rehash(size_t n) { set_.rehash(n); } + + private: + template <typename Other> + void CopyFrom(Other&& other) { + for (auto& elem : other.list_) { + set_.insert(list_.insert(list_.end(), std::move(elem))); + } + assert(set_.size() == list_.size()); + } + + template <typename U> + std::pair<iterator, bool> InsertInternal(U&& pair) { // NOLINT(build/c++11) + bool constructed = false; + auto set_iter = set_.lazy_emplace(pair.first, [&](const auto& ctor) { + constructed = true; + ctor(list_.emplace(list_.end(), std::forward<U>(pair))); + }); + return {*set_iter, constructed}; + } + + template <class K, class V> + std::pair<iterator, bool> InsertOrAssignInternal(K&& k, V&& v) { + auto [it, inserted] = + LazyEmplaceInternal(std::forward<K>(k), std::forward<V>(v)); + if (!inserted) { + // NOLINTNEXTLINE(bugprone-use-after-move) + it->second = std::forward<V>(v); + } + return {it, inserted}; + } + + template <typename K, typename... Args> + std::pair<iterator, bool> LazyEmplaceInternal(K&& key, Args&&... args) { + bool constructed = false; + auto set_iter = set_.lazy_emplace( + key, [this, &constructed, &key, &args...](const auto& ctor) { + auto list_iter = + list_.emplace(list_.end(), std::piecewise_construct, + std::forward_as_tuple(std::forward<K>(key)), + std::forward_as_tuple(std::forward<Args>(args)...)); + constructed = true; + ctor(list_iter); + }); + return {*set_iter, constructed}; + } + + // The set component, used for speedy lookups. + SetType set_; + + // The list component, used for maintaining insertion order. + ListType list_; +}; + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CONTAINER_LINKED_HASH_MAP_H_
diff --git a/absl/container/linked_hash_map_benchmark.cc b/absl/container/linked_hash_map_benchmark.cc new file mode 100644 index 0000000..5a0db38 --- /dev/null +++ b/absl/container/linked_hash_map_benchmark.cc
@@ -0,0 +1,140 @@ +// Copyright 2025 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <algorithm> +#include <cstddef> +#include <string> +#include <vector> + +#include "absl/container/linked_hash_map.h" +#include "absl/functional/function_ref.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "benchmark/benchmark.h" + +namespace { + +struct BenchmarkData { + std::vector<std::string> sample; + size_t str_bytes = 0; + explicit BenchmarkData(int size, + absl::FunctionRef<std::string(int)> factory) { + sample.reserve(size); + for (int i = 0; i < size; ++i) { + sample.push_back(factory(i)); + str_bytes += sample.back().size(); + } + } +}; + +void BenchmarkTryEmplaceStrings(benchmark::State& state, + absl::FunctionRef<std::string(int)> factory) { + BenchmarkData data(state.range(0), factory); + + // Make a batch around 1Mi bytes. + const size_t batch_size = + std::max(size_t{1}, size_t{1000000} / data.str_bytes); + std::vector<absl::linked_hash_map<std::string, int>> sets(batch_size); + + while (state.KeepRunningBatch(batch_size)) { + state.PauseTiming(); + for (auto& set : sets) set.clear(); + state.ResumeTiming(); + for (auto& set : sets) { + for (const auto& str : data.sample) { + benchmark::DoNotOptimize(set.try_emplace(str)); + } + } + } + + state.SetItemsProcessed(state.iterations() * state.range(0)); + state.SetBytesProcessed(state.iterations() * data.str_bytes); +} + +void BenchmarkInsertOrAssignStrings( + benchmark::State& state, absl::FunctionRef<std::string(int)> factory) { + BenchmarkData data(state.range(0), factory); + + // Make a batch around 1Mi bytes. + const size_t batch_size = std::max(size_t{1}, 1000000 / data.str_bytes); + std::vector<absl::linked_hash_map<std::string, int>> sets(batch_size); + + while (state.KeepRunningBatch(batch_size)) { + state.PauseTiming(); + for (auto& set : sets) set.clear(); + state.ResumeTiming(); + for (auto& set : sets) { + for (const auto& str : data.sample) { + benchmark::DoNotOptimize(set.insert_or_assign(str, 0)); + } + } + } + + state.SetItemsProcessed(state.iterations() * state.range(0)); + state.SetBytesProcessed(state.iterations() * data.str_bytes); +} + +constexpr absl::string_view kFormatShort = "%10d"; +constexpr absl::string_view kFormatLong = + "a longer string that exceeds the SSO %10d"; + +void BM_TryEmplaceShortStrings_Hit(benchmark::State& state) { + BenchmarkTryEmplaceStrings( + state, [](int i) { return absl::StrFormat(kFormatShort, i); }); +} +BENCHMARK(BM_TryEmplaceShortStrings_Hit)->Range(1, 1 << 16); + +void BM_TryEmplaceLongStrings_Hit(benchmark::State& state) { + BenchmarkTryEmplaceStrings( + state, [](int i) { return absl::StrFormat(kFormatLong, i); }); +} +BENCHMARK(BM_TryEmplaceLongStrings_Hit)->Range(1, 1 << 16); + +void BM_TryEmplaceShortStrings_Miss(benchmark::State& state) { + BenchmarkTryEmplaceStrings( + state, [](int i) { return absl::StrFormat(kFormatShort, i % 20); }); +} +BENCHMARK(BM_TryEmplaceShortStrings_Miss)->Range(1, 1 << 16); + +void BM_TryEmplaceLongStrings_Miss(benchmark::State& state) { + BenchmarkTryEmplaceStrings( + state, [](int i) { return absl::StrFormat(kFormatLong, i % 20); }); +} +BENCHMARK(BM_TryEmplaceLongStrings_Miss)->Range(1, 1 << 16); + +void BM_InsertOrAssignShortStrings_Hit(benchmark::State& state) { + BenchmarkInsertOrAssignStrings( + state, [](int i) { return absl::StrFormat(kFormatShort, i); }); +} +BENCHMARK(BM_InsertOrAssignShortStrings_Hit)->Range(1, 1 << 16); + +void BM_InsertOrAssignLongStrings_Hit(benchmark::State& state) { + BenchmarkInsertOrAssignStrings( + state, [](int i) { return absl::StrFormat(kFormatLong, i); }); +} +BENCHMARK(BM_InsertOrAssignLongStrings_Hit)->Range(1, 1 << 16); + +void BM_InsertOrAssignShortStrings_Miss(benchmark::State& state) { + BenchmarkInsertOrAssignStrings( + state, [](int i) { return absl::StrFormat(kFormatShort, i % 20); }); +} +BENCHMARK(BM_InsertOrAssignShortStrings_Miss)->Range(1, 1 << 16); + +void BM_InsertOrAssignLongStrings_Miss(benchmark::State& state) { + BenchmarkInsertOrAssignStrings( + state, [](int i) { return absl::StrFormat(kFormatLong, i % 20); }); +} +BENCHMARK(BM_InsertOrAssignLongStrings_Miss)->Range(1, 1 << 16); + +} // namespace
diff --git a/absl/container/linked_hash_map_test.cc b/absl/container/linked_hash_map_test.cc new file mode 100644 index 0000000..9f530d7 --- /dev/null +++ b/absl/container/linked_hash_map_test.cc
@@ -0,0 +1,987 @@ +// Copyright 2025 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/linked_hash_map.h" + +#include <algorithm> +#include <cstddef> +#include <memory> +#include <string> +#include <tuple> +#include <type_traits> +#include <utility> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/base/internal/exception_testing.h" +#include "absl/container/internal/hash_generator_testing.h" +#include "absl/container/internal/hash_policy_testing.h" +#include "absl/container/internal/heterogeneous_lookup_testing.h" +#include "absl/container/internal/test_instance_tracker.h" +#include "absl/container/internal/unordered_map_constructor_test.h" +#include "absl/container/internal/unordered_map_lookup_test.h" +#include "absl/container/internal/unordered_map_members_test.h" +#include "absl/container/internal/unordered_map_modifiers_test.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace container_internal { +namespace { + +using ::testing::ElementsAre; +using ::testing::Pair; +using ::testing::Pointee; + +template <class K, class V> +using Map = linked_hash_map<K, V, StatefulTestingHash, StatefulTestingEqual, + Alloc<std::pair<const K, V>>>; + +static_assert(!std::is_standard_layout<NonStandardLayout>(), ""); + +using MapTypes = + ::testing::Types<Map<int, int>, Map<std::string, int>, + Map<Enum, std::string>, Map<EnumClass, int>, + Map<int, NonStandardLayout>, Map<NonStandardLayout, int>>; + +INSTANTIATE_TYPED_TEST_SUITE_P(LinkedHashMap, ConstructorTest, MapTypes); +INSTANTIATE_TYPED_TEST_SUITE_P(LinkedHashMap, LookupTest, MapTypes); +INSTANTIATE_TYPED_TEST_SUITE_P(LinkedHashMap, MembersTest, MapTypes); +INSTANTIATE_TYPED_TEST_SUITE_P(LinkedHashMap, ModifiersTest, MapTypes); + +// Tests that the range constructor works. +TEST(LinkedHashMapTest, RangeConstruct) { + const std::pair<int, int> items[] = {{1, 2}, {2, 3}, {3, 4}}; + EXPECT_THAT((linked_hash_map<int, int>(std::begin(items), std::end(items))), + ElementsAre(Pair(1, 2), Pair(2, 3), Pair(3, 4))); +} + +// Tests that copying works. +TEST(LinkedHashMapTest, Copy) { + linked_hash_map<int, int> m{{2, 12}, {3, 13}}; + auto copy = m; + + EXPECT_TRUE(copy.contains(2)); + auto found = copy.find(2); + ASSERT_TRUE(found != copy.end()); + for (auto iter = copy.begin(); iter != copy.end(); ++iter) { + if (iter == found) return; + } + FAIL() << "Copied map's find method returned an invalid iterator."; +} + +// Tests that assignment works. +TEST(LinkedHashMapTest, Assign) { + linked_hash_map<int, int> m{{2, 12}, {3, 13}}; + linked_hash_map<int, int> n{{4, 14}}; + + n = m; + EXPECT_TRUE(n.contains(2)); + auto found = n.find(2); + ASSERT_TRUE(found != n.end()); + for (auto iter = n.begin(); iter != n.end(); ++iter) { + if (iter == found) return; + } + FAIL() << "Assigned map's find method returned an invalid iterator."; +} + +// Tests that self-assignment works. +TEST(LinkedHashMapTest, SelfAssign) { + linked_hash_map<int, int> a{{1, 1}, {2, 2}, {3, 3}}; + auto& a_ref = a; + a = a_ref; + EXPECT_THAT(a, ElementsAre(Pair(1, 1), Pair(2, 2), Pair(3, 3))); +} + +// Tests that move constructor works. +TEST(LinkedHashMapTest, Move) { + // Use unique_ptr as an example of a non-copyable type. + linked_hash_map<int, std::unique_ptr<int>> m; + m[2] = std::make_unique<int>(12); + m[3] = std::make_unique<int>(13); + linked_hash_map<int, std::unique_ptr<int>> n = std::move(m); + EXPECT_THAT(n, ElementsAre(Pair(2, Pointee(12)), Pair(3, Pointee(13)))); +} + +// Tests that self-moving works. +TEST(LinkedHashMapTest, SelfMove) { + linked_hash_map<int, int> a{{1, 1}, {2, 2}, {3, 3}}; + auto& a_ref = a; + a = std::move(a_ref); + EXPECT_THAT(a, ElementsAre(Pair(1, 1), Pair(2, 2), Pair(3, 3))); +} + +TEST(LinkedHashMapTest, CanInsertMoveOnly) { + linked_hash_map<int, std::unique_ptr<int>> m; + struct Data { + int k, v; + }; + const Data data[] = {{1, 123}, {3, 345}, {2, 234}, {4, 456}}; + for (const auto& kv : data) + m.insert({kv.k, std::make_unique<int>(int{kv.v})}); + EXPECT_TRUE(m.contains(2)); + auto found = m.find(2); + ASSERT_TRUE(found != m.end()); + EXPECT_EQ(234, *found->second); +} + +TEST(LinkedHashMapTest, CanEmplaceMoveOnly) { + linked_hash_map<int, std::unique_ptr<int>> m; + struct Data { + int k, v; + }; + const Data data[] = {{1, 123}, {3, 345}, {2, 234}, {4, 456}}; + for (const auto& kv : data) { + m.emplace(std::piecewise_construct, std::make_tuple(kv.k), + std::make_tuple(new int{kv.v})); + } + EXPECT_TRUE(m.contains(2)); + auto found = m.find(2); + ASSERT_TRUE(found != m.end()); + EXPECT_EQ(234, *found->second); +} + +struct NoCopy { + explicit NoCopy(int x) : x(x) {} + NoCopy(const NoCopy&) = delete; + NoCopy& operator=(const NoCopy&) = delete; + NoCopy(NoCopy&&) = delete; + NoCopy& operator=(NoCopy&&) = delete; + int x; +}; + +TEST(LinkedHashMapTest, CanEmplaceNoMoveNoCopy) { + linked_hash_map<int, NoCopy> m; + struct Data { + int k, v; + }; + const Data data[] = {{1, 123}, {3, 345}, {2, 234}, {4, 456}}; + for (const auto& kv : data) { + m.emplace(std::piecewise_construct, std::make_tuple(kv.k), + std::make_tuple(kv.v)); + } + EXPECT_TRUE(m.contains(2)); + auto found = m.find(2); + ASSERT_TRUE(found != m.end()); + EXPECT_EQ(234, found->second.x); +} + +TEST(LinkedHashMapTest, ConstKeys) { + linked_hash_map<int, int> m; + m.insert(std::make_pair(1, 2)); + // Test that keys are const in iteration. + std::pair<const int, int>& p = *m.begin(); + EXPECT_EQ(1, p.first); +} + +// Tests that iteration from begin() to end() works +TEST(LinkedHashMapTest, Iteration) { + linked_hash_map<int, int> m; + EXPECT_TRUE(m.begin() == m.end()); + + m.insert(std::make_pair(2, 12)); + m.insert(std::make_pair(1, 11)); + m.insert(std::make_pair(3, 13)); + + linked_hash_map<int, int>::iterator i = m.begin(); + ASSERT_TRUE(m.begin() == i); + ASSERT_TRUE(m.end() != i); + EXPECT_EQ(2, i->first); + EXPECT_EQ(12, i->second); + + ++i; + ASSERT_TRUE(m.end() != i); + EXPECT_EQ(1, i->first); + EXPECT_EQ(11, i->second); + + ++i; + ASSERT_TRUE(m.end() != i); + EXPECT_EQ(3, i->first); + EXPECT_EQ(13, i->second); + + ++i; // Should be the end of the line. + ASSERT_TRUE(m.end() == i); +} + +// Tests that reverse iteration from rbegin() to rend() works +TEST(LinkedHashMapTest, ReverseIteration) { + linked_hash_map<int, int> m; + EXPECT_TRUE(m.rbegin() == m.rend()); + + m.insert(std::make_pair(2, 12)); + m.insert(std::make_pair(1, 11)); + m.insert(std::make_pair(3, 13)); + + linked_hash_map<int, int>::reverse_iterator i = m.rbegin(); + ASSERT_TRUE(m.rbegin() == i); + ASSERT_TRUE(m.rend() != i); + EXPECT_EQ(3, i->first); + EXPECT_EQ(13, i->second); + + ++i; + ASSERT_TRUE(m.rend() != i); + EXPECT_EQ(1, i->first); + EXPECT_EQ(11, i->second); + + ++i; + ASSERT_TRUE(m.rend() != i); + EXPECT_EQ(2, i->first); + EXPECT_EQ(12, i->second); + + ++i; // Should be the end of the line. + ASSERT_TRUE(m.rend() == i); +} + +// Tests that clear() works +TEST(LinkedHashMapTest, Clear) { + linked_hash_map<int, int> m; + m.insert(std::make_pair(2, 12)); + m.insert(std::make_pair(1, 11)); + m.insert(std::make_pair(3, 13)); + + ASSERT_EQ(3, m.size()); + + m.clear(); + + EXPECT_EQ(0, m.size()); + + m.clear(); // Make sure we can call it on an empty map. + + EXPECT_EQ(0, m.size()); +} + +// Tests that size() works. +TEST(LinkedHashMapTest, Size) { + linked_hash_map<int, int> m; + EXPECT_EQ(0, m.size()); + m.insert(std::make_pair(2, 12)); + EXPECT_EQ(1, m.size()); + m.insert(std::make_pair(1, 11)); + EXPECT_EQ(2, m.size()); + m.insert(std::make_pair(3, 13)); + EXPECT_EQ(3, m.size()); + m.clear(); + EXPECT_EQ(0, m.size()); +} + +// Tests empty() +TEST(LinkedHashMapTest, Empty) { + linked_hash_map<int, int> m; + ASSERT_TRUE(m.empty()); + m.insert(std::make_pair(2, 12)); + ASSERT_FALSE(m.empty()); + m.clear(); + ASSERT_TRUE(m.empty()); +} + +TEST(LinkedHashMapTest, Erase) { + linked_hash_map<int, int> m; + ASSERT_EQ(0, m.size()); + EXPECT_EQ(0, m.erase(2)); // Nothing to erase yet + + m.insert(std::make_pair(2, 12)); + ASSERT_EQ(1, m.size()); + EXPECT_EQ(1, m.erase(2)); + EXPECT_EQ(0, m.size()); + + EXPECT_EQ(0, m.erase(2)); // Make sure nothing bad happens if we repeat. + EXPECT_EQ(0, m.size()); +} + +TEST(LinkedHashMapTest, Erase2) { + linked_hash_map<int, int> m; + ASSERT_EQ(0, m.size()); + EXPECT_EQ(0, m.erase(2)); // Nothing to erase yet + + m.insert(std::make_pair(2, 12)); + m.insert(std::make_pair(1, 11)); + m.insert(std::make_pair(3, 13)); + m.insert(std::make_pair(4, 14)); + ASSERT_EQ(4, m.size()); + + // Erase middle two + EXPECT_EQ(1, m.erase(1)); + EXPECT_EQ(1, m.erase(3)); + + EXPECT_EQ(2, m.size()); + + // Make sure we can still iterate over everything that's left. + linked_hash_map<int, int>::iterator it = m.begin(); + ASSERT_TRUE(it != m.end()); + EXPECT_EQ(12, it->second); + ++it; + ASSERT_TRUE(it != m.end()); + EXPECT_EQ(14, it->second); + ++it; + ASSERT_TRUE(it == m.end()); + + EXPECT_EQ(0, m.erase(1)); // Make sure nothing bad happens if we repeat. + ASSERT_EQ(2, m.size()); + + EXPECT_EQ(1, m.erase(2)); + EXPECT_EQ(1, m.erase(4)); + ASSERT_EQ(0, m.size()); + + EXPECT_EQ(0, m.erase(1)); // Make sure nothing bad happens if we repeat. + ASSERT_EQ(0, m.size()); +} + +// Test that erase(iter,iter) and erase(iter) compile and work. +TEST(LinkedHashMapTest, Erase3) { + linked_hash_map<int, int> m; + + m.insert(std::make_pair(1, 11)); + m.insert(std::make_pair(2, 12)); + m.insert(std::make_pair(3, 13)); + m.insert(std::make_pair(4, 14)); + + // Erase middle two + linked_hash_map<int, int>::iterator it2 = m.find(2); + linked_hash_map<int, int>::iterator it4 = m.find(4); + EXPECT_EQ(m.erase(it2, it4), m.find(4)); + EXPECT_EQ(2, m.size()); + + // Make sure we can still iterate over everything that's left. + linked_hash_map<int, int>::iterator it = m.begin(); + ASSERT_TRUE(it != m.end()); + EXPECT_EQ(11, it->second); + ++it; + ASSERT_TRUE(it != m.end()); + EXPECT_EQ(14, it->second); + ++it; + ASSERT_TRUE(it == m.end()); + + // Erase first one using an iterator. + EXPECT_EQ(m.erase(m.begin()), m.find(4)); + + // Only the last element should be left. + it = m.begin(); + ASSERT_TRUE(it != m.end()); + EXPECT_EQ(14, it->second); + ++it; + ASSERT_TRUE(it == m.end()); +} + +// Test all types of insertion +TEST(LinkedHashMapTest, Insertion) { + linked_hash_map<int, int> m; + ASSERT_EQ(0, m.size()); + std::pair<linked_hash_map<int, int>::iterator, bool> result; + + result = m.insert(std::make_pair(2, 12)); + ASSERT_EQ(1, m.size()); + EXPECT_TRUE(result.second); + EXPECT_EQ(2, result.first->first); + EXPECT_EQ(12, result.first->second); + + result = m.insert(std::make_pair(1, 11)); + ASSERT_EQ(2, m.size()); + EXPECT_TRUE(result.second); + EXPECT_EQ(1, result.first->first); + EXPECT_EQ(11, result.first->second); + + result = m.insert(std::make_pair(3, 13)); + linked_hash_map<int, int>::iterator result_iterator = result.first; + ASSERT_EQ(3, m.size()); + EXPECT_TRUE(result.second); + EXPECT_EQ(3, result.first->first); + EXPECT_EQ(13, result.first->second); + + result = m.insert(std::make_pair(3, 13)); + EXPECT_EQ(3, m.size()); + EXPECT_FALSE(result.second) << "No insertion should have occurred."; + EXPECT_TRUE(result_iterator == result.first) + << "Duplicate insertion should have given us the original iterator."; + + std::vector<std::pair<int, int>> v = {{3, 13}, {4, 14}, {5, 15}}; + m.insert(v.begin(), v.end()); + // Expect 4 and 5 inserted, 3 not inserted. + EXPECT_EQ(5, m.size()); + EXPECT_EQ(14, m.at(4)); + EXPECT_EQ(15, m.at(5)); +} + +static std::pair<const int, int> Pair(int i, int j) { return {i, j}; } + +// Test front accessors. +TEST(LinkedHashMapTest, Front) { + linked_hash_map<int, int> m; + + m.insert(std::make_pair(2, 12)); + m.insert(std::make_pair(1, 11)); + m.insert(std::make_pair(3, 13)); + + EXPECT_EQ(3, m.size()); + EXPECT_EQ(Pair(2, 12), m.front()); + m.pop_front(); + EXPECT_EQ(2, m.size()); + EXPECT_EQ(Pair(1, 11), m.front()); + m.pop_front(); + EXPECT_EQ(1, m.size()); + EXPECT_EQ(Pair(3, 13), m.front()); + m.pop_front(); + EXPECT_TRUE(m.empty()); +} + +// Test back accessors. +TEST(LinkedHashMapTest, Back) { + linked_hash_map<int, int> m; + + m.insert(std::make_pair(2, 12)); + m.insert(std::make_pair(1, 11)); + m.insert(std::make_pair(3, 13)); + + EXPECT_EQ(3, m.size()); + EXPECT_EQ(Pair(3, 13), m.back()); + m.pop_back(); + EXPECT_EQ(2, m.size()); + EXPECT_EQ(Pair(1, 11), m.back()); + m.pop_back(); + EXPECT_EQ(1, m.size()); + EXPECT_EQ(Pair(2, 12), m.back()); + m.pop_back(); + EXPECT_TRUE(m.empty()); +} + +TEST(LinkedHashMapTest, Find) { + linked_hash_map<int, int> m; + + EXPECT_TRUE(m.end() == m.find(1)) + << "We shouldn't find anything in an empty map."; + + m.insert(std::make_pair(2, 12)); + EXPECT_TRUE(m.end() == m.find(1)) + << "We shouldn't find an element that doesn't exist in the map."; + + std::pair<linked_hash_map<int, int>::iterator, bool> result = + m.insert(std::make_pair(1, 11)); + ASSERT_TRUE(result.second); + ASSERT_TRUE(m.end() != result.first); + EXPECT_TRUE(result.first == m.find(1)) + << "We should have found an element we know exists in the map."; + EXPECT_EQ(11, result.first->second); + + // Check that a follow-up insertion doesn't affect our original + m.insert(std::make_pair(3, 13)); + linked_hash_map<int, int>::iterator it = m.find(1); + ASSERT_TRUE(m.end() != it); + EXPECT_EQ(11, it->second); + + m.clear(); + EXPECT_TRUE(m.end() == m.find(1)) + << "We shouldn't find anything in a map that we've cleared."; +} + +TEST(LinkedHashMapTest, Contains) { + linked_hash_map<int, int> m; + + EXPECT_FALSE(m.contains(1)) << "An empty map shouldn't contain anything."; + + m.insert(std::make_pair(2, 12)); + EXPECT_FALSE(m.contains(1)) + << "The map shouldn't contain an element that doesn't exist."; + + m.insert(std::make_pair(1, 11)); + EXPECT_TRUE(m.contains(1)) + << "The map should contain an element that we know exists."; + + m.clear(); + EXPECT_FALSE(m.contains(1)) + << "A map that we've cleared shouldn't contain anything."; +} + +TEST(LinkedHashMapTest, At) { + linked_hash_map<int, int> m = {{1, 2}}; + EXPECT_EQ(2, m.at(1)); + ABSL_BASE_INTERNAL_EXPECT_FAIL(m.at(3), std::out_of_range, + "linked_hash_map::at"); + + const linked_hash_map<int, int> cm = {{1, 2}}; + EXPECT_EQ(2, cm.at(1)); + ABSL_BASE_INTERNAL_EXPECT_FAIL(cm.at(3), std::out_of_range, + "linked_hash_map::at"); +} + +TEST(LinkedHashMapTest, Swap) { + linked_hash_map<int, int> m1; + linked_hash_map<int, int> m2; + m1.insert(std::make_pair(1, 1)); + m1.insert(std::make_pair(2, 2)); + m2.insert(std::make_pair(3, 3)); + ASSERT_EQ(2, m1.size()); + ASSERT_EQ(1, m2.size()); + m1.swap(m2); + ASSERT_EQ(1, m1.size()); + ASSERT_EQ(2, m2.size()); +} + +TEST(LinkedHashMapTest, SelfSwap) { + linked_hash_map<int, int> a{{1, 1}, {2, 2}, {3, 3}}; + using std::swap; + swap(a, a); + EXPECT_THAT(a, ElementsAre(Pair(1, 1), Pair(2, 2), Pair(3, 3))); +} + +TEST(LinkedHashMapTest, InitializerList) { + linked_hash_map<int, int> m{{1, 2}, {3, 4}}; + ASSERT_EQ(2, m.size()); + EXPECT_TRUE(m.contains(1)); + linked_hash_map<int, int>::iterator it = m.find(1); + ASSERT_TRUE(m.end() != it); + EXPECT_EQ(2, it->second); + EXPECT_TRUE(m.contains(3)); + it = m.find(3); + ASSERT_TRUE(m.end() != it); + EXPECT_EQ(4, it->second); +} + +TEST(LinkedHashMapTest, CustomHashAndEquality) { + struct CustomIntHash { + size_t operator()(int x) const { return x; } + }; + struct CustomIntEq { + bool operator()(int x, int y) const { return x == y; } + }; + linked_hash_map<int, int, CustomIntHash, CustomIntEq> m; + m.insert(std::make_pair(1, 1)); + EXPECT_TRUE(m.contains(1)); + EXPECT_EQ(1, m[1]); +} + +TEST(LinkedHashMapTest, EqualRange) { + linked_hash_map<int, int> m{{3, 11}, {1, 13}}; + const auto& const_m = m; + + EXPECT_THAT(m.equal_range(2), testing::Pair(m.end(), m.end())); + EXPECT_THAT(const_m.equal_range(2), + testing::Pair(const_m.end(), const_m.end())); + + EXPECT_THAT(m.equal_range(1), testing::Pair(m.find(1), ++m.find(1))); + EXPECT_THAT(const_m.equal_range(1), + testing::Pair(const_m.find(1), ++const_m.find(1))); +} + +TEST(LinkedHashMapTest, ReserveWorks) { + linked_hash_map<int, int> m; + EXPECT_EQ(0, m.size()); + EXPECT_EQ(0.0, m.load_factor()); + m.reserve(10); + EXPECT_LE(10, m.capacity()); + EXPECT_EQ(0, m.size()); + EXPECT_EQ(0.0, m.load_factor()); + m.emplace(1, 1); + m.emplace(2, 2); + EXPECT_LE(10, m.capacity()); + EXPECT_EQ(2, m.size()); + EXPECT_LT(0.0, m.load_factor()); +} + +// We require key types to have hash and equals. +class CopyableMovableType + : public absl::test_internal::CopyableMovableInstance { + public: + using CopyableMovableInstance::CopyableMovableInstance; + + bool operator==(const CopyableMovableType& o) const { + return value() == o.value(); + } + + template <typename H> + friend H AbslHashValue(H h, const CopyableMovableType& c) { + return H::combine(std::move(h), c.value()); + } +}; + +TEST(LinkedHashMapTest, RValueOperatorAt) { + absl::test_internal::InstanceTracker tracker; + + linked_hash_map<CopyableMovableType, int> map; + map[CopyableMovableType(1)] = 1; + EXPECT_EQ(tracker.copies(), 0); + CopyableMovableType c(2); + map[std::move(c)] = 2; + EXPECT_EQ(tracker.copies(), 0); + EXPECT_THAT(map, ElementsAre(Pair(CopyableMovableType(1), 1), + Pair(CopyableMovableType(2), 2))); +} + +TEST(LinkedHashMapTest, HeterogeneousTests) { + absl::test_internal::InstanceTracker tracker; + + using MapType = linked_hash_map<ExpensiveType, int, HeterogeneousHash, + HeterogeneousEqual>; + MapType map; + ExpensiveType one(1); + map[one] = 1; + // Two instances: 'one' var and an instance in the map. + EXPECT_EQ(2, tracker.instances()); + EXPECT_EQ(1, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + + tracker.ResetCopiesMovesSwaps(); + map[one] = 5; + // No construction since key==1 exists. + EXPECT_EQ(2, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + + tracker.ResetCopiesMovesSwaps(); + map[CheapType(1)] = 10; + // No construction since key==1 exists. + EXPECT_EQ(2, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + + tracker.ResetCopiesMovesSwaps(); + map[CheapType(2)] = 20; + // Construction since key==2 doesn't exist in the map. + EXPECT_EQ(3, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + EXPECT_THAT(map, ElementsAre(Pair(HasExpensiveValue(1), 10), + Pair(HasExpensiveValue(2), 20))); + + // find + auto itr = map.find(CheapType(1)); + tracker.ResetCopiesMovesSwaps(); + ASSERT_NE(itr, map.end()); + EXPECT_EQ(10, itr->second); + // contains + EXPECT_TRUE(map.contains(CheapType(2))); + // count + EXPECT_EQ(1, map.count(CheapType(2))); + // equal_range + auto eq_itr_pair = map.equal_range(CheapType(2)); + ASSERT_NE(eq_itr_pair.first, map.end()); + EXPECT_EQ(20, eq_itr_pair.first->second); + // No construction for find, contains, count or equal_range. + EXPECT_EQ(3, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + + // insert + auto three = MapType::value_type(CheapType(3), 30); + tracker.ResetCopiesMovesSwaps(); + map.insert(three); + // Two instances: 'three' var and an instance in the map. + EXPECT_EQ(5, tracker.instances()); + EXPECT_EQ(1, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + + tracker.ResetCopiesMovesSwaps(); + map.insert(three); + // No additional construction since key==3 exists. + EXPECT_EQ(5, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + EXPECT_THAT(map, ElementsAre(Pair(HasExpensiveValue(1), 10), + Pair(HasExpensiveValue(2), 20), + Pair(HasExpensiveValue(3), 30))); + + // Test std::move() using operator[] and insert(). + ExpensiveType four(4); + tracker.ResetCopiesMovesSwaps(); + map[std::move(four)] = 40; + // Two constructions (regular and move). + EXPECT_EQ(7, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(1, tracker.moves()); + + auto five = MapType::value_type(CheapType(5), 50); + tracker.ResetCopiesMovesSwaps(); + map.insert(std::move(five)); + // Two constructions (regular and move). + EXPECT_EQ(9, tracker.instances()); + EXPECT_EQ(1, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + EXPECT_THAT(map, ElementsAre(Pair(HasExpensiveValue(1), 10), + Pair(HasExpensiveValue(2), 20), + Pair(HasExpensiveValue(3), 30), + Pair(HasExpensiveValue(4), 40), + Pair(HasExpensiveValue(5), 50))); + + // Insert using std::pair. + tracker.ResetCopiesMovesSwaps(); + map.insert(std::make_pair(ExpensiveType(6), 60)); + EXPECT_EQ(10, tracker.instances()); + EXPECT_EQ(1, tracker.copies()); + EXPECT_EQ(2, tracker.moves()); + + // Heterogeneous erase. + map.erase(CheapType(1)); + // No construction and instance reduced by one. + tracker.ResetCopiesMovesSwaps(); + EXPECT_EQ(9, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + EXPECT_THAT(map, ElementsAre(Pair(HasExpensiveValue(2), 20), + Pair(HasExpensiveValue(3), 30), + Pair(HasExpensiveValue(4), 40), + Pair(HasExpensiveValue(5), 50), + Pair(HasExpensiveValue(6), 60))); +} + +TEST(LinkedHashMapTest, HeterogeneousStringViewLookup) { + linked_hash_map<std::string, int> map; + map["foo"] = 1; + map["bar"] = 2; + map["blah"] = 3; + + { + absl::string_view lookup("foo"); + auto itr = map.find(lookup); + ASSERT_NE(itr, map.end()); + EXPECT_EQ(1, itr->second); + } + + // Not found. + { + absl::string_view lookup("foobar"); + EXPECT_EQ(map.end(), map.find(lookup)); + } + + { + absl::string_view lookup("blah"); + auto itr = map.find(lookup); + ASSERT_NE(itr, map.end()); + EXPECT_EQ(3, itr->second); + } +} + +TEST(LinkedHashMapTest, UniversalReferenceWorks) { + linked_hash_map<std::string, int> map; + std::string s = "very very very very very long string"; + map[s] = 1; + EXPECT_EQ(s, "very very very very very long string"); +} + +TEST(LinkedHashMap, ExtractInsert) { + linked_hash_map<int, int> m = {{1, 7}, {2, 9}}; + auto node = m.extract(1); + EXPECT_TRUE(node); + EXPECT_EQ(node.key(), 1); + EXPECT_EQ(node.mapped(), 7); + EXPECT_THAT(m, ElementsAre(Pair(2, 9))); + EXPECT_FALSE(m.contains(1)); + EXPECT_TRUE(m.contains(2)); + + node.mapped() = 17; + m.insert(std::move(node)); + EXPECT_FALSE(node); + EXPECT_THAT(m, ElementsAre(Pair(2, 9), Pair(1, 17))); + EXPECT_TRUE(m.contains(2)); + EXPECT_TRUE(m.contains(1)); + + node = m.extract(m.find(1)); + EXPECT_TRUE(node); + EXPECT_EQ(node.key(), 1); + EXPECT_EQ(node.mapped(), 17); + EXPECT_THAT(m, ElementsAre(Pair(2, 9))); +} + +TEST(LinkedHashMap, Merge) { + linked_hash_map<int, int> m = {{1, 7}, {3, 6}}; + linked_hash_map<int, int> src = {{1, 10}, {2, 9}, {4, 16}}; + + m.merge(src); + + EXPECT_THAT(m, ElementsAre(Pair(1, 7), Pair(3, 6), Pair(2, 9), Pair(4, 16))); + EXPECT_THAT(src, ElementsAre(Pair(1, 10))); + for (int i : {2, 9, 4}) { + EXPECT_FALSE(src.contains(i)); + } +} + +TEST(LinkedHashMap, Splice) { + linked_hash_map<int, int> m = {{1, 7}, {3, 6}}; + linked_hash_map<int, int> src = {{1, 10}, {2, 9}, {4, 16}}; + m.splice(m.end(), m, m.begin()); + + EXPECT_THAT(m, ElementsAre(Pair(3, 6), Pair(1, 7))); + + m.splice(m.end(), src, ++src.begin()); + EXPECT_THAT(m, ElementsAre(Pair(3, 6), Pair(1, 7), Pair(2, 9))); + EXPECT_THAT(src, ElementsAre(Pair(1, 10), Pair(4, 16))); +} + +TEST(LinkedHashMap, EraseRange) { + linked_hash_map<int, int> map = {{1, 1}, {2, 4}, {3, 9}, {4, 16}, {5, 25}, + {6, 36}, {7, 49}, {8, 64}, {9, 81}}; + auto start = map.find(3); + auto end = map.find(8); + auto itr = map.erase(start, end); + ASSERT_NE(itr, map.end()); + EXPECT_THAT(*itr, Pair(8, 64)); + EXPECT_THAT(map, + ElementsAre(Pair(1, 1), Pair(2, 4), Pair(8, 64), Pair(9, 81))); + for (int i : {1, 2, 8, 9}) { + EXPECT_TRUE(map.contains(i)); + } + for (int i : {3, 4, 5, 6, 7}) { + EXPECT_FALSE(map.contains(i)); + } +} + +TEST(LinkedHashMap, InsertInitializerList) { + linked_hash_map<int, int> m; + m.insert({{1, 7}, {2, 9}, {3, 29}}); + EXPECT_THAT(m, ElementsAre(Pair(1, 7), Pair(2, 9), Pair(3, 29))); +} + +TEST(LinkedHashMap, InsertOrAssign) { + linked_hash_map<int, int> m; + { + auto [itr, elem_inserted] = m.insert_or_assign(10, 20); + EXPECT_THAT(*itr, Pair(10, 20)); + EXPECT_EQ(elem_inserted, true); + } + { + auto [itr, elem_inserted] = m.insert_or_assign(10, 30); + EXPECT_THAT(*itr, Pair(10, 30)); + EXPECT_EQ(elem_inserted, false); + } +} + +TEST(LinkedHashMap, TryEmplace) { + linked_hash_map<int, std::pair<int, std::string>> m; + { + auto [itr, elem_inserted] = m.try_emplace(10, 20, "string"); + EXPECT_THAT(*itr, Pair(10, Pair(20, "string"))); + EXPECT_EQ(elem_inserted, true); + } + { + absl::test_internal::InstanceTracker tracker; + std::string s = "some string"; + auto [itr, elem_inserted] = m.try_emplace(10, 2, std::move(s)); + EXPECT_THAT(*itr, Pair(10, Pair(20, "string"))); + EXPECT_EQ(elem_inserted, false); + EXPECT_THAT(tracker.moves(), 0); + } +} + +TEST(LinkedHashMapTest, TryEmplace) { + linked_hash_map<int, std::tuple<int, std::string, std::unique_ptr<float>>> + map; + auto result = map.try_emplace(3, 2, "foo", new float(1.0)); + EXPECT_TRUE(result.second); + EXPECT_EQ(std::get<0>(result.first->second), 2); + EXPECT_EQ(std::get<1>(result.first->second), "foo"); + EXPECT_EQ(*std::get<2>(result.first->second), 1.0); + + // Ptr should not be moved. + auto ptr = std::make_unique<float>(3.0); + auto result2 = map.try_emplace(3, 22, "foo-bar", std::move(ptr)); + EXPECT_FALSE(result2.second); + EXPECT_EQ(std::get<0>(result.first->second), 2); + EXPECT_EQ(std::get<1>(result.first->second), "foo"); + EXPECT_EQ(*std::get<2>(result.first->second), 1.0); + EXPECT_NE(ptr.get(), nullptr); +} + +struct CountedHash { + explicit CountedHash(int* count) : count(count) {} + size_t operator()(int value) const { + ++(*count); + return value; + } + int* count = nullptr; +}; + +// Makes a map too big for small object optimization. Counts the number of +// hashes in `count`, but leaves `count` set to 0. +linked_hash_map<int, std::string, CountedHash> MakeNonSmallMap(int* count) { + const int kFirstKey = -1000; + linked_hash_map<int, std::string, CountedHash> m(0, CountedHash(count)); + for (int i = kFirstKey; i < kFirstKey + 100; ++i) { + m[i] = "foo"; + } + *count = 0; + return m; +} + +constexpr bool BuildHasDebugModeRehashes() { +#if !defined(NDEBUG) || defined(ABSL_HAVE_ADDRESS_SANITIZER) || \ + defined(ABSL_HAVE_MEMORY_SANITIZER) || defined(ABSL_HAVE_THREAD_SANITIZER) + return true; +#else + return false; +#endif +} + +TEST(LinkedHashMapTest, HashCountInOptBuilds) { + if (BuildHasDebugModeRehashes()) { + GTEST_SKIP() << "Only run under NDEBUG: `assert` statements and sanitizer " + "rehashing may cause redundant hashing."; + } + + using Map = linked_hash_map<int, std::string, CountedHash>; + { + int count = 0; + Map m = MakeNonSmallMap(&count); + m.insert({1, "foo"}); + EXPECT_EQ(count, 1); + m.erase(1); + EXPECT_EQ(count, 2); + } + { + int count = 0; + Map m = MakeNonSmallMap(&count); + m[2] = "bar"; + EXPECT_EQ(count, 1); + } + { + int count = 0; + Map m = MakeNonSmallMap(&count); + m.insert({3, "baz"}); + EXPECT_EQ(count, 1); + auto node = m.extract(3); + EXPECT_EQ(count, 2); + m.insert(std::move(node)); + EXPECT_EQ(count, 3); + } + { + int count = 0; + Map m = MakeNonSmallMap(&count); + m.insert_or_assign(4, "qux"); + EXPECT_EQ(count, 1); + } + { + int count = 0; + Map m = MakeNonSmallMap(&count); + m.emplace(5, "vog"); + EXPECT_EQ(count, 1); + } + { + int count = 0; + Map m = MakeNonSmallMap(&count); + m.try_emplace(6, 'x', 42); + EXPECT_EQ(count, 1); + } + { + int src_count = 0, dst_count = 0; + Map src = MakeNonSmallMap(&src_count); + Map dst = MakeNonSmallMap(&dst_count); + src[7] = "siete"; + dst.merge(src); + EXPECT_LE(src_count, 200); + EXPECT_LE(dst_count, 200); + } +} + +} // namespace +} // namespace container_internal +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/container/linked_hash_set.h b/absl/container/linked_hash_set.h new file mode 100644 index 0000000..1b5d578 --- /dev/null +++ b/absl/container/linked_hash_set.h
@@ -0,0 +1,527 @@ +// Copyright 2025 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: linked_hash_set.h +// ----------------------------------------------------------------------------- +// +// This is a simple insertion-ordered set. It provides O(1) amortized +// insertions and lookups, as well as iteration over the set in the insertion +// order. +// +// This class is thread-compatible. +// +// Iterators point into the list and should be stable in the face of +// mutations, except for an iterator pointing to an element that was just +// deleted. +// +// This class supports heterogeneous lookups. + +#ifndef ABSL_CONTAINER_LINKED_HASH_SET_H_ +#define ABSL_CONTAINER_LINKED_HASH_SET_H_ + +#include <cassert> +#include <cstddef> +#include <initializer_list> +#include <list> +#include <memory> +#include <type_traits> +#include <utility> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/container/flat_hash_set.h" +#include "absl/container/internal/common.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +template < + typename Key, typename KeyHash = typename absl::flat_hash_set<Key>::hasher, + typename KeyEq = typename absl::flat_hash_set<Key, KeyHash>::key_equal, + typename Alloc = std::allocator<Key>> +class linked_hash_set { + using KeyArgImpl = absl::container_internal::KeyArg< + absl::container_internal::IsTransparent<KeyEq>::value && + absl::container_internal::IsTransparent<KeyHash>::value>; + + public: + using key_type = Key; + using hasher = KeyHash; + using key_equal = KeyEq; + using value_type = key_type; + using allocator_type = Alloc; + using difference_type = ptrdiff_t; + + private: + template <class K> + using key_arg = typename KeyArgImpl::template type<K, key_type>; + + using ListType = std::list<key_type, Alloc>; + + template <class Fn> + class Wrapped { + template <typename K> + static const K& ToKey(const K& k) { + return k; + } + static const key_type& ToKey(typename ListType::const_iterator it) { + return *it; + } + static const key_type& ToKey(typename ListType::iterator it) { return *it; } + + Fn fn_; + + friend linked_hash_set; + + public: + using is_transparent = void; + + Wrapped() = default; + explicit Wrapped(Fn fn) : fn_(std::move(fn)) {} + + template <class... Args> + auto operator()(Args&&... args) const + -> decltype(this->fn_(ToKey(args)...)) { + return fn_(ToKey(args)...); + } + }; + using SetType = + absl::flat_hash_set<typename ListType::iterator, Wrapped<hasher>, + Wrapped<key_equal>, Alloc>; + + class NodeHandle { + public: + using allocator_type = linked_hash_set::allocator_type; + using value_type = linked_hash_set::value_type; + + constexpr NodeHandle() noexcept = default; + NodeHandle(NodeHandle&& nh) noexcept = default; + ~NodeHandle() = default; + NodeHandle& operator=(NodeHandle&& node) noexcept = default; + bool empty() const noexcept { return list_.empty(); } + explicit operator bool() const noexcept { return !empty(); } + allocator_type get_allocator() const { return list_.get_allocator(); } + value_type& value() { return list_.front(); } + void swap(NodeHandle& nh) noexcept { list_.swap(nh.list_); } + + private: + friend linked_hash_set; + + explicit NodeHandle(ListType list) : list_(std::move(list)) {} + ListType list_; + }; + + template <class Iterator, class NodeType> + struct InsertReturnType { + Iterator position; + bool inserted; + NodeType node; + }; + + public: + using iterator = typename ListType::const_iterator; + using const_iterator = typename ListType::const_iterator; + using reverse_iterator = typename ListType::const_reverse_iterator; + using const_reverse_iterator = typename ListType::const_reverse_iterator; + using reference = typename ListType::reference; + using const_reference = typename ListType::const_reference; + using pointer = typename std::allocator_traits<allocator_type>::pointer; + using const_pointer = + typename std::allocator_traits<allocator_type>::const_pointer; + using size_type = typename ListType::size_type; + using node_type = NodeHandle; + using insert_return_type = InsertReturnType<iterator, node_type>; + + linked_hash_set() {} + + explicit linked_hash_set(size_t bucket_count, const hasher& hash = hasher(), + const key_equal& eq = key_equal(), + const allocator_type& alloc = allocator_type()) + : set_(bucket_count, Wrapped<hasher>(hash), Wrapped<key_equal>(eq), + alloc), + list_(alloc) {} + + linked_hash_set(size_t bucket_count, const hasher& hash, + const allocator_type& alloc) + : linked_hash_set(bucket_count, hash, key_equal(), alloc) {} + + linked_hash_set(size_t bucket_count, const allocator_type& alloc) + : linked_hash_set(bucket_count, hasher(), key_equal(), alloc) {} + + explicit linked_hash_set(const allocator_type& alloc) + : linked_hash_set(0, hasher(), key_equal(), alloc) {} + + template <class InputIt> + linked_hash_set(InputIt first, InputIt last, size_t bucket_count = 0, + const hasher& hash = hasher(), + const key_equal& eq = key_equal(), + const allocator_type& alloc = allocator_type()) + : linked_hash_set(bucket_count, hash, eq, alloc) { + insert(first, last); + } + + template <class InputIter> + linked_hash_set(InputIter first, InputIter last, size_t bucket_count, + const hasher& hash, const allocator_type& alloc) + : linked_hash_set(first, last, bucket_count, hash, key_equal(), alloc) {} + + template <class InputIter> + linked_hash_set(InputIter first, InputIter last, size_t bucket_count, + const allocator_type& alloc) + : linked_hash_set(first, last, bucket_count, hasher(), key_equal(), + alloc) {} + + template <class InputIt> + linked_hash_set(InputIt first, InputIt last, const allocator_type& alloc) + : linked_hash_set(first, last, /*bucket_count=*/0, hasher(), key_equal(), + alloc) {} + + linked_hash_set(std::initializer_list<key_type> init, size_t bucket_count = 0, + const hasher& hash = hasher(), + const key_equal& eq = key_equal(), + const allocator_type& alloc = allocator_type()) + : linked_hash_set(init.begin(), init.end(), bucket_count, hash, eq, + alloc) {} + + linked_hash_set(std::initializer_list<key_type> init, size_t bucket_count, + const allocator_type& alloc) + : linked_hash_set(init, bucket_count, hasher(), key_equal(), alloc) {} + + linked_hash_set(std::initializer_list<key_type> init, size_t bucket_count, + const hasher& hash, const allocator_type& alloc) + : linked_hash_set(init, bucket_count, hash, key_equal(), alloc) {} + + linked_hash_set(std::initializer_list<key_type> init, + const allocator_type& alloc) + : linked_hash_set(init, /*bucket_count=*/0, hasher(), key_equal(), + alloc) {} + + linked_hash_set(const linked_hash_set& other) + : linked_hash_set(other.bucket_count(), other.hash_function(), + other.key_eq(), other.get_allocator()) { + CopyFrom(other); + } + + linked_hash_set(const linked_hash_set& other, const allocator_type& alloc) + : linked_hash_set(other.bucket_count(), other.hash_function(), + other.key_eq(), alloc) { + CopyFrom(other); + } + + linked_hash_set(linked_hash_set&& other) noexcept + : set_(std::move(other.set_)), list_(std::move(other.list_)) { + // Since the list and set must agree for other to end up "valid", + // explicitly clear them. + other.set_.clear(); + other.list_.clear(); + } + + linked_hash_set(linked_hash_set&& other, const allocator_type& alloc) + : linked_hash_set(0, other.hash_function(), other.key_eq(), alloc) { + if (get_allocator() == other.get_allocator()) { + *this = std::move(other); + } else { + CopyFrom(std::move(other)); + } + } + + linked_hash_set& operator=(const linked_hash_set& other) { + if (this != &other) { + // Make a new set, with other's hash/eq/alloc. + set_ = SetType(other.bucket_count(), other.set_.hash_function(), + other.set_.key_eq(), other.get_allocator()); + // Copy the list, with other's allocator. + list_ = ListType(other.get_allocator()); + CopyFrom(other); + } + return *this; + } + + linked_hash_set& operator=(linked_hash_set&& other) noexcept { + if (this != &other) { + set_ = std::move(other.set_); + list_ = std::move(other.list_); + other.set_.clear(); + other.list_.clear(); + } + return *this; + } + + linked_hash_set& operator=(std::initializer_list<key_type> values) { + clear(); + insert(values.begin(), values.end()); + return *this; + } + + // Derive size from set_, as list::size might be O(N). + size_type size() const { return set_.size(); } + size_type max_size() const noexcept { return ~size_type{}; } + bool empty() const { return set_.empty(); } + + // Iteration is list-like, in insertion order. + // These are all forwarded. + iterator begin() { return list_.begin(); } + iterator end() { return list_.end(); } + const_iterator begin() const { return list_.begin(); } + const_iterator end() const { return list_.end(); } + const_iterator cbegin() const { return list_.cbegin(); } + const_iterator cend() const { return list_.cend(); } + reverse_iterator rbegin() { return list_.rbegin(); } + reverse_iterator rend() { return list_.rend(); } + const_reverse_iterator rbegin() const { return list_.rbegin(); } + const_reverse_iterator rend() const { return list_.rend(); } + const_reverse_iterator crbegin() const { return list_.crbegin(); } + const_reverse_iterator crend() const { return list_.crend(); } + reference front() { return list_.front(); } + reference back() { return list_.back(); } + const_reference front() const { return list_.front(); } + const_reference back() const { return list_.back(); } + + void pop_front() { erase(begin()); } + void pop_back() { erase(std::prev(end())); } + + ABSL_ATTRIBUTE_REINITIALIZES void clear() { + set_.clear(); + list_.clear(); + } + + void reserve(size_t n) { set_.reserve(n); } + size_t bucket_count() const { return set_.bucket_count(); } + size_t capacity() const { return set_.capacity(); } + float load_factor() const { return set_.load_factor(); } + + hasher hash_function() const { return set_.hash_function().fn_; } + key_equal key_eq() const { return set_.key_eq().fn_; } + allocator_type get_allocator() const { return list_.get_allocator(); } + + template <typename K = key_type> + size_type erase(const key_arg<K>& key) { + auto found = set_.find(key); + if (found == set_.end()) return 0; + auto list_it = *found; + // Erase set entry first since it refers to the list element. + set_.erase(found); + list_.erase(list_it); + return 1; + } + + iterator erase(const_iterator position) { + auto found = set_.find(position); + assert(*found == position); + set_.erase(found); + return list_.erase(position); + } + + iterator erase(const_iterator first, const_iterator last) { + while (first != last) first = erase(first); + return first; + } + + template <typename K = key_type> + iterator find(const key_arg<K>& key) { + auto found = set_.find(key); + if (found == set_.end()) return end(); + return *found; + } + + template <typename K = key_type> + const_iterator find(const key_arg<K>& key) const { + auto found = set_.find(key); + if (found == set_.end()) return end(); + return *found; + } + + template <typename K = key_type> + size_t count(const key_arg<K>& key) const { + return contains(key) ? 1 : 0; + } + template <typename K = key_type> + bool contains(const key_arg<K>& key) const { + return set_.contains(key); + } + + template <typename K = key_type> + std::pair<iterator, iterator> equal_range(const key_arg<K>& key) { + auto iter = set_.find(key); + if (iter == set_.end()) return {end(), end()}; + return {*iter, std::next(*iter)}; + } + + template <typename K = key_type> + std::pair<const_iterator, const_iterator> equal_range( + const key_arg<K>& key) const { + auto iter = set_.find(key); + if (iter == set_.end()) return {end(), end()}; + return {*iter, std::next(*iter)}; + } + + template <typename K = key_type> + std::pair<iterator, bool> insert(const key_arg<K>& k) { + return InsertInternal(list_.end(), k); + } + template <typename K = key_type, K* = nullptr> + std::pair<iterator, bool> insert(key_arg<K>&& k) { + return InsertInternal(list_.end(), std::move(k)); + } + + template <typename K = key_type, + std::enable_if_t< + !std::is_convertible_v<const key_arg<K>&, const_iterator> && + !std::is_convertible_v<const key_arg<K>&, iterator>, + int> = 0> + iterator insert(const_iterator hint, const key_arg<K>& k) { + return InsertInternal(hint, k).first; + } + template < + typename K = key_type, K* = nullptr, + std::enable_if_t<!std::is_convertible_v<key_arg<K>&&, const_iterator> && + !std::is_convertible_v<key_arg<K>&&, iterator>, + int> = 0> + iterator insert(const_iterator hint, key_arg<K>&& k) { + return InsertInternal(hint, std::move(k)).first; + } + + void insert(std::initializer_list<key_type> ilist) { + insert(ilist.begin(), ilist.end()); + } + + template <class InputIt> + void insert(InputIt first, InputIt last) { + for (; first != last; ++first) insert(*first); + } + + insert_return_type insert(node_type&& node) { + if (node.empty()) return {end(), false, node_type()}; + if (auto [set_itr, inserted] = set_.emplace(node.list_.begin()); inserted) { + list_.splice(list_.end(), node.list_); + return {*set_itr, true, node_type()}; + } else { + return {*set_itr, false, std::move(node)}; + } + } + + iterator insert(const_iterator, node_type&& node) { + return insert(std::move(node)).first; + } + + template <typename... Args> + std::pair<iterator, bool> emplace(Args&&... args) { + return EmplaceInternal(list_.end(), std::forward<Args>(args)...); + } + + template <typename... Args> + iterator emplace_hint(const_iterator hint, Args&&... args) { + return EmplaceInternal(hint, std::forward<Args>(args)...).first; + } + + template <typename H, typename E> + void merge(linked_hash_set<Key, H, E, Alloc>& src) { + auto itr = src.list_.begin(); + while (itr != src.list_.end()) { + if (contains(*itr)) { + ++itr; + } else { + insert(src.extract(itr++)); + } + } + } + + template <typename H, typename E> + void merge(linked_hash_set<Key, H, E, Alloc>&& src) { + merge(src); + } + + node_type extract(const_iterator position) { + set_.erase(position); + ListType extracted_node_list; + extracted_node_list.splice(extracted_node_list.end(), list_, position); + return node_type(std::move(extracted_node_list)); + } + + template <class K = key_type, typename std::enable_if_t< + !std::is_same<K, iterator>::value, int> = 0> + node_type extract(const key_arg<K>& key) { + auto node = set_.extract(key); + if (node.empty()) return node_type(); + ListType extracted_node_list; + extracted_node_list.splice(extracted_node_list.end(), list_, node.value()); + return node_type(std::move(extracted_node_list)); + } + + void swap(linked_hash_set& other) noexcept { + using std::swap; + swap(set_, other.set_); + swap(list_, other.list_); + } + + friend bool operator==(const linked_hash_set& a, const linked_hash_set& b) { + if (a.size() != b.size()) return false; + const linked_hash_set* outer = &a; + const linked_hash_set* inner = &b; + if (outer->capacity() > inner->capacity()) std::swap(outer, inner); + for (const value_type& elem : *outer) + if (!inner->contains(elem)) return false; + return true; + } + + friend bool operator!=(const linked_hash_set& a, const linked_hash_set& b) { + return !(a == b); + } + + void rehash(size_t n) { set_.rehash(n); } + + private: + template <typename Other> + void CopyFrom(Other&& other) { + for (auto& elem : other.list_) { + set_.insert(list_.insert(list_.end(), std::move(elem))); + } + assert(set_.size() == list_.size()); + } + + template <typename... Args> + std::pair<iterator, bool> EmplaceInternal(const_iterator hint, + Args&&... args) { + ListType node_donor; + auto list_iter = + node_donor.emplace(node_donor.end(), std::forward<Args>(args)...); + auto ins = set_.insert(list_iter); + if (!ins.second) return {*ins.first, false}; + list_.splice(hint, node_donor, list_iter); + return {list_iter, true}; + } + + template <typename U> + std::pair<iterator, bool> InsertInternal(const_iterator hint, + U&& key) { // NOLINT(build/c++11) + bool constructed = false; + auto set_iter = set_.lazy_emplace(key, [&](const auto& ctor) { + constructed = true; + ctor(list_.emplace(hint, std::forward<U>(key))); + }); + return {*set_iter, constructed}; + } + + // The set component, used for speedy lookups. + SetType set_; + + // The list component, used for maintaining insertion order. + ListType list_; +}; + +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_CONTAINER_LINKED_HASH_SET_H_
diff --git a/absl/container/linked_hash_set_benchmark.cc b/absl/container/linked_hash_set_benchmark.cc new file mode 100644 index 0000000..e790e7d --- /dev/null +++ b/absl/container/linked_hash_set_benchmark.cc
@@ -0,0 +1,84 @@ +// Copyright 2025 The Abseil Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <algorithm> +#include <cstddef> +#include <string> +#include <vector> + +#include "absl/container/linked_hash_set.h" +#include "absl/functional/function_ref.h" +#include "absl/strings/str_format.h" +#include "absl/strings/string_view.h" +#include "benchmark/benchmark.h" + +namespace { + +void BenchmarkInsertStrings(benchmark::State& state, + absl::FunctionRef<std::string(int)> factory) { + std::vector<std::string> sample; + size_t str_bytes = 0; + for (int i = 0; i < state.range(0); ++i) { + sample.push_back(factory(i)); + str_bytes += sample.back().size(); + } + + // Make a batch around 1Mi bytes. + const size_t batch_size = std::max(size_t{1}, size_t{1000000} / str_bytes); + std::vector<absl::linked_hash_set<std::string>> sets(batch_size); + + while (state.KeepRunningBatch(batch_size)) { + state.PauseTiming(); + for (auto& set : sets) set.clear(); + state.ResumeTiming(); + for (auto& set : sets) { + for (const auto& str : sample) { + benchmark::DoNotOptimize(set.insert(str)); + } + } + } + + state.SetItemsProcessed(state.iterations() * state.range(0)); + state.SetBytesProcessed(state.iterations() * str_bytes); +} + +constexpr absl::string_view kFormatShort = "%10d"; +constexpr absl::string_view kFormatLong = + "a longer string that exceeds the SSO %10d"; + +void BM_InsertShortStrings_Hit(benchmark::State& state) { + BenchmarkInsertStrings( + state, [](int i) { return absl::StrFormat(kFormatShort, i); }); +} +BENCHMARK(BM_InsertShortStrings_Hit)->Range(1, 1 << 16); + +void BM_InsertLongStrings_Hit(benchmark::State& state) { + BenchmarkInsertStrings(state, + [](int i) { return absl::StrFormat(kFormatLong, i); }); +} +BENCHMARK(BM_InsertLongStrings_Hit)->Range(1, 1 << 16); + +void BM_InsertShortStrings_Miss(benchmark::State& state) { + BenchmarkInsertStrings( + state, [](int i) { return absl::StrFormat(kFormatShort, i % 20); }); +} +BENCHMARK(BM_InsertShortStrings_Miss)->Range(1, 1 << 16); + +void BM_InsertLongStrings_Miss(benchmark::State& state) { + BenchmarkInsertStrings( + state, [](int i) { return absl::StrFormat(kFormatLong, i % 20); }); +} +BENCHMARK(BM_InsertLongStrings_Miss)->Range(1, 1 << 16); + +} // namespace
diff --git a/absl/container/linked_hash_set_test.cc b/absl/container/linked_hash_set_test.cc new file mode 100644 index 0000000..9a3af62 --- /dev/null +++ b/absl/container/linked_hash_set_test.cc
@@ -0,0 +1,947 @@ +// Copyright 2025 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/linked_hash_set.h" + +#include <algorithm> +#include <cmath> +#include <cstddef> +#include <memory> +#include <string> +#include <utility> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/container/internal/hash_generator_testing.h" +#include "absl/container/internal/hash_policy_testing.h" +#include "absl/container/internal/heterogeneous_lookup_testing.h" +#include "absl/container/internal/test_instance_tracker.h" +#include "absl/container/internal/unordered_set_constructor_test.h" +#include "absl/container/internal/unordered_set_lookup_test.h" +#include "absl/container/internal/unordered_set_members_test.h" +#include "absl/container/internal/unordered_set_modifiers_test.h" +#include "absl/strings/string_view.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace container_internal { +namespace { + +using ::testing::ElementsAre; +using ::testing::ElementsAreArray; +using ::testing::Pointee; + +template <class T> +using Set = + linked_hash_set<T, StatefulTestingHash, StatefulTestingEqual, Alloc<T>>; + +using SetTypes = + ::testing::Types<Set<int>, Set<std::string>, Set<Enum>, Set<EnumClass>>; + +INSTANTIATE_TYPED_TEST_SUITE_P(LinkedHashSet, ConstructorTest, SetTypes); +INSTANTIATE_TYPED_TEST_SUITE_P(LinkedHashSet, LookupTest, SetTypes); +INSTANTIATE_TYPED_TEST_SUITE_P(LinkedHashSet, MembersTest, SetTypes); +INSTANTIATE_TYPED_TEST_SUITE_P(LinkedHashSet, ModifiersTest, SetTypes); + +// Tests that the range constructor works. +TEST(LinkedHashSetTest, RangeConstruct) { + const auto items = {1, 2, 3}; + EXPECT_THAT(linked_hash_set<int>(items.begin(), items.end()), + ElementsAre(1, 2, 3)); +} + +// Tests that copying works. +TEST(LinkedHashSetTest, Copy) { + linked_hash_set<int> m{4, 8, 15, 16, 23, 42}; + auto copy = m; + + auto found = copy.find(8); + ASSERT_TRUE(found != copy.end()); + for (auto iter = copy.begin(); iter != copy.end(); ++iter) { + if (iter == found) return; + } + FAIL() << "Copied set's find method returned an invalid iterator."; +} + +// Tests that assignment works. +TEST(LinkedHashSetTest, Assign) { + linked_hash_set<int> m{2, 3}; + linked_hash_set<int> n{4}; + + n = m; + EXPECT_TRUE(n.contains(2)); + auto found = n.find(2); + ASSERT_TRUE(found != n.end()); + for (auto iter = n.begin(); iter != n.end(); ++iter) { + if (iter == found) return; + } + FAIL() << "Assigned set's find method returned an invalid iterator."; +} + +// Tests that self-assignment works. +TEST(LinkedHashSetTest, SelfAssign) { + linked_hash_set<int> a{1, 2, 3}; + auto& a_ref = a; + a = a_ref; + + EXPECT_TRUE(a.contains(2)); + auto found = a.find(2); + ASSERT_TRUE(found != a.end()); + for (auto iter = a.begin(); iter != a.end(); ++iter) { + if (iter == found) return; + } + FAIL() << "Assigned set's find method returned an invalid iterator."; +} + +// Tests that move constructor works. +TEST(LinkedHashSetTest, Move) { + // Use unique_ptr as an example of a non-copyable type. + linked_hash_set<std::unique_ptr<int>> m; + m.insert(std::make_unique<int>(2)); + m.insert(std::make_unique<int>(3)); + linked_hash_set<std::unique_ptr<int>> n = std::move(m); + EXPECT_THAT(n, ElementsAre(Pointee(2), Pointee(3))); +} + +// Tests that self-moving works. +TEST(LinkedHashSetTest, SelfMove) { + linked_hash_set<int> a{1, 2, 3}; + auto& a_ref = a; + a = std::move(a_ref); + EXPECT_THAT(a, ElementsAre(1, 2, 3)); +} + +struct IntUniquePtrHash { + size_t operator()(const std::unique_ptr<int>& p) const { + return static_cast<size_t>(*p); + } +}; + +struct IntUniquePtrEq { + size_t operator()(const std::unique_ptr<int>& a, + const std::unique_ptr<int>& b) const { + return *a == *b; + } +}; + +// Pretty artificial for a set, but unique_ptr is a convenient move-only type. +TEST(LinkedHashSetTest, CanInsertMoveOnly) { + linked_hash_set<std::unique_ptr<int>, IntUniquePtrHash, IntUniquePtrEq> s; + std::vector<int> data = {4, 8, 15, 16, 23, 42}; + for (int x : data) s.insert(std::make_unique<int>(x)); + EXPECT_EQ(s.size(), data.size()); + for (const std::unique_ptr<int>& elt : s) { + EXPECT_TRUE(s.contains(elt)); + EXPECT_TRUE(s.find(elt) != s.end()); + } +} + +TEST(LinkedHashSetTest, CanMoveMoveOnly) { + linked_hash_set<std::unique_ptr<int>, IntUniquePtrHash, IntUniquePtrEq> s; + std::vector<int> data = {4, 8, 15, 16, 23, 42}; + for (int x : data) s.insert(std::make_unique<int>(x)); + linked_hash_set<std::unique_ptr<int>, IntUniquePtrHash, IntUniquePtrEq> ss = + std::move(s); + EXPECT_EQ(ss.size(), data.size()); +} + +TEST(LinkedHashSetTest, CanEmplaceMoveOnly) { + linked_hash_set<std::unique_ptr<int>, IntUniquePtrHash, IntUniquePtrEq> s; + std::vector<int> data = {4, 8, 15, 16, 23, 42}; + for (const int x : data) { + s.emplace(new int{x}); + } + EXPECT_EQ(s.size(), data.size()); + for (const std::unique_ptr<int>& elt : s) { + EXPECT_TRUE(s.contains(elt)); + EXPECT_TRUE(s.find(elt) != s.end()); + } +} + +TEST(LinkedHashSetTest, CanInsertTransparent) { + linked_hash_set<std::string> s; + s.insert(absl::string_view("foo")); + s.insert(absl::string_view("bar")); + s.insert(absl::string_view("foo")); + EXPECT_THAT(s, ElementsAre("foo", "bar")); +} + +// Tests that iteration from begin() to end() works +TEST(LinkedHashSetTest, Iteration) { + linked_hash_set<int> m; + EXPECT_TRUE(m.begin() == m.end()); + + m.insert(2); + m.insert(1); + m.insert(3); + + linked_hash_set<int>::iterator i = m.begin(); + ASSERT_TRUE(m.begin() == i); + ASSERT_TRUE(m.end() != i); + EXPECT_EQ(2, *i); + + ++i; + ASSERT_TRUE(m.end() != i); + EXPECT_EQ(1, *i); + + ++i; + ASSERT_TRUE(m.end() != i); + EXPECT_EQ(3, *i); + + ++i; + ASSERT_TRUE(m.end() == i); +} + +// Tests that reverse iteration from rbegin() to rend() works +TEST(LinkedHashSetTest, ReverseIteration) { + linked_hash_set<int> m; + EXPECT_TRUE(m.rbegin() == m.rend()); + + m.insert(2); + m.insert(1); + m.insert(3); + + linked_hash_set<int>::reverse_iterator i = m.rbegin(); + ASSERT_TRUE(m.rbegin() == i); + ASSERT_TRUE(m.rend() != i); + EXPECT_EQ(3, *i); + + ++i; + ASSERT_TRUE(m.rend() != i); + EXPECT_EQ(1, *i); + + ++i; + ASSERT_TRUE(m.rend() != i); + EXPECT_EQ(2, *i); + + ++i; + ASSERT_TRUE(m.rend() == i); +} + +// Tests that clear() works +TEST(LinkedHashSetTest, Clear) { + linked_hash_set<int> m{2, 1, 3}; + ASSERT_EQ(3, m.size()); + + m.clear(); + EXPECT_EQ(0, m.size()); + EXPECT_FALSE(m.contains(1)); + EXPECT_TRUE(m.find(1) == m.end()); + + // Make sure we can call it on an empty set. + m.clear(); + EXPECT_EQ(0, m.size()); +} + +// Tests that size() works. +TEST(LinkedHashSetTest, Size) { + linked_hash_set<int> m; + EXPECT_EQ(0, m.size()); + m.insert(2); + EXPECT_EQ(1, m.size()); + m.insert(11); + EXPECT_EQ(2, m.size()); + m.insert(0); + EXPECT_EQ(3, m.size()); + m.insert(0); + EXPECT_EQ(3, m.size()); + m.clear(); + EXPECT_EQ(0, m.size()); +} + +// Tests empty() +TEST(LinkedHashSetTest, Empty) { + linked_hash_set<int> m; + ASSERT_TRUE(m.empty()); + m.insert(2); + ASSERT_FALSE(m.empty()); + m.clear(); + ASSERT_TRUE(m.empty()); +} + +TEST(LinkedHashSetTest, Erase) { + linked_hash_set<int> m; + ASSERT_EQ(0, m.size()); + EXPECT_EQ(0, m.erase(2)); // Nothing to erase yet + + m.insert(2); + ASSERT_EQ(1, m.size()); + EXPECT_EQ(1, m.erase(2)); + EXPECT_EQ(0, m.size()); + EXPECT_TRUE(m.empty()); + + EXPECT_EQ(0, m.erase(2)); // Make sure nothing bad happens if we repeat. + EXPECT_EQ(0, m.size()); + EXPECT_TRUE(m.empty()); +} + +TEST(LinkedHashSetTest, Erase2) { + linked_hash_set<int> m; + ASSERT_EQ(0, m.size()); + EXPECT_EQ(0, m.erase(2)); // Nothing to erase yet + + m.insert(2); + m.insert(1); + m.insert(3); + m.insert(4); + ASSERT_EQ(4, m.size()); + + // Erase middle two + EXPECT_EQ(1, m.erase(1)); + EXPECT_EQ(1, m.erase(3)); + + EXPECT_EQ(2, m.size()); + + // Make sure we can still iterate over everything that's left. + linked_hash_set<int>::iterator it = m.begin(); + ASSERT_TRUE(it != m.end()); + EXPECT_EQ(2, *it); + ++it; + ASSERT_TRUE(it != m.end()); + EXPECT_EQ(4, *it); + ++it; + ASSERT_TRUE(it == m.end()); + + EXPECT_EQ(0, m.erase(1)); // Make sure nothing bad happens if we repeat. + ASSERT_EQ(2, m.size()); + + EXPECT_EQ(1, m.erase(2)); + EXPECT_EQ(1, m.erase(4)); + ASSERT_EQ(0, m.size()); + EXPECT_TRUE(m.empty()); + + EXPECT_EQ(0, m.erase(1)); // Make sure nothing bad happens if we repeat. + ASSERT_EQ(0, m.size()); + EXPECT_TRUE(m.empty()); +} + +// Test that erase(iter,iter) and erase(iter) compile and work. +TEST(LinkedHashSetTest, Erase3) { + linked_hash_set<int> m; + + m.insert(1); + m.insert(2); + m.insert(3); + m.insert(4); + + // Erase middle two + linked_hash_set<int>::iterator it2 = m.find(2); + linked_hash_set<int>::iterator it4 = m.find(4); + EXPECT_EQ(m.erase(it2, it4), m.find(4)); + EXPECT_FALSE(m.contains(2)); + EXPECT_TRUE(m.find(2) == m.end()); + EXPECT_FALSE(m.contains(3)); + EXPECT_TRUE(m.find(3) == m.end()); + EXPECT_EQ(2, m.size()); + + // Make sure we can still iterate over everything that's left. + linked_hash_set<int>::iterator it = m.begin(); + ASSERT_TRUE(it != m.end()); + EXPECT_EQ(1, *it); + ++it; + ASSERT_TRUE(it != m.end()); + EXPECT_EQ(4, *it); + ++it; + ASSERT_TRUE(it == m.end()); + + // Erase first one using an iterator. + EXPECT_EQ(m.erase(m.begin()), m.find(4)); + EXPECT_FALSE(m.contains(1)); + EXPECT_TRUE(m.find(1) == m.end()); + + // Only the last element should be left. + EXPECT_TRUE(m.contains(4)); + it = m.begin(); + ASSERT_TRUE(it != m.end()); + EXPECT_EQ(4, *it); + ++it; + ASSERT_TRUE(it == m.end()); +} + +// Test all types of insertion +TEST(LinkedHashSetTest, Insertion) { + linked_hash_set<int> m; + ASSERT_EQ(0, m.size()); + std::pair<linked_hash_set<int>::iterator, bool> result; + + result = m.insert(2); + ASSERT_EQ(1, m.size()); + EXPECT_TRUE(result.second); + EXPECT_EQ(2, *result.first); + EXPECT_TRUE(m.contains(2)); + EXPECT_TRUE(m.find(2) != m.end()); + + result = m.insert(1); + ASSERT_EQ(2, m.size()); + EXPECT_TRUE(result.second); + EXPECT_EQ(1, *result.first); + EXPECT_TRUE(m.contains(1)); + EXPECT_TRUE(m.find(1) != m.end()); + + result = m.insert(3); + linked_hash_set<int>::iterator result_iterator = result.first; + ASSERT_EQ(3, m.size()); + EXPECT_TRUE(result.second); + EXPECT_EQ(3, *result.first); + EXPECT_TRUE(m.contains(3)); + EXPECT_TRUE(m.find(3) != m.end()); + + result = m.insert(3); + EXPECT_EQ(3, m.size()); + EXPECT_FALSE(result.second) << "No insertion should have occurred."; + EXPECT_TRUE(result_iterator == result.first) + << "Duplicate insertion should have given us the original iterator."; + EXPECT_TRUE(m.contains(3)); + EXPECT_TRUE(m.find(3) != m.end()); + + std::vector<int> v = {3, 4, 5}; + m.insert(v.begin(), v.end()); + // Expect 4 and 5 inserted, 3 not inserted. + EXPECT_EQ(5, m.size()); + EXPECT_TRUE(m.contains(4)); + EXPECT_NE(m.find(4), m.end()); + EXPECT_TRUE(m.contains(5)); + EXPECT_NE(m.find(5), m.end()); +} + +TEST(LinkedHashSetTest, HintedInsertionMoveable) { + linked_hash_set<int> m = {1, 3}; + m.insert(m.find(3), 2); + EXPECT_THAT(m, ElementsAre(1, 2, 3)); +} + +TEST(LinkedHashSetTest, HintedInsertionReference) { + linked_hash_set<int> m = {1, 3}; + const int val = 2; + m.insert(m.find(3), val); + EXPECT_THAT(m, ElementsAre(1, 2, 3)); +} + +TEST(LinkedHashSetTest, HintedEmplaceMoveable) { + linked_hash_set<int> m = {1, 3}; + m.emplace_hint(m.find(3), 2); + EXPECT_THAT(m, ElementsAre(1, 2, 3)); +} + +TEST(LinkedHashSetTest, HintedEmplaceReference) { + linked_hash_set<int> m = {1, 3}; + const int val = 2; + m.emplace_hint(m.find(3), val); + EXPECT_THAT(m, ElementsAre(1, 2, 3)); +} + +// Test front accessors. +TEST(LinkedHashSetTest, Front) { + linked_hash_set<int> m; + + m.insert(222); + m.insert(111); + m.insert(333); + + EXPECT_EQ(3, m.size()); + EXPECT_EQ(222, m.front()); + m.pop_front(); + EXPECT_EQ(2, m.size()); + EXPECT_EQ(111, m.front()); + m.pop_front(); + EXPECT_EQ(1, m.size()); + EXPECT_EQ(333, m.front()); + m.pop_front(); + EXPECT_TRUE(m.empty()); +} + +// Test back accessors. +TEST(LinkedHashSetTest, Back) { + linked_hash_set<int> m; + + m.insert(222); + m.insert(111); + m.insert(333); + + EXPECT_EQ(3, m.size()); + EXPECT_EQ(333, m.back()); + m.pop_back(); + EXPECT_EQ(2, m.size()); + EXPECT_EQ(111, m.back()); + m.pop_back(); + EXPECT_EQ(1, m.size()); + EXPECT_EQ(222, m.back()); + m.pop_back(); + EXPECT_TRUE(m.empty()); +} + +TEST(LinkedHashSetTest, Find) { + linked_hash_set<int> m; + + EXPECT_TRUE(m.end() == m.find(1)) + << "We shouldn't find anything in an empty set."; + + m.insert(2); + EXPECT_TRUE(m.end() == m.find(1)) + << "We shouldn't find an element that doesn't exist in the set."; + + std::pair<linked_hash_set<int>::iterator, bool> result = m.insert(1); + ASSERT_TRUE(result.second); + ASSERT_TRUE(m.end() != result.first); + EXPECT_TRUE(result.first == m.find(1)) + << "We should have found an element we know exists in the set."; + EXPECT_EQ(1, *result.first); + + // Check that a follow-up insertion doesn't affect our original + m.insert(3); + linked_hash_set<int>::iterator it = m.find(1); + ASSERT_TRUE(m.end() != it); + EXPECT_EQ(1, *it); + + m.clear(); + EXPECT_TRUE(m.end() == m.find(1)) + << "We shouldn't find anything in a set that we've cleared."; +} + +TEST(LinkedHashSetTest, Contains) { + linked_hash_set<int> m; + + EXPECT_FALSE(m.contains(1)) << "The empty set shouldn't contain anything."; + + m.insert(2); + EXPECT_FALSE(m.contains(1)) + << "contains() should not return true for an element that doesn't exist " + << "in the set."; + + m.insert(1); + EXPECT_TRUE(m.contains(1)) + << "contains() should return true for an element we know exists in the " + << "set."; + + m.clear(); + EXPECT_FALSE(m.contains(1)) + << "A set that we've cleared shouldn't contain anything."; +} + +TEST(LinkedHashSetTest, Swap) { + linked_hash_set<int> m1; + linked_hash_set<int> m2; + m1.insert(1); + m1.insert(2); + m2.insert(3); + ASSERT_EQ(2, m1.size()); + ASSERT_EQ(1, m2.size()); + m1.swap(m2); + ASSERT_EQ(1, m1.size()); + ASSERT_EQ(2, m2.size()); +} + +TEST(LinkedHashSetTest, SelfSwap) { + linked_hash_set<int> a{1, 2, 3}; + using std::swap; + swap(a, a); + EXPECT_THAT(a, ElementsAre(1, 2, 3)); +} + +TEST(LinkedHashSetTest, InitializerList) { + linked_hash_set<int> m{1, 3}; + ASSERT_EQ(2, m.size()); + EXPECT_TRUE(m.contains(1)); + linked_hash_set<int>::iterator it = m.find(1); + ASSERT_TRUE(m.end() != it); + EXPECT_EQ(1, *it); + it = m.find(3); + EXPECT_TRUE(m.contains(3)); + ASSERT_TRUE(m.end() != it); + EXPECT_EQ(3, *it); +} + +TEST(LinkedHashSetTest, CustomHashAndEquality) { + struct CustomIntHash { + size_t operator()(int x) const { return 0; } + }; + struct CustomIntEq { + bool operator()(int x, int y) const { return abs(x) == abs(y); } + }; + linked_hash_set<int, CustomIntHash, CustomIntEq> m; + m.insert(1); + EXPECT_EQ(1, m.size()); + m.insert(2); + EXPECT_EQ(2, m.size()); + EXPECT_FALSE(m.insert(-2).second); + EXPECT_EQ(2, m.size()); + EXPECT_TRUE(m.contains(-1)); + EXPECT_TRUE(m.find(-1) != m.end()); +} + +TEST(LinkedHashSetTest, EqualRange) { + linked_hash_set<int> m{3, 1}; + const auto& const_m = m; + + EXPECT_THAT(m.equal_range(2), testing::Pair(m.end(), m.end())); + EXPECT_THAT(const_m.equal_range(2), + testing::Pair(const_m.end(), const_m.end())); + + EXPECT_THAT(m.equal_range(1), testing::Pair(m.find(1), ++m.find(1))); + EXPECT_THAT(const_m.equal_range(1), + testing::Pair(const_m.find(1), ++const_m.find(1))); +} + +TEST(LinkedHashSetTest, ReserveWorks) { + linked_hash_set<int> m; + EXPECT_EQ(0, m.size()); + EXPECT_EQ(0.0, m.load_factor()); + m.reserve(10); + EXPECT_LE(10, m.capacity()); + EXPECT_EQ(0, m.size()); + EXPECT_EQ(0.0, m.load_factor()); + m.insert(1); + m.insert(2); + EXPECT_LE(10, m.capacity()); + EXPECT_EQ(2, m.size()); + EXPECT_LT(0.0, m.load_factor()); +} + +TEST(LinkedHashSetTest, HeterogeneousTests) { + absl::test_internal::InstanceTracker tracker; + + linked_hash_set<ExpensiveType, HeterogeneousHash, HeterogeneousEqual> set; + ExpensiveType one(1); + tracker.ResetCopiesMovesSwaps(); + set.insert(one); + // Two instances: 'one' var and an instance in the set. + EXPECT_EQ(2, tracker.instances()); + EXPECT_EQ(1, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + + tracker.ResetCopiesMovesSwaps(); + set.insert(one); + // No construction since key==1 exists. + EXPECT_EQ(2, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + + tracker.ResetCopiesMovesSwaps(); + set.emplace(CheapType(1)); + // No construction since key==1 exists. + EXPECT_EQ(2, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + + tracker.ResetCopiesMovesSwaps(); + set.emplace(CheapType(2)); + // Construction since key==2 doesn't exist in the set. + EXPECT_EQ(3, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + EXPECT_THAT(set, ElementsAre(HasExpensiveValue(1), HasExpensiveValue(2))); + + // find + tracker.ResetCopiesMovesSwaps(); + auto itr = set.find(CheapType(1)); + ASSERT_NE(itr, set.end()); + EXPECT_EQ(1, itr->value()); + // contains + EXPECT_TRUE(set.contains(CheapType(2))); + // count + EXPECT_EQ(1, set.count(CheapType(2))); + // equal_range + auto eq_itr_pair = set.equal_range(CheapType(2)); + ASSERT_NE(eq_itr_pair.first, set.end()); + EXPECT_EQ(2, eq_itr_pair.first->value()); + // No construction for find, contains, count or equal_range. + EXPECT_EQ(3, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + + // emplace + tracker.ResetCopiesMovesSwaps(); + set.emplace(3); + // Just one construction. + EXPECT_EQ(4, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + + tracker.ResetCopiesMovesSwaps(); + set.emplace(3); + // No additional construction since key==3 exists. + EXPECT_EQ(4, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + EXPECT_THAT(set, ElementsAre(HasExpensiveValue(1), HasExpensiveValue(2), + HasExpensiveValue(3))); + + // Test std::move() using insert(). + ExpensiveType four(4); + tracker.ResetCopiesMovesSwaps(); + set.insert(std::move(four)); + // Two constructions (regular and move). + EXPECT_EQ(6, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(1, tracker.moves()); + + EXPECT_THAT(set, ElementsAre(HasExpensiveValue(1), HasExpensiveValue(2), + HasExpensiveValue(3), HasExpensiveValue(4))); + + tracker.ResetCopiesMovesSwaps(); + set.erase(CheapType(1)); + // No construction and instance reduced by one. + EXPECT_EQ(5, tracker.instances()); + EXPECT_EQ(0, tracker.copies()); + EXPECT_EQ(0, tracker.moves()); + EXPECT_THAT(set, ElementsAre(HasExpensiveValue(2), HasExpensiveValue(3), + HasExpensiveValue(4))); +} + +TEST(LinkedHashSetTest, HeterogeneousStringViewLookup) { + linked_hash_set<std::string> set; + set.insert("foo"); + set.insert("bar"); + set.insert("blah"); + + { + absl::string_view lookup("foo"); + auto itr = set.find(lookup); + ASSERT_NE(itr, set.end()); + EXPECT_EQ("foo", *itr); + } + + // Not found. + { + absl::string_view lookup("foobar"); + EXPECT_EQ(set.end(), set.find(lookup)); + } + + { + absl::string_view lookup("blah"); + auto itr = set.find(lookup); + ASSERT_NE(itr, set.end()); + EXPECT_EQ("blah", *itr); + } +} + +TEST(LinkedHashSetTest, EmplaceString) { + std::vector<std::string> v = {"a", "b"}; + linked_hash_set<absl::string_view> hs(v.begin(), v.end()); + EXPECT_THAT(hs, ElementsAreArray(v)); +} + +TEST(LinkedHashSetTest, BitfieldArgument) { + union { + int n : 1; + }; + n = 0; + linked_hash_set<int> s = {n}; + s.insert(n); + s.insert(s.end(), n); + s.insert({n}); + s.erase(n); + s.count(n); + s.find(n); + s.contains(n); + s.equal_range(n); +} + +TEST(LinkedHashSetTest, MergeExtractInsert) { + struct Hash { + size_t operator()(const std::unique_ptr<int>& p) const { return *p; } + }; + struct Eq { + bool operator()(const std::unique_ptr<int>& a, + const std::unique_ptr<int>& b) const { + return *a == *b; + } + }; + linked_hash_set<std::unique_ptr<int>, Hash, Eq> set1, set2; + set1.insert(std::make_unique<int>(7)); + set1.insert(std::make_unique<int>(17)); + + set2.insert(std::make_unique<int>(7)); + set2.insert(std::make_unique<int>(19)); + + EXPECT_THAT(set1, ElementsAre(Pointee(7), Pointee(17))); + EXPECT_THAT(set2, ElementsAre(Pointee(7), Pointee(19))); + + set1.merge(set2); + + EXPECT_THAT(set1, ElementsAre(Pointee(7), Pointee(17), Pointee(19))); + EXPECT_THAT(set2, ElementsAre(Pointee(7))); + + auto node = set1.extract(std::make_unique<int>(7)); + EXPECT_TRUE(node); + EXPECT_THAT(node.value(), Pointee(7)); + EXPECT_THAT(set1, ElementsAre(Pointee(17), Pointee(19))); + + auto insert_result = set2.insert(std::move(node)); + EXPECT_FALSE(node); + EXPECT_FALSE(insert_result.inserted); + EXPECT_TRUE(insert_result.node); + EXPECT_THAT(insert_result.node.value(), Pointee(7)); + EXPECT_EQ(**insert_result.position, 7); + EXPECT_NE(insert_result.position->get(), insert_result.node.value().get()); + EXPECT_THAT(set2, ElementsAre(Pointee(7))); + + node = set1.extract(std::make_unique<int>(17)); + EXPECT_TRUE(node); + EXPECT_THAT(node.value(), Pointee(17)); + EXPECT_THAT(set1, ElementsAre(Pointee(19))); + + node.value() = std::make_unique<int>(23); + + insert_result = set2.insert(std::move(node)); + EXPECT_FALSE(node); + EXPECT_TRUE(insert_result.inserted); + EXPECT_FALSE(insert_result.node); + EXPECT_EQ(**insert_result.position, 23); + EXPECT_THAT(set2, ElementsAre(Pointee(7), Pointee(23))); +} + +TEST(LinkedHashSet, ExtractInsert) { + linked_hash_set<int> s = {1, 7, 2, 9}; + auto node = s.extract(1); + EXPECT_TRUE(node); + EXPECT_EQ(node.value(), 1); + EXPECT_THAT(s, ElementsAre(7, 2, 9)); + EXPECT_FALSE(s.contains(1)); + + node.value() = 17; + s.insert(std::move(node)); + EXPECT_FALSE(node); + EXPECT_THAT(s, ElementsAre(7, 2, 9, 17)); + EXPECT_TRUE(s.contains(17)); + + node = s.extract(s.find(9)); + EXPECT_TRUE(node); + EXPECT_EQ(node.value(), 9); + EXPECT_THAT(s, ElementsAre(7, 2, 17)); + EXPECT_FALSE(s.contains(9)); +} + +TEST(LinkedHashSet, Merge) { + linked_hash_set<int> m = {1, 7, 3, 6, 10}; + linked_hash_set<int> src = {1, 2, 9, 10, 4, 16}; + + m.merge(src); + + EXPECT_THAT(m, ElementsAre(1, 7, 3, 6, 10, 2, 9, 4, 16)); + for (int i : {1, 7, 3, 6, 10, 2, 9, 4, 16}) { + EXPECT_TRUE(m.contains(i)); + } + EXPECT_THAT(src, ElementsAre(1, 10)); + for (int i : {1, 10}) { + EXPECT_TRUE(src.contains(i)); + } + for (int i : {2, 9, 4, 16}) { + EXPECT_FALSE(src.contains(i)); + } +} + +TEST(LinkedHashSet, EraseRange) { + linked_hash_set<int> set = {1, 2, 3, 4, 5, 25, 36, 7, 8, 9, 81}; + auto start = set.find(3); + auto end = set.find(8); + auto itr = set.erase(start, end); + ASSERT_NE(itr, set.end()); + EXPECT_THAT(*itr, 8); + EXPECT_THAT(set, ElementsAre(1, 2, 8, 9, 81)); + for (int i : {1, 2, 8, 9, 81}) { + EXPECT_TRUE(set.contains(i)); + } + for (int i : {3, 4, 5, 25, 36, 7}) { + EXPECT_FALSE(set.contains(i)); + } +} + +TEST(LinkedHashSet, InsertInitializerList) { + linked_hash_set<int> set; + set.insert({1, 7, 2, 9, 3, 29}); + EXPECT_THAT(set, ElementsAre(1, 7, 2, 9, 3, 29)); + for (int i : {1, 7, 2, 9, 3, 29}) { + EXPECT_TRUE(set.contains(i)); + } +} + +struct CountedHash { + explicit CountedHash(int* count) : count(count) {} + size_t operator()(int value) const { + ++(*count); + return value; + } + int* count = nullptr; +}; + +// Makes a set too big for small object optimization. Counts the number of +// hashes in `count`, but leaves `count` set to 0. +linked_hash_set<int, CountedHash> MakeNonSmallSet(int* count) { + const int kFirstKey = -1000; + linked_hash_set<int, CountedHash> s(0, CountedHash(count)); + for (int i = kFirstKey; i < kFirstKey + 100; ++i) { + s.insert(i); + } + *count = 0; + return s; +} + +constexpr bool BuildHasDebugModeRehashes() { +#if !defined(NDEBUG) || defined(ABSL_HAVE_ADDRESS_SANITIZER) || \ + defined(ABSL_HAVE_MEMORY_SANITIZER) || defined(ABSL_HAVE_THREAD_SANITIZER) + return true; +#else + return false; +#endif +} + +TEST(LinkedHashSetTest, HashCountInOptBuilds) { + if (BuildHasDebugModeRehashes()) { + GTEST_SKIP() << "Only run under NDEBUG: `assert` statements and sanitizer " + "rehashing may cause redundant hashing."; + } + + using Set = linked_hash_set<int, CountedHash>; + { + int count = 0; + Set s = MakeNonSmallSet(&count); + s.insert(1); + EXPECT_EQ(count, 1); + s.erase(1); + EXPECT_EQ(count, 2); + } + { + int count = 0; + Set s = MakeNonSmallSet(&count); + s.insert(3); + EXPECT_EQ(count, 1); + auto node = s.extract(3); + EXPECT_EQ(count, 2); + s.insert(std::move(node)); + EXPECT_EQ(count, 3); + } + { + int count = 0; + Set s = MakeNonSmallSet(&count); + s.emplace(5); + EXPECT_EQ(count, 1); + } + { + int src_count = 0, dst_count = 0; + Set src = MakeNonSmallSet(&src_count); + Set dst = MakeNonSmallSet(&dst_count); + src.insert(7); + dst.merge(src); + EXPECT_LE(src_count, 200); + EXPECT_LE(dst_count, 200); + } +} + +} // namespace +} // namespace container_internal +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/container/node_hash_map.h b/absl/container/node_hash_map.h index 8aed18b..580a044 100644 --- a/absl/container/node_hash_map.h +++ b/absl/container/node_hash_map.h
@@ -110,25 +110,30 @@ // absl::node_hash_map<std::string, std::string> ducks = // {{"a", "huey"}, {"b", "dewey"}, {"c", "louie"}}; // -// // Insert a new element into the node hash map -// ducks.insert({"d", "donald"}}; +// // Insert a new element into the node hash map +// ducks.insert({"d", "donald"}}; // -// // Force a rehash of the node hash map -// ducks.rehash(0); +// // Force a rehash of the node hash map +// ducks.rehash(0); // -// // Find the element with the key "b" -// std::string search_key = "b"; -// auto result = ducks.find(search_key); -// if (result != ducks.end()) { -// std::cout << "Result: " << result->second << std::endl; -// } -template <class Key, class Value, class Hash = DefaultHashContainerHash<Key>, - class Eq = DefaultHashContainerEq<Key>, - class Alloc = std::allocator<std::pair<const Key, Value>>> +// // Find the element with the key "b" +// std::string search_key = "b"; +// auto result = ducks.find(search_key); +// if (result != ducks.end()) { +// std::cout << "Result: " << result->second << std::endl; +// } +template < + class Key, class Value, + class Hash = + typename container_internal::NodeHashMapPolicy<Key, Value>::DefaultHash, + class Eq = + typename container_internal::NodeHashMapPolicy<Key, Value>::DefaultEq, + class Alloc = typename container_internal::NodeHashMapPolicy< + Key, Value>::DefaultAlloc> class ABSL_ATTRIBUTE_OWNER node_hash_map - : public absl::container_internal::raw_hash_map< + : public absl::container_internal::InstantiateRawHashMap< absl::container_internal::NodeHashMapPolicy<Key, Value>, Hash, Eq, - Alloc> { + Alloc>::type { using Base = typename node_hash_map::raw_hash_map; public: @@ -153,9 +158,9 @@ // // * Copy assignment operator // - // // Hash functor and Comparator are copied as well - // absl::node_hash_map<int, std::string> map4; - // map4 = map3; + // // Hash functor and Comparator are copied as well + // absl::node_hash_map<int, std::string> map4; + // map4 = map3; // // * Move constructor // @@ -455,7 +460,9 @@ // // Sets the number of slots in the `node_hash_map` to the number needed to // accommodate at least `count` total elements without exceeding the current - // maximum load factor, and may rehash the container if needed. + // maximum load factor, and may rehash the container if needed. After this + // returns, it is guaranteed that `count - size()` elements can be inserted + // into the `node_hash_map` without another rehash. using Base::reserve; // node_hash_map::at() @@ -627,6 +634,10 @@ using mapped_type = Value; using init_type = std::pair</*non const*/ key_type, mapped_type>; + using DefaultHash = DefaultHashContainerHash<Key>; + using DefaultEq = DefaultHashContainerEq<Key>; + using DefaultAlloc = std::allocator<std::pair<const Key, Value>>; + template <class Allocator, class... Args> static value_type* new_element(Allocator* alloc, Args&&... args) { using PairAlloc = typename absl::allocator_traits< @@ -663,10 +674,10 @@ static Value& value(value_type* elem) { return elem->second; } static const Value& value(const value_type* elem) { return elem->second; } - template <class Hash> + template <class Hash, bool kIsDefault> static constexpr HashSlotFn get_hash_slot_fn() { return memory_internal::IsLayoutCompatible<Key, Value>::value - ? &TypeErasedDerefAndApplyToSlotFn<Hash, Key> + ? &TypeErasedDerefAndApplyToSlotFn<Hash, Key, kIsDefault> : nullptr; } };
diff --git a/absl/container/node_hash_set.h b/absl/container/node_hash_set.h index 6240e2d..f69c6ab 100644 --- a/absl/container/node_hash_set.h +++ b/absl/container/node_hash_set.h
@@ -108,21 +108,26 @@ // absl::node_hash_set<std::string> ducks = // {"huey", "dewey", "louie"}; // -// // Insert a new element into the node hash set -// ducks.insert("donald"); +// // Insert a new element into the node hash set +// ducks.insert("donald"); // -// // Force a rehash of the node hash set -// ducks.rehash(0); +// // Force a rehash of the node hash set +// ducks.rehash(0); // -// // See if "dewey" is present -// if (ducks.contains("dewey")) { -// std::cout << "We found dewey!" << std::endl; -// } -template <class T, class Hash = DefaultHashContainerHash<T>, - class Eq = DefaultHashContainerEq<T>, class Alloc = std::allocator<T>> +// // See if "dewey" is present +// if (ducks.contains("dewey")) { +// std::cout << "We found dewey!" << std::endl; +// } +template < + class T, + class Hash = typename container_internal::NodeHashSetPolicy<T>::DefaultHash, + class Eq = typename container_internal::NodeHashSetPolicy<T>::DefaultEq, + class Alloc = + typename container_internal::NodeHashSetPolicy<T>::DefaultAlloc> class ABSL_ATTRIBUTE_OWNER node_hash_set - : public absl::container_internal::raw_hash_set< - absl::container_internal::NodeHashSetPolicy<T>, Hash, Eq, Alloc> { + : public absl::container_internal::InstantiateRawHashSet< + absl::container_internal::NodeHashSetPolicy<T>, Hash, Eq, + Alloc>::type { using Base = typename node_hash_set::raw_hash_set; public: @@ -147,9 +152,9 @@ // // * Copy assignment operator // - // // Hash functor and Comparator are copied as well - // absl::node_hash_set<std::string> set4; - // set4 = set3; + // // Hash functor and Comparator are copied as well + // absl::node_hash_set<std::string> set4; + // set4 = set3; // // * Move constructor // @@ -390,7 +395,9 @@ // // Sets the number of slots in the `node_hash_set` to the number needed to // accommodate at least `count` total elements without exceeding the current - // maximum load factor, and may rehash the container if needed. + // maximum load factor, and may rehash the container if needed. After this + // returns, it is guaranteed that `count - size()` elements can be inserted + // into the `node_hash_set` without another rehash. using Base::reserve; // node_hash_set::contains() @@ -527,6 +534,10 @@ using init_type = T; using constant_iterators = std::true_type; + using DefaultHash = DefaultHashContainerHash<T>; + using DefaultEq = DefaultHashContainerEq<T>; + using DefaultAlloc = std::allocator<T>; + template <class Allocator, class... Args> static T* new_element(Allocator* alloc, Args&&... args) { using ValueAlloc = @@ -557,9 +568,9 @@ static size_t element_space_used(const T*) { return sizeof(T); } - template <class Hash> + template <class Hash, bool kIsDefault> static constexpr HashSlotFn get_hash_slot_fn() { - return &TypeErasedDerefAndApplyToSlotFn<Hash, T>; + return &TypeErasedDerefAndApplyToSlotFn<Hash, T, kIsDefault>; } }; } // namespace container_internal
diff --git a/absl/container/node_hash_set_test.cc b/absl/container/node_hash_set_test.cc index e616ac1..e1f5bd9 100644 --- a/absl/container/node_hash_set_test.cc +++ b/absl/container/node_hash_set_test.cc
@@ -35,8 +35,7 @@ ABSL_NAMESPACE_BEGIN namespace container_internal { namespace { -using ::absl::container_internal::hash_internal::Enum; -using ::absl::container_internal::hash_internal::EnumClass; + using ::testing::IsEmpty; using ::testing::Pointee; using ::testing::UnorderedElementsAre;
diff --git a/absl/copts/GENERATED_AbseilCopts.cmake b/absl/copts/GENERATED_AbseilCopts.cmake index 7d8af92..b08c34c 100644 --- a/absl/copts/GENERATED_AbseilCopts.cmake +++ b/absl/copts/GENERATED_AbseilCopts.cmake
@@ -10,6 +10,46 @@ "/D_CRT_SECURE_NO_WARNINGS" "/D_SCL_SECURE_NO_WARNINGS" "/D_ENABLE_EXTENDED_ALIGNED_STORAGE" + "-Wmost" + "-Wextra" + "-Wc++98-compat-extra-semi" + "-Wcast-qual" + "-Wconversion" + "-Wdeprecated-pragma" + "-Wfloat-overflow-conversion" + "-Wfloat-zero-conversion" + "-Wfor-loop-analysis" + "-Wformat-security" + "-Wgnu-redeclared-enum" + "-Winfinite-recursion" + "-Winvalid-constexpr" + "-Wliteral-conversion" + "-Wmissing-declarations" + "-Wnullability-completeness" + "-Woverlength-strings" + "-Wpointer-arith" + "-Wself-assign" + "-Wshadow-all" + "-Wshorten-64-to-32" + "-Wsign-conversion" + "-Wstring-conversion" + "-Wtautological-overlap-compare" + "-Wtautological-unsigned-zero-compare" + "-Wthread-safety" + "-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" + "-Wno-unused-command-line-argument" + "-DNOMINMAX" ) list(APPEND ABSL_CLANG_CL_TEST_FLAGS @@ -19,6 +59,43 @@ "/D_CRT_SECURE_NO_WARNINGS" "/D_SCL_SECURE_NO_WARNINGS" "/D_ENABLE_EXTENDED_ALIGNED_STORAGE" + "-Wmost" + "-Wextra" + "-Wc++98-compat-extra-semi" + "-Wcast-qual" + "-Wconversion" + "-Wdeprecated-pragma" + "-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" + "-Wthread-safety" + "-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" + "-Wno-unused-command-line-argument" + "-DNOMINMAX" "-Wno-deprecated-declarations" "-Wno-implicit-int-conversion" "-Wno-missing-prototypes" @@ -84,6 +161,7 @@ list(APPEND ABSL_LLVM_FLAGS "-Wall" + "-Wmost" "-Wextra" "-Wc++98-compat-extra-semi" "-Wcast-qual" @@ -121,11 +199,13 @@ "-Wno-implicit-float-conversion" "-Wno-implicit-int-float-conversion" "-Wno-unknown-warning-option" + "-Wno-unused-command-line-argument" "-DNOMINMAX" ) list(APPEND ABSL_LLVM_TEST_FLAGS "-Wall" + "-Wmost" "-Wextra" "-Wc++98-compat-extra-semi" "-Wcast-qual" @@ -160,6 +240,7 @@ "-Wno-implicit-float-conversion" "-Wno-implicit-int-float-conversion" "-Wno-unknown-warning-option" + "-Wno-unused-command-line-argument" "-DNOMINMAX" "-Wno-deprecated-declarations" "-Wno-implicit-int-conversion"
diff --git a/absl/copts/GENERATED_copts.bzl b/absl/copts/GENERATED_copts.bzl index 23896e9..8c1d7a0 100644 --- a/absl/copts/GENERATED_copts.bzl +++ b/absl/copts/GENERATED_copts.bzl
@@ -11,6 +11,46 @@ "/D_CRT_SECURE_NO_WARNINGS", "/D_SCL_SECURE_NO_WARNINGS", "/D_ENABLE_EXTENDED_ALIGNED_STORAGE", + "-Wmost", + "-Wextra", + "-Wc++98-compat-extra-semi", + "-Wcast-qual", + "-Wconversion", + "-Wdeprecated-pragma", + "-Wfloat-overflow-conversion", + "-Wfloat-zero-conversion", + "-Wfor-loop-analysis", + "-Wformat-security", + "-Wgnu-redeclared-enum", + "-Winfinite-recursion", + "-Winvalid-constexpr", + "-Wliteral-conversion", + "-Wmissing-declarations", + "-Wnullability-completeness", + "-Woverlength-strings", + "-Wpointer-arith", + "-Wself-assign", + "-Wshadow-all", + "-Wshorten-64-to-32", + "-Wsign-conversion", + "-Wstring-conversion", + "-Wtautological-overlap-compare", + "-Wtautological-unsigned-zero-compare", + "-Wthread-safety", + "-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", + "-Wno-unused-command-line-argument", + "-DNOMINMAX", ] ABSL_CLANG_CL_TEST_FLAGS = [ @@ -20,6 +60,43 @@ "/D_CRT_SECURE_NO_WARNINGS", "/D_SCL_SECURE_NO_WARNINGS", "/D_ENABLE_EXTENDED_ALIGNED_STORAGE", + "-Wmost", + "-Wextra", + "-Wc++98-compat-extra-semi", + "-Wcast-qual", + "-Wconversion", + "-Wdeprecated-pragma", + "-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", + "-Wthread-safety", + "-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", + "-Wno-unused-command-line-argument", + "-DNOMINMAX", "-Wno-deprecated-declarations", "-Wno-implicit-int-conversion", "-Wno-missing-prototypes", @@ -85,6 +162,7 @@ ABSL_LLVM_FLAGS = [ "-Wall", + "-Wmost", "-Wextra", "-Wc++98-compat-extra-semi", "-Wcast-qual", @@ -122,11 +200,13 @@ "-Wno-implicit-float-conversion", "-Wno-implicit-int-float-conversion", "-Wno-unknown-warning-option", + "-Wno-unused-command-line-argument", "-DNOMINMAX", ] ABSL_LLVM_TEST_FLAGS = [ "-Wall", + "-Wmost", "-Wextra", "-Wc++98-compat-extra-semi", "-Wcast-qual", @@ -161,6 +241,7 @@ "-Wno-implicit-float-conversion", "-Wno-implicit-int-float-conversion", "-Wno-unknown-warning-option", + "-Wno-unused-command-line-argument", "-DNOMINMAX", "-Wno-deprecated-declarations", "-Wno-implicit-int-conversion",
diff --git a/absl/copts/copts.py b/absl/copts/copts.py index 8cf8f31..c1d1f4a 100644 --- a/absl/copts/copts.py +++ b/absl/copts/copts.py
@@ -41,8 +41,13 @@ "-Wno-unused-private-field", ] -ABSL_LLVM_FLAGS = [ - "-Wall", +# https://github.com/llvm/llvm-project/issues/102982 +# A list of LLVM base flags without -Wall. This is because clang-cl +# translates -Wall to -Weverything on Windows, mimicking MSVCs +# behavior. On most other platforms, -Wall is just a set of very good +# default flags. +ABSL_LLVM_BASE_FLAGS = [ + "-Wmost", "-Wextra", "-Wc++98-compat-extra-semi", "-Wcast-qual", @@ -84,10 +89,13 @@ # Disable warnings on unknown warning flags (when warning flags are # unknown on older compiler versions) "-Wno-unknown-warning-option", + "-Wno-unused-command-line-argument", # Don't define min and max macros (Build on Windows using clang) "-DNOMINMAX", ] +ABSL_LLVM_FLAGS = ["-Wall"] + ABSL_LLVM_BASE_FLAGS + ABSL_LLVM_TEST_ADDITIONAL_FLAGS = [ "-Wno-deprecated-declarations", "-Wno-implicit-int-conversion", @@ -163,9 +171,15 @@ "ABSL_LLVM_TEST_FLAGS": GccStyleFilterAndCombine( ABSL_LLVM_FLAGS, ABSL_LLVM_TEST_ADDITIONAL_FLAGS ), - "ABSL_CLANG_CL_FLAGS": MSVC_BIG_WARNING_FLAGS + MSVC_DEFINES, + "ABSL_CLANG_CL_FLAGS": ( + MSVC_BIG_WARNING_FLAGS + MSVC_DEFINES + ABSL_LLVM_BASE_FLAGS + ), "ABSL_CLANG_CL_TEST_FLAGS": ( - MSVC_BIG_WARNING_FLAGS + MSVC_DEFINES + ABSL_LLVM_TEST_ADDITIONAL_FLAGS + MSVC_BIG_WARNING_FLAGS + + MSVC_DEFINES + + GccStyleFilterAndCombine( + ABSL_LLVM_BASE_FLAGS, ABSL_LLVM_TEST_ADDITIONAL_FLAGS + ) ), "ABSL_MSVC_FLAGS": ( MSVC_BIG_WARNING_FLAGS + MSVC_WARNING_FLAGS + MSVC_DEFINES
diff --git a/absl/crc/BUILD.bazel b/absl/crc/BUILD.bazel index b659a7e..22c3cbf 100644 --- a/absl/crc/BUILD.bazel +++ b/absl/crc/BUILD.bazel
@@ -12,6 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load( "//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS",
diff --git a/absl/crc/crc32c_test.cc b/absl/crc/crc32c_test.cc index df0afb3..eed5a6c 100644 --- a/absl/crc/crc32c_test.cc +++ b/absl/crc/crc32c_test.cc
@@ -15,11 +15,14 @@ #include "absl/crc/crc32c.h" #include <algorithm> +#include <array> #include <cstddef> #include <cstdint> #include <cstring> +#include <limits> #include <sstream> #include <string> +#include <tuple> #include "gtest/gtest.h" #include "absl/crc/internal/crc32c.h" @@ -101,6 +104,33 @@ } } +// Test ExtendCrc32cByZeroes() for the full range of the size_t length, +// including every bit. This is important because ExtendCrc32cByZeroes() is +// implemented using an array of constants, where each entry in the array is +// used only when a particular bit in the size_t length is set. This test +// verifies that every entry in that array is correct. +TEST(CRC32C, ExtendByZeroesAllLengthBits) { + absl::crc32c_t base_crc = absl::crc32c_t{0xc99465aa}; + const std::array<std::tuple<uint64_t, absl::crc32c_t>, 5> kTestCases = {{ + {0, absl::crc32c_t(0xc99465aa)}, + {std::numeric_limits<uint32_t>::max(), absl::crc32c_t(0x9b1d5aaa)}, + {0x12345678, absl::crc32c_t(0xcf0e9553)}, + {std::numeric_limits<uint64_t>::max(), absl::crc32c_t(0xf5bff489)}, + {0x12345678abcdefff, absl::crc32c_t(0xaa1ffb0b)}, + }}; + for (const auto &test_case : kTestCases) { + uint64_t length = std::get<0>(test_case); + absl::crc32c_t expected_value = std::get<1>(test_case); + SCOPED_TRACE(length); + if (length > std::numeric_limits<size_t>::max()) { + // On 32-bit platforms, 64-bit lengths cannot be used or tested. + continue; + } + EXPECT_EQ(absl::ExtendCrc32cByZeroes(base_crc, static_cast<size_t>(length)), + expected_value); + } +} + TEST(CRC32C, UnextendByZeroes) { constexpr size_t kExtendByValues[] = {2, 200, 20000, 200000, 20000000}; constexpr size_t kUnextendByValues[] = {0, 100, 10000, 100000, 10000000};
diff --git a/absl/crc/internal/cpu_detect.cc b/absl/crc/internal/cpu_detect.cc index c59f773..a697601 100644 --- a/absl/crc/internal/cpu_detect.cc +++ b/absl/crc/internal/cpu_detect.cc
@@ -145,6 +145,14 @@ } case 0x5e: // Skylake (client) return CpuType::kIntelSkylake; + case 0x6a: // Ice Lake + return CpuType::kIntelIcelake; + case 0x8f: // Sapphire Rapids + return CpuType::kIntelSapphirerapids; + case 0xcf: // Emerald Rapids + return CpuType::kIntelEmeraldrapids; + case 0xad: // Granite Rapids + return CpuType::kIntelGraniterapidsap; default: return CpuType::kUnknown; } @@ -210,6 +218,14 @@ return CpuType::kUnknown; } break; + case 0x1A: + switch (model_num) { + case 0x2: + return CpuType::kAmdTurin; + default: + return CpuType::kUnknown; + } + break; default: return CpuType::kUnknown; } @@ -259,6 +275,7 @@ case 0xd40: return CpuType::kArmNeoverseV1; case 0xd49: return CpuType::kArmNeoverseN2; case 0xd4f: return CpuType::kArmNeoverseV2; + case 0xd8e: return CpuType::kArmNeoverseN3; default: return CpuType::kUnknown; }
diff --git a/absl/crc/internal/cpu_detect.h b/absl/crc/internal/cpu_detect.h index 01e1959..e76a802 100644 --- a/absl/crc/internal/cpu_detect.h +++ b/absl/crc/internal/cpu_detect.h
@@ -30,10 +30,15 @@ kAmdNaples, kAmdMilan, kAmdGenoa, + kAmdTurin, kAmdRyzenV3000, kIntelCascadelakeXeon, kIntelSkylakeXeon, kIntelBroadwell, + kIntelIcelake, + kIntelSapphirerapids, + kIntelEmeraldrapids, + kIntelGraniterapidsap, kIntelSkylake, kIntelIvybridge, kIntelSandybridge, @@ -42,7 +47,8 @@ kArmNeoverseV1, kAmpereSiryn, kArmNeoverseN2, - kArmNeoverseV2 + kArmNeoverseV2, + kArmNeoverseN3, }; // Returns the type of host CPU this code is running on. Returns kUnknown if
diff --git a/absl/crc/internal/crc_memcpy_x86_arm_combined.cc b/absl/crc/internal/crc_memcpy_x86_arm_combined.cc index 38f61e9..247b3aa 100644 --- a/absl/crc/internal/crc_memcpy_x86_arm_combined.cc +++ b/absl/crc/internal/crc_memcpy_x86_arm_combined.cc
@@ -422,6 +422,11 @@ }; // INTEL_SANDYBRIDGE performs better with SSE than AVX. case CpuType::kIntelSandybridge: + // Use SIMD memcpy on ARM cores. + case CpuType::kArmNeoverseN1: + case CpuType::kArmNeoverseN2: + case CpuType::kArmNeoverseV1: + case CpuType::kArmNeoverseV2: return { /*.temporal=*/new AcceleratedCrcMemcpyEngine<3, 0>(), /*.non_temporal=*/new CrcNonTemporalMemcpyEngine(),
diff --git a/absl/crc/internal/crc_x86_arm_combined.cc b/absl/crc/internal/crc_x86_arm_combined.cc index 3194bec..ebd9c3f 100644 --- a/absl/crc/internal/crc_x86_arm_combined.cc +++ b/absl/crc/internal/crc_x86_arm_combined.cc
@@ -100,47 +100,67 @@ namespace { -uint32_t multiply(uint32_t a, uint32_t b) { - V128 power = V128_From64WithZeroFill(a); - V128 crc = V128_From64WithZeroFill(b); - V128 res = V128_PMulLow(power, crc); +// Does polynomial multiplication a * b * x^33 mod G. +// +// One of the multiplicands needs to have an extra factor of x^-33 to cancel out +// the extra factor of x^33. The extra factor of x^33 comes from: +// +// - x^1 from the carry-less multiplication, due to the +// "least-significant-bit-first" convention of CRC-32C. +// +// - x^32 from using CRC32_u64() to reduce the carry-less product to 32 bits. +// +// Both could be avoided, but at the cost of extra instructions. It's more +// efficient to just drop a factor of x^33 from one of the multiplicands. +uint32_t MultiplyWithExtraX33(uint32_t a, uint32_t b) { + V128 a_vec = V128_From64WithZeroFill(a); + V128 b_vec = V128_From64WithZeroFill(b); + V128 res = V128_PMulLow(a_vec, b_vec); - // Combine crc values. - // - // Adding res to itself is equivalent to multiplying by 2, - // or shifting left by 1. Addition is used as not all compilers - // are able to generate optimal code without this hint. - // https://godbolt.org/z/rr3fMnf39 - res = V128_Add64(res, res); - return static_cast<uint32_t>(V128_Extract32<1>(res)) ^ - CRC32_u32(0, static_cast<uint32_t>(V128_Low64(res))); + return CRC32_u64(0, static_cast<uint64_t>(V128_Low64(res))); } -// Powers of crc32c polynomial, for faster ExtendByZeros. -// Verified against folly: -// folly/hash/detail/Crc32CombineDetail.cpp +// The number of low-order bits that ComputeZeroConstant() drops from the +// length, i.e. treats as zeroes +constexpr int kNumDroppedBits = 4; + +// Precomputed constants for faster ExtendByZeroes(). This was generated by +// gen_crc32c_consts.py. The entry at index i is x^(2^(i + 3 + kNumDroppedBits) +// - 33) mod G. That is x^-33 times the polynomial by which the CRC value needs +// to be multiplied to extend it by 2^(i + 3 + kNumDroppedBits) zero bits, or +// equivalently 2^(i + kNumDroppedBits) zero bytes. The extra factor of x^-33 +// cancels out the extra factor of x^33 that MultiplyWithExtraX33() introduces. 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, + 0x493c7d27, 0xba4fc28e, 0x9e4addf8, 0x0d3b6092, 0xb9e02b86, 0xdd7e3b0c, + 0x170076fa, 0xa51b6135, 0x82f89c77, 0x54a86326, 0x1dc403cc, 0x5ae703ab, + 0xc5013a36, 0xac2ac6dd, 0x9b4615a9, 0x688d1c61, 0xf6af14e6, 0xb6ffe386, + 0xb717425b, 0x478b0d30, 0x54cc62e5, 0x7b2102ee, 0x8a99adef, 0xa7568c8f, + 0xd610d67e, 0x6b086b3f, 0xd94f3c0b, 0xbf818109, 0x780d5a4d, 0x05ec76f1, + 0x00000001, 0x493c7d27, 0xba4fc28e, 0x9e4addf8, 0x0d3b6092, 0xb9e02b86, + 0xdd7e3b0c, 0x170076fa, 0xa51b6135, 0x82f89c77, 0x54a86326, 0x1dc403cc, + 0x5ae703ab, 0xc5013a36, 0xac2ac6dd, 0x9b4615a9, 0x688d1c61, 0xf6af14e6, + 0xb6ffe386, 0xb717425b, 0x478b0d30, 0x54cc62e5, 0x7b2102ee, 0x8a99adef, + 0xa7568c8f, 0xd610d67e, 0x6b086b3f, 0xd94f3c0b, 0xbf818109, 0x780d5a4d, }; +// There must be an entry for each non-dropped bit in the size_t length. +static_assert(std::size(kCRC32CPowers) >= sizeof(size_t) * 8 - kNumDroppedBits); } // namespace -// Compute a magic constant, so that multiplying by it is the same as -// extending crc by length zeros. +// Compute a magic constant, so that multiplying by it is the same as extending +// crc by length zeros. The lowest kNumDroppedBits of the length are ignored and +// treated as zeroes; the caller is assumed to handle any nonzero bits there. +#if defined(NDEBUG) && ABSL_HAVE_CPP_ATTRIBUTE(clang::no_sanitize) +// The array accesses in this are safe: `length >= size_t{1} << +// kNumDroppedBits`, so `countr_zero(length >> kNumDroppedBits) < sizeof(size_t) +// * 8 - kNumDroppedBits`, and `length & (length - 1)` cannot introduce bits +// `>= sizeof(size_t) * 8 - kNumDroppedBits`. The compiler cannot prove this, so +// manually disable bounds checking. +[[clang::no_sanitize("array-bounds")]] +#endif uint32_t CRC32AcceleratedX86ARMCombined::ComputeZeroConstant( size_t length) const { - // Lowest 2 bits are handled separately in ExtendByZeroes - length >>= 2; + length >>= kNumDroppedBits; int index = absl::countr_zero(length); uint32_t prev = kCRC32CPowers[index]; @@ -149,7 +169,7 @@ while (length) { // For each bit of length, extend by 2**n zeros. index = absl::countr_zero(length); - prev = multiply(prev, kCRC32CPowers[index]); + prev = MultiplyWithExtraX33(prev, kCRC32CPowers[index]); length &= length - 1; } return prev; @@ -159,22 +179,13 @@ 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)); + if (length & 1) val = CRC32_u8(val, 0); + if (length & 2) val = CRC32_u16(val, 0); + if (length & 4) val = CRC32_u32(val, 0); + if (length & 8) val = CRC32_u64(val, 0); + static_assert(kNumDroppedBits == 4); + if (length >= size_t{1} << kNumDroppedBits) { + val = MultiplyWithExtraX33(val, ComputeZeroConstant(length)); } *crc = val; } @@ -306,6 +317,46 @@ return crc; } + // Same as Process64BytesCRC, but just interleaved for 2 streams. + ABSL_ATTRIBUTE_ALWAYS_INLINE void Process64BytesCRC2Streams( + const uint8_t* p0, const uint8_t* p1, uint64_t* crc) const { + uint64_t crc0 = crc[0]; + uint64_t crc1 = crc[1]; + for (int i = 0; i < 8; i++) { + crc0 = CRC32_u64(static_cast<uint32_t>(crc0), + absl::little_endian::Load64(p0)); + crc1 = CRC32_u64(static_cast<uint32_t>(crc1), + absl::little_endian::Load64(p1)); + p0 += 8; + p1 += 8; + } + crc[0] = crc0; + crc[1] = crc1; + } + + // Same as Process64BytesCRC, but just interleaved for 3 streams. + ABSL_ATTRIBUTE_ALWAYS_INLINE void Process64BytesCRC3Streams( + const uint8_t* p0, const uint8_t* p1, const uint8_t* p2, + uint64_t* crc) const { + uint64_t crc0 = crc[0]; + uint64_t crc1 = crc[1]; + uint64_t crc2 = crc[2]; + for (int i = 0; i < 8; i++) { + crc0 = CRC32_u64(static_cast<uint32_t>(crc0), + absl::little_endian::Load64(p0)); + crc1 = CRC32_u64(static_cast<uint32_t>(crc1), + absl::little_endian::Load64(p1)); + crc2 = CRC32_u64(static_cast<uint32_t>(crc2), + absl::little_endian::Load64(p2)); + p0 += 8; + p1 += 8; + p2 += 8; + } + crc[0] = crc0; + crc[1] = crc1; + crc[2] = crc2; + } + // Constants generated by './scripts/gen-crc-consts.py x86_pclmul // crc32_lsb_0x82f63b78' from the Linux kernel. alignas(16) static constexpr uint64_t kFoldAcross512Bits[2] = { @@ -339,7 +390,8 @@ class CRC32AcceleratedX86ARMCombinedMultipleStreams : public CRC32AcceleratedX86ARMCombinedMultipleStreamsBase { ABSL_ATTRIBUTE_HOT - void Extend(uint32_t* crc, const void* bytes, size_t length) const override { + void Extend(uint32_t* crc, const void* bytes, + const 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, @@ -349,47 +401,15 @@ 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, p); - length &= ~size_t{4}; - } - if (length & 2) { - ABSL_INTERNAL_STEP2(l, p); - length &= ~size_t{2}; - } - if (length & 1) { - ABSL_INTERNAL_STEP1(l, p); - 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) { + if (strategy != CutoffStrategy::Unroll64CRC && (length < kSmallCutoff)) { + // fallthrough; Use the same strategy as we do for processing the + // remaining bytes after any other strategy. + } else if (length < kMediumCutoff) { + // 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. l64 = l; if (strategy == CutoffStrategy::Fold3) { uint64_t l641 = 0; @@ -438,6 +458,7 @@ p += 64; } } + l = static_cast<uint32_t>(l64); } else { // There is a lot of data, we can ignore combine costs and run all // requested streams (num_crc_streams + num_pclmul_streams), @@ -471,9 +492,19 @@ 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; + if (num_crc_streams == 1) { + l64_crc[0] = Process64BytesCRC(crc_streams[0], l64_crc[0]); + crc_streams[0] += 16 * 4; + } else if (num_crc_streams == 2) { + Process64BytesCRC2Streams(crc_streams[0], crc_streams[1], l64_crc); + crc_streams[0] += 16 * 4; + crc_streams[1] += 16 * 4; + } else { + Process64BytesCRC3Streams(crc_streams[0], crc_streams[1], + crc_streams[2], l64_crc); + crc_streams[0] += 16 * 4; + crc_streams[1] += 16 * 4; + crc_streams[2] += 16 * 4; } V128 partialCRC[kMaxStreams][4]; @@ -511,24 +542,28 @@ // } // 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_crc_streams == 1) { + l64_crc[0] = Process64BytesCRC(crc_streams[0], l64_crc[0]); + crc_streams[0] += 16 * 4; + } else if (num_crc_streams == 2) { + Process64BytesCRC2Streams(crc_streams[0], crc_streams[1], l64_crc); + crc_streams[0] += 16 * 4; + crc_streams[1] += 16 * 4; + } else { + Process64BytesCRC3Streams(crc_streams[0], crc_streams[1], + crc_streams[2], l64_crc); + crc_streams[0] += 16 * 4; + crc_streams[1] += 16 * 4; + crc_streams[2] += 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; @@ -542,14 +577,15 @@ } // Combine all streams into single result. + static_assert(64 % (1 << kNumDroppedBits) == 0); 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 = MultiplyWithExtraX33(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 = MultiplyWithExtraX33(static_cast<uint32_t>(l64), magic); l64 ^= l64_pclmul[i]; } @@ -559,15 +595,26 @@ } else { p = crc_streams[num_crc_streams - 1]; } + l = static_cast<uint32_t>(l64); } - l = static_cast<uint32_t>(l64); + uint64_t remaining_bytes = static_cast<uint64_t>(e - p); + // Process the remaining bytes. while ((e - p) >= 16) { ABSL_INTERNAL_STEP8(l, p); ABSL_INTERNAL_STEP8(l, p); } - // Process the last few bytes - while (p != e) { + + if (remaining_bytes & 8) { + ABSL_INTERNAL_STEP8(l, p); + } + if (remaining_bytes & 4) { + ABSL_INTERNAL_STEP4(l, p); + } + if (remaining_bytes & 2) { + ABSL_INTERNAL_STEP2(l, p); + } + if (remaining_bytes & 1) { ABSL_INTERNAL_STEP1(l, p); } @@ -593,6 +640,8 @@ case CpuType::kAmdRome: case CpuType::kAmdNaples: case CpuType::kAmdMilan: + case CpuType::kAmdGenoa: + case CpuType::kAmdTurin: return new CRC32AcceleratedX86ARMCombinedMultipleStreams< 3, 1, CutoffStrategy::Fold3>(); // PCLMULQDQ is fast, use combined PCLMULQDQ + CRC implementation. @@ -600,6 +649,10 @@ case CpuType::kIntelSkylakeXeon: case CpuType::kIntelBroadwell: case CpuType::kIntelSkylake: + case CpuType::kIntelIcelake: + case CpuType::kIntelSapphirerapids: + case CpuType::kIntelEmeraldrapids: + case CpuType::kIntelGraniterapidsap: return new CRC32AcceleratedX86ARMCombinedMultipleStreams< 3, 2, CutoffStrategy::Fold3>(); // PCLMULQDQ is slow, don't use it. @@ -611,6 +664,7 @@ case CpuType::kArmNeoverseN1: case CpuType::kArmNeoverseN2: case CpuType::kArmNeoverseV1: + case CpuType::kArmNeoverseN3: return new CRC32AcceleratedX86ARMCombinedMultipleStreams< 1, 1, CutoffStrategy::Unroll64CRC>(); case CpuType::kAmpereSiryn:
diff --git a/absl/crc/internal/gen_crc32c_consts.py b/absl/crc/internal/gen_crc32c_consts.py new file mode 100755 index 0000000..f78ae30 --- /dev/null +++ b/absl/crc/internal/gen_crc32c_consts.py
@@ -0,0 +1,90 @@ +#!/usr/bin/env python3 +# +# Copyright 2025 The Abseil Authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""This script generates kCRC32CPowers[].""" + + +def poly_mul(a, b): + """Polynomial multiplication: a * b.""" + product = 0 + for i in range(b.bit_length()): + if (b & (1 << i)) != 0: + product ^= a << i + return product + + +def poly_div(a, b): + """Polynomial division: floor(a / b).""" + q = 0 + while a.bit_length() >= b.bit_length(): + q ^= 1 << (a.bit_length() - b.bit_length()) + a ^= b << (a.bit_length() - b.bit_length()) + return q + + +def poly_reduce(a, b): + """Polynomial reduction: a mod b.""" + return a ^ poly_mul(poly_div(a, b), b) + + +def poly_exp(a, b, g): + """Polynomial exponentiation: a^b mod g.""" + if b == 1: + return poly_reduce(a, g) + c = poly_exp(a, b // 2, g) + c = poly_mul(c, c) + if b % 2 != 0: + c = poly_mul(c, a) + return poly_reduce(c, g) + + +def bitreflect(a, num_bits): + """Reflects the bits of the given integer.""" + if a.bit_length() > num_bits: + raise ValueError(f'Integer has more than {num_bits} bits') + return sum(((a >> i) & 1) << (num_bits - 1 - i) for i in range(num_bits)) + + +G = 0x11EDC6F41 # The CRC-32C reducing polynomial, in the "natural" bit order +CRC_BITS = 32 # The degree of G, i.e. the 32 in "CRC-32C" +LSB_FIRST = True # CRC-32C is a least-significant-bit-first CRC +NUM_SIZE_BITS = 64 # The maximum number of bits in the length (size_t) +NUM_DROPPED_BITS = 4 # The number of bits dropped from the length +LOG2_BITS_PER_BYTE = 3 # log2 of the number of bits in a byte, i.e. log2(8) +X = 2 # The polynomial 'x', in the "natural" bit order + + +def print_crc32c_powers(): + """Generates kCRC32CPowers[]. + + kCRC32CPowers[] is an array of length NUM_SIZE_BITS - NUM_DROPPED_BITS, + whose i'th entry is x^(2^(i + LOG2_BITS_PER_BYTE + NUM_DROPPED_BITS) - + CRC_BITS - 1) mod G. See kCRC32CPowers[] in the C++ source for more info. + """ + for i in range(NUM_SIZE_BITS - NUM_DROPPED_BITS): + poly = poly_exp( + X, + 2 ** (i + LOG2_BITS_PER_BYTE + NUM_DROPPED_BITS) + - CRC_BITS + - (1 if LSB_FIRST else 0), + G, + ) + poly = bitreflect(poly, CRC_BITS) + print(f'0x{poly:0{2*CRC_BITS//8}x}, ', end='') + + +if __name__ == '__main__': + print_crc32c_powers()
diff --git a/absl/crc/internal/non_temporal_arm_intrinsics.h b/absl/crc/internal/non_temporal_arm_intrinsics.h index 9e5ccfc..a0d2f08 100644 --- a/absl/crc/internal/non_temporal_arm_intrinsics.h +++ b/absl/crc/internal/non_temporal_arm_intrinsics.h
@@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// SKIP_ABSL_INLINE_NAMESPACE_CHECK + #ifndef ABSL_CRC_INTERNAL_NON_TEMPORAL_ARM_INTRINSICS_H_ #define ABSL_CRC_INTERNAL_NON_TEMPORAL_ARM_INTRINSICS_H_ @@ -21,7 +23,7 @@ #include <arm_neon.h> typedef int64x2_t __m128i; /* 128-bit vector containing integers */ -#define vreinterpretq_m128i_s32(x) vreinterpretq_s64_s32(x) +#define vreinterpretq_m128i_s64(x) (x) #define vreinterpretq_s64_m128i(x) (x) // Guarantees that every preceding store is globally visible before any @@ -44,7 +46,7 @@ // 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)); + return vreinterpretq_m128i_s64(vld1q_s64((const int64_t*)p)); } // Stores the data in a to the address p without polluting the caches. If the
diff --git a/absl/debugging/BUILD.bazel b/absl/debugging/BUILD.bazel index cd0f1de..aad5e28 100644 --- a/absl/debugging/BUILD.bazel +++ b/absl/debugging/BUILD.bazel
@@ -14,6 +14,9 @@ # limitations under the License. # +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load( "//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS", @@ -33,6 +36,33 @@ licenses(["notice"]) cc_library( + name = "borrowed_fixup_buffer", + srcs = ["internal/borrowed_fixup_buffer.cc"], + hdrs = ["internal/borrowed_fixup_buffer.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:malloc_internal", + "//absl/hash", + ], +) + +cc_test( + name = "borrowed_fixup_buffer_test", + srcs = ["internal/borrowed_fixup_buffer_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":borrowed_fixup_buffer", + "//absl/base:config", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_library( name = "stacktrace", srcs = [ "internal/stacktrace_aarch64-inl.inc", @@ -51,10 +81,12 @@ copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ + ":borrowed_fixup_buffer", ":debugging_internal", "//absl/base:config", "//absl/base:core_headers", "//absl/base:dynamic_annotations", + "//absl/base:malloc_internal", "//absl/base:raw_logging_internal", ], ) @@ -65,9 +97,11 @@ copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ + ":borrowed_fixup_buffer", ":stacktrace", "//absl/base:config", "//absl/base:core_headers", + "//absl/base:errno_saver", "//absl/types:span", "@googletest//:gtest", "@googletest//:gtest_main", @@ -443,6 +477,7 @@ ":stacktrace", "//absl/base:config", "//absl/base:core_headers", + "//absl/cleanup", "@google_benchmark//:benchmark_main", ], )
diff --git a/absl/debugging/CMakeLists.txt b/absl/debugging/CMakeLists.txt index 60b138a..ab3a795 100644 --- a/absl/debugging/CMakeLists.txt +++ b/absl/debugging/CMakeLists.txt
@@ -18,6 +18,38 @@ absl_cc_library( NAME + borrowed_fixup_buffer + SRCS + "internal/borrowed_fixup_buffer.cc" + HDRS + "internal/borrowed_fixup_buffer.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::core_headers + absl::hash + absl::malloc_internal + PUBLIC +) + +absl_cc_test( + NAME + borrowed_fixup_buffer_test + SRCS + "internal/borrowed_fixup_buffer_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::borrowed_fixup_buffer + absl::config + GTest::gmock_main +) + +absl_cc_library( + NAME stacktrace HDRS "stacktrace.h" @@ -38,10 +70,12 @@ LINKOPTS $<$<BOOL:${EXECINFO_LIBRARY}>:${EXECINFO_LIBRARY}> DEPS + absl::borrowed_fixup_buffer absl::debugging_internal absl::config absl::core_headers absl::dynamic_annotations + absl::malloc_internal absl::raw_logging_internal PUBLIC ) @@ -57,6 +91,7 @@ absl::stacktrace absl::config absl::core_headers + absl::errno_saver absl::span GTest::gmock_main )
diff --git a/absl/debugging/failure_signal_handler.cc b/absl/debugging/failure_signal_handler.cc index d31f5a1..16609f1 100644 --- a/absl/debugging/failure_signal_handler.cc +++ b/absl/debugging/failure_signal_handler.cc
@@ -157,8 +157,8 @@ #ifdef ABSL_HAVE_SIGALTSTACK static bool SetupAlternateStackOnce() { -#if defined(__wasm__) || defined(__asjms__) - const size_t page_mask = getpagesize() - 1; +#if defined(__wasm__) || defined(__asmjs__) + const size_t page_mask = static_cast<size_t>(getpagesize()) - 1; #else const size_t page_mask = static_cast<size_t>(sysconf(_SC_PAGESIZE)) - 1; #endif
diff --git a/absl/debugging/internal/borrowed_fixup_buffer.cc b/absl/debugging/internal/borrowed_fixup_buffer.cc new file mode 100644 index 0000000..507a0a2 --- /dev/null +++ b/absl/debugging/internal/borrowed_fixup_buffer.cc
@@ -0,0 +1,118 @@ +// Copyright 2025 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/internal/borrowed_fixup_buffer.h" + +#include <assert.h> +#include <limits.h> +#include <stddef.h> +#include <stdint.h> + +#include <atomic> +#include <iterator> +#include <utility> + +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/base/internal/low_level_alloc.h" +#include "absl/hash/hash.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace internal_stacktrace { + +// A buffer for holding fix-up information for stack traces of common sizes_. +struct BorrowedFixupBuffer::FixupStackBuffer { + static constexpr size_t kMaxStackElements = 128; // Can be reduced if needed + std::atomic_flag in_use{}; + uintptr_t frames[kMaxStackElements]; + int sizes[kMaxStackElements]; + + ABSL_CONST_INIT static FixupStackBuffer g_instances[kNumStaticBuffers]; +}; + +ABSL_CONST_INIT BorrowedFixupBuffer::FixupStackBuffer + BorrowedFixupBuffer::FixupStackBuffer::g_instances[kNumStaticBuffers] = {}; + +BorrowedFixupBuffer::~BorrowedFixupBuffer() { + if (borrowed_) { + std::move(*this).Unlock(); + } else { + base_internal::LowLevelAlloc::Free(frames_); + } +} + +BorrowedFixupBuffer::BorrowedFixupBuffer(size_t length) + : borrowed_(0 < length && length <= FixupStackBuffer::kMaxStackElements + ? TryLock() + : nullptr) { + if (borrowed_) { + InitViaBorrow(); + } else { + InitViaAllocation(length); + } +} + +void BorrowedFixupBuffer::InitViaBorrow() { + assert(borrowed_); + frames_ = borrowed_->frames; + sizes_ = borrowed_->sizes; +} + +void BorrowedFixupBuffer::InitViaAllocation(size_t length) { + static_assert(alignof(decltype(*frames_)) >= alignof(decltype(*sizes_)), + "contiguous layout assumes decreasing alignment, otherwise " + "padding may be needed in the middle"); + assert(!borrowed_); + + base_internal::InitSigSafeArena(); + void* buf = base_internal::LowLevelAlloc::AllocWithArena( + length * (sizeof(*frames_) + sizeof(*sizes_)), + base_internal::SigSafeArena()); + + if (buf == nullptr) { + frames_ = nullptr; + sizes_ = nullptr; + return; + } + + frames_ = new (buf) uintptr_t[length]; + sizes_ = new (static_cast<void*>(static_cast<unsigned char*>(buf) + + length * sizeof(*frames_))) int[length]; +} + +[[nodiscard]] BorrowedFixupBuffer::FixupStackBuffer* +BorrowedFixupBuffer::TryLock() { + constexpr size_t kNumSlots = std::size(FixupStackBuffer::g_instances); + const size_t i = absl::Hash<const void*>()(this) % kNumSlots; + for (size_t k = 0; k < kNumSlots; ++k) { + auto* instance = &FixupStackBuffer::g_instances[(i + k) % kNumSlots]; + // Use memory_order_acquire to ensure that no reads and writes on the + // borrowed buffer are reordered before the borrowing. + if (!instance->in_use.test_and_set(std::memory_order_acquire)) { + return instance; + } + } + return nullptr; +} + +void BorrowedFixupBuffer::Unlock() && { + // Use memory_order_release to ensure that no reads and writes on the borrowed + // buffer are reordered after the borrowing. + borrowed_->in_use.clear(std::memory_order_release); +} + +} // namespace internal_stacktrace +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/debugging/internal/borrowed_fixup_buffer.h b/absl/debugging/internal/borrowed_fixup_buffer.h new file mode 100644 index 0000000..a8f00c8 --- /dev/null +++ b/absl/debugging/internal/borrowed_fixup_buffer.h
@@ -0,0 +1,71 @@ +// Copyright 2025 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_DEBUGGING_INTERNAL_BORROWED_FIXUP_BUFFER_H_ +#define ABSL_DEBUGGING_INTERNAL_BORROWED_FIXUP_BUFFER_H_ + +#include <stddef.h> +#include <stdint.h> +#include <stdlib.h> + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace internal_stacktrace { + +// An RAII type that temporarily acquires a buffer for stack trace fix-ups from +// a pool of preallocated buffers, or attempts to allocate a new buffer if no +// such buffer is available. +// When destroyed, returns the buffer to the pool if it borrowed successfully, +// otherwise deallocates any previously allocated buffer. +class BorrowedFixupBuffer { + public: + static constexpr size_t kNumStaticBuffers = 64; + ~BorrowedFixupBuffer(); + + // The number of frames to allocate space for. Note that allocations can fail. + explicit BorrowedFixupBuffer(size_t length); + + uintptr_t* frames() const { return frames_; } + int* sizes() const { return sizes_; } + + private: + struct FixupStackBuffer; + + uintptr_t* frames_; + int* sizes_; + + // The borrowed pre-existing buffer, if any (if we haven't allocated our own) + FixupStackBuffer* const borrowed_; + + void InitViaBorrow(); + void InitViaAllocation(size_t length); + + // Attempts to opportunistically borrow a small buffer in a thread- and + // signal-safe manner. Returns nullptr on failure. + [[nodiscard]] FixupStackBuffer* TryLock(); + + // Returns the borrowed buffer. + void Unlock() &&; + + BorrowedFixupBuffer(const BorrowedFixupBuffer&) = delete; + BorrowedFixupBuffer& operator=(const BorrowedFixupBuffer&) = delete; +}; + +} // namespace internal_stacktrace +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_DEBUGGING_INTERNAL_BORROWED_FIXUP_BUFFER_H_
diff --git a/absl/debugging/internal/borrowed_fixup_buffer_test.cc b/absl/debugging/internal/borrowed_fixup_buffer_test.cc new file mode 100644 index 0000000..a856c5d --- /dev/null +++ b/absl/debugging/internal/borrowed_fixup_buffer_test.cc
@@ -0,0 +1,97 @@ +// Copyright 2025 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/internal/borrowed_fixup_buffer.h" + +#include <stddef.h> +#include <stdint.h> + +#include <algorithm> +#include <functional> +#include <memory> + +#include "gtest/gtest.h" +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace internal_stacktrace { +namespace { + +TEST(BorrowedFixupBuffer, ProperReuse) { + uintptr_t first_borrowed_frame = 0; + uintptr_t first_borrowed_size = 0; + + // Ensure that we borrow the same buffer each time, indicating proper reuse. + // Disable loop unrolling. We need all iterations to match exactly, to coax + // reuse of the the same underlying buffer. +#if defined(__GNUC__) || defined(__clang__) +#pragma GCC unroll 1 // <= 1 disables unrolling +#endif + for (int i = 0; i < 100; ++i) { + BorrowedFixupBuffer buf0(0); + EXPECT_EQ(buf0.frames(), nullptr); + EXPECT_EQ(buf0.sizes(), nullptr); + + BorrowedFixupBuffer buf1(1); + EXPECT_NE(buf1.frames(), nullptr); + EXPECT_NE(buf1.sizes(), nullptr); + if (first_borrowed_frame == 0) { + first_borrowed_frame = reinterpret_cast<uintptr_t>(buf1.frames()); + } else { + EXPECT_EQ(reinterpret_cast<uintptr_t>(buf1.frames()), + first_borrowed_frame); + } + if (first_borrowed_size == 0) { + first_borrowed_size = reinterpret_cast<uintptr_t>(buf1.sizes()); + } else { + EXPECT_EQ(reinterpret_cast<uintptr_t>(buf1.sizes()), first_borrowed_size); + } + + BorrowedFixupBuffer buf2(2); + EXPECT_NE(buf2.frames(), buf1.frames()); + EXPECT_NE(buf2.sizes(), buf1.sizes()); + EXPECT_NE(buf2.frames(), nullptr); + EXPECT_NE(buf2.sizes(), nullptr); + } +} + +TEST(BorrowedFixupBuffer, NoOverlap) { + using BufferPtr = std::unique_ptr<BorrowedFixupBuffer>; + static constexpr std::less<const void*> less; + static constexpr size_t kBufLen = 5; + static constexpr size_t kNumBuffers = + BorrowedFixupBuffer::kNumStaticBuffers * 37 + 1; + + auto bufs = std::make_unique<BufferPtr[]>(kNumBuffers); + for (size_t i = 0; i < kNumBuffers; ++i) { + bufs[i] = std::make_unique<BorrowedFixupBuffer>(kBufLen); + } + + std::sort(bufs.get(), bufs.get() + kNumBuffers, + [](const BufferPtr& a, const BufferPtr& b) { + return less(a->frames(), b->frames()); + }); + + // Verify there are no overlaps + for (size_t i = 1; i < kNumBuffers; ++i) { + EXPECT_FALSE(less(bufs[i]->frames(), bufs[i - 1]->frames() + kBufLen)); + EXPECT_FALSE(less(bufs[i]->sizes(), bufs[i - 1]->sizes() + kBufLen)); + } +} + +} // namespace +} // namespace internal_stacktrace +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/debugging/internal/demangle.cc b/absl/debugging/internal/demangle.cc index 5f62ebb..a8d7511 100644 --- a/absl/debugging/internal/demangle.cc +++ b/absl/debugging/internal/demangle.cc
@@ -28,7 +28,7 @@ #include "absl/base/config.h" #include "absl/debugging/internal/demangle_rust.h" -#if ABSL_INTERNAL_HAS_CXA_DEMANGLE +#ifdef ABSL_INTERNAL_HAS_CXA_DEMANGLE #include <cxxabi.h> #endif @@ -2941,7 +2941,7 @@ std::string out; int status = 0; char* demangled = nullptr; -#if ABSL_INTERNAL_HAS_CXA_DEMANGLE +#ifdef ABSL_INTERNAL_HAS_CXA_DEMANGLE demangled = abi::__cxa_demangle(mangled, nullptr, nullptr, &status); #endif if (status == 0 && demangled != nullptr) {
diff --git a/absl/debugging/internal/demangle_test.cc b/absl/debugging/internal/demangle_test.cc index 2012184..1731197 100644 --- a/absl/debugging/internal/demangle_test.cc +++ b/absl/debugging/internal/demangle_test.cc
@@ -1936,11 +1936,11 @@ return g_demangle_result; } -// Demangle stack consumption should be within 8kB for simple mangled names +// Demangle stack consumption should be within 9kB for simple mangled names // with some level of nesting. With alternate signal stack we have 64K, // but some signal handlers run on thread stack, and could have arbitrarily // little space left (so we don't want to make this number too large). -const int kStackConsumptionUpperLimit = 8192; +const int kStackConsumptionUpperLimit = 9670; // Returns a mangled name nested to the given depth. static std::string NestedMangledName(int depth) {
diff --git a/absl/debugging/internal/elf_mem_image.h b/absl/debugging/internal/elf_mem_image.h index 1fac29c..c8a1036 100644 --- a/absl/debugging/internal/elf_mem_image.h +++ b/absl/debugging/internal/elf_mem_image.h
@@ -32,10 +32,10 @@ #error ABSL_HAVE_ELF_MEM_IMAGE cannot be directly set #endif -#if defined(__ELF__) && !defined(__OpenBSD__) && !defined(__QNX__) && \ - !defined(__native_client__) && !defined(__asmjs__) && \ - !defined(__wasm__) && !defined(__HAIKU__) && !defined(__sun) && \ - !defined(__VXWORKS__) && !defined(__hexagon__) && !defined(__XTENSA__) +#if defined(__ELF__) && !defined(__OpenBSD__) && !defined(__QNX__) && \ + !defined(__asmjs__) && !defined(__wasm__) && !defined(__HAIKU__) && \ + !defined(__sun) && !defined(__VXWORKS__) && !defined(__hexagon__) && \ + !defined(__XTENSA__) #define ABSL_HAVE_ELF_MEM_IMAGE 1 #endif
diff --git a/absl/debugging/internal/examine_stack.cc b/absl/debugging/internal/examine_stack.cc index 3dd6ba1..cc24ebd 100644 --- a/absl/debugging/internal/examine_stack.cc +++ b/absl/debugging/internal/examine_stack.cc
@@ -149,6 +149,10 @@ debug_stack_trace_hook = hook; } +SymbolizeUrlEmitterLegacy GetDebugStackTraceHookLegacy() { + return debug_stack_trace_hook; +} + SymbolizeUrlEmitter GetDebugStackTraceHook() { return debug_stack_trace_hook; } // Returns the program counter from signal context, nullptr if
diff --git a/absl/debugging/internal/examine_stack.h b/absl/debugging/internal/examine_stack.h index 190af87..2094d62 100644 --- a/absl/debugging/internal/examine_stack.h +++ b/absl/debugging/internal/examine_stack.h
@@ -32,12 +32,19 @@ // `hook` may be called from a signal handler. typedef void (*SymbolizeUrlEmitter)(void* const stack[], int depth, OutputWriter* writer, void* writer_arg); +typedef void (*SymbolizeUrlEmitterLegacy)(void* const stack[], int depth, + OutputWriter* writer, + void* writer_arg); // Registration of SymbolizeUrlEmitter for use inside of a signal handler. // This is inherently unsafe and must be signal safe code. void RegisterDebugStackTraceHook(SymbolizeUrlEmitter hook); SymbolizeUrlEmitter GetDebugStackTraceHook(); +// Currently exact copy of above. Needed for the 3-CL dance due to +// TCMallocDebugStackTraceHook dependency on this API. +SymbolizeUrlEmitterLegacy GetDebugStackTraceHookLegacy(); + // Returns the program counter from signal context, or nullptr if // unknown. `vuc` is a ucontext_t*. We use void* to avoid the use of // ucontext_t on non-POSIX systems.
diff --git a/absl/debugging/internal/stacktrace_aarch64-inl.inc b/absl/debugging/internal/stacktrace_aarch64-inl.inc index 1746b5d..bbdce77 100644 --- a/absl/debugging/internal/stacktrace_aarch64-inl.inc +++ b/absl/debugging/internal/stacktrace_aarch64-inl.inc
@@ -123,7 +123,7 @@ // earlier in the stack than the old_frame_pointer, then use it. If it is // later, then we have already unwound through it and it needs no special // handling. - if (pre_signal_frame_pointer >= old_frame_pointer) { + if (pre_signal_frame_pointer > old_frame_pointer) { new_frame_pointer = pre_signal_frame_pointer; } }
diff --git a/absl/debugging/internal/stacktrace_config.h b/absl/debugging/internal/stacktrace_config.h index 8f84a87..6fce5e9 100644 --- a/absl/debugging/internal/stacktrace_config.h +++ b/absl/debugging/internal/stacktrace_config.h
@@ -42,11 +42,14 @@ #define ABSL_STACKTRACE_INL_HEADER \ "absl/debugging/internal/stacktrace_emscripten-inl.inc" +// Android local modification: ignore __ANDROID_API__, use a weak symbol +// instead. This code must compile with API lower than 33, but we want to +// provide backtracing if supported by the OS at runtime. #elif defined(__ANDROID__) -#if defined(ABSL_HAVE_THREAD_LOCAL) +#ifdef ABSL_HAVE_THREAD_LOCAL #define ABSL_STACKTRACE_INL_HEADER \ "absl/debugging/internal/stacktrace_generic-inl.inc" -#endif +#endif // defined(ABSL_HAVE_THREAD_LOCAL) #elif defined(__linux__) && !defined(__ANDROID__)
diff --git a/absl/debugging/internal/stacktrace_emscripten-inl.inc b/absl/debugging/internal/stacktrace_emscripten-inl.inc index 2f39c70..9f87006 100644 --- a/absl/debugging/internal/stacktrace_emscripten-inl.inc +++ b/absl/debugging/internal/stacktrace_emscripten-inl.inc
@@ -52,13 +52,6 @@ // Waiting until static initializers run seems to be late enough. // This file is included into stacktrace.cc so this will only run once. ABSL_ATTRIBUTE_UNUSED static int stacktraces_enabler = []() { - // Check if we can even create stacktraces. If not, bail early and leave - // disable_stacktraces set as-is. - // clang-format off - if (!EM_ASM_INT({ return (typeof wasmOffsetConverter !== 'undefined'); })) { - return 0; - } - // clang-format on disable_stacktraces.store(false, std::memory_order_relaxed); return 0; }();
diff --git a/absl/debugging/internal/stacktrace_powerpc-inl.inc b/absl/debugging/internal/stacktrace_powerpc-inl.inc index f82ca8f..ade4edf 100644 --- a/absl/debugging/internal/stacktrace_powerpc-inl.inc +++ b/absl/debugging/internal/stacktrace_powerpc-inl.inc
@@ -223,8 +223,9 @@ } if (sizes != nullptr) { if (next_sp > sp) { - sizes[n] = absl::debugging_internal::StripPointerMetadata(next_sp) - - absl::debugging_internal::StripPointerMetadata(sp); + sizes[n] = static_cast<int>( + absl::debugging_internal::StripPointerMetadata(next_sp) - + absl::debugging_internal::StripPointerMetadata(sp)); } else { // A frame-size of 0 is used to indicate unknown frame size. sizes[n] = 0;
diff --git a/absl/debugging/internal/stacktrace_riscv-inl.inc b/absl/debugging/internal/stacktrace_riscv-inl.inc index f9919c6..7ae7fef 100644 --- a/absl/debugging/internal/stacktrace_riscv-inl.inc +++ b/absl/debugging/internal/stacktrace_riscv-inl.inc
@@ -162,7 +162,8 @@ absl::debugging_internal::StripPointerMetadata(frame_pointer); } if (sizes != nullptr) { - 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_x86-inl.inc b/absl/debugging/internal/stacktrace_x86-inl.inc index 96b128e..27ee32a 100644 --- a/absl/debugging/internal/stacktrace_x86-inl.inc +++ b/absl/debugging/internal/stacktrace_x86-inl.inc
@@ -245,6 +245,7 @@ } #endif + const size_t page_size = static_cast<size_t>(getpagesize()); const uintptr_t old_fp_u = reinterpret_cast<uintptr_t>(old_fp); const uintptr_t new_fp_u = reinterpret_cast<uintptr_t>(new_fp); @@ -261,19 +262,19 @@ // it's supposed to. if (STRICT_UNWINDING && (!WITH_CONTEXT || uc == nullptr || new_fp_u != GetFP(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; - + // With the stack growing downwards, older stack frame should be + // at a greater address that the current one. However if we get multiple + // signals handled on altstack the new frame pointer might return to the + // main stack, but be different than the value from the most recent + // ucontext. // 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) { + if (new_fp_u <= old_fp_u || new_fp_u - old_fp_u > kMaxFrameBytes) { + if (stack_high < kUnknownStackEnd && page_size < 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. @@ -310,7 +311,12 @@ if (new_fp_u >= 0xffffe000) return nullptr; #endif #if !defined(_WIN32) - if (!STRICT_UNWINDING) { + const uintptr_t old_fp_page = old_fp_u & ~(page_size - 1); + const uintptr_t new_fp_page = new_fp_u & ~(page_size - 1); + if (old_fp_page == new_fp_page && (new_fp_u & (sizeof(void*) - 1)) == 0) { + // We dereferenced the old_fp above, so it is safe to dereference + // new_fp if it's on the same page as the old_fp and is aligned. + } else if (!STRICT_UNWINDING) { // Lax sanity checks cause a crash in 32-bit tcmalloc/crash_reason_test // on AMD-based machines with VDSO-enabled kernels. // Make an extra sanity check to insure new_fp is readable.
diff --git a/absl/debugging/internal/symbolize.h b/absl/debugging/internal/symbolize.h index 5593fde..509f426 100644 --- a/absl/debugging/internal/symbolize.h +++ b/absl/debugging/internal/symbolize.h
@@ -28,8 +28,8 @@ #ifdef ABSL_INTERNAL_HAVE_ELF_SYMBOLIZE #error ABSL_INTERNAL_HAVE_ELF_SYMBOLIZE cannot be directly set -#elif defined(__ELF__) && defined(__GLIBC__) && !defined(__native_client__) \ - && !defined(__asmjs__) && !defined(__wasm__) +#elif defined(__ELF__) && defined(__GLIBC__) && !defined(__asmjs__) \ + && !defined(__wasm__) #define ABSL_INTERNAL_HAVE_ELF_SYMBOLIZE 1 #include <elf.h>
diff --git a/absl/debugging/internal/vdso_support.cc b/absl/debugging/internal/vdso_support.cc index 8a588ea..f7e2a44 100644 --- a/absl/debugging/internal/vdso_support.cc +++ b/absl/debugging/internal/vdso_support.cc
@@ -17,6 +17,7 @@ // VDSOSupport -- a class representing kernel VDSO (if present). #include "absl/debugging/internal/vdso_support.h" +#include "absl/base/attributes.h" #ifdef ABSL_HAVE_VDSO_SUPPORT // defined in vdso_support.h @@ -190,6 +191,9 @@ // This function must be very fast, and may be called from very // low level (e.g. tcmalloc). Hence I avoid things like // GoogleOnceInit() and ::operator new. +// The destination in VDSO is unknown to CFI and VDSO does not set MSAN +// shadow for the return value. +ABSL_ATTRIBUTE_NO_SANITIZE_CFI ABSL_ATTRIBUTE_NO_SANITIZE_MEMORY int GetCPU() { unsigned cpu;
diff --git a/absl/debugging/stacktrace.cc b/absl/debugging/stacktrace.cc index f71e80c..1a770af 100644 --- a/absl/debugging/stacktrace.cc +++ b/absl/debugging/stacktrace.cc
@@ -38,6 +38,7 @@ #include <stddef.h> #include <stdint.h> +#include <stdlib.h> #include <algorithm> #include <atomic> @@ -46,46 +47,23 @@ #include "absl/base/config.h" #include "absl/base/optimization.h" #include "absl/base/port.h" +#include "absl/debugging/internal/borrowed_fixup_buffer.h" #include "absl/debugging/internal/stacktrace_config.h" -#ifdef ABSL_INTERNAL_HAVE_ALLOCA -#error ABSL_INTERNAL_HAVE_ALLOCA cannot be directly set -#endif - -#ifdef _WIN32 -#include <malloc.h> -#define ABSL_INTERNAL_HAVE_ALLOCA 1 -#else -#ifdef __has_include -#if __has_include(<alloca.h>) -#include <alloca.h> -#define ABSL_INTERNAL_HAVE_ALLOCA 1 -#elif !defined(alloca) -static void* alloca(size_t) noexcept { return nullptr; } -#endif -#endif -#endif - -#ifdef ABSL_INTERNAL_HAVE_ALLOCA -static constexpr bool kHaveAlloca = true; -#else -static constexpr bool kHaveAlloca = false; -#endif - #if defined(ABSL_STACKTRACE_INL_HEADER) #include ABSL_STACKTRACE_INL_HEADER #else -# error Cannot calculate stack trace: will need to write for your environment +#error Cannot calculate stack trace: will need to write for your environment -# include "absl/debugging/internal/stacktrace_aarch64-inl.inc" -# include "absl/debugging/internal/stacktrace_arm-inl.inc" -# include "absl/debugging/internal/stacktrace_emscripten-inl.inc" -# include "absl/debugging/internal/stacktrace_generic-inl.inc" -# include "absl/debugging/internal/stacktrace_powerpc-inl.inc" -# include "absl/debugging/internal/stacktrace_riscv-inl.inc" -# include "absl/debugging/internal/stacktrace_unimplemented-inl.inc" -# include "absl/debugging/internal/stacktrace_win32-inl.inc" -# include "absl/debugging/internal/stacktrace_x86-inl.inc" +#include "absl/debugging/internal/stacktrace_aarch64-inl.inc" +#include "absl/debugging/internal/stacktrace_arm-inl.inc" +#include "absl/debugging/internal/stacktrace_emscripten-inl.inc" +#include "absl/debugging/internal/stacktrace_generic-inl.inc" +#include "absl/debugging/internal/stacktrace_powerpc-inl.inc" +#include "absl/debugging/internal/stacktrace_riscv-inl.inc" +#include "absl/debugging/internal/stacktrace_unimplemented-inl.inc" +#include "absl/debugging/internal/stacktrace_win32-inl.inc" +#include "absl/debugging/internal/stacktrace_x86-inl.inc" #endif namespace absl { @@ -97,25 +75,63 @@ template <bool IS_STACK_FRAMES, bool IS_WITH_CONTEXT> ABSL_ATTRIBUTE_ALWAYS_INLINE inline int Unwind(void** result, uintptr_t* frames, - int* sizes, int max_depth, + int* sizes, size_t max_depth, int skip_count, const void* uc, - int* min_dropped_frames) { + int* min_dropped_frames, + bool unwind_with_fixup = true) { + unwind_with_fixup = + unwind_with_fixup && internal_stacktrace::ShouldFixUpStack(); + +#ifdef _WIN32 + if (unwind_with_fixup) { + // TODO(b/434184677): Fixups are flaky and not supported on Windows + unwind_with_fixup = false; +#ifndef NDEBUG + abort(); +#endif + } +#endif + + // Some implementations of FixUpStack may need to be passed frame + // information from Unwind, even if the caller doesn't need that + // information. We allocate the necessary buffers for such implementations + // here. + const internal_stacktrace::BorrowedFixupBuffer fixup_buffer( + unwind_with_fixup ? max_depth : 0); + if (frames == nullptr) { + frames = fixup_buffer.frames(); + } + if (sizes == nullptr) { + sizes = fixup_buffer.sizes(); + } + Unwinder g = custom.load(std::memory_order_acquire); - int size; + size_t size; // Add 1 to skip count for the unwinder function itself ++skip_count; if (g != nullptr) { - size = (*g)(result, sizes, max_depth, skip_count, uc, min_dropped_frames); + size = static_cast<size_t>((*g)(result, sizes, static_cast<int>(max_depth), + skip_count, uc, min_dropped_frames)); // Frame pointers aren't returned by existing hooks, so clear them. if (frames != nullptr) { std::fill(frames, frames + size, uintptr_t()); } } else { - size = UnwindImpl<IS_STACK_FRAMES, IS_WITH_CONTEXT>( - result, frames, sizes, max_depth, skip_count, uc, min_dropped_frames); + size = static_cast<size_t>( + unwind_with_fixup + ? UnwindImpl<true, IS_WITH_CONTEXT>( + result, frames, sizes, static_cast<int>(max_depth), + skip_count, uc, min_dropped_frames) + : UnwindImpl<IS_STACK_FRAMES, IS_WITH_CONTEXT>( + result, frames, sizes, static_cast<int>(max_depth), + skip_count, uc, min_dropped_frames)); } + if (unwind_with_fixup) { + internal_stacktrace::FixUpStack(result, frames, sizes, max_depth, size); + } + ABSL_BLOCK_TAIL_CALL_OPTIMIZATION(); - return size; + return static_cast<int>(size); } } // anonymous namespace @@ -123,15 +139,8 @@ ABSL_ATTRIBUTE_NOINLINE ABSL_ATTRIBUTE_NO_TAIL_CALL int internal_stacktrace::GetStackFrames(void** result, uintptr_t* frames, int* sizes, int max_depth, int skip_count) { - if (internal_stacktrace::ShouldFixUpStack()) { - size_t depth = static_cast<size_t>(Unwind<true, true>( - result, frames, sizes, max_depth, skip_count, nullptr, nullptr)); - internal_stacktrace::FixUpStack(result, frames, sizes, - static_cast<size_t>(max_depth), depth); - return static_cast<int>(depth); - } - - return Unwind<true, false>(result, frames, sizes, max_depth, skip_count, + return Unwind<true, false>(result, frames, sizes, + static_cast<size_t>(max_depth), skip_count, nullptr, nullptr); } @@ -140,56 +149,32 @@ int* sizes, int max_depth, int skip_count, const void* uc, int* min_dropped_frames) { - if (internal_stacktrace::ShouldFixUpStack()) { - size_t depth = static_cast<size_t>(Unwind<true, true>( - result, frames, sizes, max_depth, skip_count, uc, min_dropped_frames)); - internal_stacktrace::FixUpStack(result, frames, sizes, - static_cast<size_t>(max_depth), depth); - return static_cast<int>(depth); - } - - return Unwind<true, true>(result, frames, sizes, max_depth, skip_count, uc, + return Unwind<true, true>(result, frames, sizes, + static_cast<size_t>(max_depth), skip_count, uc, min_dropped_frames); } +ABSL_ATTRIBUTE_NOINLINE ABSL_ATTRIBUTE_NO_TAIL_CALL int +internal_stacktrace::GetStackTraceNoFixup(void** result, int max_depth, + int skip_count) { + return Unwind<false, false>(result, nullptr, nullptr, + static_cast<size_t>(max_depth), skip_count, + nullptr, nullptr, /*unwind_with_fixup=*/false); +} + ABSL_ATTRIBUTE_NOINLINE ABSL_ATTRIBUTE_NO_TAIL_CALL int GetStackTrace( void** result, int max_depth, int skip_count) { - if (internal_stacktrace::ShouldFixUpStack()) { - if constexpr (kHaveAlloca) { - const size_t nmax = static_cast<size_t>(max_depth); - uintptr_t* frames = - static_cast<uintptr_t*>(alloca(nmax * sizeof(*frames))); - int* sizes = static_cast<int*>(alloca(nmax * sizeof(*sizes))); - size_t depth = static_cast<size_t>(Unwind<true, false>( - result, frames, sizes, max_depth, skip_count, nullptr, nullptr)); - internal_stacktrace::FixUpStack(result, frames, sizes, nmax, depth); - return static_cast<int>(depth); - } - } - - return Unwind<false, false>(result, nullptr, nullptr, max_depth, skip_count, + return Unwind<false, false>(result, nullptr, nullptr, + static_cast<size_t>(max_depth), skip_count, nullptr, nullptr); } ABSL_ATTRIBUTE_NOINLINE ABSL_ATTRIBUTE_NO_TAIL_CALL int GetStackTraceWithContext(void** result, int max_depth, int skip_count, const void* uc, int* min_dropped_frames) { - if (internal_stacktrace::ShouldFixUpStack()) { - if constexpr (kHaveAlloca) { - const size_t nmax = static_cast<size_t>(max_depth); - uintptr_t* frames = - static_cast<uintptr_t*>(alloca(nmax * sizeof(*frames))); - int* sizes = static_cast<int*>(alloca(nmax * sizeof(*sizes))); - size_t depth = static_cast<size_t>( - Unwind<true, true>(result, frames, sizes, max_depth, skip_count, uc, - min_dropped_frames)); - internal_stacktrace::FixUpStack(result, frames, sizes, nmax, depth); - return static_cast<int>(depth); - } - } - - return Unwind<false, true>(result, nullptr, nullptr, max_depth, skip_count, - uc, min_dropped_frames); + return Unwind<false, true>(result, nullptr, nullptr, + static_cast<size_t>(max_depth), skip_count, uc, + min_dropped_frames); } void SetStackUnwinder(Unwinder w) {
diff --git a/absl/debugging/stacktrace.h b/absl/debugging/stacktrace.h index 8777172..79f7651 100644 --- a/absl/debugging/stacktrace.h +++ b/absl/debugging/stacktrace.h
@@ -69,6 +69,9 @@ int* sizes, int max_depth, int skip_count, const void* uc, int* min_dropped_frames); +// As above, but skips fix-ups for efficiency. +extern int GetStackTraceNoFixup(void** result, int max_depth, int skip_count); + // Same as `absl::DefaultStackUnwinder`, but with an optional `frames` parameter // to allow callers to receive the raw stack frame addresses. // This is internal for now; do not depend on this externally.
diff --git a/absl/debugging/stacktrace_benchmark.cc b/absl/debugging/stacktrace_benchmark.cc index 9360baf..eef9850 100644 --- a/absl/debugging/stacktrace_benchmark.cc +++ b/absl/debugging/stacktrace_benchmark.cc
@@ -12,12 +12,25 @@ // See the License for the specific language governing permissions and // limitations under the License. +#include <stddef.h> +#include <stdint.h> + #include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/optimization.h" +#include "absl/cleanup/cleanup.h" #include "absl/debugging/stacktrace.h" #include "benchmark/benchmark.h" +static bool g_enable_fixup = false; + +#if ABSL_HAVE_ATTRIBUTE_WEAK +// Override these weak symbols if possible. +bool absl::internal_stacktrace::ShouldFixUpStack() { return g_enable_fixup; } +void absl::internal_stacktrace::FixUpStack(void**, uintptr_t*, int*, size_t, + size_t&) {} +#endif + namespace absl { ABSL_NAMESPACE_BEGIN namespace { @@ -42,14 +55,24 @@ func(state, --x, depth); } +template <bool EnableFixup> void BM_GetStackTrace(benchmark::State& state) { + const Cleanup restore_state( + [prev = g_enable_fixup]() { g_enable_fixup = prev; }); + g_enable_fixup = EnableFixup; int depth = state.range(0); for (auto s : state) { func(state, depth, depth); } } -BENCHMARK(BM_GetStackTrace)->DenseRange(10, kMaxStackDepth, 10); +#if ABSL_HAVE_ATTRIBUTE_WEAK +auto& BM_GetStackTraceWithFixup = BM_GetStackTrace<true>; +BENCHMARK(BM_GetStackTraceWithFixup)->DenseRange(10, kMaxStackDepth, 10); +#endif + +auto& BM_GetStackTraceWithoutFixup = BM_GetStackTrace<false>; +BENCHMARK(BM_GetStackTraceWithoutFixup)->DenseRange(10, kMaxStackDepth, 10); } // namespace ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/debugging/stacktrace_test.cc b/absl/debugging/stacktrace_test.cc index 4477d84..177db64 100644 --- a/absl/debugging/stacktrace_test.cc +++ b/absl/debugging/stacktrace_test.cc
@@ -18,17 +18,23 @@ #include <stdint.h> #include <algorithm> +#include <cerrno> +#include <csignal> +#include <cstring> +#include <memory> #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/base/attributes.h" #include "absl/base/config.h" +#include "absl/base/internal/errno_saver.h" #include "absl/base/optimization.h" #include "absl/types/span.h" static int g_should_fixup_calls = 0; static int g_fixup_calls = 0; static bool g_enable_fixup = false; +static uintptr_t g_last_fixup_frame_address = 0; #if ABSL_HAVE_ATTRIBUTE_WEAK bool absl::internal_stacktrace::ShouldFixUpStack() { @@ -38,6 +44,11 @@ void absl::internal_stacktrace::FixUpStack(void**, uintptr_t*, int*, size_t, size_t&) { + const void* frame_address = nullptr; +#if ABSL_HAVE_BUILTIN(__builtin_frame_address) + frame_address = __builtin_frame_address(0); +#endif + g_last_fixup_frame_address = reinterpret_cast<uintptr_t>(frame_address); ++g_fixup_calls; } #endif @@ -82,11 +93,29 @@ // This is a separate function to avoid inlining. ABSL_ATTRIBUTE_NOINLINE static void FixupNoFixupEquivalenceNoInline() { -#if defined(__riscv) - GTEST_SKIP() << "Skipping test on RISC-V due to pre-existing failure"; +#if !ABSL_HAVE_ATTRIBUTE_WEAK + const char* kSkipReason = "Need weak symbol support"; +#elif defined(__riscv) + const char* kSkipReason = + "Skipping test on RISC-V due to pre-existing failure"; +#elif defined(_WIN32) + // TODO(b/434184677): Add support for fixups on Windows if needed + const char* kSkipReason = + "Skipping test on Windows due to lack of support for fixups"; +#else + const char* kSkipReason = nullptr; #endif -#if ABSL_HAVE_ATTRIBUTE_WEAK + // This conditional is to avoid an unreachable code warning. + if (kSkipReason != nullptr) { + GTEST_SKIP() << kSkipReason; + } + + bool can_rely_on_frame_pointers = false; + if (!can_rely_on_frame_pointers) { + GTEST_SKIP() << "Frame pointers are required, but not guaranteed in OSS"; + } + // This test is known not to pass on MSVC (due to weak symbols) const Cleanup restore_state([enable_fixup = g_enable_fixup, @@ -206,15 +235,122 @@ ContainerEq(absl::MakeSpan(b.frames, static_cast<size_t>(b.depth)))); EXPECT_GT(g_should_fixup_calls, 0); EXPECT_GE(g_should_fixup_calls, g_fixup_calls); - - // ========================================================================== -#else - GTEST_SKIP() << "Need weak symbol support"; -#endif } TEST(StackTrace, FixupNoFixupEquivalence) { FixupNoFixupEquivalenceNoInline(); } +TEST(StackTrace, FixupLowStackUsage) { +#if !ABSL_HAVE_ATTRIBUTE_WEAK + const char* kSkipReason = "Skipping test on MSVC due to weak symbols"; +#elif defined(_WIN32) + // TODO(b/434184677): Add support for fixups on Windows if needed + const char* kSkipReason = + "Skipping test on Windows due to lack of support for fixups"; +#else + const char* kSkipReason = nullptr; +#endif + + // This conditional is to avoid an unreachable code warning. + if (kSkipReason != nullptr) { + GTEST_SKIP() << kSkipReason; + } + + const Cleanup restore_state([enable_fixup = g_enable_fixup, + fixup_calls = g_fixup_calls, + should_fixup_calls = g_should_fixup_calls]() { + g_enable_fixup = enable_fixup; + g_fixup_calls = fixup_calls; + g_should_fixup_calls = should_fixup_calls; + }); + + g_enable_fixup = true; + + // Request a ton of stack frames, regardless of how many are actually used. + // It's fine to request more frames than we have, since functions preallocate + // memory before discovering how high the stack really is, and we're really + // just trying to make sure the preallocations don't overflow the stack. + // + // Note that we loop in order to cover all sides of any branches in the + // implementation that switch allocation behavior (e.g., from stack to heap) + // and to ensure that no sides allocate too much stack space. + constexpr size_t kPageSize = 4096; + for (size_t depth = 2; depth < (1 << 20); depth += depth / 2) { + const auto stack = std::make_unique<void*[]>(depth); + const auto frames = std::make_unique<int[]>(depth); + + absl::GetStackFrames(stack.get(), frames.get(), static_cast<int>(depth), 0); + const void* frame_address = nullptr; +#if ABSL_HAVE_BUILTIN(__builtin_frame_address) + frame_address = __builtin_frame_address(0); +#endif + size_t stack_usage = + reinterpret_cast<uintptr_t>(frame_address) - g_last_fixup_frame_address; + EXPECT_LT(stack_usage, kPageSize); + } +} + +TEST(StackTrace, CustomUnwinderPerformsFixup) { +#if !ABSL_HAVE_ATTRIBUTE_WEAK + const char* kSkipReason = "Need weak symbol support"; +#elif defined(_WIN32) + // TODO(b/434184677): Add support for fixups on Windows if needed + const char* kSkipReason = + "Skipping test on Windows due to lack of support for fixups"; +#else + const char* kSkipReason = nullptr; +#endif + + // This conditional is to avoid an unreachable code warning. + if (kSkipReason != nullptr) { + GTEST_SKIP() << kSkipReason; + } + + constexpr int kSkip = 1; // Skip our own frame, whose return PCs won't match + constexpr auto kStackCount = 1; + + absl::SetStackUnwinder(absl::DefaultStackUnwinder); + const Cleanup restore_state([enable_fixup = g_enable_fixup, + fixup_calls = g_fixup_calls, + should_fixup_calls = g_should_fixup_calls]() { + absl::SetStackUnwinder(nullptr); + g_enable_fixup = enable_fixup; + g_fixup_calls = fixup_calls; + g_should_fixup_calls = should_fixup_calls; + }); + + StackTrace trace; + + g_enable_fixup = true; + g_should_fixup_calls = 0; + g_fixup_calls = 0; + absl::GetStackTrace(trace.result, kSkip, kStackCount); + EXPECT_GT(g_should_fixup_calls, 0); + EXPECT_GT(g_fixup_calls, 0); + + g_enable_fixup = true; + g_should_fixup_calls = 0; + g_fixup_calls = 0; + absl::GetStackFrames(trace.result, trace.sizes, kSkip, kStackCount); + EXPECT_GT(g_should_fixup_calls, 0); + EXPECT_GT(g_fixup_calls, 0); + + g_enable_fixup = true; + g_should_fixup_calls = 0; + g_fixup_calls = 0; + absl::GetStackTraceWithContext(trace.result, kSkip, kStackCount, nullptr, + nullptr); + EXPECT_GT(g_should_fixup_calls, 0); + EXPECT_GT(g_fixup_calls, 0); + + g_enable_fixup = true; + g_should_fixup_calls = 0; + g_fixup_calls = 0; + absl::GetStackFramesWithContext(trace.result, trace.sizes, kSkip, kStackCount, + nullptr, nullptr); + EXPECT_GT(g_should_fixup_calls, 0); + EXPECT_GT(g_fixup_calls, 0); +} + #if ABSL_HAVE_BUILTIN(__builtin_frame_address) struct FrameInfo { const void* return_address; @@ -295,4 +431,75 @@ } #endif +// This test is Linux specific. +#if defined(__linux__) +const void* g_return_address = nullptr; +bool g_sigusr2_raised = false; + +void SigUsr2Handler(int, siginfo_t*, void* uc) { + absl::base_internal::ErrnoSaver errno_saver; + // Many platforms don't support this by default. + bool support_is_expected = false; + constexpr int kMaxStackDepth = 64; + void* result[kMaxStackDepth]; + int depth = + absl::GetStackTraceWithContext(result, kMaxStackDepth, 0, uc, nullptr); + // Verify we can unwind past the nested signal handlers. + if (support_is_expected) { + EXPECT_THAT(absl::MakeSpan(result, static_cast<size_t>(depth)), + Contains(g_return_address).Times(1)); + } + depth = absl::GetStackTrace(result, kMaxStackDepth, 0); + if (support_is_expected) { + EXPECT_THAT(absl::MakeSpan(result, static_cast<size_t>(depth)), + Contains(g_return_address).Times(1)); + } + g_sigusr2_raised = true; +} + +void SigUsr1Handler(int, siginfo_t*, void*) { + raise(SIGUSR2); + ABSL_BLOCK_TAIL_CALL_OPTIMIZATION(); +} + +ABSL_ATTRIBUTE_NOINLINE void RaiseSignal() { + g_return_address = __builtin_return_address(0); + raise(SIGUSR1); + ABSL_BLOCK_TAIL_CALL_OPTIMIZATION(); +} + +ABSL_ATTRIBUTE_NOINLINE void TestNestedSignal() { + constexpr size_t kAltstackSize = 1 << 14; + // Allocate altstack on regular stack to make sure it'll have a higher + // address than some of the regular stack frames. + char space[kAltstackSize]; + stack_t altstack; + stack_t old_stack; + altstack.ss_sp = space; + altstack.ss_size = kAltstackSize; + altstack.ss_flags = 0; + ASSERT_EQ(sigaltstack(&altstack, &old_stack), 0) << strerror(errno); + struct sigaction act; + struct sigaction oldusr1act; + struct sigaction oldusr2act; + act.sa_sigaction = SigUsr1Handler; + act.sa_flags = SA_SIGINFO | SA_ONSTACK; + sigemptyset(&act.sa_mask); + ASSERT_EQ(sigaction(SIGUSR1, &act, &oldusr1act), 0) << strerror(errno); + act.sa_sigaction = SigUsr2Handler; + ASSERT_EQ(sigaction(SIGUSR2, &act, &oldusr2act), 0) << strerror(errno); + RaiseSignal(); + ASSERT_EQ(sigaltstack(&old_stack, nullptr), 0) << strerror(errno); + ASSERT_EQ(sigaction(SIGUSR1, &oldusr1act, nullptr), 0) << strerror(errno); + ASSERT_EQ(sigaction(SIGUSR2, &oldusr2act, nullptr), 0) << strerror(errno); + ABSL_BLOCK_TAIL_CALL_OPTIMIZATION(); +} + +TEST(StackTrace, NestedSignal) { + // Verify we can unwind past the nested signal handlers. + TestNestedSignal(); + EXPECT_TRUE(g_sigusr2_raised); +} +#endif + } // namespace
diff --git a/absl/debugging/symbolize_elf.inc b/absl/debugging/symbolize_elf.inc index 9836c93..0317bbc 100644 --- a/absl/debugging/symbolize_elf.inc +++ b/absl/debugging/symbolize_elf.inc
@@ -167,22 +167,22 @@ // We are using SpinLock and not a Mutex here, because we may be called // from inside Mutex::Lock itself, and it prohibits recursive calls. // This happens in e.g. base/stacktrace_syscall_unittest. -// Moreover, we are using only TryLock(), if the decorator list +// Moreover, we are using only try_lock(), if the decorator list // is being modified (is busy), we skip all decorators, and possibly // loose some info. Sorry, that's the best we could do. ABSL_CONST_INIT absl::base_internal::SpinLock g_decorators_mu( - absl::kConstInit, absl::base_internal::SCHEDULE_KERNEL_ONLY); + absl::base_internal::SCHEDULE_KERNEL_ONLY); const int kMaxFileMappingHints = 8; int g_num_file_mapping_hints; FileMappingHint g_file_mapping_hints[kMaxFileMappingHints]; // Protects g_file_mapping_hints. ABSL_CONST_INIT absl::base_internal::SpinLock g_file_mapping_mu( - absl::kConstInit, absl::base_internal::SCHEDULE_KERNEL_ONLY); + absl::base_internal::SCHEDULE_KERNEL_ONLY); // Async-signal-safe function to zero a buffer. // memset() is not guaranteed to be async-signal-safe. -static void SafeMemZero(void* p, size_t size) { +static void SafeMemZero(void *p, size_t size) { unsigned char *c = static_cast<unsigned char *>(p); while (size--) { *c++ = 0; @@ -233,29 +233,6 @@ }; // --------------------------------------------------------------- -// An async-signal-safe arena for LowLevelAlloc -static std::atomic<base_internal::LowLevelAlloc::Arena *> g_sig_safe_arena; - -static base_internal::LowLevelAlloc::Arena *SigSafeArena() { - return g_sig_safe_arena.load(std::memory_order_acquire); -} - -static void InitSigSafeArena() { - if (SigSafeArena() == nullptr) { - base_internal::LowLevelAlloc::Arena *new_arena = - base_internal::LowLevelAlloc::NewArena( - base_internal::LowLevelAlloc::kAsyncSignalSafe); - base_internal::LowLevelAlloc::Arena *old_value = nullptr; - if (!g_sig_safe_arena.compare_exchange_strong(old_value, new_arena, - std::memory_order_release, - std::memory_order_relaxed)) { - // We lost a race to allocate an arena; deallocate. - base_internal::LowLevelAlloc::DeleteArena(new_arena); - } - } -} - -// --------------------------------------------------------------- // An AddrMap is a vector of ObjFile, using SigSafeArena() for allocation. class AddrMap { @@ -287,7 +264,7 @@ size_t new_allocated = allocated_ * 2 + 50; ObjFile *new_obj_ = static_cast<ObjFile *>(base_internal::LowLevelAlloc::AllocWithArena( - new_allocated * sizeof(*new_obj_), SigSafeArena())); + new_allocated * sizeof(*new_obj_), base_internal::SigSafeArena())); if (obj_) { memcpy(new_obj_, obj_, allocated_ * sizeof(*new_obj_)); base_internal::LowLevelAlloc::Free(obj_); @@ -335,8 +312,9 @@ private: char *CopyString(const char *s) { size_t len = strlen(s); - char *dst = static_cast<char *>( - base_internal::LowLevelAlloc::AllocWithArena(len + 1, SigSafeArena())); + char *dst = + static_cast<char *>(base_internal::LowLevelAlloc::AllocWithArena( + len + 1, base_internal::SigSafeArena())); ABSL_RAW_CHECK(dst != nullptr, "out of memory"); memcpy(dst, s, len + 1); return dst; @@ -441,14 +419,14 @@ // Return (and set null) g_cached_symbolized_state if it is not null. // Otherwise return a new symbolizer. static Symbolizer *AllocateSymbolizer() { - InitSigSafeArena(); + base_internal::InitSigSafeArena(); Symbolizer *symbolizer = g_cached_symbolizer.exchange(nullptr, std::memory_order_acquire); if (symbolizer != nullptr) { return symbolizer; } return new (base_internal::LowLevelAlloc::AllocWithArena( - SymbolizerSize(), SigSafeArena())) Symbolizer(); + SymbolizerSize(), base_internal::SigSafeArena())) Symbolizer(); } // Set g_cached_symbolize_state to s if it is null, otherwise @@ -1469,14 +1447,15 @@ constexpr int interesting = PF_X | PF_R; #endif - if (phdr.p_type != PT_LOAD - || (phdr.p_flags & interesting) != interesting) { + if (phdr.p_type != PT_LOAD || + (phdr.p_flags & interesting) != interesting) { // Not a LOAD segment, not executable code, and not a function // descriptor. continue; } if (num_interesting_load_segments < obj->phdr.size()) { - memcpy(&obj->phdr[num_interesting_load_segments++], &phdr, sizeof(phdr)); + memcpy(&obj->phdr[num_interesting_load_segments++], &phdr, + sizeof(phdr)); } else { ABSL_RAW_LOG( WARNING, "%s: too many interesting LOAD segments: %zu >= %zu", @@ -1525,7 +1504,8 @@ ABSL_RAW_CHECK(p.p_type == PT_NULL, "unexpected p_type"); break; } - if (pc < reinterpret_cast<void *>(start_addr + p.p_vaddr + p.p_memsz)) { + if (pc < + reinterpret_cast<void *>(start_addr + p.p_vaddr + p.p_memsz)) { phdr = &p; break; } @@ -1569,7 +1549,7 @@ #endif } - if (g_decorators_mu.TryLock()) { + if (g_decorators_mu.try_lock()) { if (g_num_decorators > 0) { SymbolDecoratorArgs decorator_args = { pc, relocation, fd, symbol_buf_, sizeof(symbol_buf_), @@ -1579,7 +1559,7 @@ g_decorators[i].fn(&decorator_args); } } - g_decorators_mu.Unlock(); + g_decorators_mu.unlock(); } if (symbol_buf_[0] == '\0') { return nullptr; @@ -1625,17 +1605,17 @@ } bool RemoveAllSymbolDecorators(void) { - if (!g_decorators_mu.TryLock()) { + if (!g_decorators_mu.try_lock()) { // Someone else is using decorators. Get out. return false; } g_num_decorators = 0; - g_decorators_mu.Unlock(); + g_decorators_mu.unlock(); return true; } bool RemoveSymbolDecorator(int ticket) { - if (!g_decorators_mu.TryLock()) { + if (!g_decorators_mu.try_lock()) { // Someone else is using decorators. Get out. return false; } @@ -1649,14 +1629,14 @@ break; } } - g_decorators_mu.Unlock(); + g_decorators_mu.unlock(); return true; // Decorator is known to be removed. } int InstallSymbolDecorator(SymbolDecorator decorator, void *arg) { static int ticket = 0; - if (!g_decorators_mu.TryLock()) { + if (!g_decorators_mu.try_lock()) { // Someone else is using decorators. Get out. return -2; } @@ -1667,18 +1647,18 @@ g_decorators[g_num_decorators] = {decorator, arg, ticket++}; ++g_num_decorators; } - g_decorators_mu.Unlock(); + g_decorators_mu.unlock(); return ret; } -bool RegisterFileMappingHint(const void *start, const void *end, uint64_t offset, - const char *filename) { +bool RegisterFileMappingHint(const void *start, const void *end, + uint64_t offset, const char *filename) { SAFE_ASSERT(start <= end); SAFE_ASSERT(filename != nullptr); - InitSigSafeArena(); + base_internal::InitSigSafeArena(); - if (!g_file_mapping_mu.TryLock()) { + if (!g_file_mapping_mu.try_lock()) { return false; } @@ -1688,8 +1668,9 @@ } else { // TODO(ckennelly): Move this into a string copy routine. size_t len = strlen(filename); - char *dst = static_cast<char *>( - base_internal::LowLevelAlloc::AllocWithArena(len + 1, SigSafeArena())); + char *dst = + static_cast<char *>(base_internal::LowLevelAlloc::AllocWithArena( + len + 1, base_internal::SigSafeArena())); ABSL_RAW_CHECK(dst != nullptr, "out of memory"); memcpy(dst, filename, len + 1); @@ -1700,13 +1681,13 @@ hint.filename = dst; } - g_file_mapping_mu.Unlock(); + g_file_mapping_mu.unlock(); return ret; } bool GetFileMappingHint(const void **start, const void **end, uint64_t *offset, const char **filename) { - if (!g_file_mapping_mu.TryLock()) { + if (!g_file_mapping_mu.try_lock()) { return false; } bool found = false; @@ -1729,7 +1710,7 @@ break; } } - g_file_mapping_mu.Unlock(); + g_file_mapping_mu.unlock(); return found; } @@ -1765,7 +1746,8 @@ } // namespace absl extern "C" bool AbslInternalGetFileMappingHint(const void **start, - const void **end, uint64_t *offset, + const void **end, + uint64_t *offset, const char **filename) { return absl::debugging_internal::GetFileMappingHint(start, end, offset, filename);
diff --git a/absl/debugging/symbolize_emscripten.inc b/absl/debugging/symbolize_emscripten.inc index f6da0ac..5756325 100644 --- a/absl/debugging/symbolize_emscripten.inc +++ b/absl/debugging/symbolize_emscripten.inc
@@ -26,33 +26,20 @@ extern "C" { const char* emscripten_pc_get_function(const void* pc); +void* emscripten_stack_snapshot(); } -// clang-format off -EM_JS(bool, HaveOffsetConverter, (), - { return typeof wasmOffsetConverter !== 'undefined'; }); -// clang-format on - namespace absl { ABSL_NAMESPACE_BEGIN -void InitializeSymbolizer(const char*) { - if (!HaveOffsetConverter()) { - ABSL_RAW_LOG(INFO, - "Symbolization unavailable. Rebuild with -sWASM=1 " - "and -sUSE_OFFSET_CONVERTER=1."); - } -} +void InitializeSymbolizer(const char*) {} bool Symbolize(const void* pc, char* out, int out_size) { - // Check if we have the offset converter necessary for pc_get_function. - // Without it, the program will abort(). - if (!HaveOffsetConverter()) { - return false; - } if (pc == nullptr || out_size <= 0) { return false; } + // Need to capture a snapshot first. + emscripten_stack_snapshot(); const char* func_name = emscripten_pc_get_function(pc); if (func_name == nullptr) { return false;
diff --git a/absl/debugging/symbolize_test.cc b/absl/debugging/symbolize_test.cc index 69b0a26..2dd5da5 100644 --- a/absl/debugging/symbolize_test.cc +++ b/absl/debugging/symbolize_test.cc
@@ -107,8 +107,6 @@ #endif #if !defined(__EMSCRIPTEN__) -static void *GetPCFromFnPtr(void *ptr) { return ptr; } - // Used below to hopefully inhibit some compiler/linker optimizations // that may remove kHpageTextPadding, kPadding0, and kPadding1 from // the binary. @@ -119,12 +117,6 @@ const char kHpageTextPadding[kHpageSize * 4] ABSL_ATTRIBUTE_SECTION_VARIABLE( .text) = ""; -#else -static void *GetPCFromFnPtr(void *ptr) { - return EM_ASM_PTR( - { return wasmOffsetConverter.convert(wasmTable.get($0).name, 0); }, ptr); -} - #endif // !defined(__EMSCRIPTEN__) static char try_symbolize_buffer[4096]; @@ -174,12 +166,10 @@ TEST(Symbolize, Cached) { // Compilers should give us pointers to them. - EXPECT_STREQ("nonstatic_func", - TrySymbolize(GetPCFromFnPtr((void *)(&nonstatic_func)))); + EXPECT_STREQ("nonstatic_func", TrySymbolize((void*)(&nonstatic_func))); // The name of an internal linkage symbol is not specified; allow either a // mangled or an unmangled name here. - const char *static_func_symbol = - TrySymbolize(GetPCFromFnPtr((void *)(&static_func))); + const char* static_func_symbol = TrySymbolize((void*)(&static_func)); EXPECT_TRUE(strcmp("static_func", static_func_symbol) == 0 || strcmp("static_func()", static_func_symbol) == 0); @@ -189,50 +179,38 @@ TEST(Symbolize, Truncation) { constexpr char kNonStaticFunc[] = "nonstatic_func"; EXPECT_STREQ("nonstatic_func", - TrySymbolizeWithLimit(GetPCFromFnPtr((void *)(&nonstatic_func)), + TrySymbolizeWithLimit((void*)(&nonstatic_func), strlen(kNonStaticFunc) + 1)); EXPECT_STREQ("nonstatic_...", - TrySymbolizeWithLimit(GetPCFromFnPtr((void *)(&nonstatic_func)), + TrySymbolizeWithLimit((void*)(&nonstatic_func), strlen(kNonStaticFunc) + 0)); EXPECT_STREQ("nonstatic...", - TrySymbolizeWithLimit(GetPCFromFnPtr((void *)(&nonstatic_func)), + TrySymbolizeWithLimit((void*)(&nonstatic_func), strlen(kNonStaticFunc) - 1)); - EXPECT_STREQ("n...", TrySymbolizeWithLimit( - GetPCFromFnPtr((void *)(&nonstatic_func)), 5)); - EXPECT_STREQ("...", TrySymbolizeWithLimit( - GetPCFromFnPtr((void *)(&nonstatic_func)), 4)); - EXPECT_STREQ("..", TrySymbolizeWithLimit( - GetPCFromFnPtr((void *)(&nonstatic_func)), 3)); - EXPECT_STREQ( - ".", TrySymbolizeWithLimit(GetPCFromFnPtr((void *)(&nonstatic_func)), 2)); - EXPECT_STREQ( - "", TrySymbolizeWithLimit(GetPCFromFnPtr((void *)(&nonstatic_func)), 1)); - EXPECT_EQ(nullptr, TrySymbolizeWithLimit( - GetPCFromFnPtr((void *)(&nonstatic_func)), 0)); + EXPECT_STREQ("n...", TrySymbolizeWithLimit((void*)(&nonstatic_func), 5)); + EXPECT_STREQ("...", TrySymbolizeWithLimit((void*)(&nonstatic_func), 4)); + EXPECT_STREQ("..", TrySymbolizeWithLimit((void*)(&nonstatic_func), 3)); + EXPECT_STREQ(".", TrySymbolizeWithLimit((void*)(&nonstatic_func), 2)); + EXPECT_STREQ("", TrySymbolizeWithLimit((void*)(&nonstatic_func), 1)); + EXPECT_EQ(nullptr, TrySymbolizeWithLimit((void*)(&nonstatic_func), 0)); } TEST(Symbolize, SymbolizeWithDemangling) { Foo::func(100); #ifdef __EMSCRIPTEN__ // Emscripten's online symbolizer is more precise with arguments. - EXPECT_STREQ("Foo::func(int)", - TrySymbolize(GetPCFromFnPtr((void *)(&Foo::func)))); + EXPECT_STREQ("Foo::func(int)", TrySymbolize((void*)(&Foo::func))); #else - EXPECT_STREQ("Foo::func()", - TrySymbolize(GetPCFromFnPtr((void *)(&Foo::func)))); + EXPECT_STREQ("Foo::func()", TrySymbolize((void*)(&Foo::func))); #endif } TEST(Symbolize, SymbolizeSplitTextSections) { - EXPECT_STREQ("unlikely_func()", - TrySymbolize(GetPCFromFnPtr((void *)(&unlikely_func)))); - EXPECT_STREQ("hot_func()", TrySymbolize(GetPCFromFnPtr((void *)(&hot_func)))); - EXPECT_STREQ("startup_func()", - TrySymbolize(GetPCFromFnPtr((void *)(&startup_func)))); - EXPECT_STREQ("exit_func()", - TrySymbolize(GetPCFromFnPtr((void *)(&exit_func)))); - EXPECT_STREQ("regular_func()", - TrySymbolize(GetPCFromFnPtr((void *)(®ular_func)))); + EXPECT_STREQ("unlikely_func()", TrySymbolize((void*)(&unlikely_func))); + EXPECT_STREQ("hot_func()", TrySymbolize((void*)(&hot_func))); + EXPECT_STREQ("startup_func()", TrySymbolize((void*)(&startup_func))); + EXPECT_STREQ("exit_func()", TrySymbolize((void*)(&exit_func))); + EXPECT_STREQ("regular_func()", TrySymbolize((void*)(®ular_func))); } // Tests that verify that Symbolize stack footprint is within some limit. @@ -630,5 +608,12 @@ #endif #endif +#if !defined(__EMSCRIPTEN__) + // All of these test cases rely on symbolizing function pointers. + // On most platforms, function pointers directly map to PC. + // In WebAssembly, function pointers are indices into the function table + // and there is no longer a mapping from function index back into the + // file offset for symbolization. return RUN_ALL_TESTS(); +#endif // !defined(__EMSCRIPTEN__) }
diff --git a/absl/debugging/symbolize_win32.inc b/absl/debugging/symbolize_win32.inc index 589890f..eaeefb9 100644 --- a/absl/debugging/symbolize_win32.inc +++ b/absl/debugging/symbolize_win32.inc
@@ -15,7 +15,6 @@ // See "Retrieving Symbol Information by Address": // https://msdn.microsoft.com/en-us/library/windows/desktop/ms680578(v=vs.85).aspx -#include <ntstatus.h> #include <windows.h> #include <winternl.h> @@ -58,10 +57,9 @@ // NTSTATUS (0xC0000004), which can happen when a module load occurs during // the SymInitialize call. In this case, we should try calling SymInitialize // again. + constexpr DWORD kStatusInfoLengthMismatch = DWORD{0xC0000004}; syminitialize_error = GetLastError(); - // Both NTSTATUS and DWORD are 32-bit numbers, which makes the cast safe. - if (syminitialize_error != - static_cast<DWORD>(STATUS_INFO_LENGTH_MISMATCH)) { + if (syminitialize_error != kStatusInfoLengthMismatch) { break; } }
diff --git a/absl/flags/BUILD.bazel b/absl/flags/BUILD.bazel index 620af2b..f928609 100644 --- a/absl/flags/BUILD.bazel +++ b/absl/flags/BUILD.bazel
@@ -14,6 +14,9 @@ # limitations under the License. # +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load( "//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS", @@ -165,6 +168,7 @@ linkopts = ABSL_DEFAULT_LINKOPTS, visibility = [ "//absl/flags:__pkg__", + "//absl/flags/rust:__pkg__", ], deps = [ ":commandlineflag",
diff --git a/absl/flags/declare.h b/absl/flags/declare.h index 8d2a856..a0b12bb 100644 --- a/absl/flags/declare.h +++ b/absl/flags/declare.h
@@ -59,10 +59,19 @@ // Internal implementation of ABSL_DECLARE_FLAG to allow macro expansion of its // arguments. Clients must use ABSL_DECLARE_FLAG instead. +// +// The non-MSVC implementation declares the flag twice. This is to allow +// applying attributes to the second declaration. However, this causes a +// compile error (C4273) in MSVC if a `__declspec` is prepended to the macro. +#if defined(_MSC_VER) +#define ABSL_DECLARE_FLAG_INTERNAL(type, name) \ + extern absl::Flag<type> FLAGS_##name +#else #define ABSL_DECLARE_FLAG_INTERNAL(type, name) \ extern absl::Flag<type> FLAGS_##name; \ namespace absl /* block flags in namespaces */ {} \ /* second redeclaration is to allow applying attributes */ \ extern absl::Flag<type> FLAGS_##name +#endif // _MSC_VER #endif // ABSL_FLAGS_DECLARE_H_
diff --git a/absl/flags/flag.h b/absl/flags/flag.h index e052d5f..4c328e3 100644 --- a/absl/flags/flag.h +++ b/absl/flags/flag.h
@@ -241,7 +241,8 @@ /* default value argument. That keeps temporaries alive */ \ /* long enough for NonConst to work correctly. */ \ static constexpr absl::string_view Value( \ - absl::string_view absl_flag_help = ABSL_FLAG_IMPL_FLAGHELP(txt)) { \ + absl::string_view absl_flag_help ABSL_ATTRIBUTE_LIFETIME_BOUND = \ + ABSL_FLAG_IMPL_FLAGHELP(txt)) { \ return absl_flag_help; \ } \ static std::string NonConst() { return std::string(Value()); } \
diff --git a/absl/flags/internal/commandlineflag.h b/absl/flags/internal/commandlineflag.h index daef4e3..e2c4c82 100644 --- a/absl/flags/internal/commandlineflag.h +++ b/absl/flags/internal/commandlineflag.h
@@ -58,7 +58,7 @@ virtual ~FlagStateInterface(); // Restores the flag originated this object to the saved state. - virtual void Restore() const = 0; + virtual void Restore() && = 0; }; } // namespace flags_internal
diff --git a/absl/flags/internal/flag.cc b/absl/flags/internal/flag.cc index 37f6ef1..8e89425 100644 --- a/absl/flags/internal/flag.cc +++ b/absl/flags/internal/flag.cc
@@ -26,6 +26,7 @@ #include <memory> #include <string> #include <typeinfo> +#include <utility> #include <vector> #include "absl/base/attributes.h" @@ -73,8 +74,8 @@ // need to acquire these locks themselves. class MutexRelock { public: - explicit MutexRelock(absl::Mutex& mu) : mu_(mu) { mu_.Unlock(); } - ~MutexRelock() { mu_.Lock(); } + explicit MutexRelock(absl::Mutex& mu) : mu_(mu) { mu_.unlock(); } + ~MutexRelock() { mu_.lock(); } MutexRelock(const MutexRelock&) = delete; MutexRelock& operator=(const MutexRelock&) = delete; @@ -88,9 +89,9 @@ // we move the memory to the freelist where it lives indefinitely, so it can // still be safely accessed. This also prevents leak checkers from complaining // about the leaked memory that can no longer be accessed through any pointer. -absl::Mutex* FreelistMutex() { +absl::Mutex& FreelistMutex() { static absl::NoDestructor<absl::Mutex> mutex; - return mutex.get(); + return *mutex; } ABSL_CONST_INIT std::vector<void*>* s_freelist ABSL_GUARDED_BY(FreelistMutex()) ABSL_PT_GUARDED_BY(FreelistMutex()) = nullptr; @@ -139,8 +140,8 @@ friend class FlagImpl; // Restores the flag to the saved state. - void Restore() const override { - if (!flag_impl_.RestoreState(*this)) return; + void Restore() && override { + if (!std::move(flag_impl_).RestoreState(*this)) return; ABSL_INTERNAL_LOG(INFO, absl::StrCat("Restore saved value of ", flag_impl_.Name(), @@ -248,12 +249,12 @@ seq_lock_.MarkInitialized(); } -absl::Mutex* FlagImpl::DataGuard() const { +absl::Mutex& FlagImpl::DataGuard() const { absl::call_once(const_cast<FlagImpl*>(this)->init_control_, &FlagImpl::Init, const_cast<FlagImpl*>(this)); // data_guard_ is initialized inside Init. - return reinterpret_cast<absl::Mutex*>(&data_guard_); + return *reinterpret_cast<absl::Mutex*>(&data_guard_); } void FlagImpl::AssertValidType(FlagFastTypeId rhs_type_id, @@ -375,7 +376,7 @@ } std::string FlagImpl::CurrentValue() const { - auto* guard = DataGuard(); // Make sure flag initialized + auto& guard = DataGuard(); // Make sure flag initialized switch (ValueStorageKind()) { case FlagValueStorageKind::kValueAndInitBit: case FlagValueStorageKind::kOneWordAtomic: { @@ -429,8 +430,8 @@ // and it also can be different by the time the callback invocation is // completed. Requires that *primary_lock be held in exclusive mode; it may be // released and reacquired by the implementation. - MutexRelock relock(*DataGuard()); - absl::MutexLock lock(&callback_->guard); + MutexRelock relock(DataGuard()); + absl::MutexLock lock(callback_->guard); cb(); } @@ -535,7 +536,7 @@ } void FlagImpl::Read(void* dst) const { - auto* guard = DataGuard(); // Make sure flag initialized + auto& guard = DataGuard(); // Make sure flag initialized switch (ValueStorageKind()) { case FlagValueStorageKind::kValueAndInitBit: case FlagValueStorageKind::kOneWordAtomic: { @@ -567,14 +568,14 @@ int64_t FlagImpl::ReadOneWord() const { assert(ValueStorageKind() == FlagValueStorageKind::kOneWordAtomic || ValueStorageKind() == FlagValueStorageKind::kValueAndInitBit); - auto* guard = DataGuard(); // Make sure flag initialized + auto& guard = DataGuard(); // Make sure flag initialized (void)guard; return OneWordValue().load(std::memory_order_acquire); } bool FlagImpl::ReadOneBool() const { assert(ValueStorageKind() == FlagValueStorageKind::kValueAndInitBit); - auto* guard = DataGuard(); // Make sure flag initialized + auto& guard = DataGuard(); // Make sure flag initialized (void)guard; return absl::bit_cast<FlagValueAndInitBit<bool>>( OneWordValue().load(std::memory_order_acquire))
diff --git a/absl/flags/internal/flag.h b/absl/flags/internal/flag.h index b61a247..cab9d16 100644 --- a/absl/flags/internal/flag.h +++ b/absl/flags/internal/flag.h
@@ -601,17 +601,17 @@ data_guard_{} {} // Constant access methods - int64_t ReadOneWord() const ABSL_LOCKS_EXCLUDED(*DataGuard()); - bool ReadOneBool() const ABSL_LOCKS_EXCLUDED(*DataGuard()); - void Read(void* dst) const override ABSL_LOCKS_EXCLUDED(*DataGuard()); - void Read(bool* value) const ABSL_LOCKS_EXCLUDED(*DataGuard()) { + int64_t ReadOneWord() const ABSL_LOCKS_EXCLUDED(DataGuard()); + bool ReadOneBool() const ABSL_LOCKS_EXCLUDED(DataGuard()); + void Read(void* dst) const override ABSL_LOCKS_EXCLUDED(DataGuard()); + void Read(bool* value) const ABSL_LOCKS_EXCLUDED(DataGuard()) { *value = ReadOneBool(); } template <typename T, absl::enable_if_t<flags_internal::StorageKind<T>() == FlagValueStorageKind::kOneWordAtomic, int> = 0> - void Read(T* value) const ABSL_LOCKS_EXCLUDED(*DataGuard()) { + void Read(T* value) const ABSL_LOCKS_EXCLUDED(DataGuard()) { int64_t v = ReadOneWord(); std::memcpy(value, static_cast<const void*>(&v), sizeof(T)); } @@ -619,17 +619,17 @@ typename std::enable_if<flags_internal::StorageKind<T>() == FlagValueStorageKind::kValueAndInitBit, int>::type = 0> - void Read(T* value) const ABSL_LOCKS_EXCLUDED(*DataGuard()) { + void Read(T* value) const ABSL_LOCKS_EXCLUDED(DataGuard()) { *value = absl::bit_cast<FlagValueAndInitBit<T>>(ReadOneWord()).value; } // Mutating access methods - void Write(const void* src) ABSL_LOCKS_EXCLUDED(*DataGuard()); + void Write(const void* src) ABSL_LOCKS_EXCLUDED(DataGuard()); // Interfaces to operate on callbacks. void SetCallback(const FlagCallbackFunc mutation_callback) - ABSL_LOCKS_EXCLUDED(*DataGuard()); - void InvokeCallback() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(*DataGuard()); + ABSL_LOCKS_EXCLUDED(DataGuard()); + void InvokeCallback() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(DataGuard()); // Used in read/write operations to validate source/target has correct type. // For example if flag is declared as absl::Flag<int> FLAGS_foo, a call to @@ -646,11 +646,11 @@ friend class FlagState; // Ensures that `data_guard_` is initialized and returns it. - absl::Mutex* DataGuard() const + absl::Mutex& DataGuard() const ABSL_LOCK_RETURNED(reinterpret_cast<absl::Mutex*>(data_guard_)); // Returns heap allocated value of type T initialized with default value. std::unique_ptr<void, DynValueDeleter> MakeInitValue() const - ABSL_EXCLUSIVE_LOCKS_REQUIRED(*DataGuard()); + ABSL_EXCLUSIVE_LOCKS_REQUIRED(DataGuard()); // Flag initialization called via absl::call_once. void Init(); @@ -676,16 +676,15 @@ // returns new value. Otherwise returns nullptr. std::unique_ptr<void, DynValueDeleter> TryParse(absl::string_view value, std::string& err) const - ABSL_EXCLUSIVE_LOCKS_REQUIRED(*DataGuard()); + ABSL_EXCLUSIVE_LOCKS_REQUIRED(DataGuard()); // Stores the flag value based on the pointer to the source. void StoreValue(const void* src, ValueSource source) - ABSL_EXCLUSIVE_LOCKS_REQUIRED(*DataGuard()); + ABSL_EXCLUSIVE_LOCKS_REQUIRED(DataGuard()); // Copy the flag data, protected by `seq_lock_` into `dst`. // // REQUIRES: ValueStorageKind() == kSequenceLocked. - void ReadSequenceLockedData(void* dst) const - ABSL_LOCKS_EXCLUDED(*DataGuard()); + void ReadSequenceLockedData(void* dst) const ABSL_LOCKS_EXCLUDED(DataGuard()); FlagHelpKind HelpSourceKind() const { return static_cast<FlagHelpKind>(help_source_kind_); @@ -694,7 +693,7 @@ return static_cast<FlagValueStorageKind>(value_storage_kind_); } FlagDefaultKind DefaultKind() const - ABSL_EXCLUSIVE_LOCKS_REQUIRED(*DataGuard()) { + ABSL_EXCLUSIVE_LOCKS_REQUIRED(DataGuard()) { return static_cast<FlagDefaultKind>(def_kind_); } @@ -705,30 +704,30 @@ std::string Help() const override; FlagFastTypeId TypeId() const override; bool IsSpecifiedOnCommandLine() const override - ABSL_LOCKS_EXCLUDED(*DataGuard()); - std::string DefaultValue() const override ABSL_LOCKS_EXCLUDED(*DataGuard()); - std::string CurrentValue() const override ABSL_LOCKS_EXCLUDED(*DataGuard()); + ABSL_LOCKS_EXCLUDED(DataGuard()); + std::string DefaultValue() const override ABSL_LOCKS_EXCLUDED(DataGuard()); + std::string CurrentValue() const override ABSL_LOCKS_EXCLUDED(DataGuard()); bool ValidateInputValue(absl::string_view value) const override - ABSL_LOCKS_EXCLUDED(*DataGuard()); + ABSL_LOCKS_EXCLUDED(DataGuard()); void CheckDefaultValueParsingRoundtrip() const override - ABSL_LOCKS_EXCLUDED(*DataGuard()); + ABSL_LOCKS_EXCLUDED(DataGuard()); - int64_t ModificationCount() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(*DataGuard()); + int64_t ModificationCount() const ABSL_EXCLUSIVE_LOCKS_REQUIRED(DataGuard()); // Interfaces to save and restore flags to/from persistent state. // Returns current flag state or nullptr if flag does not support // saving and restoring a state. std::unique_ptr<FlagStateInterface> SaveState() override - ABSL_LOCKS_EXCLUDED(*DataGuard()); + ABSL_LOCKS_EXCLUDED(DataGuard()); // Restores the flag state to the supplied state object. If there is // nothing to restore returns false. Otherwise returns true. bool RestoreState(const FlagState& flag_state) - ABSL_LOCKS_EXCLUDED(*DataGuard()); + ABSL_LOCKS_EXCLUDED(DataGuard()); bool ParseFrom(absl::string_view value, FlagSettingMode set_mode, ValueSource source, std::string& error) override - ABSL_LOCKS_EXCLUDED(*DataGuard()); + ABSL_LOCKS_EXCLUDED(DataGuard()); // Immutable flag's state. @@ -758,9 +757,9 @@ // locks. uint8_t def_kind_ : 2; // Has this flag's value been modified? - bool modified_ : 1 ABSL_GUARDED_BY(*DataGuard()); + bool modified_ : 1 ABSL_GUARDED_BY(DataGuard()); // Has this flag been specified on command line. - bool on_command_line_ : 1 ABSL_GUARDED_BY(*DataGuard()); + bool on_command_line_ : 1 ABSL_GUARDED_BY(DataGuard()); // Unique tag for absl::call_once call to initialize this flag. absl::once_flag init_control_; @@ -769,7 +768,7 @@ flags_internal::SequenceLock seq_lock_; // Optional flag's callback and absl::Mutex to guard the invocations. - FlagCallback* callback_ ABSL_GUARDED_BY(*DataGuard()); + FlagCallback* callback_ ABSL_GUARDED_BY(DataGuard()); // Either a pointer to the function generating the default value based on the // value specified in ABSL_FLAG or pointer to the dynamically set default // value via SetCommandLineOptionWithMode. def_kind_ is used to distinguish
diff --git a/absl/flags/internal/program_name.cc b/absl/flags/internal/program_name.cc index fb06643..23185c6 100644 --- a/absl/flags/internal/program_name.cc +++ b/absl/flags/internal/program_name.cc
@@ -29,9 +29,9 @@ ABSL_NAMESPACE_BEGIN namespace flags_internal { -static absl::Mutex* ProgramNameMutex() { +static absl::Mutex& ProgramNameMutex() { static absl::NoDestructor<absl::Mutex> mutex; - return mutex.get(); + return *mutex; } ABSL_CONST_INIT static std::string* program_name ABSL_GUARDED_BY( ProgramNameMutex()) ABSL_PT_GUARDED_BY(ProgramNameMutex()) = nullptr;
diff --git a/absl/flags/internal/usage.cc b/absl/flags/internal/usage.cc index fc68b03..3c44271 100644 --- a/absl/flags/internal/usage.cc +++ b/absl/flags/internal/usage.cc
@@ -434,9 +434,9 @@ namespace { -absl::Mutex* HelpAttributesMutex() { +absl::Mutex& HelpAttributesMutex() { static absl::NoDestructor<absl::Mutex> mutex; - return mutex.get(); + return *mutex; } ABSL_CONST_INIT std::string* match_substr ABSL_GUARDED_BY(HelpAttributesMutex()) ABSL_PT_GUARDED_BY(HelpAttributesMutex()) = nullptr;
diff --git a/absl/flags/marshalling.cc b/absl/flags/marshalling.cc index ca4a130..acdc880 100644 --- a/absl/flags/marshalling.cc +++ b/absl/flags/marshalling.cc
@@ -45,22 +45,7 @@ // AbslParseFlag specializations for boolean type. bool AbslParseFlag(absl::string_view text, bool* dst, std::string*) { - const char* kTrue[] = {"1", "t", "true", "y", "yes"}; - const char* kFalse[] = {"0", "f", "false", "n", "no"}; - static_assert(sizeof(kTrue) == sizeof(kFalse), "true_false_equal"); - - text = absl::StripAsciiWhitespace(text); - - for (size_t i = 0; i < ABSL_ARRAYSIZE(kTrue); ++i) { - if (absl::EqualsIgnoreCase(text, kTrue[i])) { - *dst = true; - return true; - } else if (absl::EqualsIgnoreCase(text, kFalse[i])) { - *dst = false; - return true; - } - } - return false; // didn't match a legal input + return SimpleAtob(absl::StripAsciiWhitespace(text), dst); } // --------------------------------------------------------------------
diff --git a/absl/flags/parse.cc b/absl/flags/parse.cc index c87cacd..4961930 100644 --- a/absl/flags/parse.cc +++ b/absl/flags/parse.cc
@@ -64,9 +64,9 @@ namespace flags_internal { namespace { -absl::Mutex* ProcessingChecksMutex() { +absl::Mutex& ProcessingChecksMutex() { static absl::NoDestructor<absl::Mutex> mutex; - return mutex.get(); + return *mutex; } ABSL_CONST_INIT bool flagfile_needs_processing @@ -76,9 +76,9 @@ ABSL_CONST_INIT bool tryfromenv_needs_processing ABSL_GUARDED_BY(ProcessingChecksMutex()) = false; -absl::Mutex* SpecifiedFlagsMutex() { +absl::Mutex& SpecifiedFlagsMutex() { static absl::NoDestructor<absl::Mutex> mutex; - return mutex.get(); + return *mutex; } ABSL_CONST_INIT std::vector<const CommandLineFlag*>* specified_flags @@ -206,8 +206,10 @@ std::string line; bool success = true; + int line_number = 0; while (std::getline(flag_file, line)) { + line_number++; absl::string_view stripped = absl::StripLeadingAsciiWhitespace(line); if (stripped.empty() || stripped[0] == '#') { @@ -229,8 +231,8 @@ } flags_internal::ReportUsageError( - absl::StrCat("Unexpected line in the flagfile ", flag_file_name, ": ", - line), + absl::StrCat("Unexpected line ", line_number, " in the flagfile ", + flag_file_name), true); success = false;
diff --git a/absl/flags/parse_test.cc b/absl/flags/parse_test.cc index 9997069..08eb81a 100644 --- a/absl/flags/parse_test.cc +++ b/absl/flags/parse_test.cc
@@ -827,7 +827,7 @@ flagfile_flag), }; EXPECT_DEATH_IF_SUPPORTED(InvokeParse(in_args5), - "Unexpected line in the flagfile .*: \\*bin\\*"); + "Unexpected line 2 in the flagfile .*"); } // --------------------------------------------------------------------
diff --git a/absl/flags/reflection.cc b/absl/flags/reflection.cc index b8b4a2e..fb64a65 100644 --- a/absl/flags/reflection.cc +++ b/absl/flags/reflection.cc
@@ -19,6 +19,7 @@ #include <atomic> #include <string> +#include <utility> #include "absl/base/config.h" #include "absl/base/no_destructor.h" @@ -53,8 +54,11 @@ // Store a flag in this registry. Takes ownership of *flag. void RegisterFlag(CommandLineFlag& flag, const char* filename); - void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION(lock_) { lock_.Lock(); } - void Unlock() ABSL_UNLOCK_FUNCTION(lock_) { lock_.Unlock(); } + void lock() ABSL_EXCLUSIVE_LOCK_FUNCTION(lock_) { lock_.lock(); } + inline void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION(lock_) { lock(); } + + void unlock() ABSL_UNLOCK_FUNCTION(lock_) { lock_.unlock(); } + inline void Unlock() ABSL_UNLOCK_FUNCTION(lock_) { unlock(); } // Returns the flag object for the specified name, or nullptr if not found. // Will emit a warning if a 'retired' flag is specified. @@ -87,8 +91,8 @@ class FlagRegistryLock { public: - explicit FlagRegistryLock(FlagRegistry& fr) : fr_(fr) { fr_.Lock(); } - ~FlagRegistryLock() { fr_.Unlock(); } + explicit FlagRegistryLock(FlagRegistry& fr) : fr_(fr) { fr_.lock(); } + ~FlagRegistryLock() { fr_.unlock(); } private: FlagRegistry& fr_; @@ -317,9 +321,9 @@ } // Restores the saved flag states into the flag registry. - void RestoreToRegistry() { + void RestoreToRegistry() && { for (const auto& flag_state : backup_registry_) { - flag_state->Restore(); + std::move(*flag_state).Restore(); } } @@ -337,7 +341,7 @@ FlagSaver::~FlagSaver() { if (!impl_) return; - impl_->RestoreToRegistry(); + std::move(*impl_).RestoreToRegistry(); delete impl_; }
diff --git a/absl/flags/usage.cc b/absl/flags/usage.cc index 267a503..e42b454 100644 --- a/absl/flags/usage.cc +++ b/absl/flags/usage.cc
@@ -40,7 +40,7 @@ // -------------------------------------------------------------------- // Sets the "usage" message to be used by help reporting routines. void SetProgramUsageMessage(absl::string_view new_usage_message) { - absl::MutexLock l(&flags_internal::usage_message_guard); + absl::MutexLock l(flags_internal::usage_message_guard); if (flags_internal::program_usage_message != nullptr) { ABSL_INTERNAL_LOG(FATAL, "SetProgramUsageMessage() called twice."); @@ -55,7 +55,7 @@ // Note: We able to return string_view here only because calling // SetProgramUsageMessage twice is prohibited. absl::string_view ProgramUsageMessage() { - absl::MutexLock l(&flags_internal::usage_message_guard); + absl::MutexLock l(flags_internal::usage_message_guard); return flags_internal::program_usage_message != nullptr ? absl::string_view(*flags_internal::program_usage_message)
diff --git a/absl/flags/usage_config.cc b/absl/flags/usage_config.cc index 5922c5e..bbc020d 100644 --- a/absl/flags/usage_config.cc +++ b/absl/flags/usage_config.cc
@@ -105,9 +105,9 @@ // -------------------------------------------------------------------- -absl::Mutex* CustomUsageConfigMutex() { +absl::Mutex& CustomUsageConfigMutex() { static absl::NoDestructor<absl::Mutex> mutex; - return mutex.get(); + return *mutex; } ABSL_CONST_INIT FlagsUsageConfig* custom_usage_config ABSL_GUARDED_BY(CustomUsageConfigMutex())
diff --git a/absl/functional/BUILD.bazel b/absl/functional/BUILD.bazel index aeed3b6..b7aa31f 100644 --- a/absl/functional/BUILD.bazel +++ b/absl/functional/BUILD.bazel
@@ -14,6 +14,9 @@ # limitations under the License. # +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load( "//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS", @@ -41,6 +44,7 @@ deps = [ "//absl/base:config", "//absl/base:core_headers", + "//absl/base:nullability", "//absl/meta:type_traits", "//absl/utility", ], @@ -58,6 +62,7 @@ ":any_invocable", "//absl/base:config", "//absl/base:core_headers", + "//absl/base:nullability", "//absl/meta:type_traits", "//absl/utility", "@googletest//:gtest", @@ -99,8 +104,10 @@ linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":any_invocable", + "//absl/base:config", "//absl/base:core_headers", "//absl/meta:type_traits", + "//absl/utility", ], ) @@ -112,8 +119,10 @@ deps = [ ":any_invocable", ":function_ref", + "//absl/base:config", "//absl/container:test_instance_tracker", "//absl/memory", + "//absl/utility", "@googletest//:gtest", "@googletest//:gtest_main", ],
diff --git a/absl/functional/CMakeLists.txt b/absl/functional/CMakeLists.txt index 91939db..07f3dc0 100644 --- a/absl/functional/CMakeLists.txt +++ b/absl/functional/CMakeLists.txt
@@ -24,6 +24,7 @@ COPTS ${ABSL_DEFAULT_COPTS} DEPS + absl::base absl::config absl::core_headers absl::type_traits @@ -41,6 +42,7 @@ ${ABSL_TEST_COPTS} DEPS absl::any_invocable + absl::base absl::config absl::core_headers absl::type_traits @@ -85,9 +87,11 @@ COPTS ${ABSL_DEFAULT_COPTS} DEPS + absl::config absl::core_headers absl::any_invocable absl::meta + absl::utility PUBLIC ) @@ -99,9 +103,11 @@ COPTS ${ABSL_TEST_COPTS} DEPS + absl::config absl::function_ref absl::memory absl::test_instance_tracker + absl::utility GTest::gmock_main )
diff --git a/absl/functional/any_invocable.h b/absl/functional/any_invocable.h index 43ea9af..6dd72b6 100644 --- a/absl/functional/any_invocable.h +++ b/absl/functional/any_invocable.h
@@ -39,7 +39,9 @@ #include <type_traits> #include <utility> +#include "absl/base/attributes.h" #include "absl/base/config.h" +#include "absl/base/nullability.h" #include "absl/functional/internal/any_invocable.h" #include "absl/meta/type_traits.h" #include "absl/utility/utility.h" @@ -158,7 +160,8 @@ // AnyInvocable<void()> empty; // empty(); // WARNING: Undefined behavior! template <class Sig> -class AnyInvocable : private internal_any_invocable::Impl<Sig> { +class ABSL_NULLABILITY_COMPATIBLE ABSL_ATTRIBUTE_OWNER AnyInvocable + : private internal_any_invocable::Impl<Sig> { private: static_assert( std::is_function<Sig>::value, @@ -169,6 +172,7 @@ public: // The return type of Sig using result_type = typename Impl::result_type; + using absl_internal_is_view = std::false_type; // Constructors @@ -295,22 +299,22 @@ // Equality operators - // Returns `true` if `*this` is empty. + // Returns `true` if `f` is empty. friend bool operator==(const AnyInvocable& f, std::nullptr_t) noexcept { return !f.HasValue(); } - // Returns `true` if `*this` is empty. + // Returns `true` if `f` is empty. friend bool operator==(std::nullptr_t, const AnyInvocable& f) noexcept { return !f.HasValue(); } - // Returns `false` if `*this` is empty. + // Returns `false` if `f` is empty. friend bool operator!=(const AnyInvocable& f, std::nullptr_t) noexcept { return f.HasValue(); } - // Returns `false` if `*this` is empty. + // Returns `false` if `f` is empty. friend bool operator!=(std::nullptr_t, const AnyInvocable& f) noexcept { return f.HasValue(); }
diff --git a/absl/functional/any_invocable_test.cc b/absl/functional/any_invocable_test.cc index 6ad6323..7ddfcab 100644 --- a/absl/functional/any_invocable_test.cc +++ b/absl/functional/any_invocable_test.cc
@@ -15,13 +15,17 @@ #include "absl/functional/any_invocable.h" #include <cstddef> +#include <cstdlib> +#include <functional> #include <initializer_list> +#include <iterator> #include <memory> #include <numeric> #include <type_traits> #include "gtest/gtest.h" #include "absl/base/config.h" +#include "absl/base/nullability.h" #include "absl/meta/type_traits.h" #include "absl/utility/utility.h" @@ -652,8 +656,8 @@ TYPED_TEST_P(AnyInvTestBasic, MoveConstructionFromEmpty) { using AnyInvType = typename TypeParam::AnyInvType; - AnyInvType source_fun; - AnyInvType fun(std::move(source_fun)); + absl_nullable AnyInvType source_fun; + absl_nullable AnyInvType fun(std::move(source_fun)); EXPECT_FALSE(static_cast<bool>(fun));
diff --git a/absl/functional/function_ref.h b/absl/functional/function_ref.h index f1d087a..3882eb0 100644 --- a/absl/functional/function_ref.h +++ b/absl/functional/function_ref.h
@@ -47,12 +47,14 @@ #define ABSL_FUNCTIONAL_FUNCTION_REF_H_ #include <cassert> -#include <functional> #include <type_traits> +#include <utility> #include "absl/base/attributes.h" +#include "absl/base/config.h" #include "absl/functional/internal/function_ref.h" #include "absl/meta/type_traits.h" +#include "absl/utility/utility.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -83,42 +85,80 @@ // bool Visitor(absl::FunctionRef<void(my_proto&, absl::string_view)> // callback); template <typename R, typename... Args> -class FunctionRef<R(Args...)> { - private: +class ABSL_ATTRIBUTE_VIEW FunctionRef<R(Args...)> { + protected: // Used to disable constructors for objects that are not compatible with the // signature of this FunctionRef. - template <typename F, typename FR = std::invoke_result_t<F, Args&&...>> + template <typename F, typename... U> using EnableIfCompatible = - typename std::enable_if<std::is_void<R>::value || - std::is_convertible<FR, R>::value>::type; + std::enable_if_t<std::is_invocable_r<R, F, U..., Args...>::value>; - public: - // Constructs a FunctionRef from any invocable type. - template <typename F, typename = EnableIfCompatible<const F&>> - // NOLINTNEXTLINE(runtime/explicit) - FunctionRef(const F& f ABSL_ATTRIBUTE_LIFETIME_BOUND) - : invoker_(&absl::functional_internal::InvokeObject<F, R, Args...>) { + // Internal constructor to supersede the copying constructor + template <typename F> + // NOLINTNEXTLINE(google-explicit-constructor) + FunctionRef(std::in_place_t, F&& f ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept + : invoker_(&absl::functional_internal::InvokeObject<F&, R, Args...>) { absl::functional_internal::AssertNonNull(f); ptr_.obj = &f; } + public: + // Constructs a FunctionRef from any invocable type. + template <typename F, + typename = EnableIfCompatible<std::enable_if_t< + !std::is_same_v<FunctionRef, absl::remove_cvref_t<F>>, F&>>> + // NOLINTNEXTLINE(google-explicit-constructor) + FunctionRef(F&& f ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept + : FunctionRef(std::in_place, std::forward<F>(f)) {} + // Overload for function pointers. This eliminates a level of indirection that // would happen if the above overload was used (it lets us store the pointer // instead of a pointer to a pointer). // // This overload is also used for references to functions, since references to // functions can decay to function pointers implicitly. - template < - typename F, typename = EnableIfCompatible<F*>, - absl::functional_internal::EnableIf<absl::is_function<F>::value> = 0> - FunctionRef(F* f) // NOLINT(runtime/explicit) + template <typename F, typename = EnableIfCompatible<F*>, + absl::functional_internal::EnableIf<std::is_function_v<F>> = 0> + // NOLINTNEXTLINE(google-explicit-constructor) + FunctionRef(F* f ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept : invoker_(&absl::functional_internal::InvokeFunction<F*, R, Args...>) { assert(f != nullptr); ptr_.fun = reinterpret_cast<decltype(ptr_.fun)>(f); } - FunctionRef& operator=(const FunctionRef& rhs) = default; - FunctionRef(const FunctionRef& rhs) = default; +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L + // Similar to the other overloads, but passes the address of a known callable + // `F` at compile time. This allows calling arbitrary functions while avoiding + // an indirection. + // Needs C++20 as `nontype_t` needs C++20 for `auto` template parameters. + template <auto F, typename = EnableIfCompatible<decltype(F)>> + FunctionRef(nontype_t<F>) noexcept // NOLINT(google-explicit-constructor) + : invoker_(&absl::functional_internal::InvokeFunction<decltype(F), F, R, + Args...>) {} + + template < + auto F, typename Obj, + typename = EnableIfCompatible<decltype(F), std::remove_reference_t<Obj>&>, + absl::functional_internal::EnableIf<!std::is_rvalue_reference_v<Obj&&>> = + 0> + // NOLINTNEXTLINE(google-explicit-constructor) + FunctionRef(nontype_t<F>, Obj&& obj ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept + : invoker_(&absl::functional_internal::InvokeObject<Obj&, decltype(F), F, + R, Args...>) { + ptr_.obj = std::addressof(obj); + } + + template <auto F, typename Obj, + typename = EnableIfCompatible<decltype(F), Obj*>> + // NOLINTNEXTLINE(google-explicit-constructor) + FunctionRef(nontype_t<F>, Obj* obj ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept + : invoker_(&absl::functional_internal::InvokePtr<Obj, decltype(F), F, R, + Args...>) { + ptr_.obj = obj; + } +#endif + + using absl_internal_is_view = std::true_type; // Call the underlying object. R operator()(Args... args) const { @@ -133,11 +173,76 @@ // Allow const qualified function signatures. Since FunctionRef requires // constness anyway we can just make this a no-op. template <typename R, typename... Args> -class FunctionRef<R(Args...) const> : public FunctionRef<R(Args...)> { +class ABSL_ATTRIBUTE_VIEW + FunctionRef<R(Args...) const> : private FunctionRef<R(Args...)> { + using Base = FunctionRef<R(Args...)>; + + template <typename F, typename... U> + using EnableIfCompatible = + typename Base::template EnableIfCompatible<F, U...>; + public: - using FunctionRef<R(Args...)>::FunctionRef; + template < + typename F, + typename = EnableIfCompatible<std::enable_if_t< + !std::is_same_v<FunctionRef, absl::remove_cvref_t<F>>, const F&>>> + // NOLINTNEXTLINE(google-explicit-constructor) + FunctionRef(const F& f ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept + : Base(std::in_place_t(), f) {} + + template <typename F, typename = EnableIfCompatible<F*>, + absl::functional_internal::EnableIf<std::is_function_v<F>> = 0> + // NOLINTNEXTLINE(google-explicit-constructor) + FunctionRef(F* f ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept : Base(f) {} + +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L + template <auto F, typename = EnableIfCompatible<decltype(F)>> + // NOLINTNEXTLINE(google-explicit-constructor) + FunctionRef(nontype_t<F> arg) noexcept : Base(arg) {} + + template <auto F, typename Obj, + typename = EnableIfCompatible<decltype(F), + const std::remove_reference_t<Obj>&>, + absl::functional_internal::EnableIf< + !std::is_rvalue_reference_v<Obj&&>> = 0> + // NOLINTNEXTLINE(google-explicit-constructor) + FunctionRef(nontype_t<F> arg, + Obj&& obj ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept + : Base(arg, std::forward<Obj>(obj)) {} + + template <auto F, typename Obj, + typename = EnableIfCompatible<decltype(F), const Obj*>> + // NOLINTNEXTLINE(google-explicit-constructor) + FunctionRef(nontype_t<F> arg, + const Obj* obj ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept + : Base(arg, obj) {} +#endif + + using absl_internal_is_view = std::true_type; + + using Base::operator(); }; +template <class F> +FunctionRef(F*) -> FunctionRef<F>; + +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L +template <auto Func> +FunctionRef(nontype_t<Func>) + -> FunctionRef<std::remove_pointer_t<decltype(Func)>>; + +template <class M, class T, M T::* Func, class U> +FunctionRef(nontype_t<Func>, U&&) + -> FunctionRef<std::enable_if_t<std::is_member_pointer_v<M T::*>, M>>; + +template <class M, class T, M T::* Func, class U> +FunctionRef(nontype_t<Func>, U&&) -> FunctionRef<std::enable_if_t< + std::is_object_v<M>, std::invoke_result_t<decltype(Func), U&>()>>; + +template <class R, class T, class... Args, R (*Func)(T, Args...), class U> +FunctionRef(nontype_t<Func>, U&&) -> FunctionRef<R(Args...)>; +#endif + ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/functional/function_ref_test.cc b/absl/functional/function_ref_test.cc index 98d11f7..97aa3d5 100644 --- a/absl/functional/function_ref_test.cc +++ b/absl/functional/function_ref_test.cc
@@ -16,27 +16,37 @@ #include <functional> #include <memory> +#include <type_traits> +#include <utility> #include "gmock/gmock.h" #include "gtest/gtest.h" #include "absl/container/internal/test_instance_tracker.h" #include "absl/functional/any_invocable.h" #include "absl/memory/memory.h" +#include "absl/utility/utility.h" namespace absl { ABSL_NAMESPACE_BEGIN namespace { -void RunFun(FunctionRef<void()> f) { f(); } - -TEST(FunctionRefTest, Lambda) { - bool ran = false; - RunFun([&] { ran = true; }); - EXPECT_TRUE(ran); -} +struct Class { + int Func() { return 42; } + int CFunc() const { return 43; } +}; int Function() { return 1337; } +template <typename T> +T Dereference(const T* v) { + return *v; +} + +template <typename T> +T Copy(const T& v) { + return v; +} + TEST(FunctionRefTest, Function1) { FunctionRef<int()> ref(&Function); EXPECT_EQ(1337, ref()); @@ -251,11 +261,11 @@ std::is_same<Invoker<void, Trivial>, void (*)(VoidPtr, Trivial)>::value, "Small trivial types should be passed by value"); static_assert(std::is_same<Invoker<void, LargeTrivial>, - void (*)(VoidPtr, LargeTrivial &&)>::value, + void (*)(VoidPtr, LargeTrivial&&)>::value, "Large trivial types should be passed by rvalue reference"); static_assert( std::is_same<Invoker<void, CopyableMovableInstance>, - void (*)(VoidPtr, CopyableMovableInstance &&)>::value, + void (*)(VoidPtr, CopyableMovableInstance&&)>::value, "Types with copy/move ctor should be passed by rvalue reference"); // References are passed as references. @@ -268,7 +278,7 @@ "Reference types should be preserved"); static_assert( std::is_same<Invoker<void, CopyableMovableInstance&&>, - void (*)(VoidPtr, CopyableMovableInstance &&)>::value, + void (*)(VoidPtr, CopyableMovableInstance&&)>::value, "Reference types should be preserved"); // Make sure the address of an object received by reference is the same as the @@ -298,6 +308,109 @@ ref(obj); } +TEST(FunctionRefTest, CorrectConstQualifiers) { + struct S { + int operator()() { return 42; } + int operator()() const { return 1337; } + }; + S s; + EXPECT_EQ(42, FunctionRef<int()>(s)()); + EXPECT_EQ(1337, FunctionRef<int() const>(s)()); + EXPECT_EQ(1337, FunctionRef<int()>(std::as_const(s))()); + EXPECT_EQ(1337, FunctionRef<int() const>(std::as_const(s))()); +} + +TEST(FunctionRefTest, Lambdas) { + // Stateless lambdas implicitly convert to function pointers, so their + // mutability is irrelevant. + EXPECT_TRUE(FunctionRef<bool()>([]() /*const*/ { return true; })()); + EXPECT_TRUE(FunctionRef<bool()>([]() mutable { return true; })()); + EXPECT_TRUE(FunctionRef<bool() const>([]() /*const*/ { return true; })()); +#if defined(__clang__) || (ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L && \ + defined(_MSC_VER) && !defined(__EDG__)) + // MSVC has problems compiling the following code pre-C++20: + // const auto f = []() mutable {}; + // f(); + // EDG's MSVC-compatible mode (which Visual C++ uses for Intellisense) + // exhibits the bug in C++20 as well. So we don't support them. + EXPECT_TRUE(FunctionRef<bool() const>([]() mutable { return true; })()); +#endif + + // Stateful lambdas are not implicitly convertible to function pointers, so + // a const stateful lambda is not mutably callable. + EXPECT_TRUE(FunctionRef<bool()>([v = true]() /*const*/ { return v; })()); + EXPECT_TRUE(FunctionRef<bool()>([v = true]() mutable { return v; })()); + EXPECT_TRUE( + FunctionRef<bool() const>([v = true]() /*const*/ { return v; })()); + const auto func = [v = true]() mutable { return v; }; + static_assert( + !std::is_convertible_v<decltype(func), FunctionRef<bool() const>>); +} + +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L +static_assert(std::is_same_v<decltype(FunctionRef(nontype<&Class::Func>, + std::declval<Class&>())), + FunctionRef<int()>>); +static_assert(std::is_same_v<decltype(FunctionRef(nontype<&Class::CFunc>, + std::declval<Class&>())), + FunctionRef<int() const>>); + +static_assert(std::is_same_v<decltype(FunctionRef(nontype<&Class::Func>, + std::declval<Class*>())), + FunctionRef<int()>>); +static_assert(std::is_same_v<decltype(FunctionRef(nontype<&Class::CFunc>, + std::declval<Class*>())), + FunctionRef<int() const>>); + +TEST(FunctionRefTest, NonTypeParameterWithTemporaries) { + static_assert(!std::is_constructible_v<FunctionRef<int()>, + nontype_t<&Class::Func>, Class&&>); + static_assert( + !std::is_constructible_v<FunctionRef<int()>, nontype_t<&Class::Func>, + const Class&&>); + static_assert(!std::is_constructible_v<FunctionRef<int() const>, + nontype_t<&Class::CFunc>, Class&&>); + static_assert( + !std::is_constructible_v<FunctionRef<int() const>, + nontype_t<&Class::CFunc>, const Class&&>); +} + +TEST(FunctionRefTest, NonTypeParameterWithDeductionGuides) { + EXPECT_EQ(1337, FunctionRef(nontype<&Function>)()); + EXPECT_EQ(42, FunctionRef(nontype<&Copy<int>>, + std::integral_constant<int, 42>::value)()); + EXPECT_EQ(42, FunctionRef(nontype<&Dereference<int>>, + &std::integral_constant<int, 42>::value)()); + + Class c; + EXPECT_EQ(42, FunctionRef<int()>(nontype<&Class::Func>, c)()); + EXPECT_EQ(43, FunctionRef<int() const>(nontype<&Class::CFunc>, c)()); + + EXPECT_EQ(42, FunctionRef<int()>(nontype<&Class::Func>, &c)()); + EXPECT_EQ(43, FunctionRef<int() const>(nontype<&Class::CFunc>, &c)()); +} +#endif + +TEST(FunctionRefTest, OptionalArguments) { + struct S { + int operator()(int = 0) const { return 1337; } + }; + S s; + EXPECT_EQ(1337, FunctionRef<int()>(s)()); +} + +TEST(FunctionRefTest, NonConstToConstConversion) { + // The const-qualified version might inherit from the non-const version. + // We want to make sure that this doesn't introduce a bug when an instance of + // the base (non-const) class is forwarded through the derived (const) class. + // This has the potential to trigger the copy constructor, thus incorrectly + // producing a copy rather than another indirection. + absl::FunctionRef<int()> a = +[]() { return 1; }; + absl::FunctionRef<int() const> b = a; + a = +[]() { return 2; }; + EXPECT_EQ(b(), 2); +} + } // namespace ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/functional/internal/any_invocable.h b/absl/functional/internal/any_invocable.h index 167d947..597c210 100644 --- a/absl/functional/internal/any_invocable.h +++ b/absl/functional/internal/any_invocable.h
@@ -66,6 +66,7 @@ #include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/macros.h" +#include "absl/base/nullability.h" #include "absl/base/optimization.h" #include "absl/meta/type_traits.h" #include "absl/utility/utility.h" @@ -75,7 +76,7 @@ // Defined in functional/any_invocable.h template <class Sig> -class AnyInvocable; +class ABSL_NULLABILITY_COMPATIBLE AnyInvocable; namespace internal_any_invocable { @@ -158,10 +159,18 @@ // A discriminator when calling the "manager" function that describes operation // type-erased operation should be invoked. // +// "dispose" specifies that the manager should perform a destroy. +// // "relocate_from_to" specifies that the manager should perform a move. // -// "dispose" specifies that the manager should perform a destroy. -enum class FunctionToCall : bool { relocate_from_to, dispose }; +// "relocate_from_to_and_query_rust" is identical to "relocate_from_to" for C++ +// managers, but instructs Rust managers to perform a special operation that +// can be detected by the caller. +enum class FunctionToCall : unsigned char { + dispose, + relocate_from_to, + relocate_from_to_and_query_rust, +}; // The portion of `AnyInvocable` state that contains either a pointer to the // target object or the object itself in local storage @@ -242,6 +251,7 @@ switch (operation) { case FunctionToCall::relocate_from_to: + case FunctionToCall::relocate_from_to_and_query_rust: // NOTE: Requires that the left-hand operand is already empty. ::new (static_cast<void*>(&to->storage)) T(std::move(from_object)); ABSL_FALLTHROUGH_INTENDED; @@ -276,6 +286,7 @@ TypeErasedState* const to) noexcept { switch (operation) { case FunctionToCall::relocate_from_to: + case FunctionToCall::relocate_from_to_and_query_rust: // NOTE: Requires that the left-hand operand is already empty. to->remote = from->remote; return; @@ -302,6 +313,7 @@ switch (operation) { case FunctionToCall::relocate_from_to: + case FunctionToCall::relocate_from_to_and_query_rust: // NOTE: Requires that the left-hand operand is already empty. to->remote.target = from->remote.target; return;
diff --git a/absl/functional/internal/function_ref.h b/absl/functional/internal/function_ref.h index 27d45b8..543b30f 100644 --- a/absl/functional/internal/function_ref.h +++ b/absl/functional/internal/function_ref.h
@@ -72,14 +72,52 @@ // static_cast<R> handles the case the return type is void. template <typename Obj, typename R, typename... Args> R InvokeObject(VoidPtr ptr, typename ForwardT<Args>::type... args) { - auto o = static_cast<const Obj*>(ptr.obj); - return static_cast<R>(std::invoke(*o, std::forward<Args>(args)...)); + using T = std::remove_reference_t<Obj>; + return static_cast<R>(std::invoke( + std::forward<Obj>(*const_cast<T*>(static_cast<const T*>(ptr.obj))), + std::forward<typename ForwardT<Args>::type>(args)...)); +} + +template <typename Obj, typename Fun, Fun F, typename R, typename... Args> +R InvokeObject(VoidPtr ptr, typename ForwardT<Args>::type... args) { + using T = std::remove_reference_t<Obj>; + Obj&& obj = + std::forward<Obj>(*const_cast<T*>(static_cast<const T*>(ptr.obj))); + // Avoid std::invoke() since the callee is a known function at compile time + if constexpr (std::is_member_function_pointer_v<Fun>) { + return static_cast<R>((std::forward<Obj>(obj).*F)( + std::forward<typename ForwardT<Args>::type>(args)...)); + } else { + return static_cast<R>( + F(std::forward<Obj>(obj), + std::forward<typename ForwardT<Args>::type>(args)...)); + } +} + +template <typename T, typename Fun, Fun F, typename R, typename... Args> +R InvokePtr(VoidPtr ptr, typename ForwardT<Args>::type... args) { + T* obj = const_cast<T*>(static_cast<const T*>(ptr.obj)); + // Avoid std::invoke() since the callee is a known function at compile time + if constexpr (std::is_member_function_pointer_v<Fun>) { + return static_cast<R>( + (obj->*F)(std::forward<typename ForwardT<Args>::type>(args)...)); + } else { + return static_cast<R>( + F(obj, std::forward<typename ForwardT<Args>::type>(args)...)); + } } template <typename Fun, typename R, typename... Args> R InvokeFunction(VoidPtr ptr, typename ForwardT<Args>::type... args) { auto f = reinterpret_cast<Fun>(ptr.fun); - return static_cast<R>(std::invoke(f, std::forward<Args>(args)...)); + return static_cast<R>( + std::invoke(f, std::forward<typename ForwardT<Args>::type>(args)...)); +} + +template <typename Fun, Fun F, typename R, typename... Args> +R InvokeFunction(VoidPtr, typename ForwardT<Args>::type... args) { + return static_cast<R>( + F(std::forward<typename ForwardT<Args>::type>(args)...)); } template <typename Sig> @@ -98,7 +136,7 @@ void AssertNonNull(const F&) {} template <typename F, typename C> -void AssertNonNull(F C::*f) { +void AssertNonNull(F C::* f) { assert(f != nullptr); (void)f; }
diff --git a/absl/hash/BUILD.bazel b/absl/hash/BUILD.bazel index b2ffcd0..187689f 100644 --- a/absl/hash/BUILD.bazel +++ b/absl/hash/BUILD.bazel
@@ -14,6 +14,9 @@ # limitations under the License. # +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load( "//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS", @@ -43,11 +46,11 @@ linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ ":city", - ":low_level_hash", ":weakly_mixed_integer", "//absl/base:config", "//absl/base:core_headers", "//absl/base:endian", + "//absl/base:prefetch", "//absl/container:fixed_array", "//absl/functional:function_ref", "//absl/meta:type_traits", @@ -76,6 +79,7 @@ cc_test( name = "hash_test", + size = "large", srcs = [ "hash_test.cc", "internal/hash_test.h", @@ -186,22 +190,6 @@ ) cc_library( - name = "low_level_hash", - srcs = ["internal/low_level_hash.cc"], - hdrs = ["internal/low_level_hash.h"], - copts = ABSL_DEFAULT_COPTS, - linkopts = ABSL_DEFAULT_LINKOPTS, - visibility = ["//visibility:private"], - deps = [ - "//absl/base:config", - "//absl/base:core_headers", - "//absl/base:endian", - "//absl/base:prefetch", - "//absl/numeric:int128", - ], -) - -cc_library( name = "weakly_mixed_integer", hdrs = ["internal/weakly_mixed_integer.h"], copts = ABSL_DEFAULT_COPTS, @@ -223,7 +211,7 @@ linkopts = ABSL_DEFAULT_LINKOPTS, visibility = ["//visibility:private"], deps = [ - ":low_level_hash", + ":hash", "//absl/strings", "@googletest//:gtest", "@googletest//:gtest_main",
diff --git a/absl/hash/CMakeLists.txt b/absl/hash/CMakeLists.txt index 6996d93..b439e4c 100644 --- a/absl/hash/CMakeLists.txt +++ b/absl/hash/CMakeLists.txt
@@ -38,7 +38,6 @@ absl::optional absl::variant absl::utility - absl::low_level_hash absl::weakly_mixed_integer PUBLIC ) @@ -156,24 +155,6 @@ # Internal-only target, do not depend on directly. absl_cc_library( NAME - low_level_hash - HDRS - "internal/low_level_hash.h" - SRCS - "internal/low_level_hash.cc" - COPTS - ${ABSL_DEFAULT_COPTS} - DEPS - absl::config - absl::core_headers - absl::endian - absl::int128 - absl::prefetch -) - -# Internal-only target, do not depend on directly. -absl_cc_library( - NAME weakly_mixed_integer HDRS "internal/weakly_mixed_integer.h" @@ -191,7 +172,7 @@ COPTS ${ABSL_TEST_COPTS} DEPS - absl::low_level_hash + absl::hash absl::strings GTest::gmock_main )
diff --git a/absl/hash/hash_benchmark.cc b/absl/hash/hash_benchmark.cc index 73b037d..b39ef75 100644 --- a/absl/hash/hash_benchmark.cc +++ b/absl/hash/hash_benchmark.cc
@@ -338,6 +338,16 @@ }(); } // namespace +struct PodPairInt64 { + int64_t a; + int64_t b; + + template <typename H> + friend H AbslHashValue(H h, const PodPairInt64& p) { + return H::combine(std::move(h), p.a, p.b); + } +}; + template <class T> struct PodRand { static_assert(std::is_pod<T>::value, ""); @@ -378,6 +388,7 @@ MAKE_LATENCY_BENCHMARK(AbslHash, Int32, PodRand<int32_t>) MAKE_LATENCY_BENCHMARK(AbslHash, Int64, PodRand<int64_t>) +MAKE_LATENCY_BENCHMARK(AbslHash, PairInt64, PodRand<PodPairInt64>) MAKE_LATENCY_BENCHMARK(AbslHash, String3, StringRand<3>) MAKE_LATENCY_BENCHMARK(AbslHash, String5, StringRand<5>) MAKE_LATENCY_BENCHMARK(AbslHash, String9, StringRand<9>)
diff --git a/absl/hash/hash_test.cc b/absl/hash/hash_test.cc index 7582f54..89e0470 100644 --- a/absl/hash/hash_test.cc +++ b/absl/hash/hash_test.cc
@@ -58,17 +58,17 @@ namespace { +using ::absl::Hash; +using ::absl::container_internal::hashtable_debug_internal:: + HashtableDebugAccess; +using ::absl::hash_internal::SpyHashState; using ::absl::hash_test_internal::is_hashable; using ::absl::hash_test_internal::TypeErasedContainer; using ::absl::hash_test_internal::TypeErasedValue; -using ::testing::SizeIs; template <typename T> using TypeErasedVector = TypeErasedContainer<std::vector<T>>; -using absl::Hash; -using absl::hash_internal::SpyHashState; - template <typename T> class HashValueIntTest : public testing::Test { }; @@ -171,6 +171,9 @@ constexpr size_t kLog2NumValues = 5; constexpr size_t kNumValues = 1 << kLog2NumValues; + int64_t test_count = 0; + int64_t total_stuck_bit_count = 0; + for (size_t align = 1; align < kTotalSize / kNumValues; align < 8 ? align += 1 : align < 1024 ? align += 8 : align += 32) { SCOPED_TRACE(align); @@ -188,9 +191,17 @@ // 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; - // Test that there are at most 3 stuck bits. - EXPECT_LE(absl::popcount(stuck_bits), 3) << "0x" << std::hex << stuck_bits; + int stuck_bit_count = absl::popcount(stuck_bits); + size_t max_stuck_bits = 5; + EXPECT_LE(stuck_bit_count, max_stuck_bits) + << "0x" << std::hex << stuck_bits; + + total_stuck_bit_count += stuck_bit_count; + ++test_count; } + // Test that average across alignments are at most 0.2 stuck bits. + // As of 2025-05-30 test is also passing with 0.07 stuck bits. + EXPECT_LE(total_stuck_bit_count, 0.2 * test_count); } TEST(HashValueTest, PointerToMember) { @@ -1224,4 +1235,86 @@ absl::Hash<AutoReturnTypeUser>{}(AutoReturnTypeUser{1, s})); } +TEST(HashOf, DoubleSignCollision) { + // These values differ only in their most significant bit. + EXPECT_NE(absl::HashOf(-1.0), absl::HashOf(1.0)); +} + +// Test for collisions in short strings if PrecombineLengthMix is low quality. +TEST(PrecombineLengthMix, ShortStringCollision) { +#if defined(__wasm__) + GTEST_SKIP() << "Fails flakily on wasm due to no ASLR and 32-bit size_t."; +#endif + std::string s1 = "00"; + std::string s2 = "000"; + constexpr char kMinChar = 0; + constexpr char kMaxChar = 32; + for (s1[0] = kMinChar; s1[0] < kMaxChar; ++s1[0]) { + for (s1[1] = kMinChar; s1[1] < kMaxChar; ++s1[1]) { + for (s2[0] = kMinChar; s2[0] < kMaxChar; ++s2[0]) { + for (s2[1] = kMinChar; s2[1] < kMaxChar; ++s2[1]) { + for (s2[2] = kMinChar; s2[2] < kMaxChar; ++s2[2]) { + ASSERT_NE(absl::HashOf(s1), absl::HashOf(s2)) + << "s1[0]: " << static_cast<int>(s1[0]) + << "; s1[1]: " << static_cast<int>(s1[1]) + << "; s2[0]: " << static_cast<int>(s2[0]) + << "; s2[1]: " << static_cast<int>(s2[1]) + << "; s2[2]: " << static_cast<int>(s2[2]); + } + } + } + } + } +} + +// Test that we don't cause excessive collisions on the hash table for +// doubles in the range [-1024, 1024]. See cl/773069881 for more information. +TEST(SwisstableCollisions, DoubleRange) { + absl::flat_hash_set<double> set; + for (double t = -1024.0; t < 1024.0; t += 1.0) { + set.insert(t); + ASSERT_LT(HashtableDebugAccess<decltype(set)>::GetNumProbes(set, t), 64) + << t; + } +} + +// Test that for each pair of adjacent bytes in a string, if there's only +// entropy in those two bytes, then we don't have excessive collisions. +TEST(SwisstableCollisions, LowEntropyStrings) { + constexpr char kMinChar = 0; + constexpr char kMaxChar = 64; + // These sizes cover the different hashing cases. + for (size_t size : {8u, 16u, 32u, 64u, 128u}) { + for (size_t b = 0; b < size - 1; ++b) { + absl::flat_hash_set<std::string> set; + std::string s(size, '\0'); + for (char c1 = kMinChar; c1 < kMaxChar; ++c1) { + for (char c2 = kMinChar; c2 < kMaxChar; ++c2) { + s[b] = c1; + s[b + 1] = c2; + set.insert(s); + ASSERT_LT(HashtableDebugAccess<decltype(set)>::GetNumProbes(set, s), + 64) + << "size: " << size << "; bit: " << b; + } + } + } + } +} + +// Test that we don't have excessive collisions when keys are consecutive +// integers rotated by N bits. +TEST(SwisstableCollisions, LowEntropyInts) { + constexpr int kSizeTBits = sizeof(size_t) * 8; + for (int bit = 0; bit < kSizeTBits; ++bit) { + absl::flat_hash_set<size_t> set; + for (size_t i = 0; i < 128 * 1024; ++i) { + size_t v = absl::rotl(i, bit); + set.insert(v); + ASSERT_LT(HashtableDebugAccess<decltype(set)>::GetNumProbes(set, v), 48) + << bit << " " << i; + } + } +} + } // namespace
diff --git a/absl/hash/internal/hash.cc b/absl/hash/internal/hash.cc index 9abace5..23caae9 100644 --- a/absl/hash/internal/hash.cc +++ b/absl/hash/internal/hash.cc
@@ -14,52 +14,299 @@ #include "absl/hash/internal/hash.h" +#include <cassert> #include <cstddef> #include <cstdint> #include <type_traits> #include "absl/base/attributes.h" #include "absl/base/config.h" -#include "absl/hash/internal/low_level_hash.h" +#include "absl/base/internal/unaligned_access.h" +#include "absl/base/optimization.h" +#include "absl/base/prefetch.h" +#include "absl/hash/internal/city.h" + +#ifdef ABSL_AES_INTERNAL_HAVE_X86_SIMD +#error ABSL_AES_INTERNAL_HAVE_X86_SIMD cannot be directly set +#elif defined(__SSE4_2__) && defined(__AES__) +#define ABSL_AES_INTERNAL_HAVE_X86_SIMD +#endif + + +#ifdef ABSL_AES_INTERNAL_HAVE_X86_SIMD +#include <smmintrin.h> +#include <wmmintrin.h> +#include <xmmintrin.h> +#endif // ABSL_AES_INTERNAL_HAVE_X86_SIMD namespace absl { ABSL_NAMESPACE_BEGIN namespace hash_internal { -uint64_t MixingHashState::CombineLargeContiguousImpl32( - uint64_t state, const unsigned char* first, size_t len) { +namespace { + +void PrefetchFutureDataToLocalCache(const uint8_t* ptr) { + PrefetchToLocalCache(ptr + 5 * ABSL_CACHELINE_SIZE); +} + +#ifdef ABSL_AES_INTERNAL_HAVE_X86_SIMD +uint64_t Mix4x16Vectors(__m128i a, __m128i b, __m128i c, __m128i d) { + // res128 = encrypt(a + c, d) + decrypt(b - d, a) + auto res128 = _mm_add_epi64(_mm_aesenc_si128(_mm_add_epi64(a, c), d), + _mm_aesdec_si128(_mm_sub_epi64(b, d), a)); + auto x64 = static_cast<uint64_t>(_mm_cvtsi128_si64(res128)); + auto y64 = static_cast<uint64_t>(_mm_extract_epi64(res128, 1)); + return x64 ^ y64; +} + +uint64_t LowLevelHash33To64(uint64_t seed, const uint8_t* ptr, size_t len) { + assert(len > 32); + assert(len <= 64); + __m128i state = + _mm_set_epi64x(static_cast<int64_t>(seed), static_cast<int64_t>(len)); + auto a = _mm_loadu_si128(reinterpret_cast<const __m128i*>(ptr)); + auto b = _mm_loadu_si128(reinterpret_cast<const __m128i*>(ptr + 16)); + auto* last32_ptr = ptr + len - 32; + auto c = _mm_loadu_si128(reinterpret_cast<const __m128i*>(last32_ptr)); + auto d = _mm_loadu_si128(reinterpret_cast<const __m128i*>(last32_ptr + 16)); + + // Bits of the second argument to _mm_aesdec_si128/_mm_aesenc_si128 are + // XORed with the state argument after encryption. + // We use each value as the first argument to shuffle all the bits around. + // We do not add any salt to the state or loaded data, instead we vary + // instructions used to mix bits _mm_aesdec_si128/_mm_aesenc_si128 and + // _mm_add_epi64/_mm_sub_epi64. + // _mm_add_epi64/_mm_sub_epi64 are combined to one instruction with data + // loading like `vpaddq xmm1, xmm0, xmmword ptr [rdi]`. + auto na = _mm_aesdec_si128(_mm_add_epi64(state, a), state); + auto nb = _mm_aesdec_si128(_mm_sub_epi64(state, b), state); + auto nc = _mm_aesenc_si128(_mm_add_epi64(state, c), state); + auto nd = _mm_aesenc_si128(_mm_sub_epi64(state, d), state); + + // We perform another round of encryption to mix bits between two halves of + // the input. + return Mix4x16Vectors(na, nb, nc, nd); +} + +[[maybe_unused]] ABSL_ATTRIBUTE_NOINLINE uint64_t +LowLevelHashLenGt64(uint64_t seed, const void* data, size_t len) { + assert(len > 64); + const uint8_t* ptr = static_cast<const uint8_t*>(data); + const uint8_t* last_32_ptr = ptr + len - 32; + + // If we have more than 64 bytes, we're going to handle chunks of 64 + // bytes at a time. We're going to build up four separate hash states + // which we will then hash together. This avoids short dependency chains. + __m128i state0 = + _mm_set_epi64x(static_cast<int64_t>(seed), static_cast<int64_t>(len)); + __m128i state1 = state0; + __m128i state2 = state1; + __m128i state3 = state2; + + // Mixing two 128-bit vectors at a time with corresponding states. + // All variables are mixed slightly differently to avoid hash collision + // due to trivial byte rotation. + // We combine state and data with _mm_add_epi64/_mm_sub_epi64 before applying + // AES encryption to make hash function dependent on the order of the blocks. + // See comments in LowLevelHash33To64 for more considerations. + auto mix_ab = [&state0, + &state1](const uint8_t* p) ABSL_ATTRIBUTE_ALWAYS_INLINE { + // i128 a = *p; + // i128 b = *(p + 16); + // state0 = decrypt(state0 + a, state0); + // state1 = decrypt(state1 - b, state1); + auto a = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p)); + auto b = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p + 16)); + state0 = _mm_aesdec_si128(_mm_add_epi64(state0, a), state0); + state1 = _mm_aesdec_si128(_mm_sub_epi64(state1, b), state1); + }; + auto mix_cd = [&state2, + &state3](const uint8_t* p) ABSL_ATTRIBUTE_ALWAYS_INLINE { + // i128 c = *p; + // i128 d = *(p + 16); + // state2 = encrypt(state2 + c, state2); + // state3 = encrypt(state3 - d, state3); + auto c = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p)); + auto d = _mm_loadu_si128(reinterpret_cast<const __m128i*>(p + 16)); + state2 = _mm_aesenc_si128(_mm_add_epi64(state2, c), state2); + state3 = _mm_aesenc_si128(_mm_sub_epi64(state3, d), state3); + }; + + do { + PrefetchFutureDataToLocalCache(ptr); + mix_ab(ptr); + mix_cd(ptr + 32); + + ptr += 64; + len -= 64; + } while (len > 64); + + // We now have a data `ptr` with at most 64 bytes. + if (len > 32) { + mix_ab(ptr); + } + mix_cd(last_32_ptr); + + return Mix4x16Vectors(state0, state1, state2, state3); +} +#else +uint64_t Mix32Bytes(const uint8_t* ptr, uint64_t current_state) { + uint64_t a = absl::base_internal::UnalignedLoad64(ptr); + uint64_t b = absl::base_internal::UnalignedLoad64(ptr + 8); + uint64_t c = absl::base_internal::UnalignedLoad64(ptr + 16); + uint64_t d = absl::base_internal::UnalignedLoad64(ptr + 24); + + uint64_t cs0 = Mix(a ^ kStaticRandomData[1], b ^ current_state); + uint64_t cs1 = Mix(c ^ kStaticRandomData[2], d ^ current_state); + return cs0 ^ cs1; +} + +uint64_t LowLevelHash33To64(uint64_t seed, const uint8_t* ptr, size_t len) { + assert(len > 32); + assert(len <= 64); + uint64_t current_state = seed ^ kStaticRandomData[0] ^ len; + const uint8_t* last_32_ptr = ptr + len - 32; + return Mix32Bytes(last_32_ptr, Mix32Bytes(ptr, current_state)); +} + +[[maybe_unused]] ABSL_ATTRIBUTE_NOINLINE uint64_t +LowLevelHashLenGt64(uint64_t seed, const void* data, size_t len) { + assert(len > 64); + const uint8_t* ptr = static_cast<const uint8_t*>(data); + uint64_t current_state = seed ^ kStaticRandomData[0] ^ len; + const uint8_t* last_32_ptr = ptr + len - 32; + // If we have more than 64 bytes, we're going to handle chunks of 64 + // bytes at a time. We're going to build up four separate hash states + // which we will then hash together. This avoids short dependency chains. + uint64_t duplicated_state0 = current_state; + uint64_t duplicated_state1 = current_state; + uint64_t duplicated_state2 = current_state; + + do { + PrefetchFutureDataToLocalCache(ptr); + + uint64_t a = absl::base_internal::UnalignedLoad64(ptr); + uint64_t b = absl::base_internal::UnalignedLoad64(ptr + 8); + uint64_t c = absl::base_internal::UnalignedLoad64(ptr + 16); + uint64_t d = absl::base_internal::UnalignedLoad64(ptr + 24); + uint64_t e = absl::base_internal::UnalignedLoad64(ptr + 32); + uint64_t f = absl::base_internal::UnalignedLoad64(ptr + 40); + uint64_t g = absl::base_internal::UnalignedLoad64(ptr + 48); + uint64_t h = absl::base_internal::UnalignedLoad64(ptr + 56); + + current_state = Mix(a ^ kStaticRandomData[1], b ^ current_state); + duplicated_state0 = Mix(c ^ kStaticRandomData[2], d ^ duplicated_state0); + + duplicated_state1 = Mix(e ^ kStaticRandomData[3], f ^ duplicated_state1); + duplicated_state2 = Mix(g ^ kStaticRandomData[4], h ^ duplicated_state2); + + ptr += 64; + len -= 64; + } while (len > 64); + + current_state = (current_state ^ duplicated_state0) ^ + (duplicated_state1 + duplicated_state2); + // We now have a data `ptr` with at most 64 bytes and the current state + // of the hashing state machine stored in current_state. + if (len > 32) { + current_state = Mix32Bytes(ptr, current_state); + } + + // We now have a data `ptr` with at most 32 bytes and the current state + // of the hashing state machine stored in current_state. But we can + // safely read from `ptr + len - 32`. + return Mix32Bytes(last_32_ptr, current_state); +} +#endif // ABSL_AES_INTERNAL_HAVE_X86_SIMD + +[[maybe_unused]] uint64_t LowLevelHashLenGt32(uint64_t seed, const void* data, + size_t len) { + assert(len > 32); + if (ABSL_PREDICT_FALSE(len > 64)) { + return LowLevelHashLenGt64(seed, data, len); + } + return LowLevelHash33To64(seed, static_cast<const uint8_t*>(data), len); +} + +ABSL_ATTRIBUTE_ALWAYS_INLINE inline uint64_t HashBlockOn32Bit( + uint64_t state, const unsigned char* data, size_t len) { + // TODO(b/417141985): expose and use CityHash32WithSeed. + // Note: we can't use PrecombineLengthMix here because len can be up to 1024. + return CombineRawImpl( + state + len, + hash_internal::CityHash32(reinterpret_cast<const char*>(data), len)); +} + +ABSL_ATTRIBUTE_NOINLINE uint64_t +SplitAndCombineOn32Bit(uint64_t state, const unsigned char* first, size_t len) { while (len >= PiecewiseChunkSize()) { - state = Mix( - state ^ hash_internal::CityHash32(reinterpret_cast<const char*>(first), - PiecewiseChunkSize()), - kMul); + state = HashBlockOn32Bit(state, first, PiecewiseChunkSize()); len -= PiecewiseChunkSize(); first += PiecewiseChunkSize(); } + // Do not call CombineContiguousImpl for empty range since it is modifying + // state. + if (len == 0) { + return state; + } // Handle the remainder. return CombineContiguousImpl(state, first, len, std::integral_constant<int, 4>{}); } -uint64_t MixingHashState::CombineLargeContiguousImpl64( - uint64_t state, const unsigned char* first, size_t len) { +ABSL_ATTRIBUTE_ALWAYS_INLINE inline uint64_t HashBlockOn64Bit( + uint64_t state, const unsigned char* data, size_t len) { +#ifdef ABSL_HAVE_INTRINSIC_INT128 + return LowLevelHashLenGt32(state, data, len); +#else + return hash_internal::CityHash64WithSeed(reinterpret_cast<const char*>(data), + len, state); +#endif +} + +ABSL_ATTRIBUTE_NOINLINE uint64_t +SplitAndCombineOn64Bit(uint64_t state, const unsigned char* first, size_t len) { while (len >= PiecewiseChunkSize()) { - state = Mix(state ^ Hash64(first, PiecewiseChunkSize()), kMul); + state = HashBlockOn64Bit(state, first, PiecewiseChunkSize()); len -= PiecewiseChunkSize(); first += PiecewiseChunkSize(); } + // Do not call CombineContiguousImpl for empty range since it is modifying + // state. + if (len == 0) { + return state; + } // Handle the remainder. return CombineContiguousImpl(state, first, len, std::integral_constant<int, 8>{}); } -ABSL_CONST_INIT const void* const MixingHashState::kSeed = &kSeed; +} // namespace -uint64_t MixingHashState::LowLevelHashImpl(const unsigned char* data, - size_t len) { - return LowLevelHashLenGt32(data, len, Seed(), &kStaticRandomData[0]); +uint64_t CombineLargeContiguousImplOn32BitLengthGt8(uint64_t state, + const unsigned char* first, + size_t len) { + assert(len > 8); + assert(sizeof(size_t) == 4); // NOLINT(misc-static-assert) + if (ABSL_PREDICT_TRUE(len <= PiecewiseChunkSize())) { + return HashBlockOn32Bit(state, first, len); + } + return SplitAndCombineOn32Bit(state, first, len); } +uint64_t CombineLargeContiguousImplOn64BitLengthGt32(uint64_t state, + const unsigned char* first, + size_t len) { + assert(len > 32); + assert(sizeof(size_t) == 8); // NOLINT(misc-static-assert) + if (ABSL_PREDICT_TRUE(len <= PiecewiseChunkSize())) { + return HashBlockOn64Bit(state, first, len); + } + return SplitAndCombineOn64Bit(state, first, len); +} + +ABSL_CONST_INIT const void* const MixingHashState::kSeed = &kSeed; + } // namespace hash_internal ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/hash/internal/hash.h b/absl/hash/internal/hash.h index eb53823..15d3d5b 100644 --- a/absl/hash/internal/hash.h +++ b/absl/hash/internal/hash.h
@@ -89,10 +89,46 @@ #include "absl/types/variant.h" #include "absl/utility/utility.h" -#if defined(__cpp_lib_filesystem) && __cpp_lib_filesystem >= 201703L +#if defined(__cpp_lib_filesystem) && __cpp_lib_filesystem >= 201703L && \ + !defined(__XTENSA__) #include <filesystem> // NOLINT #endif +// 32-bit builds with SSE 4.2 do not have _mm_crc32_u64, so the +// __x86_64__ condition is necessary. +#if defined(__SSE4_2__) && defined(__x86_64__) + +#include <x86intrin.h> +#define ABSL_HASH_INTERNAL_HAS_CRC32 +#define ABSL_HASH_INTERNAL_CRC32_U64 _mm_crc32_u64 +#define ABSL_HASH_INTERNAL_CRC32_U32 _mm_crc32_u32 +#define ABSL_HASH_INTERNAL_CRC32_U8 _mm_crc32_u8 + +// 32-bit builds with AVX do not have _mm_crc32_u64, so the _M_X64 condition is +// necessary. +#elif defined(_MSC_VER) && !defined(__clang__) && defined(__AVX__) && \ + defined(_M_X64) + +// MSVC AVX (/arch:AVX) implies SSE 4.2. +#include <intrin.h> +#define ABSL_HASH_INTERNAL_HAS_CRC32 +#define ABSL_HASH_INTERNAL_CRC32_U64 _mm_crc32_u64 +#define ABSL_HASH_INTERNAL_CRC32_U32 _mm_crc32_u32 +#define ABSL_HASH_INTERNAL_CRC32_U8 _mm_crc32_u8 + +#elif defined(__ARM_FEATURE_CRC32) + +#include <arm_acle.h> +#define ABSL_HASH_INTERNAL_HAS_CRC32 +// Casting to uint32_t to be consistent with x86 intrinsic (_mm_crc32_u64 +// accepts crc as 64 bit integer). +#define ABSL_HASH_INTERNAL_CRC32_U64(crc, data) \ + __crc32cd(static_cast<uint32_t>(crc), data) +#define ABSL_HASH_INTERNAL_CRC32_U32 __crc32cw +#define ABSL_HASH_INTERNAL_CRC32_U8 __crc32cb + +#endif + namespace absl { ABSL_NAMESPACE_BEGIN @@ -104,8 +140,6 @@ // returns the size of these chunks. constexpr size_t PiecewiseChunkSize() { return 1024; } -// PiecewiseCombiner -// // PiecewiseCombiner is an internal-only helper class for hashing a piecewise // buffer of `char` or `unsigned char` as though it were contiguous. This class // provides two methods: @@ -126,12 +160,10 @@ // return combiner.finalize(std::move(state)); class PiecewiseCombiner { public: - PiecewiseCombiner() : position_(0) {} + PiecewiseCombiner() = default; PiecewiseCombiner(const PiecewiseCombiner&) = delete; PiecewiseCombiner& operator=(const PiecewiseCombiner&) = delete; - // PiecewiseCombiner::add_buffer() - // // Appends the given range of bytes to the sequence to be hashed, which may // modify the provided hash state. template <typename H> @@ -142,8 +174,6 @@ reinterpret_cast<const unsigned char*>(data), size); } - // PiecewiseCombiner::finalize() - // // Finishes combining the hash sequence, which may may modify the provided // hash state. // @@ -156,21 +186,19 @@ private: unsigned char buf_[PiecewiseChunkSize()]; - size_t position_; + size_t position_ = 0; + bool added_something_ = false; }; -// is_hashable() -// // Trait class which returns true if T is hashable by the absl::Hash framework. // Used for the AbslHashValue implementations for composite types below. template <typename T> struct is_hashable; -// HashStateBase -// -// An internal implementation detail that contains common implementation details -// for all of the "hash state objects" objects generated by Abseil. This is not -// a public API; users should not create classes that inherit from this. +// HashStateBase is an internal implementation detail that contains common +// implementation details for all of the "hash state objects" objects generated +// by Abseil. This is not a public API; users should not create classes that +// inherit from this. // // A hash state object is the template argument `H` passed to `AbslHashValue`. // It represents an intermediate state in the computation of an unspecified hash @@ -235,8 +263,6 @@ template <typename H> class HashStateBase { public: - // HashStateBase::combine() - // // Combines an arbitrary number of values into a hash state, returning the // updated state. // @@ -256,8 +282,6 @@ static H combine(H state, const T& value, const Ts&... values); static H combine(H state) { return state; } - // HashStateBase::combine_contiguous() - // // Combines a contiguous array of `size` elements into a hash state, returning // the updated state. // @@ -297,8 +321,6 @@ }; }; -// is_uniquely_represented -// // `is_uniquely_represented<T>` is a trait class that indicates whether `T` // is uniquely represented. // @@ -333,8 +355,6 @@ template <typename T, typename Enable = void> struct is_uniquely_represented : std::false_type {}; -// is_uniquely_represented<unsigned char> -// // unsigned char is a synonym for "byte", so it is guaranteed to be // uniquely represented. template <> @@ -349,9 +369,6 @@ Integral, typename std::enable_if<std::is_integral<Integral>::value>::type> : std::true_type {}; -// is_uniquely_represented<bool> -// -// template <> struct is_uniquely_represented<bool> : std::false_type {}; @@ -373,8 +390,15 @@ } }; -// hash_bytes() -// +// For use in `raw_hash_set` to pass a seed to the hash function. +struct HashWithSeed { + template <typename Hasher, typename T> + size_t hash(const Hasher& hasher, const T& value, size_t seed) const { + // NOLINTNEXTLINE(clang-diagnostic-sign-conversion) + return hasher.hash_with_seed(value, seed); + } +}; + // Convenience function that combines `hash_state` with the byte representation // of `value`. template <typename H, typename T, @@ -422,8 +446,10 @@ template <typename H, typename B> typename std::enable_if<std::is_same<B, bool>::value, H>::type AbslHashValue( H hash_state, B value) { + // We use ~size_t{} instead of 1 so that all bits are different between + // true/false instead of only 1. return H::combine(std::move(hash_state), - static_cast<unsigned char>(value ? 1 : 0)); + static_cast<size_t>(value ? ~size_t{} : 0)); } // AbslHashValue() for hashing enum values @@ -502,10 +528,10 @@ T ptr) { auto v = reinterpret_cast<uintptr_t>(ptr); // Due to alignment, pointers tend to have low bits as zero, and the next few - // bits follow a pattern since they are also multiples of some base value. The - // byte swap in WeakMix helps ensure we still have good entropy in low bits. - // Mix pointers twice to ensure we have good entropy in low bits. - return H::combine(std::move(hash_state), v, v); + // bits follow a pattern since they are also multiples of some base value. + // The PointerAlignment test verifies that our mixing is good enough to handle + // these cases. + return H::combine(std::move(hash_state), v); } // AbslHashValue() for hashing nullptr_t @@ -559,8 +585,6 @@ return H::combine(std::move(hash_state), p.first, p.second); } -// hash_tuple() -// // Helper function for hashing a tuple. The third argument should // be an index_sequence running from 0 to tuple_size<Tuple> - 1. template <typename H, typename Tuple, size_t... Is> @@ -619,9 +643,7 @@ // `eq()` member isn't equivalent to `==` on the underlying character type. template <typename H> H AbslHashValue(H hash_state, absl::string_view str) { - return H::combine( - H::combine_contiguous(std::move(hash_state), str.data(), str.size()), - WeaklyMixedInteger{str.size()}); + return H::combine_contiguous(std::move(hash_state), str.data(), str.size()); } // Support std::wstring, std::u16string and std::u32string. @@ -632,9 +654,7 @@ H AbslHashValue( H hash_state, const std::basic_string<Char, std::char_traits<Char>, Alloc>& str) { - return H::combine( - H::combine_contiguous(std::move(hash_state), str.data(), str.size()), - WeaklyMixedInteger{str.size()}); + return H::combine_contiguous(std::move(hash_state), str.data(), str.size()); } // Support std::wstring_view, std::u16string_view and std::u32string_view. @@ -643,16 +663,15 @@ std::is_same<Char, char16_t>::value || std::is_same<Char, char32_t>::value>> H AbslHashValue(H hash_state, std::basic_string_view<Char> str) { - return H::combine( - H::combine_contiguous(std::move(hash_state), str.data(), str.size()), - WeaklyMixedInteger{str.size()}); + return H::combine_contiguous(std::move(hash_state), str.data(), str.size()); } #if defined(__cpp_lib_filesystem) && __cpp_lib_filesystem >= 201703L && \ (!defined(__ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__) || \ __ENVIRONMENT_IPHONE_OS_VERSION_MIN_REQUIRED__ >= 130000) && \ (!defined(__ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__) || \ - __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101500) + __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ >= 101500) && \ + (!defined(__XTENSA__)) #define ABSL_INTERNAL_STD_FILESYSTEM_PATH_HASH_AVAILABLE 1 @@ -727,9 +746,8 @@ typename std::enable_if<is_hashable<T>::value && !std::is_same<T, bool>::value, H>::type AbslHashValue(H hash_state, const std::vector<T, Allocator>& vector) { - return H::combine(H::combine_contiguous(std::move(hash_state), vector.data(), - vector.size()), - WeaklyMixedInteger{vector.size()}); + return H::combine_contiguous(std::move(hash_state), vector.data(), + vector.size()); } // AbslHashValue special cases for hashing std::vector<bool> @@ -888,7 +906,6 @@ return H::combine(std::move(hash_state), opt.has_value()); } -// VariantVisitor template <typename H> struct VariantVisitor { H&& hash_state; @@ -937,8 +954,6 @@ // ----------------------------------------------------------------------------- -// hash_range_or_bytes() -// // Mixes all values in the range [data, data+size) into the hash state. // This overload accepts only uniquely-represented types, and hashes them by // hashing the entire range of bytes. @@ -949,16 +964,264 @@ return H::combine_contiguous(std::move(hash_state), bytes, sizeof(T) * size); } -// hash_range_or_bytes() template <typename H, typename T> typename std::enable_if<!is_uniquely_represented<T>::value, H>::type hash_range_or_bytes(H hash_state, const T* data, size_t size) { for (const auto end = data + size; data < end; ++data) { hash_state = H::combine(std::move(hash_state), *data); } - return hash_state; + return H::combine(std::move(hash_state), + hash_internal::WeaklyMixedInteger{size}); } +inline constexpr uint64_t kMul = uint64_t{0x79d5f9e0de1e8cf5}; + +// Random data taken from the hexadecimal digits of Pi's fractional component. +// https://en.wikipedia.org/wiki/Nothing-up-my-sleeve_number +ABSL_CACHELINE_ALIGNED inline constexpr uint64_t kStaticRandomData[] = { + 0x243f'6a88'85a3'08d3, 0x1319'8a2e'0370'7344, 0xa409'3822'299f'31d0, + 0x082e'fa98'ec4e'6c89, 0x4528'21e6'38d0'1377, +}; + +// Extremely weak mixture of length that is mixed into the state before +// combining the data. It is used only for small strings. This also ensures that +// we have high entropy in all bits of the state. +inline uint64_t PrecombineLengthMix(uint64_t state, size_t len) { + ABSL_ASSUME(len + sizeof(uint64_t) <= sizeof(kStaticRandomData)); + uint64_t data = absl::base_internal::UnalignedLoad64( + reinterpret_cast<const unsigned char*>(&kStaticRandomData[0]) + len); + return state ^ data; +} + +ABSL_ATTRIBUTE_ALWAYS_INLINE inline uint64_t Mix(uint64_t lhs, uint64_t rhs) { + // Though the 128-bit product needs multiple instructions on non-x86-64 + // platforms, it is still a good balance between speed and hash quality. + absl::uint128 m = lhs; + m *= rhs; + return Uint128High64(m) ^ Uint128Low64(m); +} + +// Suppress erroneous array bounds errors on GCC. +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Warray-bounds" +#endif +inline uint32_t Read4(const unsigned char* p) { + return absl::base_internal::UnalignedLoad32(p); +} +inline uint64_t Read8(const unsigned char* p) { + return absl::base_internal::UnalignedLoad64(p); +} +#if defined(__GNUC__) && !defined(__clang__) +#pragma GCC diagnostic pop +#endif + +// Reads 9 to 16 bytes from p. +// The first 8 bytes are in .first, and the rest of the bytes are in .second +// along with duplicated bytes from .first if len<16. +inline std::pair<uint64_t, uint64_t> Read9To16(const unsigned char* p, + size_t len) { + return {Read8(p), Read8(p + len - 8)}; +} + +// Reads 4 to 8 bytes from p. +// Bytes are permuted and some input bytes may be duplicated in output. +inline uint64_t Read4To8(const unsigned char* p, size_t len) { + // If `len < 8`, we duplicate bytes. We always put low memory at the end. + // E.g., on little endian platforms: + // `ABCD` will be read as `ABCDABCD`. + // `ABCDE` will be read as `BCDEABCD`. + // `ABCDEF` will be read as `CDEFABCD`. + // `ABCDEFG` will be read as `DEFGABCD`. + // `ABCDEFGH` will be read as `EFGHABCD`. + // We also do not care about endianness. On big-endian platforms, bytes will + // be permuted differently. We always shift low memory by 32, because that + // can be pipelined earlier. Reading high memory requires computing + // `p + len - 4`. + uint64_t most_significant = + static_cast<uint64_t>(absl::base_internal::UnalignedLoad32(p)) << 32; + uint64_t least_significant = + absl::base_internal::UnalignedLoad32(p + len - 4); + return most_significant | least_significant; +} + +// Reads 1 to 3 bytes from p. Some input bytes may be duplicated in output. +inline uint32_t Read1To3(const unsigned char* p, size_t len) { + // The trick used by this implementation is to avoid branches. + // We always read three bytes by duplicating. + // E.g., + // `A` is read as `AAA`. + // `AB` is read as `ABB`. + // `ABC` is read as `ABC`. + // We always shift `p[0]` so that it can be pipelined better. + // Other bytes require extra computation to find indices. + uint32_t mem0 = (static_cast<uint32_t>(p[0]) << 16) | p[len - 1]; + uint32_t mem1 = static_cast<uint32_t>(p[len / 2]) << 8; + return mem0 | mem1; +} + +ABSL_ATTRIBUTE_ALWAYS_INLINE inline uint64_t CombineRawImpl(uint64_t state, + uint64_t value) { + return Mix(state ^ value, kMul); +} + +// Slow dispatch path for calls to CombineContiguousImpl with a size argument +// larger than inlined size. Has the same effect as calling +// CombineContiguousImpl() repeatedly with the chunk stride size. +uint64_t CombineLargeContiguousImplOn32BitLengthGt8(uint64_t state, + const unsigned char* first, + size_t len); +uint64_t CombineLargeContiguousImplOn64BitLengthGt32(uint64_t state, + const unsigned char* first, + size_t len); + +ABSL_ATTRIBUTE_ALWAYS_INLINE inline uint64_t CombineSmallContiguousImpl( + uint64_t state, const unsigned char* first, size_t len) { + ABSL_ASSUME(len <= 8); + uint64_t v; + if (len >= 4) { + v = Read4To8(first, len); + } else if (len > 0) { + v = Read1To3(first, len); + } else { + // Empty string must modify the state. + v = 0x57; + } + return CombineRawImpl(state, v); +} + +ABSL_ATTRIBUTE_ALWAYS_INLINE inline uint64_t CombineContiguousImpl9to16( + uint64_t state, const unsigned char* first, size_t len) { + ABSL_ASSUME(len >= 9); + ABSL_ASSUME(len <= 16); + // Note: any time one half of the mix function becomes zero it will fail to + // incorporate any bits from the other half. However, there is exactly 1 in + // 2^64 values for each side that achieve this, and only when the size is + // exactly 16 -- for smaller sizes there is an overlapping byte that makes + // this impossible unless the seed is *also* incredibly unlucky. + auto p = Read9To16(first, len); + return Mix(state ^ p.first, kMul ^ p.second); +} + +ABSL_ATTRIBUTE_ALWAYS_INLINE inline uint64_t CombineContiguousImpl17to32( + uint64_t state, const unsigned char* first, size_t len) { + ABSL_ASSUME(len >= 17); + ABSL_ASSUME(len <= 32); + // Do two mixes of overlapping 16-byte ranges in parallel to minimize + // latency. + const uint64_t m0 = + Mix(Read8(first) ^ kStaticRandomData[1], Read8(first + 8) ^ state); + + const unsigned char* tail_16b_ptr = first + (len - 16); + const uint64_t m1 = Mix(Read8(tail_16b_ptr) ^ kStaticRandomData[3], + Read8(tail_16b_ptr + 8) ^ state); + return m0 ^ m1; +} + +// Implementation of the base case for combine_contiguous where we actually +// mix the bytes into the state. +// Dispatch to different implementations of combine_contiguous depending +// on the value of `sizeof(size_t)`. +inline uint64_t CombineContiguousImpl( + uint64_t state, const unsigned char* first, size_t len, + std::integral_constant<int, 4> /* sizeof_size_t */) { + // For large values we use CityHash, for small ones we use custom low latency + // hash. + if (len <= 8) { + return CombineSmallContiguousImpl(PrecombineLengthMix(state, len), first, + len); + } + return CombineLargeContiguousImplOn32BitLengthGt8(state, first, len); +} + +#ifdef ABSL_HASH_INTERNAL_HAS_CRC32 +inline uint64_t CombineContiguousImpl( + uint64_t state, const unsigned char* first, size_t len, + std::integral_constant<int, 8> /* sizeof_size_t */) { + if (ABSL_PREDICT_FALSE(len > 32)) { + return CombineLargeContiguousImplOn64BitLengthGt32(state, first, len); + } + // `mul` is the salt that is used for final mixing. It is important to fill + // high 32 bits because CRC wipes out high 32 bits. + // `rotr` is important to mix `len` into high 32 bits. + uint64_t mul = absl::rotr(kMul, static_cast<int>(len)); + // Only low 32 bits of each uint64_t are used in CRC32 so we use gbswap_64 to + // move high 32 bits to low 32 bits. It has slightly smaller binary size than + // `>> 32`. `state + 8 * len` is a single instruction on both x86 and ARM, so + // we use it to better mix length. Although only the low 32 bits of the pair + // elements are used, we use pair<uint64_t, uint64_t> for better generated + // code. + std::pair<uint64_t, uint64_t> crcs = {state + 8 * len, + absl::gbswap_64(state)}; + + // All CRC operations here directly read bytes from the memory. + // Single fused instructions are used, like `crc32 rcx, qword ptr [rsi]`. + // On x86, llvm-mca reports latency `R + 2` for such fused instructions, while + // `R + 3` for two separate `mov` + `crc` instructions. `R` is the latency of + // reading the memory. Fused instructions also reduce register pressure + // allowing surrounding code to be more efficient when this code is inlined. + if (len > 8) { + crcs = {ABSL_HASH_INTERNAL_CRC32_U64(crcs.first, Read8(first)), + ABSL_HASH_INTERNAL_CRC32_U64(crcs.second, Read8(first + len - 8))}; + if (len > 16) { + // We compute the second round of dependent CRC32 operations. + crcs = {ABSL_HASH_INTERNAL_CRC32_U64(crcs.first, Read8(first + len - 16)), + ABSL_HASH_INTERNAL_CRC32_U64(crcs.second, Read8(first + 8))}; + } + } else { + if (len >= 4) { + // We use CRC for 4 bytes to benefit from the fused instruction and better + // hash quality. + // Using `xor` or `add` may reduce latency for this case, but would + // require more registers, more instructions and will have worse hash + // quality. + crcs = {ABSL_HASH_INTERNAL_CRC32_U32(static_cast<uint32_t>(crcs.first), + Read4(first)), + ABSL_HASH_INTERNAL_CRC32_U32(static_cast<uint32_t>(crcs.second), + Read4(first + len - 4))}; + } else if (len >= 1) { + // We mix three bytes all into different output registers. + // This way, we do not need shifting of these bytes (so they don't overlap + // with each other). + crcs = {ABSL_HASH_INTERNAL_CRC32_U8(static_cast<uint32_t>(crcs.first), + first[0]), + ABSL_HASH_INTERNAL_CRC32_U8(static_cast<uint32_t>(crcs.second), + first[len - 1])}; + // Middle byte is mixed weaker. It is a new byte only for len == 3. + // Mixing is independent from CRC operations so it is scheduled ASAP. + mul += first[len / 2]; + } + } + // `mul` is mixed into both sides of `Mix` to guarantee non-zero values for + // both multiplicands. Using Mix instead of just multiplication here improves + // hash quality, especially for short strings. + return Mix(mul - crcs.first, crcs.second - mul); +} +#else +inline uint64_t CombineContiguousImpl( + uint64_t state, const unsigned char* first, size_t len, + std::integral_constant<int, 8> /* sizeof_size_t */) { + // For large values we use LowLevelHash or CityHash depending on the platform, + // for small ones we use custom low latency hash. + if (len <= 8) { + return CombineSmallContiguousImpl(PrecombineLengthMix(state, len), first, + len); + } + if (len <= 16) { + return CombineContiguousImpl9to16(PrecombineLengthMix(state, len), first, + len); + } + if (len <= 32) { + return CombineContiguousImpl17to32(PrecombineLengthMix(state, len), first, + len); + } + // We must not mix length into the state here because calling + // CombineContiguousImpl twice with PiecewiseChunkSize() must be equivalent + // to calling CombineLargeContiguousImpl once with 2 * PiecewiseChunkSize(). + return CombineLargeContiguousImplOn64BitLengthGt32(state, first, len); +} +#endif // ABSL_HASH_INTERNAL_HAS_CRC32 + #if defined(ABSL_INTERNAL_LEGACY_HASH_NAMESPACE) && \ ABSL_META_INTERNAL_STD_HASH_SFINAE_FRIENDLY_ #define ABSL_HASH_INTERNAL_SUPPORT_LEGACY_HASH_ 1 @@ -966,8 +1229,6 @@ #define ABSL_HASH_INTERNAL_SUPPORT_LEGACY_HASH_ 0 #endif -// HashSelect -// // Type trait to select the appropriate hash implementation to use. // HashSelect::type<T> will give the proper hash implementation, to be invoked // as: @@ -1064,26 +1325,7 @@ struct is_hashable : std::integral_constant<bool, HashSelect::template Apply<T>::value> {}; -// MixingHashState class ABSL_DLL MixingHashState : public HashStateBase<MixingHashState> { - // absl::uint128 is not an alias or a thin wrapper around the intrinsic. - // We use the intrinsic when available to improve performance. -#ifdef ABSL_HAVE_INTRINSIC_INT128 - using uint128 = __uint128_t; -#else // ABSL_HAVE_INTRINSIC_INT128 - using uint128 = absl::uint128; -#endif // ABSL_HAVE_INTRINSIC_INT128 - - // Random data taken from the hexadecimal digits of Pi's fractional component. - // https://en.wikipedia.org/wiki/Nothing-up-my-sleeve_number - ABSL_CACHELINE_ALIGNED static constexpr uint64_t kStaticRandomData[] = { - 0x243f'6a88'85a3'08d3, 0x1319'8a2e'0370'7344, 0xa409'3822'299f'31d0, - 0x082e'fa98'ec4e'6c89, 0x4528'21e6'38d0'1377, - }; - - static constexpr uint64_t kMul = - uint64_t{0xdcb22ca68cb134ed}; - template <typename T> using IntegralFastPath = conjunction<std::is_integral<T>, is_uniquely_represented<T>, @@ -1094,8 +1336,6 @@ MixingHashState(MixingHashState&&) = default; MixingHashState& operator=(MixingHashState&&) = default; - // MixingHashState::combine_contiguous() - // // Fundamental base case for hash recursion: mixes the given range of bytes // into the hash state. static MixingHashState combine_contiguous(MixingHashState hash_state, @@ -1107,35 +1347,63 @@ } using MixingHashState::HashStateBase::combine_contiguous; - // MixingHashState::hash() - // + template <typename T> + static size_t hash(const T& value) { + return hash_with_seed(value, Seed()); + } + // For performance reasons in non-opt mode, we specialize this for // integral types. // Otherwise we would be instantiating and calling dozens of functions for // something that is just one multiplication and a couple xor's. // The result should be the same as running the whole algorithm, but faster. template <typename T, absl::enable_if_t<IntegralFastPath<T>::value, int> = 0> - static size_t hash(T value) { + static size_t hash_with_seed(T value, size_t seed) { return static_cast<size_t>( - WeakMix(Seed(), static_cast<std::make_unsigned_t<T>>(value))); + CombineRawImpl(seed, static_cast<std::make_unsigned_t<T>>(value))); } - // Overload of MixingHashState::hash() template <typename T, absl::enable_if_t<!IntegralFastPath<T>::value, int> = 0> - static size_t hash(const T& value) { - return static_cast<size_t>(combine(MixingHashState{}, value).state_); + static size_t hash_with_seed(const T& value, size_t seed) { + return static_cast<size_t>(combine(MixingHashState{seed}, value).state_); } private: - // Invoked only once for a given argument; that plus the fact that this is - // move-only ensures that there is only one non-moved-from object. - MixingHashState() : state_(Seed()) {} - friend class MixingHashState::HashStateBase; template <typename H> friend H absl::hash_internal::hash_weakly_mixed_integer(H, WeaklyMixedInteger); + // Allow the HashState type-erasure implementation to invoke + // RunCombinedUnordered() directly. + friend class absl::HashState; + friend struct CombineRaw; + // For use in Seed(). + static const void* const kSeed; + + // Invoked only once for a given argument; that plus the fact that this is + // move-only ensures that there is only one non-moved-from object. + MixingHashState() : state_(Seed()) {} + + // Workaround for MSVC bug. + // We make the type copyable to fix the calling convention, even though we + // never actually copy it. Keep it private to not affect the public API of the + // type. + MixingHashState(const MixingHashState&) = default; + + explicit MixingHashState(uint64_t state) : state_(state) {} + + // Combines a raw value from e.g. integrals/floats/pointers/etc. This allows + // us to be consistent with IntegralFastPath when combining raw types, but + // optimize Read1To3 and Read4To8 differently for the string case. + static MixingHashState combine_raw(MixingHashState hash_state, + uint64_t value) { + return MixingHashState(CombineRawImpl(hash_state.state_, value)); + } + + // Android local modification: disable unsigned integer overflow sanitizer + // here to support enabling it in the clients. + ABSL_ATTRIBUTE_NO_SANITIZE_UNSIGNED_OVERFLOW static MixingHashState combine_weakly_mixed_integer( MixingHashState hash_state, WeaklyMixedInteger value) { // Some transformation for the value is needed to make an empty @@ -1164,195 +1432,6 @@ return MixingHashState::combine(std::move(state), unordered_state); } - // Allow the HashState type-erasure implementation to invoke - // RunCombinedUnordered() directly. - friend class absl::HashState; - friend struct CombineRaw; - - // Workaround for MSVC bug. - // We make the type copyable to fix the calling convention, even though we - // never actually copy it. Keep it private to not affect the public API of the - // type. - MixingHashState(const MixingHashState&) = default; - - explicit MixingHashState(uint64_t state) : state_(state) {} - - // Combines a raw value from e.g. integrals/floats/pointers/etc. This allows - // us to be consistent with IntegralFastPath when combining raw types, but - // optimize Read1To3 and Read4To8 differently for the string case. - static MixingHashState combine_raw(MixingHashState hash_state, - uint64_t value) { - return MixingHashState(WeakMix(hash_state.state_, value)); - } - - // Implementation of the base case for combine_contiguous where we actually - // mix the bytes into the state. - // Dispatch to different implementations of the combine_contiguous depending - // on the value of `sizeof(size_t)`. - static uint64_t CombineContiguousImpl(uint64_t state, - const unsigned char* first, size_t len, - std::integral_constant<int, 4> - /* sizeof_size_t */); - static uint64_t CombineContiguousImpl(uint64_t state, - const unsigned char* first, size_t len, - std::integral_constant<int, 8> - /* sizeof_size_t */); - - ABSL_ATTRIBUTE_ALWAYS_INLINE static uint64_t CombineSmallContiguousImpl( - uint64_t state, const unsigned char* first, size_t len) { - ABSL_ASSUME(len <= 8); - uint64_t v; - if (len >= 4) { - v = Read4To8(first, len); - } else if (len > 0) { - v = Read1To3(first, len); - } else { - // Empty ranges have no effect. - return state; - } - return WeakMix(state, v); - } - - ABSL_ATTRIBUTE_ALWAYS_INLINE static uint64_t CombineContiguousImpl9to16( - uint64_t state, const unsigned char* first, size_t len) { - ABSL_ASSUME(len >= 9); - ABSL_ASSUME(len <= 16); - // Note: any time one half of the mix function becomes zero it will fail to - // incorporate any bits from the other half. However, there is exactly 1 in - // 2^64 values for each side that achieve this, and only when the size is - // exactly 16 -- for smaller sizes there is an overlapping byte that makes - // this impossible unless the seed is *also* incredibly unlucky. - auto p = Read9To16(first, len); - return Mix(state ^ p.first, kMul ^ p.second); - } - - ABSL_ATTRIBUTE_ALWAYS_INLINE static uint64_t CombineContiguousImpl17to32( - uint64_t state, const unsigned char* first, size_t len) { - ABSL_ASSUME(len >= 17); - ABSL_ASSUME(len <= 32); - // Do two mixes of overlapping 16-byte ranges in parallel to minimize - // latency. - const uint64_t m0 = - Mix(Read8(first) ^ kStaticRandomData[1], Read8(first + 8) ^ state); - - const unsigned char* tail_16b_ptr = first + (len - 16); - const uint64_t m1 = Mix(Read8(tail_16b_ptr) ^ kStaticRandomData[3], - Read8(tail_16b_ptr + 8) ^ state); - return m0 ^ m1; - } - - // Slow dispatch path for calls to CombineContiguousImpl with a size argument - // larger than PiecewiseChunkSize(). Has the same effect as calling - // CombineContiguousImpl() repeatedly with the chunk stride size. - static uint64_t CombineLargeContiguousImpl32(uint64_t state, - const unsigned char* first, - size_t len); - static uint64_t CombineLargeContiguousImpl64(uint64_t state, - const unsigned char* first, - size_t len); - - // Reads 9 to 16 bytes from p. - // The least significant 8 bytes are in .first, and the rest of the bytes are - // in .second along with duplicated bytes from .first if len<16. - static std::pair<uint64_t, uint64_t> Read9To16(const unsigned char* p, - size_t len) { - uint64_t low_mem = Read8(p); - uint64_t high_mem = Read8(p + len - 8); -#ifdef ABSL_IS_LITTLE_ENDIAN - uint64_t most_significant = high_mem; - uint64_t least_significant = low_mem; -#else - uint64_t most_significant = low_mem; - uint64_t least_significant = high_mem; -#endif - return {least_significant, most_significant}; - } - - // Reads 8 bytes from p. - static uint64_t Read8(const unsigned char* p) { - // Suppress erroneous array bounds errors on GCC. -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Warray-bounds" -#endif - return absl::base_internal::UnalignedLoad64(p); -#if defined(__GNUC__) && !defined(__clang__) -#pragma GCC diagnostic pop -#endif - } - - // Reads 4 to 8 bytes from p. Some input bytes may be duplicated in output. - static uint64_t Read4To8(const unsigned char* p, size_t len) { - // If `len < 8`, we duplicate bytes in the middle. - // E.g.: - // `ABCD` will be read as `ABCDABCD`. - // `ABCDE` will be read as `ABCDBCDE`. - // `ABCDEF` will be read as `ABCDCDEF`. - // `ABCDEFG` will be read as `ABCDDEFG`. - // We also do not care about endianness. On big-endian platforms, bytes will - // be shuffled (it's fine). We always shift low memory by 32, because that - // can be pipelined earlier. Reading high memory requires computing - // `p + len - 4`. - uint64_t most_significant = - static_cast<uint64_t>(absl::base_internal::UnalignedLoad32(p)) << 32; - uint64_t least_significant = - absl::base_internal::UnalignedLoad32(p + len - 4); - return most_significant | least_significant; - } - - // Reads 1 to 3 bytes from p. Some input bytes may be duplicated in output. - static uint32_t Read1To3(const unsigned char* p, size_t len) { - // The trick used by this implementation is to avoid branches. - // We always read three bytes by duplicating. - // E.g., - // `A` is read as `AAA`. - // `AB` is read as `ABB`. - // `ABC` is read as `ABC`. - // We always shift `p[0]` so that it can be pipelined better. - // Other bytes require extra computation to find indices. - uint32_t mem0 = (static_cast<uint32_t>(p[0]) << 16) | p[len - 1]; - uint32_t mem1 = static_cast<uint32_t>(p[len / 2]) << 8; - return mem0 | mem1; - } - - ABSL_ATTRIBUTE_ALWAYS_INLINE static uint64_t Mix(uint64_t lhs, uint64_t rhs) { - // For 32 bit platforms we are trying to use all 64 lower bits. - if constexpr (sizeof(size_t) < 8) { - uint64_t m = lhs * rhs; - return m ^ (m >> 32); - } - // Though the 128-bit product on AArch64 needs two instructions, it is - // still a good balance between speed and hash quality. - uint128 m = lhs; - m *= rhs; - return Uint128High64(m) ^ Uint128Low64(m); - } - - // Slightly lower latency than Mix, but with lower quality. The byte swap - // helps ensure that low bits still have high quality. - ABSL_ATTRIBUTE_ALWAYS_INLINE static uint64_t WeakMix(uint64_t lhs, - uint64_t rhs) { - const uint64_t n = lhs ^ rhs; - // WeakMix doesn't work well on 32-bit platforms so just use Mix. - if constexpr (sizeof(size_t) < 8) return Mix(n, kMul); - return absl::gbswap_64(n * kMul); - } - - // An extern to avoid bloat on a direct call to LowLevelHash() with fixed - // values for both the seed and salt parameters. - static uint64_t LowLevelHashImpl(const unsigned char* data, size_t len); - - ABSL_ATTRIBUTE_ALWAYS_INLINE static uint64_t Hash64(const unsigned char* data, - size_t len) { -#ifdef ABSL_HAVE_INTRINSIC_INT128 - return LowLevelHashImpl(data, len); -#else - return hash_internal::CityHash64(reinterpret_cast<const char*>(data), len); -#endif - } - - // Seed() - // // A non-deterministic seed. // // The current purpose of this seed is to generate non-deterministic results @@ -1367,64 +1446,18 @@ // // On other platforms this is still going to be non-deterministic but most // probably per-build and not per-process. - ABSL_ATTRIBUTE_ALWAYS_INLINE static uint64_t Seed() { -#if (!defined(__clang__) || __clang_major__ > 11) && \ - (!defined(__apple_build_version__) || \ - __apple_build_version__ >= 19558921) // Xcode 12 - return static_cast<uint64_t>(reinterpret_cast<uintptr_t>(&kSeed)); -#else - // Workaround the absence of - // https://github.com/llvm/llvm-project/commit/bc15bf66dcca76cc06fe71fca35b74dc4d521021. - return static_cast<uint64_t>(reinterpret_cast<uintptr_t>(kSeed)); -#endif + ABSL_ATTRIBUTE_ALWAYS_INLINE static size_t Seed() { + // Android local modification: use an address of a function in libc instead + // of a local constant, so that the result is the same even if this code is + // linked multiple times in the same process. See b/436318577. + return static_cast<size_t>(reinterpret_cast<uintptr_t>(&strcmp)); } - static const void* const kSeed; uint64_t state_; }; -// MixingHashState::CombineContiguousImpl() -inline uint64_t MixingHashState::CombineContiguousImpl( - uint64_t state, const unsigned char* first, size_t len, - std::integral_constant<int, 4> /* sizeof_size_t */) { - // For large values we use CityHash, for small ones we just use a - // multiplicative hash. - if (len <= 8) { - return CombineSmallContiguousImpl(state, first, len); - } - if (ABSL_PREDICT_TRUE(len <= PiecewiseChunkSize())) { - return Mix(state ^ hash_internal::CityHash32( - reinterpret_cast<const char*>(first), len), - kMul); - } - return CombineLargeContiguousImpl32(state, first, len); -} - -// Overload of MixingHashState::CombineContiguousImpl() -inline uint64_t MixingHashState::CombineContiguousImpl( - uint64_t state, const unsigned char* first, size_t len, - std::integral_constant<int, 8> /* sizeof_size_t */) { - // For large values we use LowLevelHash or CityHash depending on the platform, - // for small ones we just use a multiplicative hash. - if (len <= 8) { - return CombineSmallContiguousImpl(state, first, len); - } - if (len <= 16) { - return CombineContiguousImpl9to16(state, first, len); - } - if (len <= 32) { - return CombineContiguousImpl17to32(state, first, len); - } - if (ABSL_PREDICT_TRUE(len <= PiecewiseChunkSize())) { - return Mix(state ^ Hash64(first, len), kMul); - } - return CombineLargeContiguousImpl64(state, first, len); -} - struct AggregateBarrier {}; -// HashImpl - // Add a private base class to make sure this type is not an aggregate. // Aggregates can be aggregate initialized even if the default constructor is // deleted. @@ -1439,6 +1472,13 @@ size_t operator()(const T& value) const { return MixingHashState::hash(value); } + + private: + friend struct HashWithSeed; + + size_t hash_with_seed(const T& value, size_t seed) const { + return MixingHashState::hash_with_seed(value, seed); + } }; template <typename T> @@ -1453,14 +1493,12 @@ values...); } -// HashStateBase::combine_contiguous() template <typename H> template <typename T> H HashStateBase<H>::combine_contiguous(H state, const T* data, size_t size) { return hash_internal::hash_range_or_bytes(std::move(state), data, size); } -// HashStateBase::combine_unordered() template <typename H> template <typename I> H HashStateBase<H>::combine_unordered(H state, I begin, I end) { @@ -1468,7 +1506,6 @@ CombineUnorderedCallback<I>{begin, end}); } -// HashStateBase::PiecewiseCombiner::add_buffer() template <typename H> H PiecewiseCombiner::add_buffer(H state, const unsigned char* data, size_t size) { @@ -1478,7 +1515,7 @@ position_ += size; return state; } - + added_something_ = true; // If the buffer is partially filled we need to complete the buffer // and hash it. if (position_ != 0) { @@ -1501,10 +1538,14 @@ return state; } -// HashStateBase::PiecewiseCombiner::finalize() template <typename H> H PiecewiseCombiner::finalize(H state) { - // Hash the remainder left in the buffer, which may be empty + // Do not call combine_contiguous with empty remainder since it is modifying + // state. + if (added_something_ && position_ == 0) { + return state; + } + // We still call combine_contiguous for the entirely empty buffer. return H::combine_contiguous(std::move(state), buf_, position_); } @@ -1512,4 +1553,9 @@ ABSL_NAMESPACE_END } // namespace absl +#undef ABSL_HASH_INTERNAL_HAS_CRC32 +#undef ABSL_HASH_INTERNAL_CRC32_U64 +#undef ABSL_HASH_INTERNAL_CRC32_U32 +#undef ABSL_HASH_INTERNAL_CRC32_U8 + #endif // ABSL_HASH_INTERNAL_HASH_H_
diff --git a/absl/hash/internal/low_level_hash.cc b/absl/hash/internal/low_level_hash.cc deleted file mode 100644 index 1a107ec..0000000 --- a/absl/hash/internal/low_level_hash.cc +++ /dev/null
@@ -1,105 +0,0 @@ -// Copyright 2020 The Abseil Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "absl/hash/internal/low_level_hash.h" - -#include <cassert> -#include <cstddef> -#include <cstdint> - -#include "absl/base/config.h" -#include "absl/base/internal/unaligned_access.h" -#include "absl/base/optimization.h" -#include "absl/base/prefetch.h" -#include "absl/numeric/int128.h" - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace hash_internal { -namespace { -uint64_t Mix(uint64_t v0, uint64_t v1) { - absl::uint128 p = v0; - p *= v1; - return absl::Uint128Low64(p) ^ absl::Uint128High64(p); -} -uint64_t Mix32Bytes(const uint8_t* ptr, uint64_t current_state, - const uint64_t salt[5]) { - uint64_t a = absl::base_internal::UnalignedLoad64(ptr); - uint64_t b = absl::base_internal::UnalignedLoad64(ptr + 8); - uint64_t c = absl::base_internal::UnalignedLoad64(ptr + 16); - uint64_t d = absl::base_internal::UnalignedLoad64(ptr + 24); - - uint64_t cs0 = Mix(a ^ salt[1], b ^ current_state); - uint64_t cs1 = Mix(c ^ salt[2], d ^ current_state); - return cs0 ^ cs1; -} -} // namespace - -uint64_t LowLevelHashLenGt32(const void* data, size_t len, uint64_t seed, - const uint64_t salt[5]) { - assert(len > 32); - const uint8_t* ptr = static_cast<const uint8_t*>(data); - uint64_t current_state = seed ^ salt[0] ^ len; - const uint8_t* last_32_ptr = ptr + len - 32; - - if (len > 64) { - // If we have more than 64 bytes, we're going to handle chunks of 64 - // bytes at a time. We're going to build up four separate hash states - // which we will then hash together. This avoids short dependency chains. - uint64_t duplicated_state0 = current_state; - uint64_t duplicated_state1 = current_state; - uint64_t duplicated_state2 = current_state; - - do { - // Always prefetch the next cacheline. - PrefetchToLocalCache(ptr + ABSL_CACHELINE_SIZE); - - uint64_t a = absl::base_internal::UnalignedLoad64(ptr); - uint64_t b = absl::base_internal::UnalignedLoad64(ptr + 8); - uint64_t c = absl::base_internal::UnalignedLoad64(ptr + 16); - uint64_t d = absl::base_internal::UnalignedLoad64(ptr + 24); - uint64_t e = absl::base_internal::UnalignedLoad64(ptr + 32); - uint64_t f = absl::base_internal::UnalignedLoad64(ptr + 40); - uint64_t g = absl::base_internal::UnalignedLoad64(ptr + 48); - uint64_t h = absl::base_internal::UnalignedLoad64(ptr + 56); - - current_state = Mix(a ^ salt[1], b ^ current_state); - duplicated_state0 = Mix(c ^ salt[2], d ^ duplicated_state0); - - duplicated_state1 = Mix(e ^ salt[3], f ^ duplicated_state1); - duplicated_state2 = Mix(g ^ salt[4], h ^ duplicated_state2); - - ptr += 64; - len -= 64; - } while (len > 64); - - current_state = (current_state ^ duplicated_state0) ^ - (duplicated_state1 + duplicated_state2); - } - - // We now have a data `ptr` with at most 64 bytes and the current state - // of the hashing state machine stored in current_state. - if (len > 32) { - current_state = Mix32Bytes(ptr, current_state, salt); - } - - // We now have a data `ptr` with at most 32 bytes and the current state - // of the hashing state machine stored in current_state. But we can - // safely read from `ptr + len - 32`. - return Mix32Bytes(last_32_ptr, current_state, salt); -} - -} // namespace hash_internal -ABSL_NAMESPACE_END -} // namespace absl
diff --git a/absl/hash/internal/low_level_hash.h b/absl/hash/internal/low_level_hash.h deleted file mode 100644 index 49e9ec4..0000000 --- a/absl/hash/internal/low_level_hash.h +++ /dev/null
@@ -1,50 +0,0 @@ -// Copyright 2020 The Abseil Authors -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. -// -// This file provides the Google-internal implementation of LowLevelHash. -// -// LowLevelHash is a fast hash function for hash tables, the fastest we've -// currently (late 2020) found that passes the SMHasher tests. The algorithm -// relies on intrinsic 128-bit multiplication for speed. This is not meant to be -// secure - just fast. -// -// It is closely based on a version of wyhash, but does not maintain or -// guarantee future compatibility with it. - -#ifndef ABSL_HASH_INTERNAL_LOW_LEVEL_HASH_H_ -#define ABSL_HASH_INTERNAL_LOW_LEVEL_HASH_H_ - -#include <stdint.h> -#include <stdlib.h> - -#include "absl/base/config.h" - -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace hash_internal { - -// Hash function for a byte array. A 64-bit seed and a set of five 64-bit -// integers are hashed into the result. The length must be greater than 32. -// -// To allow all hashable types (including string_view and Span) to depend on -// this algorithm, we keep the API low-level, with as few dependencies as -// possible. -uint64_t LowLevelHashLenGt32(const void* data, size_t len, uint64_t seed, - const uint64_t salt[5]); - -} // namespace hash_internal -ABSL_NAMESPACE_END -} // namespace absl - -#endif // ABSL_HASH_INTERNAL_LOW_LEVEL_HASH_H_
diff --git a/absl/hash/internal/low_level_hash_test.cc b/absl/hash/internal/low_level_hash_test.cc index d370dc7..d054337 100644 --- a/absl/hash/internal/low_level_hash_test.cc +++ b/absl/hash/internal/low_level_hash_test.cc
@@ -12,29 +12,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "absl/hash/internal/low_level_hash.h" - -#include <cinttypes> +#include <cstddef> #include <cstdint> +#include <string> #include "gmock/gmock.h" #include "gtest/gtest.h" +#include "absl/hash/hash.h" #include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" #define UPDATE_GOLDEN 0 namespace { -static const uint64_t kSalt[5] = {0xa0761d6478bd642f, 0xe7037ed1a0b428dbl, - 0x8ebc6af09c88c6e3, 0x589965cc75374cc3l, - 0x1d8e4e27c47d124f}; - TEST(LowLevelHashTest, VerifyGolden) { - constexpr size_t kNumGoldenOutputs = 94; + constexpr size_t kNumGoldenOutputs = 95; static struct { absl::string_view base64_data; uint64_t seed; - } cases[] = { + } cases[kNumGoldenOutputs] = { {"VprUGNH+5NnNRaORxgH/ySrZFQFDL+4VAodhfBNinmn8cg==", uint64_t{0x531858a40bfa7ea1}}, {"gc1xZaY+q0nPcUvOOnWnT3bqfmT/geth/f7Dm2e/DemMfk4=", @@ -361,54 +358,100 @@ uint64_t{0xc9ae5c8759b4877a}}, }; -#if defined(ABSL_IS_BIG_ENDIAN) +#if defined(ABSL_IS_BIG_ENDIAN) || !defined(ABSL_HAVE_INTRINSIC_INT128) || \ + UINTPTR_MAX != UINT64_MAX constexpr uint64_t kGolden[kNumGoldenOutputs] = {}; - GTEST_SKIP() << "We only maintain golden data for little endian systems."; + // This conditional is to avoid an unreachable code warning. + bool skip = true; + if (skip) { + GTEST_SKIP() + << "We only maintain golden data for little endian 64 bit systems with " + "128 bit intristics."; + } +#elif defined(__SSE4_2__) && defined(__AES__) + constexpr uint64_t kGolden[kNumGoldenOutputs] = { + 0xd6bdb2c9ba5e55f2, 0xffd3e23d4115a8ae, 0x2c3218ef486127de, + 0x554fa7f3a262b886, 0x06304cbf82e312d3, 0x490b3fb5af80622c, + 0x7398a90b8cc59c5d, 0x65fb3168b98030ab, 0xd4564363c53617bb, + 0x0545c26351925fe7, 0xc30700723b634bf4, 0xfb23a140a76dbe94, + 0x2fa1467fe218a47c, 0x92e05ec3a7b966eb, 0x6112b56e5624dd50, + 0x8760801365f9d722, 0x41f7187b61db0e5e, 0x7fe9188a1f5f50ad, + 0x25800bd4c2002ef1, 0x91fecd33a78ef0aa, 0x93986ad71e983613, + 0xe4c78173c7ea537b, 0x0bbdc2bcabdb50b1, 0xd9aa134df2d87623, + 0x6c4907c9477a9409, 0xc3e418a5dbda52e5, 0x4d24f3e9d0dda93a, + 0xcdb565a363dbe45f, 0xa95f228c8ee57478, 0x6b8f00bab5130227, + 0x2d05a0f44818b67a, 0xd6bf7d990b5f44cb, 0xa3608bdb4712861a, + 0xf20c33e5e355330b, 0xbc86e1b13130180d, 0x0848221b397b839a, + 0x17cc0acf44a7e210, 0xc18c6dc584fe0f62, 0x896c7858a59f991d, + 0xeab1e6d7d2856ed7, 0x7e4b2d99c23edc51, 0x9aeeeb7fa46e7cf0, + 0x161b9f2e3611790f, 0x5f82aae18d971b36, 0x8d0dd9965881e162, + 0x56700ea26285895a, 0xcd919c86c29a053e, 0x3e5d589282d9a722, + 0x92caee9f48a66604, 0x7e1a2fd9b06f14b0, 0xce1d5293f95b0178, + 0x8101361290e70a11, 0x570e3e9c9eafc1c6, 0x77b6241926a7a568, + 0x313e5cb34f346699, 0xab8ebeab0514b82b, 0x6e0a43763a310408, + 0x761b76ec22b2e440, 0x4238c84a9ec00528, 0xb9ea1f6d4d5552af, + 0xd21f8f110b9dc060, 0xb3d3842b69ac3689, 0xd0a88aa1dcf59869, + 0xf3f69f637b123403, 0xf5f34b1068cac7da, 0xe69a08d604774abf, + 0x57648d3a73332437, 0x9762947f5013d00d, 0x35c5d734a0015922, + 0xbee2fe5a104ce209, 0xedb060efa6efca34, 0x5ccf0f4786d97bc2, + 0x1ef8ed72e80d7bef, 0x58522deb49c5e30f, 0xde97cd2a6f8bd13b, + 0x3fae37c6f9855d09, 0xea99ae786feca261, 0x8c6d1d46670b0943, + 0x84658b2a232c7bfb, 0x7058b7a7968de394, 0x0d44fba68e25aa8f, + 0xc7f687020f8eb00b, 0xbf9671e1196153d6, 0x1009be891b7f83e7, + 0x4f9457fb4aa12865, 0x30a49d9563643b32, 0x0302e2c5b46d5a3a, + 0x77553f42fb0bfbf7, 0x26b95e89f0077110, 0x76ce68ebe01191ba, + 0x724110fb509e4376, 0xebe74b016b5cfb88, 0x3b0fe11dcf175fc9, + 0x20b737b9c0490538, 0x0db21c429b45fd17, + }; #else constexpr uint64_t kGolden[kNumGoldenOutputs] = { - 0x59b1542b0ff6b7b8, 0x3fb979d297096db9, 0xb391802c536343a9, - 0x94e0f7e4331081c4, 0x234d95e49e3ce30e, 0xca6351a3e568ed17, - 0xa62fcf7fa334293d, 0xb03111035f546067, 0x97b8c861e013d558, - 0xb6683803d9387949, 0xce5d907e0b3cb6a1, 0xab7466fae53ed201, - 0x8f13ca3f1cac3edd, 0xa2684a99cd909a2a, 0x03194f86b9440843, - 0xab3a745d96f75a66, 0xef2448606760ec3d, 0xd999e03247d5d5c5, - 0x4a25ab345d53f926, 0xa511b829ce9fc919, 0x4b76517f8e806cbf, - 0x006efd7ee09ff8d4, 0x790a4978bd0170a1, 0xc14f6e4b2dff057e, - 0xe0d2f4ae7c836d09, 0x4e2038a491ed939d, 0x23fd6f408e9598e0, - 0xa91cf8f1d92bcb08, 0x555cdec06df49d58, 0xe7d3e14bd6a8f3bd, - 0x4fdd25c1e75c009a, 0x3dffb8acf1ffbd17, 0x56946f33ed73a705, - 0x154c633d7690f3b0, 0x3e96f8e9a58a04e0, 0xb0279b244d3ccf9c, - 0x8571e87c882b2142, 0x9d9ada45132e7b41, 0xd5667655533f1dec, - 0x70607ace4ec36463, 0x691418d2eb63116c, 0xa70179d8e7142980, - 0xf8388d756bea25a7, 0xe5127c736d9826de, 0x7f1c95f9b6b656b6, - 0x66ab835b7bf4c7b3, 0xc03423b9a6db9728, 0xe88415a2b416b76d, - 0x8afd8c14d0b56c36, 0xe9a252b3ba217dad, 0x710150f5cd87a9ff, - 0xd66b147837fad9ae, 0x1af5f8ffbaa717a7, 0xe01f88d7a9a8ac17, - 0xd67870a7251fde72, 0xf32b837f845a676b, 0x0827717b1ffe59f7, - 0x80307212ca7645fb, 0xf0d22af71ea57c80, 0x459373765f2c114b, - 0x54d26109fab9cbaf, 0xc603da4e257b93db, 0x57fa334b5689d7d5, - 0x41cd1b2a8a91f620, 0xe1d6e7cd0fb015af, 0x8608e9035eb9d795, - 0x45c7b9fae739fee1, 0x9f5ae4f7a6b597ee, 0xfb771b6e0017757d, - 0x8dac6d29cfd8d027, 0x3c9ba4fb62ce6508, 0xa971fad8243844a7, - 0xd2126f49b2ea3b64, 0x5dd78fe7ac436861, 0xfe4004a6bb3494a8, - 0xe7c01cc63d770d7c, 0xa117075b8c801d37, 0xdf1dfe75f0e73069, - 0x7285b39700cefb98, 0x5e97ea1aa9a670eb, 0xe21872db2b9137a3, - 0x12630b02c6ca405e, 0xfe1f2d802151f97a, 0xb53b0ed3dea4fb02, - 0xc6d5ed56d1dbf9fd, 0xe5b92b558a5c70cb, 0xccd6eedf97277d08, - 0x08582fff2e1494ed, 0xa41f2b3d17f1c4c7, 0x29ec07e5ef950f3d, - 0x96aba32565a97084, 0xf26870eca10cebcd, 0xbe1432feb4d33361, - 0x21993a779845e6eb, + 0x669da02f8d009e0f, 0xceb19bf2255445cd, 0x0e746992d6d43a7c, + 0x41ed623b9dcc5fde, 0x187a5a30d7c72edc, 0x949ae2a9c1eb925a, + 0x7e9c76a7b7c35e68, 0x4f96bf15b8309ff6, 0x26c0c1fde233732e, + 0xb0453f72aa151615, 0xf24b621a9ce9fece, 0x99ed798408687b5f, + 0x3b13ec1221423b66, 0xc67cf148a28afe59, 0x22f7e0173f92e3fa, + 0x14186c5fda6683a0, 0x97d608caa2603b2c, 0xfde3b0bbba24ffa9, + 0xb7068eb48c472c77, 0x9e34d72866b9fda0, 0xbbb99c884cdef88e, + 0x81d3e01f472a8a1a, 0xf84f506b3b60366d, 0xfe3f42f01300db37, + 0xe385712a51c1f836, 0x41dfd5e394245c79, 0x60855dbedadb900a, + 0xbdb4c0aa38567476, 0x9748802e8eec02cc, 0x5ced256d257f88de, + 0x55acccdf9a80f155, 0xa64b55b071afbbea, 0xa205bfe6c724ce4d, + 0x69dd26ca8ac21744, 0xef80e2ff2f6a9bc0, 0xde266c0baa202c20, + 0xfa3463080ac74c50, 0x379d968a40125c2b, 0x4cbbd0a7b3c7d648, + 0xc92afd93f4c665d2, 0x6e28f5adb7ae38dc, 0x7c689c9c237be35e, + 0xaea41b29bd9d0f73, 0x832cef631d77e59f, 0x70cac8e87bc37dd3, + 0x8e8c98bbde68e764, 0xd6117aeb3ddedded, 0xd796ab808e766240, + 0x8953d0ea1a7d9814, 0xa212eba4281b391c, 0x21a555a8939ce597, + 0x809d31660f6d81a8, 0x2356524b20ab400f, 0x5bc611e1e49d0478, + 0xba9c065e2f385ce2, 0xb0a0fd12f4e83899, 0x14d076a35b1ff2ca, + 0x8acd0bb8cf9a93c0, 0xe62e8ec094039ee4, 0x38a536a7072bdc61, + 0xca256297602524f8, 0xfc62ebfb3530caeb, 0x8d8b0c05520569f6, + 0xbbaca65cf154c59d, 0x3739b5ada7e338d3, 0xdb9ea31f47365340, + 0x410b5c9c1da56755, 0x7e0abc03dbd10283, 0x136f87be70ed442e, + 0x6b727d4feddbe1e9, 0x074ebb21183b01df, 0x3fe92185b1985484, + 0xc5d8efd3c68305ca, 0xd9bada21b17e272e, 0x64d73133e1360f83, + 0xeb8563aa993e21f9, 0xe5e8da50cceab28f, 0x7a6f92eb3223d2f3, + 0xbdaf98370ea9b31b, 0x1682a84457f077bc, 0x4abd2d33b6e3be37, + 0xb35bc81a7c9d4c04, 0x3e5bde3fb7cfe63d, 0xff3abe6e2ffec974, + 0xb8116dd26cf6feec, 0x7a77a6e4ed0cf081, 0xb71eec2d5a184316, + 0x6fa932f77b4da817, 0x795f79b33909b2c4, 0x1b8755ef6b5eb34e, + 0x2255b72d7d6b2d79, 0xf2bdafafa90bd50a, 0x442a578f02cb1fc8, + 0xc25aefe55ecf83db, 0x3114c056f9c5a676, }; #endif + auto hash_fn = [](absl::string_view s, uint64_t state) { + return absl::hash_internal::CombineLargeContiguousImplOn64BitLengthGt32( + state, reinterpret_cast<const unsigned char*>(s.data()), s.size()); + }; + #if UPDATE_GOLDEN (void)kGolden; // Silence warning. for (size_t i = 0; i < kNumGoldenOutputs; ++i) { std::string str; ASSERT_TRUE(absl::Base64Unescape(cases[i].base64_data, &str)); ASSERT_GT(str.size(), 32); - uint64_t h = absl::hash_internal::LowLevelHashLenGt32( - str.data(), str.size(), cases[i].seed, kSalt); + uint64_t h = hash_fn(str, cases[i].seed); printf("0x%016" PRIx64 ", ", h); if (i % 3 == 2) { printf("\n"); @@ -423,9 +466,7 @@ std::string str; ASSERT_TRUE(absl::Base64Unescape(cases[i].base64_data, &str)); ASSERT_GT(str.size(), 32); - EXPECT_EQ(absl::hash_internal::LowLevelHashLenGt32(str.data(), str.size(), - cases[i].seed, kSalt), - kGolden[i]); + EXPECT_EQ(hash_fn(str, cases[i].seed), kGolden[i]); } #endif }
diff --git a/absl/hash/internal/spy_hash_state.h b/absl/hash/internal/spy_hash_state.h index e403113..823e1e9 100644 --- a/absl/hash/internal/spy_hash_state.h +++ b/absl/hash/internal/spy_hash_state.h
@@ -151,6 +151,9 @@ static SpyHashStateImpl combine_contiguous(SpyHashStateImpl hash_state, const unsigned char* begin, size_t size) { + if (size == 0) { + return SpyHashStateImpl::combine_raw(std::move(hash_state), 0); + } const size_t large_chunk_stride = PiecewiseChunkSize(); // Combining a large contiguous buffer must have the same effect as // doing it piecewise by the stride length, followed by the (possibly @@ -165,6 +168,7 @@ if (size > 0) { hash_state.hash_representation_.emplace_back( reinterpret_cast<const char*>(begin), size); + hash_state = SpyHashStateImpl::combine_raw(std::move(hash_state), size); } return hash_state; } @@ -224,8 +228,9 @@ // Combines raw data from e.g. integrals/floats/pointers/etc. static SpyHashStateImpl combine_raw(SpyHashStateImpl state, uint64_t value) { - const unsigned char* data = reinterpret_cast<const unsigned char*>(&value); - return SpyHashStateImpl::combine_contiguous(std::move(state), data, 8); + state.hash_representation_.emplace_back( + reinterpret_cast<const char*>(&value), 8); + return state; } // This is true if SpyHashStateImpl<T> has been passed to a call of
diff --git a/absl/log/BUILD.bazel b/absl/log/BUILD.bazel index 62ece45..5bc7150 100644 --- a/absl/log/BUILD.bazel +++ b/absl/log/BUILD.bazel
@@ -14,6 +14,8 @@ # limitations under the License. # +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load( "//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS", @@ -78,6 +80,8 @@ ":log", "//absl/base:config", "//absl/base:core_headers", + "//absl/base:nullability", + "//absl/base:nullability_traits_internal", "//absl/strings", ], ) @@ -152,6 +156,7 @@ cc_library( name = "log_entry", + srcs = ["log_entry.cc"], hdrs = ["log_entry.h"], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, @@ -160,6 +165,7 @@ "//absl/base:core_headers", "//absl/base:log_severity", "//absl/log/internal:config", + "//absl/log/internal:proto", "//absl/strings", "//absl/time", "//absl/types:span",
diff --git a/absl/log/CMakeLists.txt b/absl/log/CMakeLists.txt index 130897f..9d097ab 100644 --- a/absl/log/CMakeLists.txt +++ b/absl/log/CMakeLists.txt
@@ -28,7 +28,6 @@ absl::core_headers absl::log_internal_check_op absl::log_internal_conditions - absl::log_internal_message absl::log_internal_strip ) @@ -47,6 +46,7 @@ absl::base absl::config absl::core_headers + absl::has_ostream_operator absl::leak_check absl::log_internal_nullguard absl::log_internal_nullstream @@ -466,6 +466,8 @@ absl::config absl::core_headers absl::log + absl::nullability + absl::nullability_traits_internal absl::strings PUBLIC ) @@ -560,6 +562,8 @@ absl_cc_library( NAME log_entry + SRCS + "log_entry.cc" HDRS "log_entry.h" COPTS @@ -570,6 +574,7 @@ absl::config absl::core_headers absl::log_internal_config + absl::log_internal_proto absl::log_severity absl::span absl::strings @@ -1199,3 +1204,36 @@ absl::log_internal_fnmatch GTest::gmock_main ) + +absl_cc_library( + NAME + log_internal_container + HDRS + "internal/container.h" + COPTS + ${ABSL_DEFAULT_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::requires_internal + absl::strings +) + +absl_cc_test( + NAME + internal_container_test + SRCS + "internal/container_test.cc" + COPTS + ${ABSL_TEST_COPTS} + LINKOPTS + ${ABSL_DEFAULT_LINKOPTS} + DEPS + absl::config + absl::log_internal_container + absl::span + absl::strings + absl::str_format + GTest::gmock_main +)
diff --git a/absl/log/check_test_impl.inc b/absl/log/check_test_impl.inc index 7a0000e..47af1dd 100644 --- a/absl/log/check_test_impl.inc +++ b/absl/log/check_test_impl.inc
@@ -13,6 +13,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// SKIP_ABSL_INLINE_NAMESPACE_CHECK + #ifndef ABSL_LOG_CHECK_TEST_IMPL_H_ #define ABSL_LOG_CHECK_TEST_IMPL_H_ @@ -22,6 +24,8 @@ #error ABSL_TEST_CHECK must be defined for these tests to work. #endif +#include <cstdint> +#include <limits> #include <ostream> #include <string> @@ -40,6 +44,7 @@ using ::testing::AllOf; using ::testing::AnyOf; +using ::testing::ContainsRegex; using ::testing::HasSubstr; using ::testing::Not; @@ -238,6 +243,72 @@ ABSL_TEST_CHECK_LT(1, 2); } +TEST(CHECKTest, TestBinaryChecksWithStringComparison) { + const std::string a = "a"; + ABSL_TEST_CHECK_EQ(a, "a"); + ABSL_TEST_CHECK_NE(a, "b"); + ABSL_TEST_CHECK_GE(a, a); + ABSL_TEST_CHECK_GE("b", a); + ABSL_TEST_CHECK_LE(a, "a"); + ABSL_TEST_CHECK_LE(a, "b"); + ABSL_TEST_CHECK_GT("b", a); + ABSL_TEST_CHECK_LT(a, "b"); +} + +TEST(CHECKDeathTest, CheckWithCharStarAndStringPrintsTheCharStar) { + std::string str = "B"; + + // When the comparison happens as strings, then we print the CharT* as a + // string. + EXPECT_DEATH(ABSL_TEST_CHECK_EQ("A", str), + R"re(Check failed: \"A\" == str \(A vs. B\))re"); +} + +#if defined(GTEST_USES_SIMPLE_RE) && GTEST_USES_SIMPLE_RE +#define POINTER_VALUE_RE R"re(\w*)re" +#else +#define POINTER_VALUE_RE R"re((0x)*[0-9a-fA-F]*)re" +#endif + +#define POINTER_VS_POINTER_RE(lhs, rhs) "\\(" lhs " vs. " rhs "\\)" + +template <typename CharT> +void TestCharStarComparison() { + // When the comparison happens as pointers, we only print the pointer and not + // interpret it as a C-String because it might not be. + // Leave the CharTs uninitialized to trigger ASan/MSan failures if we actually + // read the pointers. + CharT* p1 = new CharT; + CharT* p2 = new CharT; + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(p1, p2), + R"re(Check failed: p1 == p2 )re" POINTER_VS_POINTER_RE( + POINTER_VALUE_RE, POINTER_VALUE_RE)); + CharT as_array[10]; + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(p1, as_array), + R"re(Check failed: p1 == as_array )re" POINTER_VS_POINTER_RE( + POINTER_VALUE_RE, POINTER_VALUE_RE)); + + const void* as_void = as_array; + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(as_void, p2), + R"re(Check failed: as_void == p2 )re" POINTER_VS_POINTER_RE( + POINTER_VALUE_RE, POINTER_VALUE_RE)); + + delete p1; + delete p2; +} + +TEST(CHECKDeathTest, CheckWithCharStarStringification) { + TestCharStarComparison<char>(); + TestCharStarComparison<signed char>(); + TestCharStarComparison<unsigned char>(); + TestCharStarComparison<wchar_t>(); +#if defined(__cpp_char8_t) + TestCharStarComparison<char8_t>(); +#endif + TestCharStarComparison<char16_t>(); + TestCharStarComparison<char32_t>(); +} + // For testing using CHECK*() on anonymous enums. enum { CASE_A, CASE_B }; @@ -262,6 +333,25 @@ ABSL_TEST_CHECK_NE(nullptr, p_not_null); } +struct ExampleTypeThatHasNoStreamOperator { + bool x; + + bool operator==(const ExampleTypeThatHasNoStreamOperator& other) const { + return x == other.x; + } + bool operator==(const bool& other) const { return x == other; } +}; + +TEST(CHECKDeathTest, TestBinaryChecksWithUnprintable) { + ExampleTypeThatHasNoStreamOperator a{true}; + ExampleTypeThatHasNoStreamOperator b{false}; + ABSL_TEST_CHECK_EQ(a, a); + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b), "Check failed: a == b"); + ABSL_TEST_CHECK_EQ(a, true); + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, false), + "Check failed: a == false \\(UNPRINTABLE vs. 0\\)"); +} + #if GTEST_HAS_DEATH_TEST // Test logging of various char-typed values by failing CHECK*(). @@ -303,9 +393,11 @@ a = "xx"; EXPECT_DEATH(ABSL_TEST_CHECK_EQ(a, b), - "Check failed: a == b \\(xx vs. \\(null\\)\\)"); + R"re(Check failed: a == b )re" POINTER_VS_POINTER_RE( + POINTER_VALUE_RE, "\\(null\\)")); EXPECT_DEATH(ABSL_TEST_CHECK_EQ(b, a), - "Check failed: b == a \\(\\(null\\) vs. xx\\)"); + R"re(Check failed: b == a )re" POINTER_VS_POINTER_RE( + "\\(null\\)", POINTER_VALUE_RE)); std::nullptr_t n{}; EXPECT_DEATH(ABSL_TEST_CHECK_NE(n, nullptr), @@ -638,9 +730,8 @@ EXPECT_DEATH( ABSL_TEST_CHECK_EQ(p, nullptr), AnyOf( - HasSubstr("Check failed: p == nullptr (0000000000001234 vs. (null))"), - HasSubstr("Check failed: p == nullptr (0x1234 vs. (null))") - )); + HasSubstr("Check failed: p == nullptr (0000000000001234 vs. (null))"), + HasSubstr("Check failed: p == nullptr (0x1234 vs. (null))"))); } // An uncopyable object with operator<<. @@ -670,6 +761,273 @@ HasSubstr("Check failed: v1 == v2 (Uncopyable{1} vs. Uncopyable{2})")); } +enum class ScopedEnum { kValue1 = 1, kValue2 = 2 }; + +TEST(CHECKTest, TestScopedEnumComparisonChecks) { + ABSL_TEST_CHECK_EQ(ScopedEnum::kValue1, ScopedEnum::kValue1); + ABSL_TEST_CHECK_NE(ScopedEnum::kValue1, ScopedEnum::kValue2); + ABSL_TEST_CHECK_LT(ScopedEnum::kValue1, ScopedEnum::kValue2); + ABSL_TEST_CHECK_LE(ScopedEnum::kValue1, ScopedEnum::kValue2); + ABSL_TEST_CHECK_GT(ScopedEnum::kValue2, ScopedEnum::kValue1); + ABSL_TEST_CHECK_GE(ScopedEnum::kValue2, ScopedEnum::kValue2); + ABSL_TEST_DCHECK_EQ(ScopedEnum::kValue1, ScopedEnum::kValue1); + ABSL_TEST_DCHECK_NE(ScopedEnum::kValue1, ScopedEnum::kValue2); + ABSL_TEST_DCHECK_LT(ScopedEnum::kValue1, ScopedEnum::kValue2); + ABSL_TEST_DCHECK_LE(ScopedEnum::kValue1, ScopedEnum::kValue2); + ABSL_TEST_DCHECK_GT(ScopedEnum::kValue2, ScopedEnum::kValue1); + ABSL_TEST_DCHECK_GE(ScopedEnum::kValue2, ScopedEnum::kValue2); + + // Check that overloads work correctly with references as well. + const ScopedEnum x = ScopedEnum::kValue1; + const ScopedEnum& x_ref = x; + ABSL_TEST_CHECK_EQ(x, x_ref); + ABSL_TEST_CHECK_EQ(x_ref, x_ref); +} + +#if GTEST_HAS_DEATH_TEST +TEST(CHECKDeathTest, TestScopedEnumCheckFailureMessagePrintsIntegerValues) { + const auto e1 = ScopedEnum::kValue1; + const auto e2 = ScopedEnum::kValue2; + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(e1, e2), + ContainsRegex(R"re(Check failed:.*\(1 vs. 2\))re")); + EXPECT_DEATH(ABSL_TEST_CHECK_NE(e1, e1), + ContainsRegex(R"re(Check failed:.*\(1 vs. 1\))re")); + EXPECT_DEATH(ABSL_TEST_CHECK_GT(e1, e1), + ContainsRegex(R"re(Check failed:.*\(1 vs. 1\))re")); + EXPECT_DEATH(ABSL_TEST_CHECK_GE(e1, e2), + ContainsRegex(R"re(Check failed:.*\(1 vs. 2\))re")); + EXPECT_DEATH(ABSL_TEST_CHECK_LT(e2, e2), + ContainsRegex(R"re(Check failed:.*\(2 vs. 2\))re")); + EXPECT_DEATH(ABSL_TEST_CHECK_LE(e2, e1), + ContainsRegex(R"re(Check failed:.*\(2 vs. 1\))re")); + + const auto& e1_ref = e1; + EXPECT_DEATH(ABSL_TEST_CHECK_NE(e1_ref, e1), + ContainsRegex(R"re(Check failed:.*\(1 vs. 1\))re")); + EXPECT_DEATH(ABSL_TEST_CHECK_NE(e1_ref, e1_ref), + ContainsRegex(R"re(Check failed:.*\(1 vs. 1\))re")); + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(e2, e1_ref), + ContainsRegex(R"re(Check failed:.*\(2 vs. 1\))re")); + +#ifndef NDEBUG + EXPECT_DEATH(ABSL_TEST_DCHECK_EQ(e2, e1), + ContainsRegex(R"re(Check failed:.*\(2 vs. 1\))re")); +#else + // DHECK_EQ is not evaluated in non-debug mode. + ABSL_TEST_DCHECK_EQ(e2, e1); +#endif // NDEBUG +} +#endif // GTEST_HAS_DEATH_TEST + +enum class ScopedInt8Enum : int8_t { + kValue1 = 1, + kValue2 = 66 // Printable ascii value 'B'. +}; + +TEST(CHECKDeathTest, TestScopedInt8EnumCheckFailureMessagePrintsCharValues) { + const auto e1 = ScopedInt8Enum::kValue1; + const auto e2 = ScopedInt8Enum::kValue2; + EXPECT_DEATH( + ABSL_TEST_CHECK_EQ(e1, e2), + ContainsRegex(R"re(Check failed:.*\(signed char value 1 vs. 'B'\))re")); + EXPECT_DEATH( + ABSL_TEST_CHECK_NE(e1, e1), + ContainsRegex( + R"re(Check failed:.*\(signed char value 1 vs. signed char value 1\))re")); + EXPECT_DEATH( + ABSL_TEST_CHECK_GT(e1, e1), + ContainsRegex( + R"re(Check failed:.*\(signed char value 1 vs. signed char value 1\))re")); + EXPECT_DEATH( + ABSL_TEST_CHECK_GE(e1, e2), + ContainsRegex(R"re(Check failed:.*\(signed char value 1 vs. 'B'\))re")); + EXPECT_DEATH(ABSL_TEST_CHECK_LT(e2, e2), + ContainsRegex(R"re(Check failed:.*\('B' vs. 'B'\))re")); + EXPECT_DEATH( + ABSL_TEST_CHECK_LE(e2, e1), + ContainsRegex(R"re(Check failed:.*\('B' vs. signed char value 1\))re")); +} + +enum class ScopedUnsignedEnum : uint16_t { + kValue1 = std::numeric_limits<uint16_t>::min(), + kValue2 = std::numeric_limits<uint16_t>::max() +}; + +TEST(CHECKDeathTest, + TestScopedUnsignedEnumCheckFailureMessagePrintsCorrectValues) { + const auto e1 = ScopedUnsignedEnum::kValue1; + const auto e2 = ScopedUnsignedEnum::kValue2; + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(e1, e2), + ContainsRegex(R"re(Check failed:.*\(0 vs. 65535\))re")); + EXPECT_DEATH(ABSL_TEST_CHECK_NE(e1, e1), + ContainsRegex(R"re(Check failed:.*\(0 vs. 0\))re")); + EXPECT_DEATH(ABSL_TEST_CHECK_GT(e1, e1), + ContainsRegex(R"re(Check failed:.*\(0 vs. 0\))re")); + EXPECT_DEATH(ABSL_TEST_CHECK_GE(e1, e2), + ContainsRegex(R"re(Check failed:.*\(0 vs. 65535\))re")); + EXPECT_DEATH(ABSL_TEST_CHECK_LT(e1, e1), + ContainsRegex(R"re(Check failed:.*\(0 vs. 0\))re")); + EXPECT_DEATH(ABSL_TEST_CHECK_LE(e2, e1), + ContainsRegex(R"re(Check failed:.*\(65535 vs. 0\))re")); +} + +enum class ScopedInt64Enum : int64_t { + kMin = std::numeric_limits<int64_t>::min(), + kMax = std::numeric_limits<int64_t>::max(), +}; + +// Tests that int64-backed enums are printed correctly even for very large and +// very small values. +TEST(CHECKDeathTest, TestScopedInt64EnumCheckFailureMessage) { + const auto min = ScopedInt64Enum::kMin; + const auto max = ScopedInt64Enum::kMax; + EXPECT_DEATH( + ABSL_TEST_CHECK_EQ(max, min), + ContainsRegex( + "Check failed:.*9223372036854775807 vs. -9223372036854775808")); + EXPECT_DEATH( + ABSL_TEST_CHECK_NE(max, max), + ContainsRegex( + "Check failed:.*9223372036854775807 vs. 9223372036854775807")); + EXPECT_DEATH( + ABSL_TEST_CHECK_GT(min, min), + ContainsRegex( + "Check failed:.*-9223372036854775808 vs. -9223372036854775808")); + EXPECT_DEATH( + ABSL_TEST_CHECK_GE(min, max), + ContainsRegex( + R"(Check failed:.*-9223372036854775808 vs. 9223372036854775807)")); + EXPECT_DEATH( + ABSL_TEST_CHECK_LT(max, max), + ContainsRegex( + R"(Check failed:.*9223372036854775807 vs. 9223372036854775807)")); + EXPECT_DEATH( + ABSL_TEST_CHECK_LE(max, min), + ContainsRegex( + R"(Check failed:.*9223372036854775807 vs. -9223372036854775808)")); +} + +enum class ScopedBoolEnum : bool { + kFalse, + kTrue, +}; + +TEST(CHECKDeathTest, TestScopedBoolEnumCheckFailureMessagePrintsCorrectValues) { + const auto t = ScopedBoolEnum::kTrue; + const auto f = ScopedBoolEnum::kFalse; + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(t, f), + ContainsRegex(R"re(Check failed:.*\(1 vs. 0\))re")); + EXPECT_DEATH(ABSL_TEST_CHECK_NE(f, f), + ContainsRegex(R"re(Check failed:.*\(0 vs. 0\))re")); + EXPECT_DEATH(ABSL_TEST_CHECK_GT(f, f), + ContainsRegex(R"re(Check failed:.*\(0 vs. 0\))re")); + EXPECT_DEATH(ABSL_TEST_CHECK_GE(f, t), + ContainsRegex(R"re(Check failed:.*\(0 vs. 1\))re")); + EXPECT_DEATH(ABSL_TEST_CHECK_LT(t, t), + ContainsRegex(R"re(Check failed:.*\(1 vs. 1\))re")); + EXPECT_DEATH(ABSL_TEST_CHECK_LE(t, f), + ContainsRegex(R"re(Check failed:.*\(1 vs. 0\))re")); +} + +enum class ScopedEnumWithAbslStringify { + kValue1 = 1, + kValue2 = 2, + kValue3 = 3 +}; + +template <typename Sink> +void AbslStringify(Sink& sink, ScopedEnumWithAbslStringify v) { + switch (v) { + case ScopedEnumWithAbslStringify::kValue1: + sink.Append("AbslStringify: kValue1"); + break; + case ScopedEnumWithAbslStringify::kValue2: + sink.Append("AbslStringify: kValue2"); + break; + case ScopedEnumWithAbslStringify::kValue3: + sink.Append("AbslStringify: kValue3"); + break; + } +} + +#if GTEST_HAS_DEATH_TEST +TEST(CHECKDeathTest, TestScopedEnumUsesAbslStringify) { + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(ScopedEnumWithAbslStringify::kValue1, + ScopedEnumWithAbslStringify::kValue2), + ContainsRegex("Check failed:.*AbslStringify: kValue1 vs. " + "AbslStringify: kValue2")); +} +#endif // GTEST_HAS_DEATH_TEST + +enum class ScopedEnumWithOutputOperator { + kValue1 = 1, + kValue2 = 2, +}; + +std::ostream& operator<<(std::ostream& os, ScopedEnumWithOutputOperator v) { + switch (v) { + case ScopedEnumWithOutputOperator::kValue1: + os << "OutputOperator: kValue1"; + break; + case ScopedEnumWithOutputOperator::kValue2: + os << "OutputOperator: kValue2"; + break; + } + return os; +} + +#if GTEST_HAS_DEATH_TEST +TEST(CHECKDeathTest, TestOutputOperatorIsUsedForScopedEnum) { + EXPECT_DEATH(ABSL_TEST_CHECK_EQ(ScopedEnumWithOutputOperator::kValue1, + ScopedEnumWithOutputOperator::kValue2), + ContainsRegex("Check failed:.*OutputOperator: kValue1 vs. " + "OutputOperator: kValue2")); +} +#endif // GTEST_HAS_DEATH_TEST + +enum class ScopedEnumWithAbslStringifyAndOutputOperator { + kValue1 = 1, + kValue2 = 2, +}; + +template <typename Sink> +void AbslStringify(Sink& sink, ScopedEnumWithAbslStringifyAndOutputOperator v) { + switch (v) { + case ScopedEnumWithAbslStringifyAndOutputOperator::kValue1: + sink.Append("AbslStringify: kValue1"); + break; + case ScopedEnumWithAbslStringifyAndOutputOperator::kValue2: + sink.Append("AbslStringify: kValue2"); + break; + } +} + +std::ostream& operator<<(std::ostream& os, + ScopedEnumWithAbslStringifyAndOutputOperator v) { + switch (v) { + case ScopedEnumWithAbslStringifyAndOutputOperator::kValue1: + os << "OutputOperator: kValue1"; + break; + case ScopedEnumWithAbslStringifyAndOutputOperator::kValue2: + os << "OutputOperator: kValue2"; + break; + } + return os; +} + +#if GTEST_HAS_DEATH_TEST + +// Test that, if operator<< and AbslStringify are both defined for a scoped +// enum, streaming takes precedence over AbslStringify. +TEST(CHECKDeathTest, TestScopedEnumPrefersOutputOperatorOverAbslStringify) { + EXPECT_DEATH( + ABSL_TEST_CHECK_EQ(ScopedEnumWithAbslStringifyAndOutputOperator::kValue1, + ScopedEnumWithAbslStringifyAndOutputOperator::kValue2), + ContainsRegex("Check failed:.*OutputOperator: kValue1 vs. " + "OutputOperator: kValue2")); +} +#endif // GTEST_HAS_DEATH_TEST + } // namespace absl_log_internal // NOLINTEND(misc-definitions-in-headers)
diff --git a/absl/log/die_if_null.cc b/absl/log/die_if_null.cc index 19c6a28..0d0b78e 100644 --- a/absl/log/die_if_null.cc +++ b/absl/log/die_if_null.cc
@@ -15,6 +15,7 @@ #include "absl/log/die_if_null.h" #include "absl/base/config.h" +#include "absl/base/nullability.h" #include "absl/log/log.h" #include "absl/strings/str_cat.h" @@ -22,7 +23,8 @@ ABSL_NAMESPACE_BEGIN namespace log_internal { -void DieBecauseNull(const char* file, int line, const char* exprtext) { +void DieBecauseNull(const char* absl_nonnull file, int line, + const char* absl_nonnull exprtext) { LOG(FATAL).AtLocation(file, line) << absl::StrCat("Check failed: '", exprtext, "' Must be non-null"); }
diff --git a/absl/log/die_if_null.h b/absl/log/die_if_null.h index 8597976..ac7dbe6 100644 --- a/absl/log/die_if_null.h +++ b/absl/log/die_if_null.h
@@ -23,10 +23,13 @@ #include <stdint.h> +#include <type_traits> #include <utility> #include "absl/base/attributes.h" #include "absl/base/config.h" +#include "absl/base/internal/nullability_traits.h" +#include "absl/base/nullability.h" #include "absl/base/optimization.h" // ABSL_DIE_IF_NULL() @@ -56,12 +59,30 @@ // generates less code than its implementation would if inlined, for a slight // code size reduction each time `ABSL_DIE_IF_NULL` is called. [[noreturn]] ABSL_ATTRIBUTE_NOINLINE void DieBecauseNull( - const char* file, int line, const char* exprtext); + const char* absl_nonnull file, int line, const char* absl_nonnull exprtext); // Helper for `ABSL_DIE_IF_NULL`. + +// Since we use `remove_reference_t` before `AddNonnullIfCompatible`, we need +// to explicitly have overloads for both lvalue reference and rvalue reference +// arguments and returns. template <typename T> -[[nodiscard]] T DieIfNull(const char* file, int line, const char* exprtext, - T&& t) { +[[nodiscard]] typename absl::base_internal::AddNonnullIfCompatible< + std::remove_reference_t<T>>::type& +DieIfNull(const char* absl_nonnull file, int line, + const char* absl_nonnull 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 t; +} + +template <typename T> +[[nodiscard]] typename absl::base_internal::AddNonnullIfCompatible< + std::remove_reference_t<T>>::type&& +DieIfNull(const char* absl_nonnull file, int line, + const char* absl_nonnull 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);
diff --git a/absl/log/flags_test.cc b/absl/log/flags_test.cc index 1080ea1..f5a2b51 100644 --- a/absl/log/flags_test.cc +++ b/absl/log/flags_test.cc
@@ -93,6 +93,7 @@ TEST_F(LogFlagsTest, EmptyBacktraceAtFlag) { absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo); absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:"))))); @@ -104,6 +105,7 @@ TEST_F(LogFlagsTest, BacktraceAtNonsense) { absl::SetMinLogLevel(absl::LogSeverityAtLeast::kInfo); absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:"))))); @@ -117,6 +119,7 @@ const int log_line = __LINE__ + 1; auto do_log = [] { LOG(INFO) << "hello world"; }; absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:"))))); @@ -131,6 +134,7 @@ const int log_line = __LINE__ + 1; auto do_log = [] { LOG(INFO) << "hello world"; }; absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:"))))); @@ -145,6 +149,7 @@ const int log_line = __LINE__ + 1; auto do_log = [] { LOG(INFO) << "hello world"; }; absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:"))))); @@ -158,6 +163,7 @@ const int log_line = __LINE__ + 1; auto do_log = [] { LOG(INFO) << "hello world"; }; absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(TextMessage(Not(HasSubstr("(stacktrace:"))))); @@ -172,6 +178,7 @@ const int log_line = __LINE__ + 1; auto do_log = [] { LOG(INFO) << "hello world"; }; absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); testing::InSequence seq; EXPECT_CALL(test_sink, Send(TextMessage(HasSubstr("(stacktrace:"))));
diff --git a/absl/log/internal/BUILD.bazel b/absl/log/internal/BUILD.bazel index 953b690..32ae277 100644 --- a/absl/log/internal/BUILD.bazel +++ b/absl/log/internal/BUILD.bazel
@@ -14,6 +14,9 @@ # limitations under the License. # +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load( "//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS", @@ -56,7 +59,6 @@ deps = [ ":check_op", ":conditions", - ":log_message", ":strip", "//absl/base:core_headers", ], @@ -79,6 +81,7 @@ "//absl/base:nullability", "//absl/debugging:leak_check", "//absl/strings", + "//absl/strings:has_ostream_operator", ], ) @@ -545,3 +548,34 @@ "@google_benchmark//:benchmark_main", ], ) + +cc_library( + name = "container", + hdrs = ["container.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl:__subpackages__", + ], + deps = [ + "//absl/base:config", + "//absl/meta:requires", + "//absl/strings", + ], +) + +cc_test( + name = "container_test", + srcs = ["container_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":container", + "//absl/base:config", + "//absl/strings", + "//absl/strings:str_format", + "//absl/types:span", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +)
diff --git a/absl/log/internal/check_impl.h b/absl/log/internal/check_impl.h index 00f25f8..dc2e214 100644 --- a/absl/log/internal/check_impl.h +++ b/absl/log/internal/check_impl.h
@@ -12,13 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. +// SKIP_ABSL_INLINE_NAMESPACE_CHECK + #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
diff --git a/absl/log/internal/check_op.cc b/absl/log/internal/check_op.cc index 23db63b..be8ceaf 100644 --- a/absl/log/internal/check_op.cc +++ b/absl/log/internal/check_op.cc
@@ -101,6 +101,10 @@ } } +std::ostream& operator<<(std::ostream& os, UnprintableWrapper) { + return os << "UNPRINTABLE"; +} + // Helper functions for string comparisons. #define DEFINE_CHECK_STROP_IMPL(name, func, expected) \ const char* absl_nullable Check##func##expected##Impl( \
diff --git a/absl/log/internal/check_op.h b/absl/log/internal/check_op.h index 7253402..9bf908e 100644 --- a/absl/log/internal/check_op.h +++ b/absl/log/internal/check_op.h
@@ -40,15 +40,16 @@ #include "absl/log/internal/nullstream.h" #include "absl/log/internal/strip.h" #include "absl/strings/has_absl_stringify.h" +#include "absl/strings/has_ostream_operator.h" #include "absl/strings/string_view.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) \ +#define ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(literal) \ + (::absl::LogSeverity::kFatal >= \ + static_cast<::absl::LogSeverityAtLeast>(ABSL_MIN_LOG_LEVEL) \ + ? (literal) \ : "") #else #define ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(literal) (literal) @@ -133,41 +134,39 @@ // string literal and abort without doing any streaming. We don't need to // strip the call to stringify the non-ok `Status` as long as we don't log it; // dropping the `Status`'s message text is out of scope. -#define ABSL_LOG_INTERNAL_CHECK_OK(val, val_text) \ - for (::std::pair<const ::absl::Status* absl_nonnull, \ - const char* absl_nullable> \ - 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_CONDITION_FATAL(STATELESS, true) \ - ABSL_LOG_INTERNAL_CHECK(::absl::implicit_cast<const char* absl_nonnull>( \ - absl_log_internal_check_ok_goo.second)) \ +#define ABSL_LOG_INTERNAL_CHECK_OK(val, val_text) \ + for (::std::pair<const ::absl::Status* absl_nonnull, \ + const char* absl_nonnull> \ + 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()) \ + ? "" /* Don't use nullptr, to keep the annotation happy */ \ + : ::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_CONDITION_FATAL(STATELESS, true) \ + ABSL_LOG_INTERNAL_CHECK(absl_log_internal_check_ok_goo.second) \ .InternalStream() -#define ABSL_LOG_INTERNAL_QCHECK_OK(val, val_text) \ - for (::std::pair<const ::absl::Status* absl_nonnull, \ - const char* absl_nullable> \ - absl_log_internal_qcheck_ok_goo; \ - absl_log_internal_qcheck_ok_goo.first = \ - ::absl::log_internal::AsStatus(val), \ - absl_log_internal_qcheck_ok_goo.second = \ - ABSL_PREDICT_TRUE(absl_log_internal_qcheck_ok_goo.first->ok()) \ - ? nullptr \ - : ::absl::status_internal::MakeCheckFailString( \ - absl_log_internal_qcheck_ok_goo.first, \ - ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(val_text \ - " is OK")), \ - !ABSL_PREDICT_TRUE(absl_log_internal_qcheck_ok_goo.first->ok());) \ - ABSL_LOG_INTERNAL_CONDITION_QFATAL(STATELESS, true) \ - ABSL_LOG_INTERNAL_QCHECK(::absl::implicit_cast<const char* absl_nonnull>( \ - absl_log_internal_qcheck_ok_goo.second)) \ +#define ABSL_LOG_INTERNAL_QCHECK_OK(val, val_text) \ + for (::std::pair<const ::absl::Status* absl_nonnull, \ + const char* absl_nonnull> \ + absl_log_internal_qcheck_ok_goo; \ + absl_log_internal_qcheck_ok_goo.first = \ + ::absl::log_internal::AsStatus(val), \ + absl_log_internal_qcheck_ok_goo.second = \ + ABSL_PREDICT_TRUE(absl_log_internal_qcheck_ok_goo.first->ok()) \ + ? "" /* Don't use nullptr, to keep the annotation happy */ \ + : ::absl::status_internal::MakeCheckFailString( \ + absl_log_internal_qcheck_ok_goo.first, \ + ABSL_LOG_INTERNAL_STRIP_STRING_LITERAL(val_text \ + " is OK")), \ + !ABSL_PREDICT_TRUE(absl_log_internal_qcheck_ok_goo.first->ok());) \ + ABSL_LOG_INTERNAL_CONDITION_QFATAL(STATELESS, true) \ + ABSL_LOG_INTERNAL_QCHECK(absl_log_internal_qcheck_ok_goo.second) \ .InternalStream() namespace absl { @@ -226,6 +225,14 @@ void MakeCheckOpValueString(std::ostream& os, unsigned char v); void MakeCheckOpValueString(std::ostream& os, const void* absl_nullable p); +// A wrapper for types that have no operator<<. +struct UnprintableWrapper { + template <typename T> + explicit UnprintableWrapper(const T&) {} + + friend std::ostream& operator<<(std::ostream& os, UnprintableWrapper); +}; + namespace detect_specialization { // MakeCheckOpString is being specialized for every T and U pair that is being @@ -300,12 +307,11 @@ // 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. +// list. Eg an unscoped 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); +decltype(detect_specialization::operator<<( + std::declval<std::ostream&>(), std::declval<const T&>())) Detect(char); // A sink for AbslStringify which redirects everything to a std::ostream. class StringifySink { @@ -346,10 +352,76 @@ std::enable_if_t<HasAbslStringify<T>::value, StringifyToStreamWrapper<T>> Detect(...); // Ellipsis has lowest preference when int passed. -} // namespace detect_specialization + +// This overload triggers when T is neither possible to print nor an enum. +template <typename T> +std::enable_if_t<std::negation_v<std::disjunction< + std::is_convertible<T, int>, std::is_enum<T>, + std::is_pointer<T>, std::is_same<T, std::nullptr_t>, + HasOstreamOperator<T>, HasAbslStringify<T>>>, + UnprintableWrapper> +Detect(...); + +// Equivalent to the updated std::underlying_type from C++20, which is no +// longer undefined behavior for non-enum types. +template <typename T, typename EnableT = void> +struct UnderlyingType {}; template <typename T> -using CheckOpStreamType = decltype(detect_specialization::Detect<T>(0)); +struct UnderlyingType<T, std::enable_if_t<std::is_enum_v<T>>> { + using type = std::underlying_type_t<T>; +}; +template <typename T> +using UnderlyingTypeT = typename UnderlyingType<T>::type; + +// This overload triggers when T is a scoped enum that has not defined an output +// stream operator (operator<<) or AbslStringify. It causes the enum value to be +// converted to a type that can be streamed. For consistency with other enums, a +// scoped enum backed by a bool or char is converted to its underlying type, and +// one backed by another integer is converted to (u)int64_t. +template <typename T> +std::enable_if_t< + std::conjunction_v<std::is_enum<T>, + std::negation<std::is_convertible<T, int>>, + std::negation<HasOstreamOperator<T>>, + std::negation<HasAbslStringify<T>>>, + std::conditional_t<std::is_same_v<UnderlyingTypeT<T>, bool> || + std::is_same_v<UnderlyingTypeT<T>, char> || + std::is_same_v<UnderlyingTypeT<T>, signed char> || + std::is_same_v<UnderlyingTypeT<T>, unsigned char>, + UnderlyingTypeT<T>, + std::conditional_t<std::is_signed_v<UnderlyingTypeT<T>>, + int64_t, uint64_t>>> +Detect(...); + +template <typename T> +using Detected = decltype(Detect<T>(0)); +} // namespace detect_specialization + +// If the comparison will happen as pointers, decay `char*` arguments to `void*` +// when printing them. There is no evidence that they are a NULL terminated +// C-String so printing them as such could lead to UB, and more importantly we +// compared pointers so showing the pointers is a better result. +template <typename T> +constexpr bool IsCharStarOrVoidStar() { + if constexpr (std::is_reference_v<T>) { + return IsCharStarOrVoidStar<std::remove_reference_t<T>>(); + } else if constexpr (std::is_array_v<T>) { + return IsCharStarOrVoidStar<std::decay_t<T>>(); + } else { + using U = std::remove_const_t<std::remove_pointer_t<T>>; + return std::is_pointer_v<T> && + (std::is_same_v<char, U> || std::is_same_v<unsigned char, U> || + std::is_same_v<signed char, U> || std::is_void_v<U>); + } +} + +template <typename T1, typename T2, + typename U1 = detect_specialization::Detected<T1>, + typename U2 = detect_specialization::Detected<T2>> +using CheckOpStreamType = + std::conditional_t<IsCharStarOrVoidStar<U1>() && IsCharStarOrVoidStar<U2>(), + const void*, U1>; // Build the error message string. Specify no inlining for code size. template <typename T1, typename T2> @@ -359,10 +431,16 @@ template <typename T1, typename T2> const char* absl_nonnull MakeCheckOpString(T1 v1, T2 v2, const char* absl_nonnull exprtext) { - CheckOpMessageBuilder comb(exprtext); - MakeCheckOpValueString(comb.ForVar1(), v1); - MakeCheckOpValueString(comb.ForVar2(), v2); - return comb.NewString(); + if constexpr (std::is_same_v<CheckOpStreamType<T1, T2>, UnprintableWrapper> && + std::is_same_v<CheckOpStreamType<T2, T1>, UnprintableWrapper>) { + // No sense printing " (UNPRINTABLE vs. UNPRINTABLE)" + return exprtext; + } else { + 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 @@ -393,7 +471,7 @@ #ifdef ABSL_MIN_LOG_LEVEL #define ABSL_LOG_INTERNAL_CHECK_OP_IMPL_RESULT(U1, U2, v1, v2, exprtext) \ ((::absl::LogSeverity::kFatal >= \ - static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL)) \ + static_cast<::absl::LogSeverityAtLeast>(ABSL_MIN_LOG_LEVEL)) \ ? MakeCheckOpString<U1, U2>(v1, v2, exprtext) \ : "") #else @@ -409,8 +487,8 @@ template <typename T1, typename T2> \ inline constexpr const char* absl_nullable name##Impl( \ const T1& v1, const T2& v2, const char* absl_nonnull exprtext) { \ - using U1 = CheckOpStreamType<T1>; \ - using U2 = CheckOpStreamType<T2>; \ + using U1 = CheckOpStreamType<T1, T2>; \ + using U2 = CheckOpStreamType<T2, T1>; \ return ABSL_PREDICT_TRUE(v1 op v2) \ ? nullptr \ : ABSL_LOG_INTERNAL_CHECK_OP_IMPL_RESULT(U1, U2, U1(v1), \
diff --git a/absl/log/internal/conditions.h b/absl/log/internal/conditions.h index 6fb74b1..3325a31 100644 --- a/absl/log/internal/conditions.h +++ b/absl/log/internal/conditions.h
@@ -108,46 +108,49 @@ #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)) + (condition) && \ + ::absl::LogSeverity::kInfo >= \ + static_cast<::absl::LogSeverityAtLeast>(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)) + (condition) && \ + ::absl::LogSeverity::kWarning >= \ + static_cast<::absl::LogSeverityAtLeast>(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)) + (condition) && \ + ::absl::LogSeverity::kError >= \ + static_cast<::absl::LogSeverityAtLeast>(ABSL_MIN_LOG_LEVEL)) #define ABSL_LOG_INTERNAL_CONDITION_DO_NOT_SUBMIT(type, condition) \ ABSL_LOG_INTERNAL_CONDITION_ERROR(type, condition) // 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)) + ((condition) ? (::absl::LogSeverity::kFatal >= \ + static_cast<::absl::LogSeverityAtLeast>( \ + 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_DFATAL(type, condition) \ - ABSL_LOG_INTERNAL_##type##_CONDITION( \ - (ABSL_ASSUME(absl::kLogDebugFatal == absl::LogSeverity::kError || \ - absl::kLogDebugFatal == absl::LogSeverity::kFatal), \ - (condition) && \ - (::absl::kLogDebugFatal >= \ - static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) || \ - (::absl::kLogDebugFatal == ::absl::LogSeverity::kFatal && \ +#define ABSL_LOG_INTERNAL_CONDITION_QFATAL(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION( \ + ((condition) ? (::absl::LogSeverity::kFatal >= \ + static_cast<::absl::LogSeverityAtLeast>( \ + ABSL_MIN_LOG_LEVEL) \ + ? true \ + : (::absl::log_internal::ExitQuietly(), false)) \ + : false)) +#define ABSL_LOG_INTERNAL_CONDITION_DFATAL(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION( \ + (ABSL_ASSUME(absl::kLogDebugFatal == absl::LogSeverity::kError || \ + absl::kLogDebugFatal == absl::LogSeverity::kFatal), \ + (condition) && \ + (::absl::kLogDebugFatal >= \ + static_cast<::absl::LogSeverityAtLeast>(ABSL_MIN_LOG_LEVEL) || \ + (::absl::kLogDebugFatal == ::absl::LogSeverity::kFatal && \ (::absl::log_internal::AbortQuietly(), false))))) #define ABSL_LOG_INTERNAL_CONDITION_LEVEL(severity) \ @@ -157,13 +160,13 @@ ::absl::NormalizeLogSeverity(severity); \ absl_log_internal_severity_loop; absl_log_internal_severity_loop = 0) \ ABSL_LOG_INTERNAL_CONDITION_LEVEL_IMPL -#define ABSL_LOG_INTERNAL_CONDITION_LEVEL_IMPL(type, condition) \ - ABSL_LOG_INTERNAL_##type##_CONDITION(( \ - (condition) && \ - (absl_log_internal_severity >= \ - static_cast<::absl::LogSeverity>(ABSL_MIN_LOG_LEVEL) || \ - (absl_log_internal_severity == ::absl::LogSeverity::kFatal && \ - (::absl::log_internal::AbortQuietly(), false))))) +#define ABSL_LOG_INTERNAL_CONDITION_LEVEL_IMPL(type, condition) \ + ABSL_LOG_INTERNAL_##type##_CONDITION( \ + ((condition) && \ + (absl_log_internal_severity >= \ + static_cast<::absl::LogSeverityAtLeast>(ABSL_MIN_LOG_LEVEL) || \ + (absl_log_internal_severity == ::absl::LogSeverity::kFatal && \ + (::absl::log_internal::AbortQuietly(), false))))) #else // ndef ABSL_MIN_LOG_LEVEL #define ABSL_LOG_INTERNAL_CONDITION_INFO(type, condition) \ ABSL_LOG_INTERNAL_##type##_CONDITION(condition)
diff --git a/absl/log/internal/container.h b/absl/log/internal/container.h new file mode 100644 index 0000000..1144652 --- /dev/null +++ b/absl/log/internal/container.h
@@ -0,0 +1,312 @@ +// Copyright 2025 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 typical use looks like this: +// +// LOG(INFO) << LogContainer(container); +// +// By default, LogContainer() uses the LogShortUpTo100 policy: comma-space +// separation, no newlines, and with limit of 100 items. +// +// Policies can be specified: +// +// LOG(INFO) << LogContainer(container, LogMultiline()); +// +// The above example will print the container using newlines between elements, +// enclosed in [] braces. +// +// See below for further details on policies. + +#ifndef ABSL_LOG_INTERNAL_CONTAINER_H_ +#define ABSL_LOG_INTERNAL_CONTAINER_H_ + +#include <cstdint> +#include <limits> +#include <ostream> +#include <sstream> +#include <string> +#include <type_traits> + +#include "absl/base/config.h" +#include "absl/meta/internal/requires.h" +#include "absl/strings/str_cat.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { + +// Several policy classes below determine how LogRangeToStream will +// format a range of items. A Policy class should have these methods: +// +// Called to print an individual container element. +// void Log(ostream &out, const ElementT &element) const; +// +// Called before printing the set of elements: +// void LogOpening(ostream &out) const; +// +// Called after printing the set of elements: +// void LogClosing(ostream &out) const; +// +// Called before printing the first element: +// void LogFirstSeparator(ostream &out) const; +// +// Called before printing the remaining elements: +// void LogSeparator(ostream &out) const; +// +// Returns the maximum number of elements to print: +// int64 MaxElements() const; +// +// Called to print an indication that MaximumElements() was reached: +// void LogEllipsis(ostream &out) const; + +namespace internal { + +struct LogBase { + template <typename ElementT> + void Log(std::ostream &out, const ElementT &element) const { // NOLINT + // Fallback to `AbslStringify` if the type does not have `operator<<`. + if constexpr (meta_internal::Requires<std::ostream, ElementT>( + [](auto&& x, auto&& y) -> decltype(x << y) {})) { + out << element; + } else { + out << absl::StrCat(element); + } + } + void LogEllipsis(std::ostream &out) const { // NOLINT + out << "..."; + } +}; + +struct LogShortBase : public LogBase { + void LogOpening(std::ostream &out) const { out << "["; } // NOLINT + void LogClosing(std::ostream &out) const { out << "]"; } // NOLINT + void LogFirstSeparator(std::ostream &out) const { out << ""; } // NOLINT + void LogSeparator(std::ostream &out) const { out << ", "; } // NOLINT +}; + +struct LogMultilineBase : public LogBase { + void LogOpening(std::ostream &out) const { out << "["; } // NOLINT + void LogClosing(std::ostream &out) const { out << "\n]"; } // NOLINT + void LogFirstSeparator(std::ostream &out) const { out << "\n"; } // NOLINT + void LogSeparator(std::ostream &out) const { out << "\n"; } // NOLINT +}; + +struct LogLegacyBase : public LogBase { + void LogOpening(std::ostream &out) const { out << ""; } // NOLINT + void LogClosing(std::ostream &out) const { out << ""; } // NOLINT + void LogFirstSeparator(std::ostream &out) const { out << ""; } // NOLINT + void LogSeparator(std::ostream &out) const { out << " "; } // NOLINT +}; + +} // namespace internal + +// LogShort uses [] braces and separates items with comma-spaces. For +// example "[1, 2, 3]". +struct LogShort : public internal::LogShortBase { + int64_t MaxElements() const { return (std::numeric_limits<int64_t>::max)(); } +}; + +// LogShortUpToN(max_elements) formats the same as LogShort but prints no more +// than the max_elements elements. +class LogShortUpToN : public internal::LogShortBase { + public: + explicit LogShortUpToN(int64_t max_elements) : max_elements_(max_elements) {} + int64_t MaxElements() const { return max_elements_; } + + private: + int64_t max_elements_; +}; + +// LogShortUpTo100 formats the same as LogShort but prints no more +// than 100 elements. +struct LogShortUpTo100 : public LogShortUpToN { + LogShortUpTo100() : LogShortUpToN(100) {} +}; + +// LogMultiline uses [] braces and separates items with +// newlines. For example "[ +// 1 +// 2 +// 3 +// ]". +struct LogMultiline : public internal::LogMultilineBase { + int64_t MaxElements() const { return (std::numeric_limits<int64_t>::max)(); } +}; + +// LogMultilineUpToN(max_elements) formats the same as LogMultiline but +// prints no more than max_elements elements. +class LogMultilineUpToN : public internal::LogMultilineBase { + public: + explicit LogMultilineUpToN(int64_t max_elements) + : max_elements_(max_elements) {} + int64_t MaxElements() const { return max_elements_; } + + private: + int64_t max_elements_; +}; + +// LogMultilineUpTo100 formats the same as LogMultiline but +// prints no more than 100 elements. +struct LogMultilineUpTo100 : public LogMultilineUpToN { + LogMultilineUpTo100() : LogMultilineUpToN(100) {} +}; + +// The legacy behavior of LogSequence() does not use braces and +// separates items with spaces. For example "1 2 3". +struct LogLegacyUpTo100 : public internal::LogLegacyBase { + int64_t MaxElements() const { return 100; } +}; +struct LogLegacy : public internal::LogLegacyBase { + int64_t MaxElements() const { return (std::numeric_limits<int64_t>::max)(); } +}; + +// The default policy for new code. +typedef LogShortUpTo100 LogDefault; + +// LogRangeToStream should be used to define operator<< for +// STL and STL-like containers. For example, see stl_logging.h. +template <typename IteratorT, typename PolicyT> +inline void LogRangeToStream(std::ostream &out, // NOLINT + IteratorT begin, IteratorT end, + const PolicyT &policy) { + policy.LogOpening(out); + for (int64_t i = 0; begin != end && i < policy.MaxElements(); ++i, ++begin) { + if (i == 0) { + policy.LogFirstSeparator(out); + } else { + policy.LogSeparator(out); + } + policy.Log(out, *begin); + } + if (begin != end) { + policy.LogSeparator(out); + policy.LogEllipsis(out); + } + policy.LogClosing(out); +} + +namespace detail { + +// RangeLogger is a helper class for LogRange and LogContainer; do not use it +// directly. This object captures iterators into the argument of the LogRange +// and LogContainer functions, so its lifetime should be confined to a single +// logging statement. Objects of this type should not be assigned to local +// variables. +template <typename IteratorT, typename PolicyT> +class RangeLogger { + public: + RangeLogger(const IteratorT &begin, const IteratorT &end, + const PolicyT &policy) + : begin_(begin), end_(end), policy_(policy) {} + + friend std::ostream &operator<<(std::ostream &out, const RangeLogger &range) { + LogRangeToStream<IteratorT, PolicyT>(out, range.begin_, range.end_, + range.policy_); + return out; + } + + // operator<< above is generally recommended. However, some situations may + // require a string, so a convenience str() method is provided as well. + std::string str() const { + std::stringstream ss; + ss << *this; + return ss.str(); + } + + private: + IteratorT begin_; + IteratorT end_; + PolicyT policy_; +}; + +template <typename E> +class EnumLogger { + public: + explicit EnumLogger(E e) : e_(e) {} + + friend std::ostream &operator<<(std::ostream &out, const EnumLogger &v) { + using I = typename std::underlying_type<E>::type; + return out << static_cast<I>(v.e_); + } + + private: + E e_; +}; + +} // namespace detail + +// Log a range using "policy". For example: +// +// LOG(INFO) << LogRange(start_pos, end_pos, LogMultiline()); +// +// The above example will print the range using newlines between +// elements, enclosed in [] braces. +template <typename IteratorT, typename PolicyT> +detail::RangeLogger<IteratorT, PolicyT> LogRange(const IteratorT &begin, + const IteratorT &end, + const PolicyT &policy) { + return detail::RangeLogger<IteratorT, PolicyT>(begin, end, policy); +} + +// Log a range. For example: +// +// LOG(INFO) << LogRange(start_pos, end_pos); +// +// By default, Range() uses the LogShortUpTo100 policy: comma-space +// separation, no newlines, and with limit of 100 items. +template <typename IteratorT> +detail::RangeLogger<IteratorT, LogDefault> LogRange(const IteratorT &begin, + const IteratorT &end) { + return LogRange(begin, end, LogDefault()); +} + +// Log a container using "policy". For example: +// +// LOG(INFO) << LogContainer(container, LogMultiline()); +// +// The above example will print the container using newlines between +// elements, enclosed in [] braces. +template <typename ContainerT, typename PolicyT> +auto LogContainer(const ContainerT& container, const PolicyT& policy) + -> decltype(LogRange(container.begin(), container.end(), policy)) { + return LogRange(container.begin(), container.end(), policy); +} + +// Log a container. For example: +// +// LOG(INFO) << LogContainer(container); +// +// By default, Container() uses the LogShortUpTo100 policy: comma-space +// separation, no newlines, and with limit of 100 items. +template <typename ContainerT> +auto LogContainer(const ContainerT& container) + -> decltype(LogContainer(container, LogDefault())) { + return LogContainer(container, LogDefault()); +} + +// Log a (possibly scoped) enum. For example: +// +// enum class Color { kRed, kGreen, kBlue }; +// LOG(INFO) << LogEnum(kRed); +template <typename E> +detail::EnumLogger<E> LogEnum(E e) { + static_assert(std::is_enum<E>::value, "must be an enum"); + return detail::EnumLogger<E>(e); +} + +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_LOG_INTERNAL_CONTAINER_H_
diff --git a/absl/log/internal/container_test.cc b/absl/log/internal/container_test.cc new file mode 100644 index 0000000..0a5a058 --- /dev/null +++ b/absl/log/internal/container_test.cc
@@ -0,0 +1,254 @@ +// Copyright 2025 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/container.h" + +#include <cstdint> +#include <map> +#include <memory> +#include <ostream> +#include <set> +#include <sstream> +#include <string> +#include <utility> +#include <vector> + +#include "gtest/gtest.h" +#include "absl/base/config.h" +#include "absl/strings/str_format.h" +#include "absl/strings/str_join.h" +#include "absl/types/span.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace log_internal { +namespace { + +class ContainerLoggingTest : public ::testing::Test { + protected: + ContainerLoggingTest() : stream_(new std::stringstream) {} + std::ostream& stream() { return *stream_; } + std::string logged() { + std::string r = stream_->str(); + stream_ = std::make_unique<std::stringstream>(); + return r; + } + + private: + std::unique_ptr<std::stringstream> stream_; +}; + +TEST_F(ContainerLoggingTest, ShortRange) { + std::vector<std::string> words = {"hi", "hello"}; + LogRangeToStream(stream(), words.begin(), words.end(), LogMultiline()); + EXPECT_EQ("[\nhi\nhello\n]", logged()); +} + +TEST_F(ContainerLoggingTest, LegacyRange) { + std::vector<int> lengths = {1, 2}; + LogRangeToStream(stream(), lengths.begin(), lengths.end(), + LogLegacyUpTo100()); + EXPECT_EQ("1 2", logged()); +} + +TEST_F(ContainerLoggingTest, ToString) { + std::vector<int> lengths = {1, 2, 3, 4, 5}; + EXPECT_EQ(LogContainer(lengths).str(), "[1, 2, 3, 4, 5]"); +} + +class UserDefFriend { + public: + explicit UserDefFriend(int i) : i_(i) {} + + private: + friend std::ostream& operator<<(std::ostream& str, const UserDefFriend& i) { + return str << i.i_; + } + int i_; +}; + +TEST_F(ContainerLoggingTest, RangeOfUserDefined) { + std::vector<UserDefFriend> ints = {UserDefFriend(1), UserDefFriend(2), + UserDefFriend(3)}; + LogRangeToStream(stream(), ints.begin(), ints.begin() + 1, LogDefault()); + LogRangeToStream(stream(), ints.begin() + 1, ints.begin() + 2, + LogMultiline()); + LogRangeToStream(stream(), ints.begin() + 2, ints.begin() + 3, LogDefault()); + LogRangeToStream(stream(), ints.begin(), ints.begin(), LogMultiline()); + + EXPECT_EQ("[1][\n2\n][3][\n]", logged()); +} + +TEST_F(ContainerLoggingTest, FullContainer) { + std::vector<int> ints; + std::vector<int> ints100; + std::vector<int> ints123; + int64_t max_elements = 123; + std::string expected1; + std::string expected2; + std::string expected3; + std::string expected4; + std::string expected5; + std::string expected6; + std::string expected7; + std::string expected8; + std::string expected9; + for (int i = 0; i < 1000; ++i) { + ints.push_back(i); + if (i < 100) { + ints100.push_back(i); + } + if (i < max_elements) { + ints123.push_back(i); + } + } + expected1 = "[\n" + absl::StrJoin(ints, "\n") + "\n]"; + expected2 = "[" + absl::StrJoin(ints, ", ") + "]"; + expected3 = "[\n" + absl::StrJoin(ints100, "\n") + "\n...\n]"; + expected4 = "[" + absl::StrJoin(ints100, ", ") + ", ...]"; + expected5 = absl::StrJoin(ints100, " ") + " ..."; + expected6 = "[\n" + absl::StrJoin(ints, "\n") + "\n]"; + expected7 = "[\n" + absl::StrJoin(ints123, "\n") + "\n...\n]"; + expected8 = "[" + absl::StrJoin(ints, ", ") + "]"; + expected9 = "[" + absl::StrJoin(ints123, ", ") + ", ...]"; + + LogRangeToStream(stream(), ints.begin(), ints.end(), LogMultiline()); + EXPECT_EQ(expected1, logged()); + LogRangeToStream(stream(), ints.begin(), ints.end(), LogShort()); + EXPECT_EQ(expected2, logged()); + LogRangeToStream(stream(), ints.begin(), ints.end(), LogMultilineUpTo100()); + EXPECT_EQ(expected3, logged()); + LogRangeToStream(stream(), ints.begin(), ints.end(), LogShortUpTo100()); + EXPECT_EQ(expected4, logged()); + LogRangeToStream(stream(), ints.begin(), ints.end(), LogLegacyUpTo100()); + EXPECT_EQ(expected5, logged()); + + LogRangeToStream(stream(), ints.begin(), ints.end(), + LogMultilineUpToN(ints.size())); + EXPECT_EQ(expected6, logged()); + LogRangeToStream(stream(), ints.begin(), ints.end(), + LogMultilineUpToN(max_elements)); + EXPECT_EQ(expected7, logged()); + LogRangeToStream(stream(), ints.begin(), ints.end(), + LogShortUpToN(ints.size())); + EXPECT_EQ(expected8, logged()); + LogRangeToStream(stream(), ints.begin(), ints.end(), + LogShortUpToN(max_elements)); + EXPECT_EQ(expected9, logged()); +} + +TEST_F(ContainerLoggingTest, LogContainer) { + std::set<int> ints = {1, 2, 3}; + stream() << LogContainer(ints, LogMultiline()); + EXPECT_EQ("[\n1\n2\n3\n]", logged()); + + stream() << LogContainer(ints); + EXPECT_EQ("[1, 2, 3]", logged()); + + stream() << LogContainer(std::vector<int>(ints.begin(), ints.end()), + LogLegacyUpTo100()); + EXPECT_EQ("1 2 3", logged()); +} + +TEST_F(ContainerLoggingTest, LogMutableSpan) { + std::vector<int> ints = {1, 2, 3}; + absl::Span<int> int_span(ints); + stream() << LogContainer(int_span); + EXPECT_EQ("[1, 2, 3]", logged()); +} + +TEST_F(ContainerLoggingTest, LogRange) { + std::set<int> ints = {1, 2, 3}; + stream() << LogRange(ints.begin(), ints.end(), LogMultiline()); + EXPECT_EQ("[\n1\n2\n3\n]", logged()); + + stream() << LogRange(ints.begin(), ints.end()); + EXPECT_EQ("[1, 2, 3]", logged()); +} + +// Some class with a custom Stringify +class C { + public: + explicit C(int x) : x_(x) {} + + private: + // This is intentionally made private for the purposes of the test; + //` AbslStringify` isn't meant to be called directly, and instead invoked + // via `StrCat` and friends. + template <typename Sink> + friend void AbslStringify(Sink& sink, const C& p) { + absl::Format(&sink, "C(%d)", p.x_); + } + + int x_; +}; + +TEST_F(ContainerLoggingTest, LogContainerWithCustomStringify) { + std::vector<C> c = {C(1), C(2), C(3)}; + stream() << LogContainer(c); + EXPECT_EQ("[C(1), C(2), C(3)]", logged()); +} + +class LogEnumTest : public ContainerLoggingTest { + protected: + enum Unscoped { kUnscoped0, kUnscoped1, kUnscoped2 }; + + enum StreamableUnscoped { + kStreamableUnscoped0, + kStreamableUnscoped1, + kStreamableUnscoped2 + }; + + enum class Scoped { k0, k1, k2 }; + + enum class StreamableScoped { k0, k1, k2 }; + + friend std::ostream& operator<<(std::ostream& os, StreamableUnscoped v) { + return os << LogEnum(v); + } + + friend std::ostream& operator<<(std::ostream& os, StreamableScoped v) { + return os << LogEnum(v); + } +}; + +TEST_F(LogEnumTest, Unscoped) { + stream() << LogEnum(kUnscoped0) << "," << LogEnum(kUnscoped1) << "," + << LogEnum(kUnscoped2); + EXPECT_EQ("0,1,2", logged()); +} + +TEST_F(LogEnumTest, StreamableUnscoped) { + stream() << kStreamableUnscoped0 << "," << kStreamableUnscoped1 << "," + << kStreamableUnscoped2; + EXPECT_EQ("0,1,2", logged()); +} + +TEST_F(LogEnumTest, Scoped) { + stream() << LogEnum(Scoped::k0) << "," << LogEnum(Scoped::k1) << "," + << LogEnum(Scoped::k2); + EXPECT_EQ("0,1,2", logged()); +} + +TEST_F(LogEnumTest, StreamableScoped) { + // Test using LogEnum to implement an operator<<. + stream() << StreamableScoped::k0 << "," << StreamableScoped::k1 << "," + << StreamableScoped::k2; + EXPECT_EQ("0,1,2", logged()); +} + +} // namespace +} // namespace log_internal +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/log/internal/log_impl.h b/absl/log/internal/log_impl.h index a67f2f3..2fe73de 100644 --- a/absl/log/internal/log_impl.h +++ b/absl/log/internal/log_impl.h
@@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +// SKIP_ABSL_INLINE_NAMESPACE_CHECK + #ifndef ABSL_LOG_INTERNAL_LOG_IMPL_H_ #define ABSL_LOG_INTERNAL_LOG_IMPL_H_ @@ -23,260 +25,256 @@ // ABSL_LOG() #define ABSL_LOG_INTERNAL_LOG_IMPL(severity) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, true) \ - ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + ABSL_LOG_INTERNAL_LOG##severity.InternalStream() // ABSL_PLOG() -#define ABSL_LOG_INTERNAL_PLOG_IMPL(severity) \ - ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, true) \ - ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ +#define ABSL_LOG_INTERNAL_PLOG_IMPL(severity) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, true) \ + ABSL_LOG_INTERNAL_LOG##severity.InternalStream() \ .WithPerror() // ABSL_DLOG() #ifndef NDEBUG #define ABSL_LOG_INTERNAL_DLOG_IMPL(severity) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, true) \ - ABSL_LOGGING_INTERNAL_DLOG##severity.InternalStream() + ABSL_LOG_INTERNAL_DLOG##severity.InternalStream() #else #define ABSL_LOG_INTERNAL_DLOG_IMPL(severity) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, false) \ - ABSL_LOGGING_INTERNAL_DLOG##severity.InternalStream() + ABSL_LOG_INTERNAL_DLOG##severity.InternalStream() #endif // The `switch` ensures that this expansion is the beginning of a statement (as // opposed to an expression). The use of both `case 0` and `default` is to // suppress a compiler warning. -#define ABSL_LOG_INTERNAL_VLOG_IMPL(verbose_level) \ - switch (const int absl_logging_internal_verbose_level = (verbose_level)) \ - case 0: \ - default: \ - ABSL_LOG_INTERNAL_LOG_IF_IMPL( \ - _INFO, ABSL_VLOG_IS_ON(absl_logging_internal_verbose_level)) \ - .WithVerbosity(absl_logging_internal_verbose_level) +#define ABSL_LOG_INTERNAL_VLOG_IMPL(verbose_level) \ + switch (const int absl_log_internal_verbose_level = (verbose_level)) \ + case 0: \ + default: \ + ABSL_LOG_INTERNAL_LOG_IF_IMPL( \ + _INFO, ABSL_VLOG_IS_ON(absl_log_internal_verbose_level)) \ + .WithVerbosity(absl_log_internal_verbose_level) #ifndef NDEBUG -#define ABSL_LOG_INTERNAL_DVLOG_IMPL(verbose_level) \ - switch (const int absl_logging_internal_verbose_level = (verbose_level)) \ - case 0: \ - default: \ - ABSL_LOG_INTERNAL_DLOG_IF_IMPL( \ - _INFO, ABSL_VLOG_IS_ON(absl_logging_internal_verbose_level)) \ - .WithVerbosity(absl_logging_internal_verbose_level) +#define ABSL_LOG_INTERNAL_DVLOG_IMPL(verbose_level) \ + switch (const int absl_log_internal_verbose_level = (verbose_level)) \ + case 0: \ + default: \ + ABSL_LOG_INTERNAL_DLOG_IF_IMPL( \ + _INFO, ABSL_VLOG_IS_ON(absl_log_internal_verbose_level)) \ + .WithVerbosity(absl_log_internal_verbose_level) #else -#define ABSL_LOG_INTERNAL_DVLOG_IMPL(verbose_level) \ - switch (const int absl_logging_internal_verbose_level = (verbose_level)) \ - case 0: \ - default: \ - ABSL_LOG_INTERNAL_DLOG_IF_IMPL( \ - _INFO, false && ABSL_VLOG_IS_ON(absl_logging_internal_verbose_level)) \ - .WithVerbosity(absl_logging_internal_verbose_level) +#define ABSL_LOG_INTERNAL_DVLOG_IMPL(verbose_level) \ + switch (const int absl_log_internal_verbose_level = (verbose_level)) \ + case 0: \ + default: \ + ABSL_LOG_INTERNAL_DLOG_IF_IMPL( \ + _INFO, false && ABSL_VLOG_IS_ON(absl_log_internal_verbose_level)) \ + .WithVerbosity(absl_log_internal_verbose_level) #endif #define ABSL_LOG_INTERNAL_LOG_IF_IMPL(severity, condition) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, condition) \ - ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + ABSL_LOG_INTERNAL_LOG##severity.InternalStream() #define ABSL_LOG_INTERNAL_PLOG_IF_IMPL(severity, condition) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, condition) \ - ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ + ABSL_LOG_INTERNAL_LOG##severity.InternalStream() \ .WithPerror() #ifndef NDEBUG #define ABSL_LOG_INTERNAL_DLOG_IF_IMPL(severity, condition) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, condition) \ - ABSL_LOGGING_INTERNAL_DLOG##severity.InternalStream() + ABSL_LOG_INTERNAL_DLOG##severity.InternalStream() #else #define ABSL_LOG_INTERNAL_DLOG_IF_IMPL(severity, condition) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATELESS, false && (condition)) \ - ABSL_LOGGING_INTERNAL_DLOG##severity.InternalStream() + ABSL_LOG_INTERNAL_DLOG##severity.InternalStream() #endif // ABSL_LOG_EVERY_N #define ABSL_LOG_INTERNAL_LOG_EVERY_N_IMPL(severity, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryN, n) \ - ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + ABSL_LOG_INTERNAL_LOG##severity.InternalStream() // ABSL_LOG_FIRST_N #define ABSL_LOG_INTERNAL_LOG_FIRST_N_IMPL(severity, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(FirstN, n) \ - ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + ABSL_LOG_INTERNAL_LOG##severity.InternalStream() // ABSL_LOG_EVERY_POW_2 #define ABSL_LOG_INTERNAL_LOG_EVERY_POW_2_IMPL(severity) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryPow2) \ - ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + ABSL_LOG_INTERNAL_LOG##severity.InternalStream() // ABSL_LOG_EVERY_N_SEC #define ABSL_LOG_INTERNAL_LOG_EVERY_N_SEC_IMPL(severity, n_seconds) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryNSec, n_seconds) \ - ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + ABSL_LOG_INTERNAL_LOG##severity.InternalStream() #define ABSL_LOG_INTERNAL_PLOG_EVERY_N_IMPL(severity, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryN, n) \ - ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ + ABSL_LOG_INTERNAL_LOG##severity.InternalStream() \ .WithPerror() #define ABSL_LOG_INTERNAL_PLOG_FIRST_N_IMPL(severity, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(FirstN, n) \ - ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ + ABSL_LOG_INTERNAL_LOG##severity.InternalStream() \ .WithPerror() #define ABSL_LOG_INTERNAL_PLOG_EVERY_POW_2_IMPL(severity) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryPow2) \ - ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ + ABSL_LOG_INTERNAL_LOG##severity.InternalStream() \ .WithPerror() #define ABSL_LOG_INTERNAL_PLOG_EVERY_N_SEC_IMPL(severity, n_seconds) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, true)(EveryNSec, n_seconds) \ - ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ + ABSL_LOG_INTERNAL_LOG##severity.InternalStream() \ .WithPerror() #ifndef NDEBUG #define ABSL_LOG_INTERNAL_DLOG_EVERY_N_IMPL(severity, n) \ ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ - (EveryN, n) ABSL_LOGGING_INTERNAL_DLOG##severity.InternalStream() + (EveryN, n) ABSL_LOG_INTERNAL_DLOG##severity.InternalStream() #define ABSL_LOG_INTERNAL_DLOG_FIRST_N_IMPL(severity, n) \ ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ - (FirstN, n) ABSL_LOGGING_INTERNAL_DLOG##severity.InternalStream() + (FirstN, n) ABSL_LOG_INTERNAL_DLOG##severity.InternalStream() #define ABSL_LOG_INTERNAL_DLOG_EVERY_POW_2_IMPL(severity) \ ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ - (EveryPow2) ABSL_LOGGING_INTERNAL_DLOG##severity.InternalStream() + (EveryPow2) ABSL_LOG_INTERNAL_DLOG##severity.InternalStream() #define ABSL_LOG_INTERNAL_DLOG_EVERY_N_SEC_IMPL(severity, n_seconds) \ ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, true) \ - (EveryNSec, n_seconds) ABSL_LOGGING_INTERNAL_DLOG##severity.InternalStream() + (EveryNSec, n_seconds) ABSL_LOG_INTERNAL_DLOG##severity.InternalStream() #else // def NDEBUG #define ABSL_LOG_INTERNAL_DLOG_EVERY_N_IMPL(severity, n) \ ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ - (EveryN, n) ABSL_LOGGING_INTERNAL_DLOG##severity.InternalStream() + (EveryN, n) ABSL_LOG_INTERNAL_DLOG##severity.InternalStream() #define ABSL_LOG_INTERNAL_DLOG_FIRST_N_IMPL(severity, n) \ ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ - (FirstN, n) ABSL_LOGGING_INTERNAL_DLOG##severity.InternalStream() + (FirstN, n) ABSL_LOG_INTERNAL_DLOG##severity.InternalStream() #define ABSL_LOG_INTERNAL_DLOG_EVERY_POW_2_IMPL(severity) \ ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ - (EveryPow2) ABSL_LOGGING_INTERNAL_DLOG##severity.InternalStream() + (EveryPow2) ABSL_LOG_INTERNAL_DLOG##severity.InternalStream() #define ABSL_LOG_INTERNAL_DLOG_EVERY_N_SEC_IMPL(severity, n_seconds) \ ABSL_LOG_INTERNAL_CONDITION_INFO(STATEFUL, false) \ - (EveryNSec, n_seconds) ABSL_LOGGING_INTERNAL_DLOG##severity.InternalStream() + (EveryNSec, n_seconds) ABSL_LOG_INTERNAL_DLOG##severity.InternalStream() #endif // def NDEBUG -#define ABSL_LOG_INTERNAL_VLOG_EVERY_N_IMPL(verbose_level, n) \ - switch (const int absl_logging_internal_verbose_level = (verbose_level)) \ - case 0: \ - default: \ - ABSL_LOG_INTERNAL_CONDITION_INFO( \ - STATEFUL, ABSL_VLOG_IS_ON(absl_logging_internal_verbose_level)) \ - (EveryN, n) ABSL_LOGGING_INTERNAL_LOG_INFO.InternalStream().WithVerbosity( \ - absl_logging_internal_verbose_level) +#define ABSL_LOG_INTERNAL_VLOG_EVERY_N_IMPL(verbose_level, n) \ + switch (const int absl_log_internal_verbose_level = (verbose_level)) \ + case 0: \ + default: \ + ABSL_LOG_INTERNAL_CONDITION_INFO( \ + STATEFUL, ABSL_VLOG_IS_ON(absl_log_internal_verbose_level)) \ + (EveryN, n) ABSL_LOG_INTERNAL_LOG_INFO.InternalStream().WithVerbosity( \ + absl_log_internal_verbose_level) -#define ABSL_LOG_INTERNAL_VLOG_FIRST_N_IMPL(verbose_level, n) \ - switch (const int absl_logging_internal_verbose_level = (verbose_level)) \ - case 0: \ - default: \ - ABSL_LOG_INTERNAL_CONDITION_INFO( \ - STATEFUL, ABSL_VLOG_IS_ON(absl_logging_internal_verbose_level)) \ - (FirstN, n) ABSL_LOGGING_INTERNAL_LOG_INFO.InternalStream().WithVerbosity( \ - absl_logging_internal_verbose_level) +#define ABSL_LOG_INTERNAL_VLOG_FIRST_N_IMPL(verbose_level, n) \ + switch (const int absl_log_internal_verbose_level = (verbose_level)) \ + case 0: \ + default: \ + ABSL_LOG_INTERNAL_CONDITION_INFO( \ + STATEFUL, ABSL_VLOG_IS_ON(absl_log_internal_verbose_level)) \ + (FirstN, n) ABSL_LOG_INTERNAL_LOG_INFO.InternalStream().WithVerbosity( \ + absl_log_internal_verbose_level) -#define ABSL_LOG_INTERNAL_VLOG_EVERY_POW_2_IMPL(verbose_level) \ - switch (const int absl_logging_internal_verbose_level = (verbose_level)) \ - case 0: \ - default: \ - ABSL_LOG_INTERNAL_CONDITION_INFO( \ - STATEFUL, ABSL_VLOG_IS_ON(absl_logging_internal_verbose_level)) \ - (EveryPow2) ABSL_LOGGING_INTERNAL_LOG_INFO.InternalStream().WithVerbosity( \ - absl_logging_internal_verbose_level) +#define ABSL_LOG_INTERNAL_VLOG_EVERY_POW_2_IMPL(verbose_level) \ + switch (const int absl_log_internal_verbose_level = (verbose_level)) \ + case 0: \ + default: \ + ABSL_LOG_INTERNAL_CONDITION_INFO( \ + STATEFUL, ABSL_VLOG_IS_ON(absl_log_internal_verbose_level)) \ + (EveryPow2) ABSL_LOG_INTERNAL_LOG_INFO.InternalStream().WithVerbosity( \ + absl_log_internal_verbose_level) -#define ABSL_LOG_INTERNAL_VLOG_EVERY_N_SEC_IMPL(verbose_level, n_seconds) \ - switch (const int absl_logging_internal_verbose_level = (verbose_level)) \ - case 0: \ - default: \ - ABSL_LOG_INTERNAL_CONDITION_INFO( \ - STATEFUL, ABSL_VLOG_IS_ON(absl_logging_internal_verbose_level)) \ - (EveryNSec, n_seconds) ABSL_LOGGING_INTERNAL_LOG_INFO.InternalStream() \ - .WithVerbosity(absl_logging_internal_verbose_level) +#define ABSL_LOG_INTERNAL_VLOG_EVERY_N_SEC_IMPL(verbose_level, n_seconds) \ + switch (const int absl_log_internal_verbose_level = (verbose_level)) \ + case 0: \ + default: \ + ABSL_LOG_INTERNAL_CONDITION_INFO( \ + STATEFUL, ABSL_VLOG_IS_ON(absl_log_internal_verbose_level)) \ + (EveryNSec, n_seconds) ABSL_LOG_INTERNAL_LOG_INFO.InternalStream() \ + .WithVerbosity(absl_log_internal_verbose_level) #define ABSL_LOG_INTERNAL_LOG_IF_EVERY_N_IMPL(severity, condition, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryN, n) \ - ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + ABSL_LOG_INTERNAL_LOG##severity.InternalStream() #define ABSL_LOG_INTERNAL_LOG_IF_FIRST_N_IMPL(severity, condition, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(FirstN, n) \ - ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + ABSL_LOG_INTERNAL_LOG##severity.InternalStream() #define ABSL_LOG_INTERNAL_LOG_IF_EVERY_POW_2_IMPL(severity, condition) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryPow2) \ - ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() + ABSL_LOG_INTERNAL_LOG##severity.InternalStream() -#define ABSL_LOG_INTERNAL_LOG_IF_EVERY_N_SEC_IMPL(severity, condition, \ - n_seconds) \ - ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryNSec, \ - n_seconds) \ - ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() +#define ABSL_LOG_INTERNAL_LOG_IF_EVERY_N_SEC_IMPL(severity, condition, \ + n_seconds) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)( \ + EveryNSec, n_seconds) ABSL_LOG_INTERNAL_LOG##severity.InternalStream() #define ABSL_LOG_INTERNAL_PLOG_IF_EVERY_N_IMPL(severity, condition, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryN, n) \ - ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ + ABSL_LOG_INTERNAL_LOG##severity.InternalStream() \ .WithPerror() #define ABSL_LOG_INTERNAL_PLOG_IF_FIRST_N_IMPL(severity, condition, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(FirstN, n) \ - ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ + ABSL_LOG_INTERNAL_LOG##severity.InternalStream() \ .WithPerror() #define ABSL_LOG_INTERNAL_PLOG_IF_EVERY_POW_2_IMPL(severity, condition) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryPow2) \ - ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ + ABSL_LOG_INTERNAL_LOG##severity.InternalStream() \ .WithPerror() -#define ABSL_LOG_INTERNAL_PLOG_IF_EVERY_N_SEC_IMPL(severity, condition, \ - n_seconds) \ - ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryNSec, \ - n_seconds) \ - ABSL_LOGGING_INTERNAL_LOG##severity.InternalStream() \ - .WithPerror() +#define ABSL_LOG_INTERNAL_PLOG_IF_EVERY_N_SEC_IMPL(severity, condition, \ + n_seconds) \ + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)( \ + EveryNSec, n_seconds) ABSL_LOG_INTERNAL_LOG##severity.InternalStream() \ + .WithPerror() #ifndef NDEBUG #define ABSL_LOG_INTERNAL_DLOG_IF_EVERY_N_IMPL(severity, condition, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryN, n) \ - ABSL_LOGGING_INTERNAL_DLOG##severity.InternalStream() + ABSL_LOG_INTERNAL_DLOG##severity.InternalStream() #define ABSL_LOG_INTERNAL_DLOG_IF_FIRST_N_IMPL(severity, condition, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(FirstN, n) \ - ABSL_LOGGING_INTERNAL_DLOG##severity.InternalStream() + ABSL_LOG_INTERNAL_DLOG##severity.InternalStream() #define ABSL_LOG_INTERNAL_DLOG_IF_EVERY_POW_2_IMPL(severity, condition) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryPow2) \ - ABSL_LOGGING_INTERNAL_DLOG##severity.InternalStream() + ABSL_LOG_INTERNAL_DLOG##severity.InternalStream() #define ABSL_LOG_INTERNAL_DLOG_IF_EVERY_N_SEC_IMPL(severity, condition, \ n_seconds) \ - ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)(EveryNSec, \ - n_seconds) \ - ABSL_LOGGING_INTERNAL_DLOG##severity.InternalStream() + ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, condition)( \ + EveryNSec, n_seconds) ABSL_LOG_INTERNAL_DLOG##severity.InternalStream() #else // def NDEBUG #define ABSL_LOG_INTERNAL_DLOG_IF_EVERY_N_IMPL(severity, condition, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, false && (condition))( \ - EveryN, n) ABSL_LOGGING_INTERNAL_DLOG##severity.InternalStream() + EveryN, n) ABSL_LOG_INTERNAL_DLOG##severity.InternalStream() #define ABSL_LOG_INTERNAL_DLOG_IF_FIRST_N_IMPL(severity, condition, n) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, false && (condition))( \ - FirstN, n) ABSL_LOGGING_INTERNAL_DLOG##severity.InternalStream() + FirstN, n) ABSL_LOG_INTERNAL_DLOG##severity.InternalStream() #define ABSL_LOG_INTERNAL_DLOG_IF_EVERY_POW_2_IMPL(severity, condition) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, false && (condition))( \ - EveryPow2) ABSL_LOGGING_INTERNAL_DLOG##severity.InternalStream() + EveryPow2) ABSL_LOG_INTERNAL_DLOG##severity.InternalStream() #define ABSL_LOG_INTERNAL_DLOG_IF_EVERY_N_SEC_IMPL(severity, condition, \ n_seconds) \ ABSL_LOG_INTERNAL_CONDITION##severity(STATEFUL, false && (condition))( \ - EveryNSec, n_seconds) \ - ABSL_LOGGING_INTERNAL_DLOG##severity.InternalStream() + EveryNSec, n_seconds) ABSL_LOG_INTERNAL_DLOG##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 index 3aed3a2..52ab3d9 100644 --- a/absl/log/internal/log_message.cc +++ b/absl/log/internal/log_message.cc
@@ -150,7 +150,7 @@ } // namespace struct LogMessage::LogMessageData final { - LogMessageData(const char* absl_nonnull file, int line, + LogMessageData(absl::string_view file, int line, absl::LogSeverity severity, absl::Time timestamp); LogMessageData(const LogMessageData&) = delete; LogMessageData& operator=(const LogMessageData&) = delete; @@ -202,7 +202,7 @@ void FinalizeEncodingAndFormat(); }; -LogMessage::LogMessageData::LogMessageData(const char* absl_nonnull file, +LogMessage::LogMessageData::LogMessageData(absl::string_view file, int line, absl::LogSeverity severity, absl::Time timestamp) : extra_sinks_only(false), manipulated(nullptr) { @@ -275,6 +275,9 @@ LogMessage::LogMessage(const char* absl_nonnull file, int line, absl::LogSeverity severity) + : LogMessage(absl::string_view(file), line, severity) {} +LogMessage::LogMessage(absl::string_view file, int line, + absl::LogSeverity severity) : data_(absl::make_unique<LogMessageData>(file, line, severity, absl::Now())) { data_->first_fatal = false;
diff --git a/absl/log/internal/log_message.h b/absl/log/internal/log_message.h index 1aaf05e..5b6eed3 100644 --- a/absl/log/internal/log_message.h +++ b/absl/log/internal/log_message.h
@@ -64,9 +64,13 @@ struct WarningTag {}; struct ErrorTag {}; - // Used for `LOG`. + // Used for `LOG`. Taking `const char *` instead of `string_view` keeps + // callsites a little bit smaller at the cost of doing `strlen` at runtime. LogMessage(const char* absl_nonnull file, int line, absl::LogSeverity severity) ABSL_ATTRIBUTE_COLD; + // Used for FFI integrations that don't have a NUL-terminated string. + LogMessage(absl::string_view file, int line, + absl::LogSeverity severity) ABSL_ATTRIBUTE_COLD; // These constructors are slightly smaller/faster to call; the severity is // curried into the function pointer. LogMessage(const char* absl_nonnull file, int line,
diff --git a/absl/log/internal/log_sink_set.cc b/absl/log/internal/log_sink_set.cc index 3d5c699..c4c7e5f 100644 --- a/absl/log/internal/log_sink_set.cc +++ b/absl/log/internal/log_sink_set.cc
@@ -192,7 +192,7 @@ absl::log_internal::WriteToStderr( entry.text_message_with_prefix_and_newline(), entry.log_severity()); } else { - absl::ReaderMutexLock global_sinks_lock(&guard_); + 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. @@ -205,7 +205,7 @@ void AddLogSink(absl::LogSink* sink) ABSL_LOCKS_EXCLUDED(guard_) { { - absl::WriterMutexLock global_sinks_lock(&guard_); + absl::WriterMutexLock global_sinks_lock(guard_); auto pos = std::find(sinks_.begin(), sinks_.end(), sink); if (pos == sinks_.end()) { sinks_.push_back(sink); @@ -217,7 +217,7 @@ void RemoveLogSink(absl::LogSink* sink) ABSL_LOCKS_EXCLUDED(guard_) { { - absl::WriterMutexLock global_sinks_lock(&guard_); + absl::WriterMutexLock global_sinks_lock(guard_); auto pos = std::find(sinks_.begin(), sinks_.end(), sink); if (pos != sinks_.end()) { sinks_.erase(pos); @@ -235,7 +235,7 @@ guard_.AssertReaderHeld(); FlushLogSinksLocked(); } else { - absl::ReaderMutexLock global_sinks_lock(&guard_); + 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
diff --git a/absl/log/internal/strip.h b/absl/log/internal/strip.h index 60ef878..6fd857b 100644 --- a/absl/log/internal/strip.h +++ b/absl/log/internal/strip.h
@@ -26,57 +26,56 @@ #include "absl/log/internal/log_message.h" #include "absl/log/internal/nullstream.h" -// `ABSL_LOGGING_INTERNAL_LOG_*` evaluates to a temporary `LogMessage` object or +// `ABSL_LOG_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 \ +#define ABSL_LOG_INTERNAL_LOG_INFO ::absl::log_internal::NullStream() +#define ABSL_LOG_INTERNAL_LOG_WARNING ::absl::log_internal::NullStream() +#define ABSL_LOG_INTERNAL_LOG_ERROR ::absl::log_internal::NullStream() +#define ABSL_LOG_INTERNAL_LOG_FATAL ::absl::log_internal::NullStreamFatal() +#define ABSL_LOG_INTERNAL_LOG_QFATAL ::absl::log_internal::NullStreamFatal() +#define ABSL_LOG_INTERNAL_LOG_DFATAL \ ::absl::log_internal::NullStreamMaybeFatal(::absl::kLogDebugFatal) -#define ABSL_LOGGING_INTERNAL_LOG_LEVEL(severity) \ +#define ABSL_LOG_INTERNAL_LOG_LEVEL(severity) \ ::absl::log_internal::NullStreamMaybeFatal(absl_log_internal_severity) // Fatal `DLOG`s expand a little differently to avoid being `[[noreturn]]`. -#define ABSL_LOGGING_INTERNAL_DLOG_FATAL \ +#define ABSL_LOG_INTERNAL_DLOG_FATAL \ ::absl::log_internal::NullStreamMaybeFatal(::absl::LogSeverity::kFatal) -#define ABSL_LOGGING_INTERNAL_DLOG_QFATAL \ +#define ABSL_LOG_INTERNAL_DLOG_QFATAL \ ::absl::log_internal::NullStreamMaybeFatal(::absl::LogSeverity::kFatal) -#define ABSL_LOG_INTERNAL_CHECK(failure_message) ABSL_LOGGING_INTERNAL_LOG_FATAL -#define ABSL_LOG_INTERNAL_QCHECK(failure_message) \ - ABSL_LOGGING_INTERNAL_LOG_QFATAL +#define ABSL_LOG_INTERNAL_CHECK(failure_message) ABSL_LOG_INTERNAL_LOG_FATAL +#define ABSL_LOG_INTERNAL_QCHECK(failure_message) ABSL_LOG_INTERNAL_LOG_QFATAL #else // !defined(STRIP_LOG) || !STRIP_LOG -#define ABSL_LOGGING_INTERNAL_LOG_INFO \ - ::absl::log_internal::LogMessage( \ +#define ABSL_LOG_INTERNAL_LOG_INFO \ + ::absl::log_internal::LogMessage( \ __FILE__, __LINE__, ::absl::log_internal::LogMessage::InfoTag{}) -#define ABSL_LOGGING_INTERNAL_LOG_WARNING \ - ::absl::log_internal::LogMessage( \ +#define ABSL_LOG_INTERNAL_LOG_WARNING \ + ::absl::log_internal::LogMessage( \ __FILE__, __LINE__, ::absl::log_internal::LogMessage::WarningTag{}) -#define ABSL_LOGGING_INTERNAL_LOG_ERROR \ - ::absl::log_internal::LogMessage( \ +#define ABSL_LOG_INTERNAL_LOG_ERROR \ + ::absl::log_internal::LogMessage( \ __FILE__, __LINE__, ::absl::log_internal::LogMessage::ErrorTag{}) -#define ABSL_LOGGING_INTERNAL_LOG_FATAL \ +#define ABSL_LOG_INTERNAL_LOG_FATAL \ ::absl::log_internal::LogMessageFatal(__FILE__, __LINE__) -#define ABSL_LOGGING_INTERNAL_LOG_QFATAL \ +#define ABSL_LOG_INTERNAL_LOG_QFATAL \ ::absl::log_internal::LogMessageQuietlyFatal(__FILE__, __LINE__) -#define ABSL_LOGGING_INTERNAL_LOG_DFATAL \ +#define ABSL_LOG_INTERNAL_LOG_DFATAL \ ::absl::log_internal::LogMessage(__FILE__, __LINE__, ::absl::kLogDebugFatal) -#define ABSL_LOGGING_INTERNAL_LOG_LEVEL(severity) \ +#define ABSL_LOG_INTERNAL_LOG_LEVEL(severity) \ ::absl::log_internal::LogMessage(__FILE__, __LINE__, \ absl_log_internal_severity) // Fatal `DLOG`s expand a little differently to avoid being `[[noreturn]]`. -#define ABSL_LOGGING_INTERNAL_DLOG_FATAL \ +#define ABSL_LOG_INTERNAL_DLOG_FATAL \ ::absl::log_internal::LogMessageDebugFatal(__FILE__, __LINE__) -#define ABSL_LOGGING_INTERNAL_DLOG_QFATAL \ +#define ABSL_LOG_INTERNAL_DLOG_QFATAL \ ::absl::log_internal::LogMessageQuietlyDebugFatal(__FILE__, __LINE__) // These special cases dispatch to special-case constructors that allow us to @@ -89,12 +88,12 @@ #endif // !defined(STRIP_LOG) || !STRIP_LOG // This part of a non-fatal `DLOG`s expands the same as `LOG`. -#define ABSL_LOGGING_INTERNAL_DLOG_INFO ABSL_LOGGING_INTERNAL_LOG_INFO -#define ABSL_LOGGING_INTERNAL_DLOG_WARNING ABSL_LOGGING_INTERNAL_LOG_WARNING -#define ABSL_LOGGING_INTERNAL_DLOG_ERROR ABSL_LOGGING_INTERNAL_LOG_ERROR -#define ABSL_LOGGING_INTERNAL_DLOG_DFATAL ABSL_LOGGING_INTERNAL_LOG_DFATAL -#define ABSL_LOGGING_INTERNAL_DLOG_LEVEL ABSL_LOGGING_INTERNAL_LOG_LEVEL +#define ABSL_LOG_INTERNAL_DLOG_INFO ABSL_LOG_INTERNAL_LOG_INFO +#define ABSL_LOG_INTERNAL_DLOG_WARNING ABSL_LOG_INTERNAL_LOG_WARNING +#define ABSL_LOG_INTERNAL_DLOG_ERROR ABSL_LOG_INTERNAL_LOG_ERROR +#define ABSL_LOG_INTERNAL_DLOG_DFATAL ABSL_LOG_INTERNAL_LOG_DFATAL +#define ABSL_LOG_INTERNAL_DLOG_LEVEL ABSL_LOG_INTERNAL_LOG_LEVEL -#define ABSL_LOGGING_INTERNAL_LOG_DO_NOT_SUBMIT ABSL_LOGGING_INTERNAL_LOG_ERROR +#define ABSL_LOG_INTERNAL_LOG_DO_NOT_SUBMIT ABSL_LOG_INTERNAL_LOG_ERROR #endif // ABSL_LOG_INTERNAL_STRIP_H_
diff --git a/absl/log/internal/vlog_config.cc b/absl/log/internal/vlog_config.cc index f7c61be..e9b4827 100644 --- a/absl/log/internal/vlog_config.cc +++ b/absl/log/internal/vlog_config.cc
@@ -45,13 +45,25 @@ namespace log_internal { namespace { -bool ModuleIsPath(absl::string_view module_pattern) { + #ifdef _WIN32 - return module_pattern.find_first_of("/\\") != module_pattern.npos; +constexpr char kPathSeparators[] = "/\\"; #else - return module_pattern.find('/') != module_pattern.npos; +constexpr char kPathSeparators[] = "/"; #endif + +bool ModuleIsPath(absl::string_view module_pattern) { + return module_pattern.find_first_of(kPathSeparators) != module_pattern.npos; } + +absl::string_view Basename(absl::string_view file) { + auto sep = file.find_last_of(kPathSeparators); + if (sep != file.npos) { + file.remove_prefix(sep + 1); + } + return file; +} + } // namespace bool VLogSite::SlowIsEnabled(int stale_v, int level) { @@ -90,16 +102,16 @@ // To avoid problems with the heap checker which calls into `VLOG`, `mutex` must // be a `SpinLock` that prevents fiber scheduling instead of a `Mutex`. ABSL_CONST_INIT absl::base_internal::SpinLock mutex( - absl::kConstInit, absl::base_internal::SCHEDULE_KERNEL_ONLY); + absl::base_internal::SCHEDULE_KERNEL_ONLY); // `GetUpdateSitesMutex()` serializes updates to all of the sites (i.e. those in // `site_list_head`) themselves. -absl::Mutex* GetUpdateSitesMutex() { +absl::Mutex& GetUpdateSitesMutex() { // Chromium requires no global destructors, so we can't use the // absl::kConstInit idiom since absl::Mutex as a non-trivial destructor. static absl::NoDestructor<absl::Mutex> update_sites_mutex ABSL_ACQUIRED_AFTER( mutex); - return update_sites_mutex.get(); + return *update_sites_mutex; } ABSL_CONST_INIT int global_v ABSL_GUARDED_BY(mutex) = 0; @@ -129,21 +141,9 @@ // parsing flags). We can't allocate in `VLOG`, so we treat null as empty // here and press on. if (!infos || infos->empty()) return current_global_v; - // Get basename for file - absl::string_view basename = file; - { - const size_t sep = basename.rfind('/'); - if (sep != basename.npos) { - basename.remove_prefix(sep + 1); -#ifdef _WIN32 - } else { - const size_t sep = basename.rfind('\\'); - if (sep != basename.npos) basename.remove_prefix(sep + 1); -#endif - } - } - absl::string_view stem = file, stem_basename = basename; + absl::string_view stem = file; + absl::string_view stem_basename = Basename(stem); { const size_t sep = stem_basename.find('.'); if (sep != stem_basename.npos) { @@ -159,10 +159,10 @@ // If there are any slashes in the pattern, try to match the full // name. if (FNMatch(info.module_pattern, stem)) { - return info.vlog_level == kUseFlag ? current_global_v : info.vlog_level; + return info.vlog_level; } } else if (FNMatch(info.module_pattern, stem_basename)) { - return info.vlog_level == kUseFlag ? current_global_v : info.vlog_level; + return info.vlog_level; } } @@ -222,7 +222,7 @@ } // namespace int VLogLevel(absl::string_view file) ABSL_LOCKS_EXCLUDED(mutex) { - absl::base_internal::SpinLockHolder l(&mutex); + absl::base_internal::SpinLockHolder l(mutex); return VLogLevel(file, vmodule_info, global_v); } @@ -267,7 +267,7 @@ // have to wait on all updates in order to acquire `mutex` and initialize // themselves. absl::MutexLock ul(GetUpdateSitesMutex()); - mutex.Unlock(); + mutex.unlock(); VLogSite* n = site_list_head.load(std::memory_order_seq_cst); // Because sites are added to the list in the order they are executed, there // tend to be clusters of entries with the same file. @@ -299,7 +299,7 @@ if (!absl::SimpleAtoi(glob_level.substr(eq + 1), &level)) continue; glob_levels.emplace_back(glob, level); } - mutex.Lock(); // Unlocked by UpdateVLogSites(). + mutex.lock(); // unlocked by UpdateVLogSites(). get_vmodule_info().clear(); for (const auto& it : glob_levels) { const absl::string_view glob = it.first; @@ -311,10 +311,10 @@ int UpdateGlobalVLogLevel(int v) ABSL_LOCKS_EXCLUDED(mutex, GetUpdateSitesMutex()) { - mutex.Lock(); // Unlocked by UpdateVLogSites(). + mutex.lock(); // unlocked by UpdateVLogSites(). const int old_global_v = global_v; if (v == global_v) { - mutex.Unlock(); + mutex.unlock(); return old_global_v; } global_v = v; @@ -324,7 +324,7 @@ int PrependVModule(absl::string_view module_pattern, int log_level) ABSL_LOCKS_EXCLUDED(mutex, GetUpdateSitesMutex()) { - mutex.Lock(); // Unlocked by UpdateVLogSites(). + mutex.lock(); // unlocked by UpdateVLogSites(). int old_v = PrependVModuleLocked(module_pattern, log_level); UpdateVLogSites(); return old_v;
diff --git a/absl/log/internal/vlog_config.h b/absl/log/internal/vlog_config.h index 84e817a..da020f8 100644 --- a/absl/log/internal/vlog_config.h +++ b/absl/log/internal/vlog_config.h
@@ -48,7 +48,6 @@ int RegisterAndInitialize(VLogSite* absl_nonnull v); void UpdateVLogSites(); -constexpr int kUseFlag = (std::numeric_limits<int16_t>::min)(); // Represents a unique callsite for a `VLOG()` or `VLOG_IS_ON()` call. //
diff --git a/absl/log/log.h b/absl/log/log.h index f1cab9d..371c32f 100644 --- a/absl/log/log.h +++ b/absl/log/log.h
@@ -247,11 +247,11 @@ // However, simply testing whether verbose logging is enabled can be expensive. // If you don't intend to enable verbose logging in non-debug builds, consider // using `DVLOG` instead. -#define VLOG(severity) ABSL_LOG_INTERNAL_VLOG_IMPL(severity) +#define VLOG(verbose_level) ABSL_LOG_INTERNAL_VLOG_IMPL(verbose_level) // `DVLOG` behaves like `VLOG` in debug mode (i.e. `#ifndef NDEBUG`). // Otherwise, it compiles away and does nothing. -#define DVLOG(severity) ABSL_LOG_INTERNAL_DVLOG_IMPL(severity) +#define DVLOG(verbose_level) ABSL_LOG_INTERNAL_DVLOG_IMPL(verbose_level) // `LOG_IF` and friends add a second argument which specifies a condition. If // the condition is false, nothing is logged.
diff --git a/absl/log/log_basic_test_impl.inc b/absl/log/log_basic_test_impl.inc index c4b4e24..3f007dc 100644 --- a/absl/log/log_basic_test_impl.inc +++ b/absl/log/log_basic_test_impl.inc
@@ -94,6 +94,7 @@ absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const int log_line = __LINE__ + 1; auto do_log = [] { ABSL_TEST_LOG(INFO) << "hello world"; }; @@ -125,6 +126,7 @@ absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const int log_line = __LINE__ + 1; auto do_log = [] { ABSL_TEST_LOG(WARNING) << "hello world"; }; @@ -156,6 +158,7 @@ absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const int log_line = __LINE__ + 1; auto do_log = [] { ABSL_TEST_LOG(ERROR) << "hello world"; }; @@ -187,6 +190,7 @@ absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const int log_line = __LINE__ + 1; auto do_log = [] { ABSL_TEST_LOG(DO_NOT_SUBMIT) << "hello world"; }; @@ -233,6 +237,7 @@ { absl::ScopedMockLog test_sink( absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send) .Times(AnyNumber()) @@ -299,6 +304,7 @@ { absl::ScopedMockLog test_sink( absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send) .Times(AnyNumber()) @@ -336,6 +342,7 @@ absl::log_internal::ScopedMinLogLevel scoped_min_log_level(GetParam()); absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const int log_line = __LINE__ + 1; auto do_log = [] { ABSL_TEST_LOG(DFATAL) << "hello world"; }; @@ -375,6 +382,7 @@ { absl::ScopedMockLog test_sink( absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send) .Times(AnyNumber()) @@ -456,6 +464,7 @@ for (auto severity : {absl::LogSeverity::kInfo, absl::LogSeverity::kWarning, absl::LogSeverity::kError}) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const int log_line = __LINE__ + 2; auto do_log = [severity] { @@ -506,6 +515,7 @@ { absl::ScopedMockLog test_sink( absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send) .Times(AnyNumber()) @@ -567,6 +577,7 @@ } absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(LogSeverity(Eq(absl::LogSeverity::kInfo)))); @@ -583,6 +594,7 @@ } absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(LogSeverity(Eq(absl::LogSeverity::kError))));
diff --git a/absl/log/log_entry.cc b/absl/log/log_entry.cc new file mode 100644 index 0000000..358b8f5 --- /dev/null +++ b/absl/log/log_entry.cc
@@ -0,0 +1,263 @@ +// Copyright 2025 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 <cstddef> +#include <cstdint> +#include <iomanip> +#include <ios> +#include <ostream> + +#include "absl/base/config.h" +#include "absl/log/internal/proto.h" +#include "absl/strings/escaping.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" +#include "absl/types/span.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace { +// message `logging.proto.Event` +enum EventTag : uint8_t { + kFileName = 2, + kFileLine = 3, + kTimeNsecs = 4, + kSeverity = 5, + kThreadId = 6, + kValue = 7, + kSequenceNumber = 9, + kThreadName = 10, +}; + +// message `logging.proto.Value` +enum ValueTag : uint8_t { + kString = 1, + kStringLiteral = 6, +}; + +// enum `logging.proto.Severity` +enum Severity : int { + FINEST = 300, + FINER = 400, + FINE = 500, + VERBOSE_0 = 600, + CONFIG = 700, + INFO = 800, + NOTICE = 850, + WARNING = 900, + ERROR = 950, + SEVERE = 1000, + FATAL = 1100, +}; + +void PrintEscapedRangeTo(const absl::string_view str, + const absl::string_view substr, std::ostream* os) { + const absl::string_view head = + str.substr(0, static_cast<size_t>(substr.data() - str.data())); + const char old_fill = os->fill(); + const auto old_flags = os->flags(); + *os << std::right + << std::setw(static_cast<int>(absl::CHexEscape(head).size())) << ""; + switch (substr.size()) { + case 0: + *os << "\\"; + break; + case 1: + *os << "^"; + break; + default: + *os << "[" << std::setw(static_cast<int>(absl::CHexEscape(substr).size())) + << std::setfill('-') << ")"; + break; + } + os->fill(old_fill); + os->flags(old_flags); +} +} // namespace +void PrintTo(const LogEntry& entry, std::ostream* os) { + auto text_message_with_prefix_and_newline_and_nul = absl::string_view( + entry.text_message_with_prefix_and_newline_and_nul_.data(), + entry.text_message_with_prefix_and_newline_and_nul_.size()); + *os << "LogEntry {\n" + << " source_filename: \"" << absl::CHexEscape(entry.source_filename()) + << "\"\n" + << " source_basename: \"" << absl::CHexEscape(entry.source_basename()) + << "\"\n" + << " source_line: " << entry.source_line() << "\n" + << " prefix: " << std::boolalpha << entry.prefix() << "\n" + << " log_severity: " << entry.log_severity() << "\n" + << " verbosity: " << entry.verbosity(); + if (entry.verbosity() == absl::LogEntry::kNoVerbosityLevel) { + *os << " (kNoVerbosityLevel)"; + } + *os << "\n" + << " timestamp: " << entry.timestamp() << "\n" + << " tid: " << entry.tid() << "\n" + << " text_message_with_prefix_and_newline_and_nul_: \"" + << absl::CHexEscape(text_message_with_prefix_and_newline_and_nul) + << "\"\n" + << " text_message_with_prefix_and_newline: "; + PrintEscapedRangeTo(text_message_with_prefix_and_newline_and_nul, + entry.text_message_with_prefix_and_newline(), os); + *os << "\n" + << " text_message_with_prefix: "; + PrintEscapedRangeTo(text_message_with_prefix_and_newline_and_nul, + entry.text_message_with_prefix(), os); + *os << "\n" + << " text_message_with_newline: "; + PrintEscapedRangeTo(text_message_with_prefix_and_newline_and_nul, + entry.text_message_with_newline(), os); + *os << "\n" + << " text_message: "; + PrintEscapedRangeTo(text_message_with_prefix_and_newline_and_nul, + entry.text_message(), os); + *os << "\n" + << " text_message_with_prefix_and_newline_c_str: "; + PrintEscapedRangeTo( + text_message_with_prefix_and_newline_and_nul, + // NOLINTNEXTLINE(bugprone-string-constructor) + absl::string_view(entry.text_message_with_prefix_and_newline_c_str(), 0), + os); + *os << "\n" + << " encoded_message (raw): \"" + << absl::CHexEscape(entry.encoded_message()) << "\"\n" + << " encoded_message {\n"; + absl::Span<const char> event = entry.encoded_message(); + log_internal::ProtoField field; + while (field.DecodeFrom(&event)) { + switch (field.tag()) { + case EventTag::kFileName: + *os << " file_name: \"" << absl::CHexEscape(field.string_value()) + << "\"\n"; + break; + case EventTag::kFileLine: + *os << " file_line: " << field.int32_value() << "\n"; + break; + case EventTag::kTimeNsecs: + *os << " time_nsecs: " << field.int64_value() << " (" + << absl::FromUnixNanos(field.int64_value()) << ")\n"; + break; + case EventTag::kSeverity: + *os << " severity: " << field.int32_value(); + switch (field.int32_value()) { + case Severity::FINEST: + *os << " (FINEST)"; + break; + case Severity::FINER: + *os << " (FINER)"; + break; + case Severity::FINE: + *os << " (FINE)"; + break; + case Severity::VERBOSE_0: + *os << " (VERBOSE_0)"; + break; + case Severity::CONFIG: + *os << " (CONFIG)"; + break; + case Severity::INFO: + *os << " (INFO)"; + break; + case Severity::NOTICE: + *os << " (NOTICE)"; + break; + case Severity::WARNING: + *os << " (WARNING)"; + break; + case Severity::ERROR: + *os << " (ERROR)"; + break; + case Severity::SEVERE: + *os << " (SEVERE)"; + break; + case Severity::FATAL: + *os << " (FATAL)"; + break; + } + *os << "\n"; + break; + case EventTag::kThreadId: + *os << " thread_id: " << field.int64_value() << "\n"; + break; + case EventTag::kValue: { + *os << " value {\n"; + auto value = field.bytes_value(); + while (field.DecodeFrom(&value)) { + switch (field.tag()) { + case ValueTag::kString: + *os << " str: \"" << absl::CHexEscape(field.string_value()) + << "\"\n"; + break; + case ValueTag::kStringLiteral: + *os << " literal: \"" + << absl::CHexEscape(field.string_value()) << "\"\n"; + break; + default: + *os << " unknown field " << field.tag(); + switch (field.type()) { + case log_internal::WireType::kVarint: + *os << " (VARINT): " << std::hex << std::showbase + << field.uint64_value() << std::dec << "\n"; + break; + case log_internal::WireType::k64Bit: + *os << " (I64): " << std::hex << std::showbase + << field.uint64_value() << std::dec << "\n"; + break; + case log_internal::WireType::kLengthDelimited: + *os << " (LEN): \"" << absl::CHexEscape(field.string_value()) + << "\"\n"; + break; + case log_internal::WireType::k32Bit: + *os << " (I32): " << std::hex << std::showbase + << field.uint32_value() << std::dec << "\n"; + break; + } + break; + } + } + *os << " }\n"; + break; + } + default: + *os << " unknown field " << field.tag(); + switch (field.type()) { + case log_internal::WireType::kVarint: + *os << " (VARINT): " << std::hex << std::showbase + << field.uint64_value() << std::dec << "\n"; + break; + case log_internal::WireType::k64Bit: + *os << " (I64): " << std::hex << std::showbase + << field.uint64_value() << std::dec << "\n"; + break; + case log_internal::WireType::kLengthDelimited: + *os << " (LEN): \"" << absl::CHexEscape(field.string_value()) + << "\"\n"; + break; + case log_internal::WireType::k32Bit: + *os << " (I32): " << std::hex << std::showbase + << field.uint32_value() << std::dec << "\n"; + break; + } + break; + } + } + *os << " }\n" + << " stacktrace: \"" << absl::CHexEscape(entry.stacktrace()) << "\"\n" + << "}"; +} + +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/log/log_entry.h b/absl/log/log_entry.h index 7a55dfe..c566856 100644 --- a/absl/log/log_entry.h +++ b/absl/log/log_entry.h
@@ -25,6 +25,7 @@ #define ABSL_LOG_LOG_ENTRY_H_ #include <cstddef> +#include <ostream> #include <string> #include "absl/base/attributes.h" @@ -213,6 +214,7 @@ friend class log_internal::LogEntryTestPeer; friend class log_internal::LogMessage; + friend void PrintTo(const absl::LogEntry& entry, std::ostream* os); }; ABSL_NAMESPACE_END
diff --git a/absl/log/log_format_test.cc b/absl/log/log_format_test.cc index 6b7d1e5..9f1cc6b 100644 --- a/absl/log/log_format_test.cc +++ b/absl/log/log_format_test.cc
@@ -73,6 +73,7 @@ TEST(LogFormatTest, NoMessage) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const int log_line = __LINE__ + 1; auto do_log = [] { LOG(INFO); }; @@ -95,6 +96,7 @@ TYPED_TEST(CharLogFormatTest, Printable) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const TypeParam value = 'x'; auto comparison_stream = ComparisonStream(); @@ -112,6 +114,7 @@ TYPED_TEST(CharLogFormatTest, Unprintable) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); constexpr auto value = static_cast<TypeParam>(0xeeu); auto comparison_stream = ComparisonStream(); @@ -130,6 +133,7 @@ TEST(WideCharLogFormatTest, Printable) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(AllOf(TextMessage(Eq("€")), ENCODED_MESSAGE(HasValues( @@ -142,6 +146,7 @@ TEST(WideCharLogFormatTest, Unprintable) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); // Using NEL (Next Line) Unicode character (U+0085). // It is encoded as "\xC2\x85" in UTF-8. @@ -163,6 +168,7 @@ TYPED_TEST(UnsignedIntLogFormatTest, Positive) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const TypeParam value = 224; auto comparison_stream = ComparisonStream(); @@ -181,6 +187,7 @@ TYPED_TEST(UnsignedIntLogFormatTest, BitfieldPositive) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const struct { TypeParam bits : 6; @@ -206,6 +213,7 @@ TYPED_TEST(SignedIntLogFormatTest, Positive) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const TypeParam value = 224; auto comparison_stream = ComparisonStream(); @@ -224,6 +232,7 @@ TYPED_TEST(SignedIntLogFormatTest, Negative) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const TypeParam value = -112; auto comparison_stream = ComparisonStream(); @@ -242,6 +251,7 @@ TYPED_TEST(SignedIntLogFormatTest, BitfieldPositive) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const struct { TypeParam bits : 6; @@ -261,6 +271,7 @@ TYPED_TEST(SignedIntLogFormatTest, BitfieldNegative) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const struct { TypeParam bits : 6; @@ -305,6 +316,7 @@ TYPED_TEST(UnsignedEnumLogFormatTest, Positive) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const TypeParam value = static_cast<TypeParam>(224); auto comparison_stream = ComparisonStream(); @@ -323,6 +335,7 @@ TYPED_TEST(UnsignedEnumLogFormatTest, BitfieldPositive) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const struct { TypeParam bits : 6; @@ -365,6 +378,7 @@ TYPED_TEST(SignedEnumLogFormatTest, Positive) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const TypeParam value = static_cast<TypeParam>(224); auto comparison_stream = ComparisonStream(); @@ -383,6 +397,7 @@ TYPED_TEST(SignedEnumLogFormatTest, Negative) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const TypeParam value = static_cast<TypeParam>(-112); auto comparison_stream = ComparisonStream(); @@ -401,6 +416,7 @@ TYPED_TEST(SignedEnumLogFormatTest, BitfieldPositive) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const struct { TypeParam bits : 6; @@ -420,6 +436,7 @@ TYPED_TEST(SignedEnumLogFormatTest, BitfieldNegative) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const struct { TypeParam bits : 6; @@ -441,6 +458,7 @@ TEST(FloatLogFormatTest, Positive) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const float value = 6.02e23f; auto comparison_stream = ComparisonStream(); @@ -458,6 +476,7 @@ TEST(FloatLogFormatTest, Negative) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const float value = -6.02e23f; auto comparison_stream = ComparisonStream(); @@ -475,6 +494,7 @@ TEST(FloatLogFormatTest, NegativeExponent) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const float value = 6.02e-23f; auto comparison_stream = ComparisonStream(); @@ -492,6 +512,7 @@ TEST(DoubleLogFormatTest, Positive) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const double value = 6.02e23; auto comparison_stream = ComparisonStream(); @@ -509,6 +530,7 @@ TEST(DoubleLogFormatTest, Negative) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const double value = -6.02e23; auto comparison_stream = ComparisonStream(); @@ -526,6 +548,7 @@ TEST(DoubleLogFormatTest, NegativeExponent) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const double value = 6.02e-23; auto comparison_stream = ComparisonStream(); @@ -548,6 +571,7 @@ TYPED_TEST(FloatingPointLogFormatTest, Zero) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const TypeParam value = 0.0; auto comparison_stream = ComparisonStream(); @@ -565,6 +589,7 @@ TYPED_TEST(FloatingPointLogFormatTest, Integer) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const TypeParam value = 1.0; auto comparison_stream = ComparisonStream(); @@ -582,6 +607,7 @@ TYPED_TEST(FloatingPointLogFormatTest, Infinity) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const TypeParam value = std::numeric_limits<TypeParam>::infinity(); auto comparison_stream = ComparisonStream(); @@ -600,6 +626,7 @@ TYPED_TEST(FloatingPointLogFormatTest, NegativeInfinity) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const TypeParam value = -std::numeric_limits<TypeParam>::infinity(); auto comparison_stream = ComparisonStream(); @@ -618,6 +645,7 @@ TYPED_TEST(FloatingPointLogFormatTest, NaN) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const TypeParam value = std::numeric_limits<TypeParam>::quiet_NaN(); auto comparison_stream = ComparisonStream(); @@ -635,6 +663,7 @@ TYPED_TEST(FloatingPointLogFormatTest, NegativeNaN) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const TypeParam value = std::copysign(std::numeric_limits<TypeParam>::quiet_NaN(), -1.0); @@ -671,6 +700,7 @@ TYPED_TEST(VoidPtrLogFormatTest, Null) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const TypeParam value = nullptr; auto comparison_stream = ComparisonStream(); @@ -688,6 +718,7 @@ TYPED_TEST(VoidPtrLogFormatTest, NonNull) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const TypeParam value = reinterpret_cast<TypeParam>(0xdeadbeefULL); auto comparison_stream = ComparisonStream(); @@ -715,6 +746,7 @@ TYPED_TEST(VolatilePtrLogFormatTest, Null) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const TypeParam value = nullptr; auto comparison_stream = ComparisonStream(); @@ -742,6 +774,7 @@ TYPED_TEST(VolatilePtrLogFormatTest, NonNull) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const TypeParam value = reinterpret_cast<TypeParam>(0xdeadbeefLL); auto comparison_stream = ComparisonStream(); @@ -777,6 +810,7 @@ TYPED_TEST(CharPtrLogFormatTest, Null) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); // Streaming `([cv] char *)nullptr` into a `std::ostream` is UB, and some C++ // standard library implementations choose to crash. We take measures to log @@ -797,6 +831,7 @@ TYPED_TEST(CharPtrLogFormatTest, NonNull) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); TypeParam data[] = {'v', 'a', 'l', 'u', 'e', '\0'}; TypeParam* const value = data; @@ -821,6 +856,7 @@ TYPED_TEST(WideCharPtrLogFormatTest, Null) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); TypeParam* const value = nullptr; @@ -834,6 +870,7 @@ TYPED_TEST(WideCharPtrLogFormatTest, NonNull) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); TypeParam data[] = {'v', 'a', 'l', 'u', 'e', '\0'}; TypeParam* const value = data; @@ -848,6 +885,7 @@ TEST(BoolLogFormatTest, True) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const bool value = true; auto comparison_stream = ComparisonStream(); @@ -866,6 +904,7 @@ TEST(BoolLogFormatTest, False) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const bool value = false; auto comparison_stream = ComparisonStream(); @@ -884,6 +923,7 @@ TEST(LogFormatTest, StringLiteral) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); auto comparison_stream = ComparisonStream(); comparison_stream << "value"; @@ -900,6 +940,7 @@ TEST(LogFormatTest, WideStringLiteral) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(AllOf(TextMessage(Eq("value")), ENCODED_MESSAGE(HasValues(ElementsAre( @@ -911,6 +952,7 @@ TEST(LogFormatTest, CharArray) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); char value[] = "value"; auto comparison_stream = ComparisonStream(); @@ -929,6 +971,7 @@ TEST(LogFormatTest, WideCharArray) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); wchar_t value[] = L"value"; @@ -967,6 +1010,7 @@ TYPED_TEST(WideStringLogFormatTest, NonLiterals) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); TypeParam value = ABSL_LOG_INTERNAL_WIDE_LITERAL; absl::string_view utf8_value = GetUtf8TestString(); @@ -981,6 +1025,7 @@ TEST(WideStringLogFormatTest, StringView) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); std::wstring_view value = ABSL_LOG_INTERNAL_WIDE_LITERAL; absl::string_view utf8_value = GetUtf8TestString(); @@ -995,6 +1040,7 @@ TEST(WideStringLogFormatTest, Literal) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); absl::string_view utf8_value = GetUtf8TestString(); @@ -1011,6 +1057,7 @@ TYPED_TEST(WideStringLogFormatTest, IsolatedLowSurrogatesAreReplaced) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); TypeParam value = L"AAA \xDC00 BBB"; // NOLINTNEXTLINE(readability/utf8) @@ -1027,6 +1074,7 @@ TYPED_TEST(WideStringLogFormatTest, DISABLED_IsolatedHighSurrogatesAreReplaced) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); TypeParam value = L"AAA \xD800 BBB"; // NOLINTNEXTLINE(readability/utf8) @@ -1044,6 +1092,7 @@ TYPED_TEST(WideStringLogFormatTest, DISABLED_ConsecutiveHighSurrogatesAreReplaced) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); TypeParam value = L"AAA \xD800\xD800 BBB"; // NOLINTNEXTLINE(readability/utf8) @@ -1061,6 +1110,7 @@ TYPED_TEST(WideStringLogFormatTest, DISABLED_HighHighLowSurrogateSequencesAreReplaced) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); TypeParam value = L"AAA \xD800\xD800\xDC00 BBB"; // NOLINTNEXTLINE(readability/utf8) @@ -1078,6 +1128,7 @@ TYPED_TEST(WideStringLogFormatTest, DISABLED_TrailingHighSurrogatesAreReplaced) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); TypeParam value = L"AAA \xD800"; // NOLINTNEXTLINE(readability/utf8) @@ -1094,6 +1145,7 @@ TYPED_TEST(WideStringLogFormatTest, EmptyWideString) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); TypeParam value = L""; @@ -1121,6 +1173,7 @@ TEST(LogFormatTest, Custom) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); CustomClass value; auto comparison_stream = ComparisonStream(); @@ -1147,6 +1200,7 @@ TEST(LogFormatTest, CustomNonCopyable) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); CustomClassNonCopyable value; auto comparison_stream = ComparisonStream(); @@ -1174,6 +1228,7 @@ TEST(LogFormatTest, AbslStringifyExample) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); Point p; @@ -1205,6 +1260,7 @@ TEST(LogFormatTest, CustomWithAbslStringifyAndOstream) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); PointWithAbslStringifiyAndOstream p; @@ -1228,6 +1284,7 @@ TEST(LogFormatTest, AbslStringifyStreamsNothing) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); PointStreamsNothing p; @@ -1254,6 +1311,7 @@ TEST(LogFormatTest, AbslStringifyMultipleAppend) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); PointMultipleAppend p; @@ -1269,6 +1327,7 @@ TEST(ManipulatorLogFormatTest, BoolAlphaTrue) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const bool value = true; auto comparison_stream = ComparisonStream(); @@ -1293,6 +1352,7 @@ TEST(ManipulatorLogFormatTest, BoolAlphaFalse) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const bool value = false; auto comparison_stream = ComparisonStream(); @@ -1317,6 +1377,7 @@ TEST(ManipulatorLogFormatTest, ShowPoint) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const double value = 77.0; auto comparison_stream = ComparisonStream(); @@ -1341,6 +1402,7 @@ TEST(ManipulatorLogFormatTest, ShowPos) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const int value = 77; auto comparison_stream = ComparisonStream(); @@ -1364,6 +1426,7 @@ TEST(ManipulatorLogFormatTest, UppercaseFloat) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const double value = 7.7e7; auto comparison_stream = ComparisonStream(); @@ -1388,6 +1451,7 @@ TEST(ManipulatorLogFormatTest, Hex) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const int value = 0x77; auto comparison_stream = ComparisonStream(); @@ -1405,6 +1469,7 @@ TEST(ManipulatorLogFormatTest, Oct) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const int value = 077; auto comparison_stream = ComparisonStream(); @@ -1423,6 +1488,7 @@ TEST(ManipulatorLogFormatTest, Dec) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const int value = 77; auto comparison_stream = ComparisonStream(); @@ -1440,6 +1506,7 @@ TEST(ManipulatorLogFormatTest, ShowbaseHex) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const int value = 0x77; auto comparison_stream = ComparisonStream(); @@ -1466,6 +1533,7 @@ TEST(ManipulatorLogFormatTest, ShowbaseOct) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const int value = 077; auto comparison_stream = ComparisonStream(); @@ -1491,6 +1559,7 @@ TEST(ManipulatorLogFormatTest, UppercaseHex) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const int value = 0xbeef; auto comparison_stream = ComparisonStream(); @@ -1518,6 +1587,7 @@ TEST(ManipulatorLogFormatTest, FixedFloat) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const double value = 7.7e7; auto comparison_stream = ComparisonStream(); @@ -1535,6 +1605,7 @@ TEST(ManipulatorLogFormatTest, ScientificFloat) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const double value = 7.7e7; auto comparison_stream = ComparisonStream(); @@ -1558,6 +1629,7 @@ #else TEST(ManipulatorLogFormatTest, FixedAndScientificFloat) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const double value = 7.7e7; auto comparison_stream = ComparisonStream(); @@ -1591,6 +1663,7 @@ #else TEST(ManipulatorLogFormatTest, HexfloatFloat) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const double value = 7.7e7; auto comparison_stream = ComparisonStream(); @@ -1612,6 +1685,7 @@ TEST(ManipulatorLogFormatTest, DefaultFloatFloat) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const double value = 7.7e7; auto comparison_stream = ComparisonStream(); @@ -1629,6 +1703,7 @@ TEST(ManipulatorLogFormatTest, Ends) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); auto comparison_stream = ComparisonStream(); comparison_stream << std::ends; @@ -1645,6 +1720,7 @@ TEST(ManipulatorLogFormatTest, Endl) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); auto comparison_stream = ComparisonStream(); comparison_stream << std::endl; @@ -1662,6 +1738,7 @@ TEST(ManipulatorLogFormatTest, SetIosFlags) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const int value = 0x77; auto comparison_stream = ComparisonStream(); @@ -1691,6 +1768,7 @@ TEST(ManipulatorLogFormatTest, SetBase) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const int value = 0x77; auto comparison_stream = ComparisonStream(); @@ -1715,6 +1793,7 @@ TEST(ManipulatorLogFormatTest, SetPrecision) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const double value = 6.022140857e23; auto comparison_stream = ComparisonStream(); @@ -1736,6 +1815,7 @@ TEST(ManipulatorLogFormatTest, SetPrecisionOverflow) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const double value = 6.022140857e23; auto comparison_stream = ComparisonStream(); @@ -1753,6 +1833,7 @@ TEST(ManipulatorLogFormatTest, SetW) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const int value = 77; auto comparison_stream = ComparisonStream(); @@ -1774,6 +1855,7 @@ TEST(ManipulatorLogFormatTest, Left) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const int value = -77; auto comparison_stream = ComparisonStream(); @@ -1791,6 +1873,7 @@ TEST(ManipulatorLogFormatTest, Right) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const int value = -77; auto comparison_stream = ComparisonStream(); @@ -1808,6 +1891,7 @@ TEST(ManipulatorLogFormatTest, Internal) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const int value = -77; auto comparison_stream = ComparisonStream(); @@ -1825,6 +1909,7 @@ TEST(ManipulatorLogFormatTest, SetFill) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); const int value = 77; auto comparison_stream = ComparisonStream(); @@ -1851,6 +1936,7 @@ TEST(ManipulatorLogFormatTest, FromCustom) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); FromCustomClass value; auto comparison_stream = ComparisonStream(); @@ -1873,6 +1959,7 @@ TEST(ManipulatorLogFormatTest, CustomClassStreamsNothing) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); StreamsNothing value; auto comparison_stream = ComparisonStream(); @@ -1900,6 +1987,7 @@ TEST(ManipulatorLogFormatTest, IOManipsDoNotAffectAbslStringify) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); PointPercentV p; @@ -1915,6 +2003,7 @@ TEST(StructuredLoggingOverflowTest, TruncatesStrings) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); // 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 @@ -1937,6 +2026,7 @@ TEST(StructuredLoggingOverflowTest, TruncatesWideStrings) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); // 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 @@ -1967,6 +2057,7 @@ TEST(StructuredLoggingOverflowTest, TruncatesInsertionOperators) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); // 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 @@ -2018,6 +2109,7 @@ // 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).Times(0); EXPECT_CALL(test_sink, Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( AllOf(SizeIs(longest_fit), Each(Eq('x'))))), @@ -2028,6 +2120,7 @@ } { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( AllOf(SizeIs(longest_fit - 1), Each(Eq('x'))))), @@ -2038,6 +2131,7 @@ } { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( AllOf(SizeIs(longest_fit - 2), Each(Eq('x'))))), @@ -2048,6 +2142,7 @@ } { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( AllOf(SizeIs(longest_fit - 3), Each(Eq('x'))))), @@ -2058,6 +2153,7 @@ } { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(AllOf(ENCODED_MESSAGE(HasOneStrAndOneLiteralThat( AllOf(SizeIs(longest_fit - 4), Each(Eq('x'))), @@ -2070,6 +2166,7 @@ } { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL( test_sink, Send(AllOf(ENCODED_MESSAGE(HasOneStrAndOneLiteralThat( @@ -2087,6 +2184,7 @@ // 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).Times(0); EXPECT_CALL(test_sink, Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( AllOf(SizeIs(longest_fit), Each(Eq('x'))))), @@ -2097,6 +2195,7 @@ } { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( AllOf(SizeIs(longest_fit - 1), Each(Eq('x'))))), @@ -2108,6 +2207,7 @@ } { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( AllOf(SizeIs(longest_fit - 2), Each(Eq('x'))))), @@ -2119,6 +2219,7 @@ } { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( AllOf(SizeIs(longest_fit - 3), Each(Eq('x'))))), @@ -2130,6 +2231,7 @@ } { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(AllOf(ENCODED_MESSAGE(HasOneStrThat( AllOf(SizeIs(longest_fit - 4), Each(Eq('x'))))), @@ -2143,6 +2245,7 @@ } { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL( test_sink, Send(AllOf(ENCODED_MESSAGE(HasTwoStrsThat(
diff --git a/absl/log/log_modifier_methods_test.cc b/absl/log/log_modifier_methods_test.cc index 4cee0c0..7893557 100644 --- a/absl/log/log_modifier_methods_test.cc +++ b/absl/log/log_modifier_methods_test.cc
@@ -60,6 +60,7 @@ TEST(TailCallsModifiesTest, AtLocationFileLine) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL( test_sink, @@ -89,6 +90,7 @@ TEST(TailCallsModifiesTest, NoPrefix) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(AllOf(Prefix(IsFalse()), TextPrefix(IsEmpty()), TextMessageWithPrefix(Eq("hello world"))))); @@ -99,6 +101,7 @@ TEST(TailCallsModifiesTest, NoPrefixNoMessageNoShirtNoShoesNoService) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(AllOf(Prefix(IsFalse()), TextPrefix(IsEmpty()), @@ -110,6 +113,7 @@ TEST(TailCallsModifiesTest, WithVerbosity) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(Verbosity(Eq(2)))); @@ -119,6 +123,7 @@ TEST(TailCallsModifiesTest, WithVerbosityNoVerbosity) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(Verbosity(Eq(absl::LogEntry::kNoVerbosityLevel)))); @@ -130,6 +135,7 @@ TEST(TailCallsModifiesTest, WithTimestamp) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(Timestamp(Eq(absl::UnixEpoch())))); @@ -139,6 +145,7 @@ TEST(TailCallsModifiesTest, WithThreadID) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(AllOf(ThreadID(Eq(absl::LogEntry::tid_t{1234}))))); @@ -157,6 +164,7 @@ } forwarding_sink; absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL( test_sink, @@ -185,6 +193,7 @@ TEST(TailCallsModifiesTest, WithPerror) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL( test_sink, @@ -211,6 +220,7 @@ { absl::ScopedMockLog test_sink( absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); auto do_log = [&test_sink] { LOG(QFATAL).ToSinkOnly(&test_sink.UseAsLocalSink()) << "hello world";
diff --git a/absl/log/log_streamer_test.cc b/absl/log/log_streamer_test.cc index 4fe88e9..f226fef 100644 --- a/absl/log/log_streamer_test.cc +++ b/absl/log/log_streamer_test.cc
@@ -66,6 +66,7 @@ TEST(LogStreamerTest, LogInfoStreamer) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL( test_sink, @@ -87,6 +88,7 @@ TEST(LogStreamerTest, LogWarningStreamer) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL( test_sink, @@ -109,6 +111,7 @@ TEST(LogStreamerTest, LogErrorStreamer) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL( test_sink, @@ -133,6 +136,7 @@ EXPECT_EXIT( { absl::ScopedMockLog test_sink; + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send) .Times(AnyNumber()) @@ -164,6 +168,7 @@ #ifdef NDEBUG TEST(LogStreamerTest, LogDebugFatalStreamer) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL( test_sink, @@ -188,6 +193,7 @@ EXPECT_EXIT( { absl::ScopedMockLog test_sink; + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send) .Times(AnyNumber()) @@ -218,6 +224,7 @@ TEST(LogStreamerTest, LogStreamer) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL( test_sink, @@ -244,6 +251,7 @@ EXPECT_EXIT( { absl::ScopedMockLog test_sink; + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send) .Times(AnyNumber()) @@ -275,6 +283,7 @@ TEST(LogStreamerTest, PassedByReference) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL( test_sink, @@ -291,6 +300,7 @@ TEST(LogStreamerTest, StoredAsLocal) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); auto streamer = absl::LogInfoStreamer("path/file.cc", 1234); WriteToStream("foo", &streamer.stream()); @@ -328,6 +338,7 @@ TEST(LogStreamerTest, LogsEmptyLine) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL(test_sink, Send(AllOf(SourceFilename(Eq("path/file.cc")), SourceLine(Eq(1234)), TextMessage(Eq("")), @@ -345,8 +356,7 @@ EXPECT_EXIT( { absl::ScopedMockLog test_sink; - - EXPECT_CALL(test_sink, Log) + EXPECT_CALL(test_sink, Send) .Times(AnyNumber()) .WillRepeatedly(DeathTestUnexpectedLogging()); @@ -368,6 +378,7 @@ TEST(LogStreamerTest, MoveConstruction) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); EXPECT_CALL( test_sink, @@ -389,6 +400,7 @@ TEST(LogStreamerTest, MoveAssignment) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); testing::InSequence seq; EXPECT_CALL( @@ -423,6 +435,7 @@ TEST(LogStreamerTest, CorrectDefaultFlags) { absl::ScopedMockLog test_sink(absl::MockLogDefault::kDisallowUnexpected); + EXPECT_CALL(test_sink, Send).Times(0); // The `boolalpha` and `showbase` flags should be set by default, to match // `LOG`.
diff --git a/absl/log/scoped_mock_log.h b/absl/log/scoped_mock_log.h index a383066..9700873 100644 --- a/absl/log/scoped_mock_log.h +++ b/absl/log/scoped_mock_log.h
@@ -160,7 +160,13 @@ // 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. + // forward the call to the `Log` mock. Tests using `Send` are advised to call + // + // `EXPECT_CALL(sink, Send).Times(0);` + // + // prior to specifying other expectations to suppress forwarding to `Log`. + // That way, unexpected calls show up as calls to `Send` with complete data + // and metadata for easier debugging. MOCK_METHOD(void, Send, (const absl::LogEntry&)); // Implements the mock method:
diff --git a/absl/log/structured_test.cc b/absl/log/structured_test.cc index 9fe0756..cde8199 100644 --- a/absl/log/structured_test.cc +++ b/absl/log/structured_test.cc
@@ -50,6 +50,7 @@ stream << LoggingDefaults << absl::LogAsLiteral(not_a_literal); absl::ScopedMockLog sink; + EXPECT_CALL(sink, Send).Times(0); EXPECT_CALL(sink, Send(AllOf(TextMessage(MatchesOstream(stream)), TextMessage(Eq("hello world")),
diff --git a/absl/memory/BUILD.bazel b/absl/memory/BUILD.bazel index d50a502..81e12fa 100644 --- a/absl/memory/BUILD.bazel +++ b/absl/memory/BUILD.bazel
@@ -14,6 +14,8 @@ # limitations under the License. # +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load( "//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS",
diff --git a/absl/meta/BUILD.bazel b/absl/meta/BUILD.bazel index d01cb8a..26468c6 100644 --- a/absl/meta/BUILD.bazel +++ b/absl/meta/BUILD.bazel
@@ -14,6 +14,8 @@ # limitations under the License. # +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load( "//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS", @@ -33,6 +35,57 @@ licenses(["notice"]) cc_library( + name = "constexpr_testing", + testonly = 1, + hdrs = ["internal/constexpr_testing.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl:__subpackages__", + ], + deps = [ + "//absl/base:config", + ], +) + +cc_test( + name = "constexpr_testing_test", + srcs = ["internal/constexpr_testing_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":constexpr_testing", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "requires", + hdrs = ["internal/requires.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl:__subpackages__", + ], + deps = [ + "//absl/base:config", + ], +) + +cc_test( + name = "requires_test", + srcs = ["internal/requires_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":requires", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_library( name = "type_traits", hdrs = ["type_traits.h"], copts = ABSL_DEFAULT_COPTS,
diff --git a/absl/meta/CMakeLists.txt b/absl/meta/CMakeLists.txt index d509114..c98c360 100644 --- a/absl/meta/CMakeLists.txt +++ b/absl/meta/CMakeLists.txt
@@ -16,6 +16,52 @@ absl_cc_library( NAME + constexpr_testing_internal + HDRS + "internal/constexpr_testing.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::config +) + +absl_cc_test( + NAME + constexpr_testing_test + SRCS + "internal/constexpr_testing_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::constexpr_testing_internal + GTest::gmock_main +) + +absl_cc_library( + NAME + requires_internal + HDRS + "internal/requires.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::config +) + +absl_cc_test( + NAME + requires_test + SRCS + "internal/requires_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::requires_internal + GTest::gmock_main +) + +absl_cc_library( + NAME type_traits HDRS "type_traits.h"
diff --git a/absl/meta/internal/constexpr_testing.h b/absl/meta/internal/constexpr_testing.h new file mode 100644 index 0000000..eddf64b --- /dev/null +++ b/absl/meta/internal/constexpr_testing.h
@@ -0,0 +1,73 @@ +// Copyright 2025 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_META_INTERNAL_CONSTEXPR_TESTING_H_ +#define ABSL_META_INTERNAL_CONSTEXPR_TESTING_H_ + +#include <type_traits> + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace meta_internal { + +// HasConstexprEvaluation([] { ... }) will evaluate to `true` if the +// lambda can be evaluated in a constant expression and `false` +// otherwise. +// The return type of the lambda is not relevant, as long as the whole +// evaluation works in a constant expression. +template <typename F> +constexpr bool HasConstexprEvaluation(F f); + +/// Implementation details below /// + +namespace internal_constexpr_evaluation { + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wuninitialized" +#endif +// This will give a constexpr instance of `F`. +// This works for captureless lambdas because they have no state and the copy +// constructor does not look at the input reference. +template <typename F> +constexpr F default_instance = default_instance<F>; +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +template <typename F> +constexpr std::integral_constant<bool, (default_instance<F>(), true)> Tester( + int) { + return {}; +} + +template <typename S> +constexpr std::false_type Tester(char) { + return {}; +} + +} // namespace internal_constexpr_evaluation + +template <typename F> +constexpr bool HasConstexprEvaluation(F) { + return internal_constexpr_evaluation::Tester<F>(0); +} + +} // namespace meta_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_META_INTERNAL_CONSTEXPR_TESTING_H_
diff --git a/absl/meta/internal/constexpr_testing_test.cc b/absl/meta/internal/constexpr_testing_test.cc new file mode 100644 index 0000000..50c8c53 --- /dev/null +++ b/absl/meta/internal/constexpr_testing_test.cc
@@ -0,0 +1,40 @@ +// Copyright 2025 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/meta/internal/constexpr_testing.h" + +#include <map> +#include <string_view> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" + +namespace { + +TEST(ConstexprTesting, Basic) { + using absl::meta_internal::HasConstexprEvaluation; + + EXPECT_TRUE(HasConstexprEvaluation([] {})); + static constexpr int const_global = 7; + EXPECT_TRUE(HasConstexprEvaluation([] { return const_global; })); + EXPECT_TRUE(HasConstexprEvaluation([] { return 0; })); + EXPECT_TRUE(HasConstexprEvaluation([] { return std::string_view{}; })); + + static int nonconst_global; + EXPECT_FALSE(HasConstexprEvaluation([] { return nonconst_global; })); + EXPECT_FALSE(HasConstexprEvaluation([] { std::abort(); })); + EXPECT_FALSE(HasConstexprEvaluation([] { return std::map<int, int>(); })); +} + +} // namespace
diff --git a/absl/meta/internal/requires.h b/absl/meta/internal/requires.h new file mode 100644 index 0000000..2166e99 --- /dev/null +++ b/absl/meta/internal/requires.h
@@ -0,0 +1,67 @@ +// 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. + +#ifndef ABSL_META_INTERNAL_REQUIRES_H_ +#define ABSL_META_INTERNAL_REQUIRES_H_ + +#include <type_traits> + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace meta_internal { + +// C++17 port of the C++20 `requires` expressions. +// It allows easy inline test of properties of types in template code. +// https://en.cppreference.com/w/cpp/language/constraints#Requires_expressions +// +// Example usage: +// +// if constexpr (Requires<T>([](auto&& x) -> decltype(x.foo()) {})) { +// // T has foo() +// return t.foo(); +// } else if constexpr (Requires<T>([](auto&& x) -> decltype(Bar(x)) {})) { +// // Can call Bar with T +// return Bar(t); +// } else if constexpr (Requires<T, U>( +// // Can test expression with multiple inputs +// [](auto&& x, auto&& y) -> decltype(x + y) {})) { +// return t + t2; +// } +// +// The `Requires` function takes a list of types and a generic lambda where all +// arguments are of type `auto&&`. The lambda is never actually invoked and the +// body must be empty. +// When used this way, `Requires` returns whether the expression inside +// `decltype` is well-formed, when the lambda parameters have the types that +// are specified by the corresponding template arguments. +// +// NOTE: C++17 does not allow lambdas in template parameters, which means that +// code like the following is _not_ valid in C++17: +// +// template <typename T, +// typename = std::enable_if_t<gtl::Requires<T>( +// [] (auto&& v) -> decltype(<expr>) {})>> +// +template <typename... T, typename F> +constexpr bool Requires(F) { + return std::is_invocable_v<F, T...>; +} + +} // namespace meta_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_META_INTERNAL_REQUIRES_H_
diff --git a/absl/meta/internal/requires_test.cc b/absl/meta/internal/requires_test.cc new file mode 100644 index 0000000..046d8f3 --- /dev/null +++ b/absl/meta/internal/requires_test.cc
@@ -0,0 +1,66 @@ +// 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/meta/internal/requires.h" + +#include <string> +#include <vector> + +#include "gtest/gtest.h" + +namespace { + +TEST(RequiresTest, SimpleLambdasWork) { + static_assert(absl::meta_internal::Requires([] {})); + static_assert(absl::meta_internal::Requires<int>([](auto&&) {})); + static_assert( + absl::meta_internal::Requires<int, char>([](auto&&, auto&&) {})); +} + +template <typename T> +inline constexpr bool has_cstr = + absl::meta_internal::Requires<T>([](auto&& x) -> decltype(x.c_str()) {}); + +template <typename T, typename U> +inline constexpr bool have_plus = absl::meta_internal::Requires<T, U>( + [](auto&& x, auto&& y) -> decltype(x + y) {}); + +TEST(RequiresTest, CanTestProperties) { + static_assert(has_cstr<std::string>); + static_assert(!has_cstr<std::vector<int>>); + + static_assert(have_plus<int, double>); + static_assert(have_plus<std::string, std::string>); + static_assert(!have_plus<std::string, double>); +} + +TEST(RequiresTest, WorksWithUnmovableTypes) { + struct S { + S(const S&) = delete; + int foo() { return 0; } + }; + static_assert( + absl::meta_internal::Requires<S>([](auto&& x) -> decltype(x.foo()) {})); + static_assert( + !absl::meta_internal::Requires<S>([](auto&& x) -> decltype(x.bar()) {})); +} + +TEST(RequiresTest, WorksWithArrays) { + static_assert( + absl::meta_internal::Requires<int[2]>([](auto&& x) -> decltype(x[1]) {})); + static_assert( + !absl::meta_internal::Requires<int[2]>([](auto&& x) -> decltype(-x) {})); +} + +} // namespace
diff --git a/absl/meta/type_traits.h b/absl/meta/type_traits.h index 02c1e63..59eb38b 100644 --- a/absl/meta/type_traits.h +++ b/absl/meta/type_traits.h
@@ -40,6 +40,7 @@ #include <string> #include <string_view> #include <type_traits> +#include <utility> #include <vector> #include "absl/base/attributes.h" @@ -168,6 +169,28 @@ using remove_cvref_t = typename remove_cvref<T>::type; #endif +#if defined(__cpp_lib_type_identity) && __cpp_lib_type_identity >= 201806L +template <typename T> +using type_identity = std::type_identity<T>; + +template <typename T> +using type_identity_t = std::type_identity_t<T>; +#else +// type_identity +// +// Back-fill of C++20's `std::type_identity`. +template <typename T> +struct type_identity { + typedef T type; +}; + +// type_identity_t +// +// Back-fill of C++20's `std::type_identity_t`. +template <typename T> +using type_identity_t = typename type_identity<T>::type; +#endif + namespace type_traits_internal { #if (defined(__cpp_lib_is_invocable) && __cpp_lib_is_invocable >= 201703L) || \ @@ -451,7 +474,7 @@ // Detects if a class's definition has declared itself to be an owner by // declaring -// using absl_internal_is_view = std::true_type; +// using absl_internal_is_view = std::false_type; // as a member. // Types that don't want either must either omit this declaration entirely, or // (if e.g. inheriting from a base class) define the member to something that @@ -479,6 +502,17 @@ template <typename T> struct IsOwner : IsOwnerImpl<T> {}; +// This allows incomplete types to be used for associative containers, and also +// expands the set of types we can handle to include std::pair. +template <typename T1, typename T2> +struct IsOwner<std::pair<T1, T2>> + : std::integral_constant< + bool, std::conditional_t<std::is_reference_v<T1>, std::false_type, + IsOwner<std::remove_cv_t<T1>>>::value && + std::conditional_t<std::is_reference_v<T2>, std::false_type, + IsOwner<std::remove_cv_t<T2>>>::value> { +}; + template <typename T, typename Traits, typename Alloc> struct IsOwner<std::basic_string<T, Traits, Alloc>> : std::true_type {}; @@ -513,6 +547,13 @@ struct IsView : std::integral_constant<bool, std::is_pointer<T>::value || IsViewImpl<T>::value> {}; +// This allows incomplete types to be used for associative containers, and also +// expands the set of types we can handle to include std::pair. +template <typename T1, typename T2> +struct IsView<std::pair<T1, T2>> + : std::integral_constant<bool, IsView<std::remove_cv_t<T1>>::value && + IsView<std::remove_cv_t<T2>>::value> {}; + template <typename Char, typename Traits> struct IsView<std::basic_string_view<Char, Traits>> : std::true_type {};
diff --git a/absl/meta/type_traits_test.cc b/absl/meta/type_traits_test.cc index 3d55a00..9a8262d 100644 --- a/absl/meta/type_traits_test.cc +++ b/absl/meta/type_traits_test.cc
@@ -36,6 +36,9 @@ absl::conjunction<absl::type_traits_internal::IsOwner<T>, absl::negation<absl::type_traits_internal::IsView<T>>>; +static_assert( + IsOwnerAndNotView<std::pair<std::vector<int>, std::string>>::value, + "pair of owners is an owner, not a view"); static_assert(IsOwnerAndNotView<std::vector<int>>::value, "vector is an owner, not a view"); static_assert(IsOwnerAndNotView<std::string>::value, @@ -134,6 +137,17 @@ int[2]>::value)); } +TEST(TypeTraitsTest, TestTypeIdentity) { + EXPECT_TRUE((std::is_same_v<typename absl::type_identity<int>::type, int>)); + EXPECT_TRUE((std::is_same_v<absl::type_identity_t<int>, int>)); + EXPECT_TRUE((std::is_same_v<typename absl::type_identity<int&>::type, int&>)); + EXPECT_TRUE((std::is_same_v<absl::type_identity_t<int&>, int&>)); + + EXPECT_FALSE( + (std::is_same_v<typename absl::type_identity<int64_t>::type, int32_t>)); + EXPECT_FALSE((std::is_same_v<absl::type_identity_t<int64_t>, int32_t>)); +} + struct TypeA {}; struct TypeB {}; struct TypeC {};
diff --git a/absl/numeric/BUILD.bazel b/absl/numeric/BUILD.bazel index f455d1e..edfe6b6 100644 --- a/absl/numeric/BUILD.bazel +++ b/absl/numeric/BUILD.bazel
@@ -12,6 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load( "//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS",
diff --git a/absl/numeric/bits_test.cc b/absl/numeric/bits_test.cc index 3b71ccc..e2c6409 100644 --- a/absl/numeric/bits_test.cc +++ b/absl/numeric/bits_test.cc
@@ -27,15 +27,36 @@ namespace { template <typename IntT> +class UnsignedIntegerTypesTest : public ::testing::Test {}; +template <typename IntT> class IntegerTypesTest : public ::testing::Test {}; +using UnsignedIntegerTypes = + ::testing::Types<uint8_t, uint16_t, uint32_t, uint64_t>; using OneByteIntegerTypes = ::testing::Types< unsigned char, uint8_t >; +TYPED_TEST_SUITE(UnsignedIntegerTypesTest, UnsignedIntegerTypes); TYPED_TEST_SUITE(IntegerTypesTest, OneByteIntegerTypes); +TYPED_TEST(UnsignedIntegerTypesTest, ReturnTypes) { + using UIntType = TypeParam; + + static_assert(std::is_same_v<decltype(byteswap(UIntType{0})), UIntType>); + static_assert(std::is_same_v<decltype(rotl(UIntType{0}, 0)), UIntType>); + static_assert(std::is_same_v<decltype(rotr(UIntType{0}, 0)), UIntType>); + static_assert(std::is_same_v<decltype(countl_zero(UIntType{0})), int>); + static_assert(std::is_same_v<decltype(countl_one(UIntType{0})), int>); + static_assert(std::is_same_v<decltype(countr_zero(UIntType{0})), int>); + static_assert(std::is_same_v<decltype(countr_one(UIntType{0})), int>); + static_assert(std::is_same_v<decltype(popcount(UIntType{0})), int>); + static_assert(std::is_same_v<decltype(bit_ceil(UIntType{0})), UIntType>); + static_assert(std::is_same_v<decltype(bit_floor(UIntType{0})), UIntType>); + static_assert(std::is_same_v<decltype(bit_width(UIntType{0})), int>); +} + TYPED_TEST(IntegerTypesTest, HandlesTypes) { using UIntType = TypeParam; @@ -130,6 +151,9 @@ EXPECT_EQ(rotl(uint32_t{0x12345678UL}, -4), uint32_t{0x81234567UL}); EXPECT_EQ(rotl(uint64_t{0x12345678ABCDEF01ULL}, -4), uint64_t{0x112345678ABCDEF0ULL}); + + EXPECT_EQ(rotl(uint32_t{1234}, std::numeric_limits<int>::min()), + uint32_t{1234}); } TEST(Rotate, Right) { @@ -169,6 +193,9 @@ EXPECT_EQ(rotr(uint32_t{0x12345678UL}, -4), uint32_t{0x23456781UL}); EXPECT_EQ(rotr(uint64_t{0x12345678ABCDEF01ULL}, -4), uint64_t{0x2345678ABCDEF011ULL}); + + EXPECT_EQ(rotl(uint32_t{1234}, std::numeric_limits<int>::min()), + uint32_t{1234}); } TEST(Rotate, Symmetry) {
diff --git a/absl/numeric/int128.h b/absl/numeric/int128.h index ae736b2..32603b0 100644 --- a/absl/numeric/int128.h +++ b/absl/numeric/int128.h
@@ -164,9 +164,9 @@ constexpr explicit operator __int128() const; constexpr explicit operator unsigned __int128() const; #endif // ABSL_HAVE_INTRINSIC_INT128 - explicit operator float() const; - explicit operator double() const; - explicit operator long double() const; + constexpr explicit operator float() const; + constexpr explicit operator double() const; + constexpr explicit operator long double() const; // Trivial copy constructor, assignment operator and destructor. @@ -357,14 +357,18 @@ constexpr int128(unsigned long v); // NOLINT(runtime/int) constexpr int128(long long v); // NOLINT(runtime/int) constexpr int128(unsigned long long v); // NOLINT(runtime/int) + constexpr explicit int128(uint128 v); #ifdef ABSL_HAVE_INTRINSIC_INT128 constexpr int128(__int128 v); // NOLINT(runtime/explicit) constexpr explicit int128(unsigned __int128 v); -#endif // ABSL_HAVE_INTRINSIC_INT128 - constexpr explicit int128(uint128 v); + constexpr explicit int128(float v); + constexpr explicit int128(double v); + constexpr explicit int128(long double v); +#else explicit int128(float v); explicit int128(double v); explicit int128(long double v); +#endif // ABSL_HAVE_INTRINSIC_INT128 // Assignment operators from arithmetic types int128& operator=(int v); @@ -401,9 +405,9 @@ constexpr explicit operator __int128() const; constexpr explicit operator unsigned __int128() const; #endif // ABSL_HAVE_INTRINSIC_INT128 - explicit operator float() const; - explicit operator double() const; - explicit operator long double() const; + constexpr explicit operator float() const; + constexpr explicit operator double() const; + constexpr explicit operator long double() const; // Trivial copy constructor, assignment operator and destructor. @@ -609,9 +613,15 @@ constexpr uint128 operator>>(uint128 lhs, int amount); constexpr uint128 operator+(uint128 lhs, uint128 rhs); constexpr uint128 operator-(uint128 lhs, uint128 rhs); +#if defined(ABSL_HAVE_INTRINSIC_INT128) +constexpr uint128 operator*(uint128 lhs, uint128 rhs); +constexpr uint128 operator/(uint128 lhs, uint128 rhs); +constexpr uint128 operator%(uint128 lhs, uint128 rhs); +#else // ABSL_HAVE_INTRINSIC_INT128 uint128 operator*(uint128 lhs, uint128 rhs); uint128 operator/(uint128 lhs, uint128 rhs); uint128 operator%(uint128 lhs, uint128 rhs); +#endif // ABSL_HAVE_INTRINSIC_INT128 inline uint128& uint128::operator<<=(int amount) { *this = *this << amount; @@ -788,18 +798,18 @@ // Conversion operators to floating point types. -inline uint128::operator float() const { +constexpr uint128::operator float() const { // Note: This method might return Inf. constexpr float pow_2_64 = 18446744073709551616.0f; return static_cast<float>(lo_) + static_cast<float>(hi_) * pow_2_64; } -inline uint128::operator double() const { +constexpr uint128::operator double() const { constexpr double pow_2_64 = 18446744073709551616.0; return static_cast<double>(lo_) + static_cast<double>(hi_) * pow_2_64; } -inline uint128::operator long double() const { +constexpr uint128::operator long double() const { constexpr long double pow_2_64 = 18446744073709551616.0L; return static_cast<long double>(lo_) + static_cast<long double>(hi_) * pow_2_64; @@ -1021,19 +1031,15 @@ #endif } +#if !defined(ABSL_HAVE_INTRINSIC_INT128) inline uint128 operator*(uint128 lhs, uint128 rhs) { -#if defined(ABSL_HAVE_INTRINSIC_INT128) - // TODO(strel) Remove once alignment issues are resolved and unsigned __int128 - // can be used for uint128 storage. - return static_cast<unsigned __int128>(lhs) * - static_cast<unsigned __int128>(rhs); -#elif defined(_MSC_VER) && defined(_M_X64) && !defined(_M_ARM64EC) +#if defined(_MSC_VER) && defined(_M_X64) && !defined(_M_ARM64EC) uint64_t carry; uint64_t low = _umul128(Uint128Low64(lhs), Uint128Low64(rhs), &carry); return MakeUint128(Uint128Low64(lhs) * Uint128High64(rhs) + Uint128High64(lhs) * Uint128Low64(rhs) + carry, low); -#else // ABSL_HAVE_INTRINSIC128 +#else // _MSC_VER uint64_t a32 = Uint128Low64(lhs) >> 32; uint64_t a00 = Uint128Low64(lhs) & 0xffffffff; uint64_t b32 = Uint128Low64(rhs) >> 32; @@ -1045,16 +1051,24 @@ result += uint128(a32 * b00) << 32; result += uint128(a00 * b32) << 32; return result; -#endif // ABSL_HAVE_INTRINSIC128 +#endif // _MSC_VER } +#endif // ABSL_HAVE_INTRINSIC_INT128 #if defined(ABSL_HAVE_INTRINSIC_INT128) -inline uint128 operator/(uint128 lhs, uint128 rhs) { +constexpr uint128 operator*(uint128 lhs, uint128 rhs) { + // TODO(strel) Remove once alignment issues are resolved and unsigned __int128 + // can be used for uint128 storage. + return static_cast<unsigned __int128>(lhs) * + static_cast<unsigned __int128>(rhs); +} + +constexpr uint128 operator/(uint128 lhs, uint128 rhs) { return static_cast<unsigned __int128>(lhs) / static_cast<unsigned __int128>(rhs); } -inline uint128 operator%(uint128 lhs, uint128 rhs) { +constexpr uint128 operator%(uint128 lhs, uint128 rhs) { return static_cast<unsigned __int128>(lhs) % static_cast<unsigned __int128>(rhs); } @@ -1112,9 +1126,15 @@ constexpr int128 operator-(int128 v); constexpr int128 operator+(int128 lhs, int128 rhs); constexpr int128 operator-(int128 lhs, int128 rhs); +#if defined(ABSL_HAVE_INTRINSIC_INT128) +constexpr int128 operator*(int128 lhs, int128 rhs); +constexpr int128 operator/(int128 lhs, int128 rhs); +constexpr int128 operator%(int128 lhs, int128 rhs); +#else int128 operator*(int128 lhs, int128 rhs); int128 operator/(int128 lhs, int128 rhs); int128 operator%(int128 lhs, int128 rhs); +#endif // ABSL_HAVE_INTRINSIC_INT128 constexpr int128 operator|(int128 lhs, int128 rhs); constexpr int128 operator&(int128 lhs, int128 rhs); constexpr int128 operator^(int128 lhs, int128 rhs);
diff --git a/absl/numeric/int128_have_intrinsic.inc b/absl/numeric/int128_have_intrinsic.inc index 216115a..dea1d21 100644 --- a/absl/numeric/int128_have_intrinsic.inc +++ b/absl/numeric/int128_have_intrinsic.inc
@@ -73,17 +73,11 @@ constexpr int128::int128(unsigned __int128 v) : v_{static_cast<__int128>(v)} {} -inline int128::int128(float v) { - v_ = static_cast<__int128>(v); -} +constexpr int128::int128(float v) : v_{static_cast<__int128>(v)} {} -inline int128::int128(double v) { - v_ = static_cast<__int128>(v); -} +constexpr int128::int128(double v) : v_{static_cast<__int128>(v)} {} -inline int128::int128(long double v) { - v_ = static_cast<__int128>(v); -} +constexpr int128::int128(long double v) : v_{static_cast<__int128>(v)} {} constexpr int128::int128(uint128 v) : v_{static_cast<__int128>(v)} {} @@ -119,9 +113,7 @@ return static_cast<unsigned short>(v_); // NOLINT(runtime/int) } -constexpr int128::operator int() const { - return static_cast<int>(v_); -} +constexpr int128::operator int() const { return static_cast<int>(v_); } constexpr int128::operator unsigned int() const { return static_cast<unsigned int>(v_); @@ -153,17 +145,17 @@ // conversions. In that case, we do the conversion with a similar implementation // to the conversion operators in int128_no_intrinsic.inc. #if defined(__clang__) && !defined(__ppc64__) -inline int128::operator float() const { return static_cast<float>(v_); } +constexpr int128::operator float() const { return static_cast<float>(v_); } -inline int128::operator double() const { return static_cast<double>(v_); } +constexpr int128::operator double() const { return static_cast<double>(v_); } -inline int128::operator long double() const { +constexpr int128::operator long double() const { return static_cast<long double>(v_); } -#else // Clang on PowerPC +#else // Clang on PowerPC -inline int128::operator float() const { +constexpr int128::operator float() const { // We must convert the absolute value and then negate as needed, because // floating point types are typically sign-magnitude. Otherwise, the // difference between the high and low 64 bits when interpreted as two's @@ -177,7 +169,7 @@ static_cast<float>(Int128High64(*this)) * pow_2_64; } -inline int128::operator double() const { +constexpr int128::operator double() const { // See comment in int128::operator float() above. constexpr double pow_2_64 = 18446744073709551616.0; return v_ < 0 && *this != Int128Min() @@ -186,7 +178,7 @@ static_cast<double>(Int128High64(*this)) * pow_2_64; } -inline int128::operator long double() const { +constexpr int128::operator long double() const { // See comment in int128::operator float() above. constexpr long double pow_2_64 = 18446744073709551616.0L; return v_ < 0 && *this != Int128Min() @@ -254,17 +246,19 @@ return static_cast<__int128>(lhs) - static_cast<__int128>(rhs); } -inline int128 operator*(int128 lhs, int128 rhs) { +#if defined(ABSL_HAVE_INTRINSIC_INT128) +constexpr int128 operator*(int128 lhs, int128 rhs) { return static_cast<__int128>(lhs) * static_cast<__int128>(rhs); } -inline int128 operator/(int128 lhs, int128 rhs) { +constexpr int128 operator/(int128 lhs, int128 rhs) { return static_cast<__int128>(lhs) / static_cast<__int128>(rhs); } -inline int128 operator%(int128 lhs, int128 rhs) { +constexpr int128 operator%(int128 lhs, int128 rhs) { return static_cast<__int128>(lhs) % static_cast<__int128>(rhs); } +#endif // ABSL_HAVE_INTRINSIC_INT128 inline int128 int128::operator++(int) { int128 tmp(*this);
diff --git a/absl/numeric/int128_no_intrinsic.inc b/absl/numeric/int128_no_intrinsic.inc index a7cdcea..48bec2c 100644 --- a/absl/numeric/int128_no_intrinsic.inc +++ b/absl/numeric/int128_no_intrinsic.inc
@@ -132,7 +132,7 @@ return static_cast<unsigned long long>(lo_); // NOLINT(runtime/int) } -inline int128::operator float() const { +constexpr int128::operator float() const { // We must convert the absolute value and then negate as needed, because // floating point types are typically sign-magnitude. Otherwise, the // difference between the high and low 64 bits when interpreted as two's @@ -142,20 +142,18 @@ constexpr float pow_2_64 = 18446744073709551616.0f; return hi_ < 0 && *this != Int128Min() ? -static_cast<float>(-*this) - : static_cast<float>(lo_) + - static_cast<float>(hi_) * pow_2_64; + : static_cast<float>(lo_) + static_cast<float>(hi_) * pow_2_64; } -inline int128::operator double() const { +constexpr int128::operator double() const { // See comment in int128::operator float() above. constexpr double pow_2_64 = 18446744073709551616.0; return hi_ < 0 && *this != Int128Min() ? -static_cast<double>(-*this) - : static_cast<double>(lo_) + - static_cast<double>(hi_) * pow_2_64; + : static_cast<double>(lo_) + static_cast<double>(hi_) * pow_2_64; } -inline int128::operator long double() const { +constexpr int128::operator long double() const { // See comment in int128::operator float() above. constexpr long double pow_2_64 = 18446744073709551616.0L; return hi_ < 0 && *this != Int128Min()
diff --git a/absl/numeric/int128_test.cc b/absl/numeric/int128_test.cc index 13a0e7f..77ee63c 100644 --- a/absl/numeric/int128_test.cc +++ b/absl/numeric/int128_test.cc
@@ -350,7 +350,7 @@ c = a * b; EXPECT_EQ(absl::MakeUint128(0x530EDA741C71D4C3, 0xBF25975319080000), c); EXPECT_EQ(0, c - b * a); - EXPECT_EQ(a*a - b*b, (a+b) * (a-b)); + EXPECT_EQ(a * a - b * b, (a + b) * (a - b)); // Verified with dc. a = absl::MakeUint128(0x0123456789abcdef, 0xfedcba9876543210); @@ -358,7 +358,7 @@ c = a * b; EXPECT_EQ(absl::MakeUint128(0x97a87f4f261ba3f2, 0x342d0bbf48948200), c); EXPECT_EQ(0, c - b * a); - EXPECT_EQ(a*a - b*b, (a+b) * (a-b)); + EXPECT_EQ(a * a - b * b, (a + b) * (a - b)); } TEST(Uint128, AliasTests) { @@ -462,6 +462,26 @@ EXPECT_EQ(zero, absl::uint128(0)); EXPECT_EQ(one, absl::uint128(1)); EXPECT_EQ(minus_two, absl::MakeUint128(-1, -2)); + + constexpr double f = static_cast<float>(absl::uint128(123)); + EXPECT_EQ(f, 123.0f); + + constexpr double d = static_cast<double>(absl::uint128(123)); + EXPECT_EQ(d, 123.0); + + constexpr long double ld = static_cast<long double>(absl::uint128(123)); + EXPECT_EQ(ld, 123.0); + +#ifdef ABSL_HAVE_INTRINSIC_INT128 + constexpr absl::uint128 division = absl::uint128(10) / absl::uint128(2); + EXPECT_EQ(division, absl::uint128(5)); + + constexpr absl::uint128 modulus = absl::int128(10) % absl::int128(3); + EXPECT_EQ(modulus, absl::uint128(1)); + + constexpr absl::uint128 multiplication = absl::uint128(10) * absl::uint128(3); + EXPECT_EQ(multiplication, absl::uint128(30)); +#endif // ABSL_HAVE_INTRINSIC_INT128 } TEST(Uint128, NumericLimitsTest) { @@ -522,7 +542,6 @@ EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly(values)); } - TEST(Int128Uint128, ConversionTest) { absl::int128 nonnegative_signed_values[] = { 0, @@ -540,8 +559,7 @@ } absl::int128 negative_values[] = { - -1, -0x1234567890abcdef, - absl::MakeInt128(-0x5544332211ffeedd, 0), + -1, -0x1234567890abcdef, absl::MakeInt128(-0x5544332211ffeedd, 0), -absl::MakeInt128(0x76543210fedcba98, 0xabcdef0123456789)}; for (absl::int128 value : negative_values) { EXPECT_EQ(absl::uint128(-value), -absl::uint128(value)); @@ -769,6 +787,35 @@ EXPECT_EQ(minus_two, absl::MakeInt128(-1, -2)); EXPECT_GT(max, one); EXPECT_LT(min, minus_two); + + constexpr double f = static_cast<float>(absl::int128(123)); + EXPECT_EQ(f, 123.0f); + + constexpr double d = static_cast<double>(absl::int128(123)); + EXPECT_EQ(d, 123.0); + + constexpr long double ld = static_cast<long double>(absl::int128(123)); + EXPECT_EQ(ld, 123.0); + +#ifdef ABSL_HAVE_INTRINSIC_INT128 + constexpr absl::int128 f_int128(static_cast<float>(123.0)); + EXPECT_EQ(f_int128, absl::int128(123)); + + constexpr absl::int128 d_int128(static_cast<double>(123.0)); + EXPECT_EQ(d_int128, absl::int128(123)); + + constexpr absl::int128 ld_int128(static_cast<long double>(123.0)); + EXPECT_EQ(ld_int128, absl::int128(123)); + + constexpr absl::int128 division = absl::int128(10) / absl::int128(2); + EXPECT_EQ(division, absl::int128(5)); + + constexpr absl::int128 modulus = absl::int128(10) % absl::int128(3); + EXPECT_EQ(modulus, absl::int128(1)); + + constexpr absl::int128 multiplication = absl::int128(10) * absl::int128(3); + EXPECT_EQ(multiplication, absl::int128(30)); +#endif // ABSL_HAVE_INTRINSIC_INT128 } TEST(Int128, ComparisonTest) {
diff --git a/absl/numeric/internal/bits.h b/absl/numeric/internal/bits.h index e1d18b8..e681544 100644 --- a/absl/numeric/internal/bits.h +++ b/absl/numeric/internal/bits.h
@@ -77,8 +77,28 @@ static_assert(IsPowerOf2(std::numeric_limits<T>::digits), "T must have a power-of-2 size"); - return static_cast<T>(x >> (s & (std::numeric_limits<T>::digits - 1))) | - static_cast<T>(x << ((-s) & (std::numeric_limits<T>::digits - 1))); + // Rotate by s mod the number of digits to avoid unnecessary rotations. + // + // A negative s represents a left rotation instead of a right rotation. + // We compute it as an equivalent complementary right rotation by leveraging + // its two's complement representation. + // + // For example, suppose we rotate a 3-bit number by -2. + // In that case: + // * s = 0b11111111111111111111111111111110 + // * n = 8 + // * r = (0b11111111111111111111111111111110 & 0b111) = 0b110 + // + // Instead of rotating by 2 to the left, we rotate by 6 to the right, which + // is equivalent. + const int n = std::numeric_limits<T>::digits; + const int r = s & (n - 1); + + if (r == 0) { + return x; + } else { + return (x >> r) | (x << (n - r)); + } } template <class T> @@ -88,8 +108,16 @@ static_assert(IsPowerOf2(std::numeric_limits<T>::digits), "T must have a power-of-2 size"); - return static_cast<T>(x << (s & (std::numeric_limits<T>::digits - 1))) | - static_cast<T>(x >> ((-s) & (std::numeric_limits<T>::digits - 1))); + // Rotate by s mod the number of digits to avoid unnecessary rotations. + // See comment in RotateRight for a detailed explanation of the logic below. + const int n = std::numeric_limits<T>::digits; + const int r = s & (n - 1); + + if (r == 0) { + return x; + } else { + return (x << r) | (x >> (n - r)); + } } ABSL_ATTRIBUTE_ALWAYS_INLINE ABSL_INTERNAL_CONSTEXPR_POPCOUNT inline int
diff --git a/absl/profiling/BUILD.bazel b/absl/profiling/BUILD.bazel index ee4800d..00571b2 100644 --- a/absl/profiling/BUILD.bazel +++ b/absl/profiling/BUILD.bazel
@@ -12,6 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load( "//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS", @@ -49,6 +52,7 @@ cc_test( name = "sample_recorder_test", srcs = ["internal/sample_recorder_test.cc"], + copts = ABSL_TEST_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, tags = [ "no_test_wasm", @@ -69,6 +73,7 @@ name = "exponential_biased", srcs = ["internal/exponential_biased.cc"], hdrs = ["internal/exponential_biased.h"], + copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, visibility = [ "//absl:__subpackages__", @@ -139,3 +144,45 @@ "@google_benchmark//:benchmark_main", ], ) + +cc_library( + name = "profile_builder", + srcs = ["internal/profile_builder.cc"], + hdrs = ["internal/profile_builder.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl:__subpackages__", + ], + deps = [ + "//absl/base", + "//absl/base:config", + "//absl/base:raw_logging_internal", + "//absl/container:btree", + "//absl/container:flat_hash_map", + "//absl/strings", + "//absl/strings:str_format", + "//absl/types:span", + ], +) + +cc_library( + name = "hashtable", + srcs = ["hashtable.cc"], + hdrs = ["hashtable.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl:__subpackages__", + ], + deps = [ + ":profile_builder", + "//absl/base:config", + "//absl/container:hashtablez_sampler", + "//absl/status:statusor", + "//absl/strings", + "//absl/strings:string_view", + "//absl/time", + "//absl/types:span", + ], +)
diff --git a/absl/profiling/CMakeLists.txt b/absl/profiling/CMakeLists.txt index 84b8b3b..6441dae 100644 --- a/absl/profiling/CMakeLists.txt +++ b/absl/profiling/CMakeLists.txt
@@ -92,3 +92,41 @@ GTest::gmock_main ) +# Internal-only target, do not depend on directly +absl_cc_library( + NAME + profile_builder + HDRS + "internal/profile_builder.h" + SRCS + "internal/profile_builder.cc" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::config + absl::core_headers + absl::raw_logging_internal + absl::flat_hash_map + absl::btree + absl::strings + absl::str_format + absl::span +) + +absl_cc_library( + NAME + hashtable_profiler + HDRS + "hashtable.h" + SRCS + "hashtable.cc" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::profile_builder + absl::config + absl::core_headers + absl::strings + absl::span + absl::hashtablez_sampler +)
diff --git a/absl/profiling/hashtable.cc b/absl/profiling/hashtable.cc new file mode 100644 index 0000000..7c1a839 --- /dev/null +++ b/absl/profiling/hashtable.cc
@@ -0,0 +1,135 @@ +// Copyright 2025 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/profiling/hashtable.h" + +#include <atomic> +#include <cstddef> +#include <cstdint> +#include <string> +#include <utility> +#include <vector> + +#include "absl/base/config.h" +#include "absl/container/internal/hashtablez_sampler.h" +#include "absl/profiling/internal/profile_builder.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" +#include "absl/time/clock.h" +#include "absl/time/time.h" +#include "absl/types/span.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +StatusOr<std::string> MarshalHashtableProfile() { + return debugging_internal::MarshalHashtableProfile( + container_internal::GlobalHashtablezSampler(), Now()); +} + +namespace debugging_internal { + +static void DroppedHashtableSample() {} + +StatusOr<std::string> MarshalHashtableProfile( + container_internal::HashtablezSampler& sampler, Time now) { + static constexpr absl::string_view kDropFrames = + "(::)?absl::container_internal::.*|" + "(::)?absl::(flat|node)_hash_(map|set).*"; + + ProfileBuilder builder; + StringId drop_frames_id = builder.InternString(kDropFrames); + builder.set_drop_frames_id(drop_frames_id); + builder.AddSampleType(builder.InternString("capacity"), + builder.InternString("count")); + builder.set_default_sample_type_id(builder.InternString("capacity")); + + const auto capacity_id = builder.InternString("capacity"); + const auto size_id = builder.InternString("size"); + const auto num_erases_id = builder.InternString("num_erases"); + const auto num_insert_hits_id = builder.InternString("num_insert_hits"); + const auto num_rehashes_id = builder.InternString("num_rehashes"); + const auto max_probe_length_id = builder.InternString("max_probe_length"); + const auto total_probe_length_id = builder.InternString("total_probe_length"); + const auto stuck_bits_id = builder.InternString("stuck_bits"); + const auto inline_element_size_id = + builder.InternString("inline_element_size"); + const auto key_size_id = builder.InternString("key_size"); + const auto value_size_id = builder.InternString("value_size"); + const auto soo_capacity_id = builder.InternString("soo_capacity"); + const auto table_age_id = builder.InternString("table_age"); + const auto max_reserve_id = builder.InternString("max_reserve"); + + size_t dropped = + sampler.Iterate([&](const container_internal::HashtablezInfo& info) { + const size_t capacity = info.capacity.load(std::memory_order_relaxed); + std::vector<std::pair<StringId, int64_t>> labels; + + auto add_label = [&](StringId tag, uint64_t value) { + if (value == 0) { + return; + } + labels.emplace_back(tag, static_cast<int64_t>(value)); + }; + + add_label(capacity_id, capacity); + add_label(size_id, info.size.load(std::memory_order_relaxed)); + add_label(num_erases_id, + info.num_erases.load(std::memory_order_relaxed)); + // TODO(b/436909492): Revisit whether this value is useful. + add_label(num_insert_hits_id, + info.num_insert_hits.load(std::memory_order_relaxed)); + add_label(num_rehashes_id, + info.num_rehashes.load(std::memory_order_relaxed)); + add_label(max_probe_length_id, + info.max_probe_length.load(std::memory_order_relaxed)); + add_label(total_probe_length_id, + info.total_probe_length.load(std::memory_order_relaxed)); + add_label(stuck_bits_id, + (info.hashes_bitwise_and.load(std::memory_order_relaxed) | + ~info.hashes_bitwise_or.load(std::memory_order_relaxed))); + add_label(inline_element_size_id, info.inline_element_size); + add_label(key_size_id, info.key_size); + add_label(value_size_id, info.value_size); + add_label(soo_capacity_id, info.soo_capacity); + add_label( + table_age_id, + static_cast<uint64_t>(ToInt64Microseconds(now - info.create_time))); + add_label(max_reserve_id, + info.max_reserve.load(std::memory_order_relaxed)); + builder.AddSample(static_cast<int64_t>(capacity) * info.weight, + MakeSpan(info.stack, info.depth), labels); + }); + + if (dropped > 0) { + // If we dropped samples, we don't have information for them, including + // their sizes. The non-zero weight allows it to be noticed in the profile + // and examined more closely. + // + // We compensate for the fixup done by AddSample by adjusting the address + // here. + const void* kFakeStack[] = { + absl::bit_cast<void*>( + reinterpret_cast<uintptr_t>(&DroppedHashtableSample)+1)}; + builder.AddSample(static_cast<int64_t>(dropped), kFakeStack, {}); + } + builder.AddCurrentMappings(); + return std::move(builder).Emit(); +} + +} // namespace debugging_internal +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/profiling/hashtable.h b/absl/profiling/hashtable.h new file mode 100644 index 0000000..9e490dc --- /dev/null +++ b/absl/profiling/hashtable.h
@@ -0,0 +1,40 @@ +// Copyright 2025 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_PROFILING_HASHTABLE_H_ +#define ABSL_PROFILING_HASHTABLE_H_ + +#include <cstdint> +#include <string> + +#include "absl/container/internal/hashtablez_sampler.h" +#include "absl/status/statusor.h" +#include "absl/strings/string_view.h" +#include "absl/time/time.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN + +absl::StatusOr<std::string> MarshalHashtableProfile(); + +namespace debugging_internal { + +absl::StatusOr<std::string> MarshalHashtableProfile( + container_internal::HashtablezSampler& sampler, absl::Time now); + +} // namespace debugging_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_PROFILING_HASHTABLE_H_
diff --git a/absl/profiling/internal/profile_builder.cc b/absl/profiling/internal/profile_builder.cc new file mode 100644 index 0000000..1ca0d3b --- /dev/null +++ b/absl/profiling/internal/profile_builder.cc
@@ -0,0 +1,463 @@ +// Copyright 2025 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/profiling/internal/profile_builder.h" + +#ifdef __linux__ +#include <elf.h> +#include <link.h> +#endif // __linux__ + +#include <cassert> +#include <cstdint> +#include <cstring> +#include <string> +#include <utility> +#include <vector> + +#include "absl/base/casts.h" +#include "absl/base/config.h" +#include "absl/base/internal/raw_logging.h" +#include "absl/strings/escaping.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/str_format.h" +#include "absl/types/span.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace debugging_internal { + +namespace { + +// This file contains a simplified implementation of the pprof profile builder, +// which avoids a dependency on protobuf. +// +// The canonical profile proto definition is at +// https://github.com/google/pprof/blob/master/proto/profile.proto +// +// Wire-format encoding is a simple sequence of (tag, value) pairs. The tag +// is a varint-encoded integer, where the low 3 bits are the wire type, and the +// high bits are the field number. +// +// For the fields we care about, we'll be using the following wire types: +// +// Wire Type 0: Varint-encoded integer. +// Wire Type 2: Length-delimited. Used for strings and sub-messages. +enum class WireType { + kVarint = 0, + kLengthDelimited = 2, +}; + +#ifdef __linux__ +// Returns the Phdr of the first segment of the given type. +const ElfW(Phdr) * GetFirstSegment(const dl_phdr_info* const info, + const ElfW(Word) segment_type) { + for (int i = 0; i < info->dlpi_phnum; ++i) { + if (info->dlpi_phdr[i].p_type == segment_type) { + return &info->dlpi_phdr[i]; + } + } + return nullptr; +} + +// Return DT_SONAME for the given image. If there is no PT_DYNAMIC or if +// PT_DYNAMIC does not contain DT_SONAME, return nullptr. +static const char* GetSoName(const dl_phdr_info* const info) { + const ElfW(Phdr)* const pt_dynamic = GetFirstSegment(info, PT_DYNAMIC); + if (pt_dynamic == nullptr) { + return nullptr; + } + const ElfW(Dyn)* dyn = + reinterpret_cast<ElfW(Dyn)*>(info->dlpi_addr + pt_dynamic->p_vaddr); + const ElfW(Dyn)* dt_strtab = nullptr; + const ElfW(Dyn)* dt_strsz = nullptr; + const ElfW(Dyn)* dt_soname = nullptr; + for (; dyn->d_tag != DT_NULL; ++dyn) { + if (dyn->d_tag == DT_SONAME) { + dt_soname = dyn; + } else if (dyn->d_tag == DT_STRTAB) { + dt_strtab = dyn; + } else if (dyn->d_tag == DT_STRSZ) { + dt_strsz = dyn; + } + } + if (dt_soname == nullptr) { + return nullptr; + } + ABSL_RAW_CHECK(dt_strtab != nullptr, "Unexpected nullptr"); + ABSL_RAW_CHECK(dt_strsz != nullptr, "Unexpected nullptr"); + const char* const strtab = reinterpret_cast<char*>( + info->dlpi_addr + static_cast<ElfW(Word)>(dt_strtab->d_un.d_val)); + ABSL_RAW_CHECK(dt_soname->d_un.d_val < dt_strsz->d_un.d_val, + "Unexpected order"); + return strtab + dt_soname->d_un.d_val; +} + +// Helper function to get the build ID of a shared object. +std::string GetBuildId(const dl_phdr_info* const info) { + std::string result; + + // pt_note contains entries (of type ElfW(Nhdr)) starting at + // info->dlpi_addr + pt_note->p_vaddr + // with length + // pt_note->p_memsz + // + // The length of each entry is given by + // Align(sizeof(ElfW(Nhdr)) + nhdr->n_namesz) + Align(nhdr->n_descsz) + for (int i = 0; i < info->dlpi_phnum; ++i) { + const ElfW(Phdr)* pt_note = &info->dlpi_phdr[i]; + if (pt_note->p_type != PT_NOTE) continue; + + const char* note = + reinterpret_cast<char*>(info->dlpi_addr + pt_note->p_vaddr); + const char* const last = note + pt_note->p_filesz; + const ElfW(Xword) align = pt_note->p_align; + while (note < last) { + const ElfW(Nhdr)* const nhdr = reinterpret_cast<const ElfW(Nhdr)*>(note); + if (note + sizeof(*nhdr) > last) { + // Corrupt PT_NOTE + break; + } + + // Both the start and end of the descriptor are aligned by sh_addralign + // (= p_align). + const ElfW(Xword) desc_start = + (sizeof(*nhdr) + nhdr->n_namesz + align - 1) & -align; + const ElfW(Xword) size = + desc_start + ((nhdr->n_descsz + align - 1) & -align); + + // Beware of wrap-around. + if (nhdr->n_namesz >= static_cast<ElfW(Word)>(-align) || + nhdr->n_descsz >= static_cast<ElfW(Word)>(-align) || + desc_start < sizeof(*nhdr) || size < desc_start || + size > static_cast<ElfW(Xword)>(last - note)) { + // Corrupt PT_NOTE + break; + } + + if (nhdr->n_type == NT_GNU_BUILD_ID) { + const char* const note_name = note + sizeof(*nhdr); + // n_namesz is the length of note_name. + if (nhdr->n_namesz == 4 && memcmp(note_name, "GNU\0", 4) == 0) { + if (!result.empty()) { + // Repeated build-ids. Ignore them. + return ""; + } + result = absl::BytesToHexString( + absl::string_view(note + desc_start, nhdr->n_descsz)); + } + } + note += size; + } + } + + return result; +} +#endif // __linux__ + +// A varint-encoded integer. +struct Varint { + explicit Varint(uint64_t v) : value(v) {} + explicit Varint(StringId v) : value(static_cast<uint64_t>(v)) {} + explicit Varint(LocationId v) : value(static_cast<uint64_t>(v)) {} + explicit Varint(MappingId v) : value(static_cast<uint64_t>(v)) {} + + uint64_t value; + + template <typename Sink> + friend void AbslStringify(Sink& sink, const Varint& v) { + char buf[10]; + char* p = buf; + uint64_t u = v.value; + while (u >= 0x80) { + *p++ = static_cast<char>((u & 0x7f) | 0x80); + u >>= 7; + } + *p++ = static_cast<char>(u); + sink.Append(absl::string_view(buf, static_cast<size_t>(p - buf))); + } +}; + +struct Tag { + int field_number; + WireType wire_type; + + template <typename Sink> + friend void AbslStringify(Sink& sink, const Tag& t) { + absl::Format(&sink, "%v", + Varint((static_cast<uint64_t>(t.field_number) << 3) | + static_cast<uint64_t>(t.wire_type))); + } +}; + +struct LengthDelimited { + int field_number; + absl::string_view value; + + template <typename Sink> + friend void AbslStringify(Sink& sink, const LengthDelimited& ld) { + absl::Format(&sink, "%v%v%v", + Tag{ld.field_number, WireType::kLengthDelimited}, + Varint(ld.value.size()), ld.value); + } +}; + +struct VarintField { + int field_number; + Varint value; + + template <typename Sink> + friend void AbslStringify(Sink& sink, const VarintField& vf) { + absl::Format(&sink, "%v%v", Tag{vf.field_number, WireType::kVarint}, + vf.value); + } +}; + +} // namespace + +StringId ProfileBuilder::InternString(absl::string_view str) { + if (str.empty()) return StringId(0); + return string_table_.emplace(str, StringId(string_table_.size())) + .first->second; +} + +LocationId ProfileBuilder::InternLocation(const void* address) { + return location_table_ + .emplace(absl::bit_cast<uintptr_t>(address), + LocationId(location_table_.size() + 1)) + .first->second; +} + +void ProfileBuilder::AddSample( + int64_t value, absl::Span<const void* const> stack, + absl::Span<const std::pair<StringId, int64_t>> labels) { + std::string sample_proto; + absl::StrAppend( + &sample_proto, + VarintField{SampleProto::kValue, Varint(static_cast<uint64_t>(value))}); + + for (const void* addr : stack) { + // Profile addresses are raw stack unwind addresses, so they should be + // adjusted by -1 to land inside the call instruction (although potentially + // misaligned). + absl::StrAppend( + &sample_proto, + VarintField{SampleProto::kLocationId, + Varint(InternLocation(absl::bit_cast<const void*>( + absl::bit_cast<uintptr_t>(addr) - 1)))}); + } + + for (const auto& label : labels) { + std::string label_proto = + absl::StrCat(VarintField{LabelProto::kKey, Varint(label.first)}, + VarintField{LabelProto::kNum, + Varint(static_cast<uint64_t>(label.second))}); + absl::StrAppend(&sample_proto, + LengthDelimited{SampleProto::kLabel, label_proto}); + } + samples_.push_back(std::move(sample_proto)); +} + +void ProfileBuilder::AddSampleType(StringId type, StringId unit) { + std::string sample_type_proto = + absl::StrCat(VarintField{ValueTypeProto::kType, Varint(type)}, + VarintField{ValueTypeProto::kUnit, Varint(unit)}); + sample_types_.push_back(std::move(sample_type_proto)); +} + +MappingId ProfileBuilder::AddMapping(uintptr_t memory_start, + uintptr_t memory_limit, + uintptr_t file_offset, + absl::string_view filename, + absl::string_view build_id) { + size_t index = mappings_.size() + 1; + auto [it, inserted] = mapping_table_.emplace(memory_start, index); + if (!inserted) { + return static_cast<MappingId>(it->second); + } + + Mapping m; + m.start = memory_start; + m.limit = memory_limit; + m.offset = file_offset; + m.filename = std::string(filename); + m.build_id = std::string(build_id); + + mappings_.push_back(std::move(m)); + return static_cast<MappingId>(index); +} + +std::string ProfileBuilder::Emit() && { + std::string profile_proto; + for (const auto& sample_type : sample_types_) { + absl::StrAppend(&profile_proto, + LengthDelimited{ProfileProto::kSampleType, sample_type}); + } + for (const auto& sample : samples_) { + absl::StrAppend(&profile_proto, + LengthDelimited{ProfileProto::kSample, sample}); + } + + // Build mapping table. + for (size_t i = 0, n = mappings_.size(); i < n; ++i) { + const auto& mapping = mappings_[i]; + std::string mapping_proto = absl::StrCat( + VarintField{MappingProto::kId, Varint(static_cast<uint64_t>(i + 1))}, + VarintField{MappingProto::kMemoryStart, Varint(mapping.start)}, + VarintField{MappingProto::kMemoryLimit, Varint(mapping.limit)}, + VarintField{MappingProto::kFileOffset, Varint(mapping.offset)}, + VarintField{MappingProto::kFilename, + Varint(InternString(mapping.filename))}, + VarintField{MappingProto::kBuildId, + Varint(InternString(mapping.build_id))}); + + absl::StrAppend(&profile_proto, + LengthDelimited{ProfileProto::kMapping, mapping_proto}); + } + + // Build location table. + for (const auto& [address, id] : location_table_) { + std::string location = + absl::StrCat(VarintField{LocationProto::kId, Varint(id)}, + VarintField{LocationProto::kAddress, Varint(address)}); + + if (!mappings_.empty()) { + // Find the mapping ID. + auto it = mapping_table_.upper_bound(address); + if (it != mapping_table_.begin()) { + --it; + } + + // If *it contains address, add mapping to location. + const size_t mapping_index = it->second; + const Mapping& mapping = mappings_[mapping_index - 1]; + + if (it->first <= address && address < mapping.limit) { + absl::StrAppend( + &location, + VarintField{LocationProto::kMappingId, + Varint(static_cast<uint64_t>(mapping_index))}); + } + } + + absl::StrAppend(&profile_proto, + LengthDelimited{ProfileProto::kLocation, location}); + } + + std::string string_table_proto; + std::vector<absl::string_view> sorted_strings(string_table_.size()); + for (const auto& p : string_table_) { + sorted_strings[static_cast<size_t>(p.second)] = p.first; + } + for (const auto& s : sorted_strings) { + absl::StrAppend(&string_table_proto, + LengthDelimited{ProfileProto::kStringTable, s}); + } + absl::StrAppend(&profile_proto, VarintField{ProfileProto::kDropFrames, + Varint(drop_frames_id_)}); + absl::StrAppend(&profile_proto, + VarintField{ProfileProto::kComment, Varint(comment_id_)}); + absl::StrAppend(&profile_proto, VarintField{ProfileProto::kDefaultSampleType, + Varint(default_sample_type_id_)}); + return absl::StrCat(string_table_proto, profile_proto); +} + +void ProfileBuilder::set_drop_frames_id(StringId drop_frames_id) { + drop_frames_id_ = drop_frames_id; +} + +void ProfileBuilder::set_comment_id(StringId comment_id) { + comment_id_ = comment_id; +} + +void ProfileBuilder::set_default_sample_type_id( + StringId default_sample_type_id) { + default_sample_type_id_ = default_sample_type_id; +} + +void ProfileBuilder::AddCurrentMappings() { +#ifdef __linux__ + dl_iterate_phdr( + +[](dl_phdr_info* info, size_t, void* data) { + auto& builder = *reinterpret_cast<ProfileBuilder*>(data); + + // Skip dummy entry introduced since glibc 2.18. + if (info->dlpi_phdr == nullptr && info->dlpi_phnum == 0) { + return 0; + } + + const bool is_main_executable = builder.mappings_.empty(); + + // Evaluate all the loadable segments. + for (int i = 0; i < info->dlpi_phnum; ++i) { + if (info->dlpi_phdr[i].p_type != PT_LOAD) { + continue; + } + const ElfW(Phdr)* pt_load = &info->dlpi_phdr[i]; + + ABSL_RAW_CHECK(pt_load != nullptr, "Unexpected nullptr"); + + // Extract data. + const size_t memory_start = info->dlpi_addr + pt_load->p_vaddr; + const size_t memory_limit = memory_start + pt_load->p_memsz; + const size_t file_offset = pt_load->p_offset; + + // Storage for path to executable as dlpi_name isn't populated for the + // main executable. +1 to allow for the null terminator that readlink + // does not add. + char self_filename[PATH_MAX + 1]; + const char* filename = info->dlpi_name; + if (filename == nullptr || filename[0] == '\0') { + // This is either the main executable or the VDSO. The main + // executable is always the first entry processed by callbacks. + if (is_main_executable) { + // This is the main executable. + ssize_t ret = readlink("/proc/self/exe", self_filename, + sizeof(self_filename) - 1); + if (ret >= 0 && + static_cast<size_t>(ret) < sizeof(self_filename)) { + self_filename[ret] = '\0'; + filename = self_filename; + } + } else { + // This is the VDSO. + filename = GetSoName(info); + } + } + + char resolved_path[PATH_MAX]; + absl::string_view resolved_filename; + if (realpath(filename, resolved_path)) { + resolved_filename = resolved_path; + } else { + resolved_filename = filename; + } + + const std::string build_id = GetBuildId(info); + + // Add to profile. + builder.AddMapping(memory_start, memory_limit, file_offset, + resolved_filename, build_id); + } + // Keep going. + return 0; + }, + this); +#endif // __linux__ +} + +} // namespace debugging_internal +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/profiling/internal/profile_builder.h b/absl/profiling/internal/profile_builder.h new file mode 100644 index 0000000..45075e6 --- /dev/null +++ b/absl/profiling/internal/profile_builder.h
@@ -0,0 +1,138 @@ +// Copyright 2025 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_PROFILING_INTERNAL_PROFILE_BUILDER_H_ +#define ABSL_PROFILING_INTERNAL_PROFILE_BUILDER_H_ + +#include <cstddef> +#include <cstdint> +#include <string> +#include <vector> + +#include "absl/container/btree_map.h" +#include "absl/container/flat_hash_map.h" +#include "absl/strings/string_view.h" +#include "absl/types/span.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace debugging_internal { + +// Field numbers for perftools.profiles.Profile. +// https://github.com/google/pprof/blob/master/proto/profile.proto +struct ProfileProto { + static constexpr int kSampleType = 1; + static constexpr int kSample = 2; + static constexpr int kMapping = 3; + static constexpr int kLocation = 4; + static constexpr int kStringTable = 6; + static constexpr int kDropFrames = 7; + static constexpr int kComment = 13; + static constexpr int kDefaultSampleType = 14; +}; + +struct ValueTypeProto { + static constexpr int kType = 1; + static constexpr int kUnit = 2; +}; + +struct SampleProto { + static constexpr int kLocationId = 1; + static constexpr int kValue = 2; + static constexpr int kLabel = 3; +}; + +struct LabelProto { + static constexpr int kKey = 1; + static constexpr int kStr = 2; + static constexpr int kNum = 3; + static constexpr int kNumUnit = 4; +}; + +struct MappingProto { + static constexpr int kId = 1; + static constexpr int kMemoryStart = 2; + static constexpr int kMemoryLimit = 3; + static constexpr int kFileOffset = 4; + static constexpr int kFilename = 5; + static constexpr int kBuildId = 6; +}; + +struct LocationProto { + static constexpr int kId = 1; + static constexpr int kMappingId = 2; + static constexpr int kAddress = 3; +}; + +enum class StringId : size_t {}; +enum class LocationId : size_t {}; +enum class MappingId : size_t {}; + +// A helper class to build a profile protocol buffer. +class ProfileBuilder { + public: + struct Mapping { + uint64_t start; + uint64_t limit; + uint64_t offset; + std::string filename; + std::string build_id; + }; + + StringId InternString(absl::string_view str); + + LocationId InternLocation(const void* address); + + void AddSample(int64_t value, absl::Span<const void* const> stack, + absl::Span<const std::pair<StringId, int64_t>> labels); + + void AddSampleType(StringId type, StringId unit); + + // Adds the current process mappings to the profile. + void AddCurrentMappings(); + + // Adds a single mapping to the profile and to lookup cache and returns the + // resulting ID. + MappingId AddMapping(uintptr_t memory_start, uintptr_t memory_limit, + uintptr_t file_offset, absl::string_view filename, + absl::string_view build_id); + + std::string Emit() &&; + + void set_drop_frames_id(StringId drop_frames_id); + void set_comment_id(StringId comment_id); + void set_default_sample_type_id(StringId default_sample_type_id); + + private: + absl::flat_hash_map<std::string, StringId> string_table_{{"", StringId(0)}}; + absl::flat_hash_map<uintptr_t, LocationId> location_table_; + // mapping_table_ stores the start address of each mapping in mapping_ + // to its index. + absl::btree_map<uintptr_t, size_t> mapping_table_; + std::vector<Mapping> mappings_; + + std::vector<std::string> sample_types_; + std::vector<std::string> samples_; + + StringId drop_frames_id_{}; + StringId comment_id_{}; + StringId default_sample_type_id_{}; +}; + +} // namespace debugging_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_PROFILING_INTERNAL_PROFILE_BUILDER_H_
diff --git a/absl/profiling/internal/sample_recorder.h b/absl/profiling/internal/sample_recorder.h index 371f6c4..88a4b27 100644 --- a/absl/profiling/internal/sample_recorder.h +++ b/absl/profiling/internal/sample_recorder.h
@@ -75,7 +75,7 @@ // Iterates over all the registered `StackInfo`s. Returning the number of // samples that have been dropped. - int64_t Iterate(const std::function<void(const T& stack)>& f); + size_t Iterate(const std::function<void(const T& stack)>& f); size_t GetMaxSamples() const; void SetMaxSamples(size_t max); @@ -130,7 +130,7 @@ template <typename T> SampleRecorder<T>::SampleRecorder() : dropped_samples_(0), size_estimate_(0), all_(nullptr), dispose_(nullptr) { - absl::MutexLock l(&graveyard_.init_mu); + absl::MutexLock l(graveyard_.init_mu); graveyard_.dead = &graveyard_; } @@ -159,8 +159,8 @@ dispose(*sample); } - absl::MutexLock graveyard_lock(&graveyard_.init_mu); - absl::MutexLock sample_lock(&sample->init_mu); + absl::MutexLock graveyard_lock(graveyard_.init_mu); + absl::MutexLock sample_lock(sample->init_mu); sample->dead = graveyard_.dead; graveyard_.dead = sample; } @@ -168,7 +168,7 @@ template <typename T> template <typename... Targs> T* SampleRecorder<T>::PopDead(Targs... args) { - absl::MutexLock graveyard_lock(&graveyard_.init_mu); + absl::MutexLock graveyard_lock(graveyard_.init_mu); // The list is circular, so eventually it collapses down to // graveyard_.dead == &graveyard_ @@ -176,7 +176,7 @@ T* sample = graveyard_.dead; if (sample == &graveyard_) return nullptr; - absl::MutexLock sample_lock(&sample->init_mu); + absl::MutexLock sample_lock(sample->init_mu); graveyard_.dead = sample->dead; sample->dead = nullptr; sample->PrepareForSampling(std::forward<Targs>(args)...); @@ -198,7 +198,7 @@ // Resurrection failed. Hire a new warlock. sample = new T(); { - absl::MutexLock sample_lock(&sample->init_mu); + 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. @@ -222,11 +222,11 @@ } template <typename T> -int64_t SampleRecorder<T>::Iterate( +size_t SampleRecorder<T>::Iterate( const std::function<void(const T& stack)>& f) { T* s = all_.load(std::memory_order_acquire); while (s != nullptr) { - absl::MutexLock l(&s->init_mu); + absl::MutexLock l(s->init_mu); if (s->dead == nullptr) { f(*s); }
diff --git a/absl/random/BUILD.bazel b/absl/random/BUILD.bazel index 887ab0f..8986211 100644 --- a/absl/random/BUILD.bazel +++ b/absl/random/BUILD.bazel
@@ -16,6 +16,9 @@ # ABSL random-number generation libraries. +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load( "//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS",
diff --git a/absl/random/internal/BUILD.bazel b/absl/random/internal/BUILD.bazel index 994fb5c..1a3fef8 100644 --- a/absl/random/internal/BUILD.bazel +++ b/absl/random/internal/BUILD.bazel
@@ -15,6 +15,9 @@ # load("@bazel_skylib//lib:selects.bzl", "selects") +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") # Internal-only implementation classes for Abseil Random load( @@ -799,6 +802,7 @@ linkopts = ABSL_DEFAULT_LINKOPTS, tags = [ "benchmark", + "no_test_ios_sim_arm64", "no_test_ios_x86_64", "no_test_loonix", # Crashing. "no_test_wasm",
diff --git a/absl/random/internal/entropy_pool.cc b/absl/random/internal/entropy_pool.cc index fa47d0d..1386700 100644 --- a/absl/random/internal/entropy_pool.cc +++ b/absl/random/internal/entropy_pool.cc
@@ -55,7 +55,7 @@ RandenTraits::kCapacityBytes / sizeof(uint32_t); void Init(absl::Span<const uint32_t> data) { - SpinLockHolder l(&mu_); // Always uncontested. + SpinLockHolder l(mu_); // Always uncontested. std::copy(data.begin(), data.end(), std::begin(state_)); next_ = kState; } @@ -84,7 +84,7 @@ }; void RandenPoolEntry::Fill(uint8_t* out, size_t bytes) { - SpinLockHolder l(&mu_); + SpinLockHolder l(mu_); while (bytes > 0) { MaybeRefill(); size_t remaining = available() * sizeof(state_[0]);
diff --git a/absl/random/internal/entropy_pool_test.cc b/absl/random/internal/entropy_pool_test.cc index 89ea72f..7739f19 100644 --- a/absl/random/internal/entropy_pool_test.cc +++ b/absl/random/internal/entropy_pool_test.cc
@@ -44,7 +44,7 @@ threads.emplace_back([&]() { std::vector<result_type> v(kValuesPerThread); GetEntropyFromRandenPool(v.data(), sizeof(result_type) * v.size()); - absl::MutexLock l(&mu); + absl::MutexLock l(mu); data.push_back(std::move(v)); }); }
diff --git a/absl/random/internal/fastmath_test.cc b/absl/random/internal/fastmath_test.cc index 0d6f9dc..0b9ae5e 100644 --- a/absl/random/internal/fastmath_test.cc +++ b/absl/random/internal/fastmath_test.cc
@@ -16,12 +16,10 @@ #include "gtest/gtest.h" -#if defined(__native_client__) || defined(__EMSCRIPTEN__) -// NACL has a less accurate implementation of std::log2 than most of +#if defined(__EMSCRIPTEN__) +// Emscripten has a less accurate implementation of std::log2 than most of // the other platforms. For some values which should have integral results, -// sometimes NACL returns slightly larger values. -// -// The MUSL libc used by emscripten also has a similar bug. +// sometimes Emscripten returns slightly larger values. #define ABSL_RANDOM_INACCURATE_LOG2 #endif
diff --git a/absl/random/internal/nonsecure_base_test.cc b/absl/random/internal/nonsecure_base_test.cc index 6b6f2d5..6e3e712 100644 --- a/absl/random/internal/nonsecure_base_test.cc +++ b/absl/random/internal/nonsecure_base_test.cc
@@ -214,7 +214,7 @@ std::vector<result_type> v(kValuesPerThread); std::generate(v.begin(), v.end(), [&]() { return gen(); }); - absl::MutexLock l(&mu); + absl::MutexLock l(mu); data.push_back(std::move(v)); }); }
diff --git a/absl/random/internal/platform.h b/absl/random/internal/platform.h index bd2993e..a6fde4b 100644 --- a/absl/random/internal/platform.h +++ b/absl/random/internal/platform.h
@@ -35,7 +35,6 @@ // Darwin (macOS and iOS) __APPLE__ // Akaros (http://akaros.org) __ros__ // Windows _WIN32 -// NaCL __native_client__ // AsmJS __asmjs__ // WebAssembly __wasm__ // Fuchsia __Fuchsia__ @@ -125,12 +124,6 @@ #endif -// NaCl does not allow AES. -#if defined(__native_client__) -#undef ABSL_HAVE_ACCELERATED_AES -#define ABSL_HAVE_ACCELERATED_AES 0 -#endif - // ABSL_RANDOM_INTERNAL_AES_DISPATCH indicates whether the currently active // platform has, or should use run-time dispatch for selecting the // accelerated Randen implementation. @@ -162,10 +155,4 @@ #define ABSL_RANDOM_INTERNAL_AES_DISPATCH 1 #endif -// NaCl does not allow dispatch. -#if defined(__native_client__) -#undef ABSL_RANDOM_INTERNAL_AES_DISPATCH -#define ABSL_RANDOM_INTERNAL_AES_DISPATCH 0 -#endif - #endif // ABSL_RANDOM_INTERNAL_PLATFORM_H_
diff --git a/absl/random/internal/randen_engine_test.cc b/absl/random/internal/randen_engine_test.cc index a94f491..122d90b 100644 --- a/absl/random/internal/randen_engine_test.cc +++ b/absl/random/internal/randen_engine_test.cc
@@ -632,7 +632,6 @@ // // linux, optimized ~5ns // ppc, optimized ~7ns - // nacl (slow), ~1100ns // // `kCount` is chosen below so that, in debug builds and without hardware // acceleration, the test (assuming ~1us per call) should finish in ~0.1s
diff --git a/absl/random/internal/seed_material.cc b/absl/random/internal/seed_material.cc index 8099ec7..b6380c8 100644 --- a/absl/random/internal/seed_material.cc +++ b/absl/random/internal/seed_material.cc
@@ -41,12 +41,7 @@ #include "absl/types/optional.h" #include "absl/types/span.h" -#if defined(__native_client__) - -#include <nacl/nacl_random.h> -#define ABSL_RANDOM_USE_NACL_SECURE_RANDOM 1 - -#elif defined(_WIN32) +#if defined(_WIN32) #include <windows.h> #define ABSL_RANDOM_USE_BCRYPT 1 @@ -109,27 +104,6 @@ return BCRYPT_SUCCESS(ret); } -#elif defined(ABSL_RANDOM_USE_NACL_SECURE_RANDOM) - -// On NaCL use nacl_secure_random to acquire bytes. -bool ReadSeedMaterialFromOSEntropyImpl(absl::Span<uint32_t> values) { - auto buffer = reinterpret_cast<uint8_t*>(values.data()); - size_t buffer_size = sizeof(uint32_t) * values.size(); - - uint8_t* output_ptr = buffer; - while (buffer_size > 0) { - size_t nread = 0; - const int error = nacl_secure_random(output_ptr, buffer_size, &nread); - if (error != 0 || nread > buffer_size) { - ABSL_RAW_LOG(ERROR, "Failed to read secure_random seed data: %d", error); - return false; - } - output_ptr += nread; - buffer_size -= nread; - } - return true; -} - #elif defined(__Fuchsia__) bool ReadSeedMaterialFromOSEntropyImpl(absl::Span<uint32_t> values) {
diff --git a/absl/random/mock_distributions_test.cc b/absl/random/mock_distributions_test.cc index 622aff7..93af3f9 100644 --- a/absl/random/mock_distributions_test.cc +++ b/absl/random/mock_distributions_test.cc
@@ -69,10 +69,11 @@ .WillOnce(Return(0.001)); EXPECT_EQ(absl::Gaussian<double>(gen, 0.0, 1.0), 0.001); - EXPECT_NE(absl::LogUniform<int>(gen, 0, 1000000, 2), 2040); - EXPECT_CALL(absl::MockLogUniform<int>(), Call(gen, 0, 1000000, 2)) - .WillOnce(Return(2040)); - EXPECT_EQ(absl::LogUniform<int>(gen, 0, 1000000, 2), 2040); + const int kHigh = (1 << 30) - 1; + EXPECT_NE(absl::LogUniform<int>(gen, 0, kHigh, 2), kHigh); + EXPECT_CALL(absl::MockLogUniform<int>(), Call(gen, 0, kHigh, 2)) + .WillOnce(Return(kHigh)); + EXPECT_EQ(absl::LogUniform<int>(gen, 0, kHigh, 2), kHigh); } TEST(MockDistributions, UniformUInt128BoundariesAreAllowed) {
diff --git a/absl/status/BUILD.bazel b/absl/status/BUILD.bazel index b61abeb..394222f 100644 --- a/absl/status/BUILD.bazel +++ b/absl/status/BUILD.bazel
@@ -17,6 +17,9 @@ # It will expand later to have utilities around `Status` like `StatusOr`, # `StatusBuilder` and macros. +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load( "//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS", @@ -189,3 +192,20 @@ "@googletest//:gtest_main", ], ) + +cc_test( + name = "status_matchers_with_unqualified_macros_test", + size = "small", + srcs = ["status_matchers_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + local_defines = ["ABSL_DEFINE_UNQUALIFIED_STATUS_TESTING_MACROS"], + deps = [ + ":status", + ":status_matchers", + ":statusor", + "//absl/strings", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +)
diff --git a/absl/status/CMakeLists.txt b/absl/status/CMakeLists.txt index e140365..d0d134c 100644 --- a/absl/status/CMakeLists.txt +++ b/absl/status/CMakeLists.txt
@@ -141,3 +141,19 @@ absl::status_matchers GTest::gmock_main ) + +absl_cc_test( + NAME + status_matchers_with_unqualified_macros_test + SRCS + "status_matchers_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEFINES + "ABSL_DEFINE_UNQUALIFIED_STATUS_TESTING_MACROS" + DEPS + absl::status + absl::statusor + absl::status_matchers + GTest::gmock_main +)
diff --git a/absl/status/internal/status_internal.h b/absl/status/internal/status_internal.h index 45b90f3..35a9f9b 100644 --- a/absl/status/internal/status_internal.h +++ b/absl/status/internal/status_internal.h
@@ -38,9 +38,11 @@ // TODO(b/176172494): ABSL_MUST_USE_RESULT should expand to the more strict // [[nodiscard]]. For now, just use [[nodiscard]] directly when it is available. #if ABSL_HAVE_CPP_ATTRIBUTE(nodiscard) -class [[nodiscard]] ABSL_ATTRIBUTE_TRIVIAL_ABI Status; +class [[nodiscard]] ABSL_ATTRIBUTE_TRIVIAL_ABI + Status; #else -class ABSL_MUST_USE_RESULT ABSL_ATTRIBUTE_TRIVIAL_ABI Status; +class ABSL_MUST_USE_RESULT ABSL_ATTRIBUTE_TRIVIAL_ABI + Status; #endif ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/status/internal/status_matchers.h b/absl/status/internal/status_matchers.h index 0750622..d11742b 100644 --- a/absl/status/internal/status_matchers.h +++ b/absl/status/internal/status_matchers.h
@@ -69,8 +69,8 @@ bool MatchAndExplain( StatusOrType actual_value, ::testing::MatchResultListener* result_listener) const override { - if (!GetStatus(actual_value).ok()) { - *result_listener << "which has status " << GetStatus(actual_value); + if (!actual_value.ok()) { + *result_listener << "which has status " << actual_value.status(); return false; }
diff --git a/absl/status/internal/statusor_internal.h b/absl/status/internal/statusor_internal.h index e986611..8a4e2f5 100644 --- a/absl/status/internal/statusor_internal.h +++ b/absl/status/internal/statusor_internal.h
@@ -29,7 +29,8 @@ ABSL_NAMESPACE_BEGIN template <typename T> -class ABSL_MUST_USE_RESULT StatusOr; +class ABSL_MUST_USE_RESULT + StatusOr; namespace internal_statusor { @@ -46,6 +47,16 @@ struct HasConversionOperatorToStatusOr<T, U, decltype(test<T, U>(0))> : std::true_type {}; +// Detects whether `T` is equality-comparable. +template <typename T, typename = void> +struct IsEqualityComparable : std::false_type {}; + +template <typename T> +struct IsEqualityComparable< + T, std::enable_if_t<std::is_convertible< + decltype(std::declval<T>() == std::declval<T>()), + bool>::value>> : std::true_type {}; + // Detects whether `T` is constructible or convertible from `StatusOr<U>`. template <typename T, typename U> using IsConstructibleOrConvertibleFromStatusOr = @@ -80,17 +91,34 @@ struct IsDirectInitializationAmbiguous<T, absl::StatusOr<V>> : public IsConstructibleOrConvertibleFromStatusOr<T, V> {}; +// Checks whether the conversion from U to T can be done without dangling +// temporaries. +// REQUIRES: T and U are references. +template <typename T, typename U> +using IsReferenceConversionValid = absl::conjunction< // + std::is_reference<T>, std::is_reference<U>, + // The references are convertible. This checks for + // lvalue/rvalue compatibility. + std::is_convertible<U, T>, + // The pointers are convertible. This checks we don't have + // a temporary. + std::is_convertible<std::remove_reference_t<U>*, + std::remove_reference_t<T>*>>; + // Checks against the constraints of the direction initialization, i.e. when // `StatusOr<T>::StatusOr(U&&)` should participate in overload resolution. template <typename T, typename U> using IsDirectInitializationValid = absl::disjunction< // Short circuits if T is basically U. - std::is_same<T, absl::remove_cvref_t<U>>, - absl::negation<absl::disjunction< - std::is_same<absl::StatusOr<T>, absl::remove_cvref_t<U>>, - std::is_same<absl::Status, absl::remove_cvref_t<U>>, - std::is_same<absl::in_place_t, absl::remove_cvref_t<U>>, - IsDirectInitializationAmbiguous<T, U>>>>; + std::is_same<T, absl::remove_cvref_t<U>>, // + std::conditional_t< + std::is_reference_v<T>, // + IsReferenceConversionValid<T, U>, + absl::negation<absl::disjunction< + std::is_same<absl::StatusOr<T>, absl::remove_cvref_t<U>>, + std::is_same<absl::Status, absl::remove_cvref_t<U>>, + std::is_same<absl::in_place_t, absl::remove_cvref_t<U>>, + IsDirectInitializationAmbiguous<T, U>>>>>; // This trait detects whether `StatusOr<T>::operator=(U&&)` is ambiguous, which // is equivalent to whether all the following conditions are met: @@ -130,7 +158,9 @@ template <bool Explicit, typename T, typename U, bool Lifetimebound> using IsConstructionValid = absl::conjunction< Equality<Lifetimebound, - type_traits_internal::IsLifetimeBoundAssignment<T, U>>, + absl::disjunction< + std::is_reference<T>, + type_traits_internal::IsLifetimeBoundAssignment<T, U>>>, IsDirectInitializationValid<T, U&&>, std::is_constructible<T, U&&>, Equality<!Explicit, std::is_convertible<U&&, T>>, absl::disjunction< @@ -146,8 +176,13 @@ template <typename T, typename U, bool Lifetimebound> using IsAssignmentValid = absl::conjunction< Equality<Lifetimebound, - type_traits_internal::IsLifetimeBoundAssignment<T, U>>, - std::is_constructible<T, U&&>, std::is_assignable<T&, U&&>, + absl::disjunction< + std::is_reference<T>, + type_traits_internal::IsLifetimeBoundAssignment<T, U>>>, + std::conditional_t<std::is_reference_v<T>, + IsReferenceConversionValid<T, U&&>, + absl::conjunction<std::is_constructible<T, U&&>, + std::is_assignable<T&, U&&>>>, absl::disjunction< std::is_same<T, absl::remove_cvref_t<U>>, absl::conjunction< @@ -168,6 +203,9 @@ typename UQ> using IsConstructionFromStatusOrValid = absl::conjunction< absl::negation<std::is_same<T, U>>, + // If `T` is a reference, then U must be a compatible one. + absl::disjunction<absl::negation<std::is_reference<T>>, + IsReferenceConversionValid<T, U>>, Equality<Lifetimebound, type_traits_internal::IsLifetimeBoundAssignment<T, U>>, std::is_constructible<T, UQ>, @@ -183,6 +221,16 @@ absl::negation<IsConstructibleOrConvertibleOrAssignableFromStatusOr< T, absl::remove_cvref_t<U>>>>; +template <typename T, typename U, bool Lifetimebound> +using IsValueOrValid = absl::conjunction< + // If `T` is a reference, then U must be a compatible one. + absl::disjunction<absl::negation<std::is_reference<T>>, + IsReferenceConversionValid<T, U>>, + Equality<Lifetimebound, + absl::disjunction< + std::is_reference<T>, + type_traits_internal::IsLifetimeBoundAssignment<T, U>>>>; + class Helper { public: // Move type-agnostic error handling to the .cc. @@ -199,6 +247,26 @@ new (p) T(std::forward<Args>(args)...); } +template <typename T> +class Reference { + public: + constexpr explicit Reference(T ref ABSL_ATTRIBUTE_LIFETIME_BOUND) + : payload_(std::addressof(ref)) {} + + Reference(const Reference&) = default; + Reference& operator=(const Reference&) = default; + Reference& operator=(T value) { + payload_ = std::addressof(value); + return *this; + } + + operator T() const { return static_cast<T>(*payload_); } // NOLINT + T get() const { return *this; } + + private: + std::remove_reference_t<T>* absl_nonnull payload_; +}; + // Helper base class to hold the data and all operations. // We move all this to a base class to allow mixing with the appropriate // TraitsBase specialization. @@ -207,6 +275,14 @@ template <typename U> friend class StatusOrData; + decltype(auto) MaybeMoveData() { + if constexpr (std::is_reference_v<T>) { + return data_.get(); + } else { + return std::move(data_); + } + } + public: StatusOrData() = delete; @@ -221,7 +297,7 @@ StatusOrData(StatusOrData&& other) noexcept { if (other.ok()) { - MakeValue(std::move(other.data_)); + MakeValue(other.MaybeMoveData()); MakeStatus(); } else { MakeStatus(std::move(other.status_)); @@ -241,7 +317,7 @@ template <typename U> explicit StatusOrData(StatusOrData<U>&& other) { if (other.ok()) { - MakeValue(std::move(other.data_)); + MakeValue(other.MaybeMoveData()); MakeStatus(); } else { MakeStatus(std::move(other.status_)); @@ -254,13 +330,6 @@ MakeStatus(); } - explicit StatusOrData(const T& value) : data_(value) { - MakeStatus(); - } - explicit StatusOrData(T&& value) : data_(std::move(value)) { - MakeStatus(); - } - template <typename U, absl::enable_if_t<std::is_constructible<absl::Status, U&&>::value, int> = 0> @@ -280,7 +349,7 @@ StatusOrData& operator=(StatusOrData&& other) { if (this == &other) return *this; if (other.ok()) - Assign(std::move(other.data_)); + Assign(other.MaybeMoveData()); else AssignStatus(std::move(other.status_)); return *this; @@ -289,7 +358,9 @@ ~StatusOrData() { if (ok()) { status_.~Status(); - data_.~T(); + if constexpr (!std::is_trivially_destructible_v<T>) { + data_.~T(); + } } else { status_.~Status(); } @@ -330,11 +401,13 @@ // When T is const, we need some non-const object we can cast to void* for // the placement new. dummy_ is that object. Dummy dummy_; - T data_; + std::conditional_t<std::is_reference_v<T>, Reference<T>, T> data_; }; void Clear() { - if (ok()) data_.~T(); + if constexpr (!std::is_trivially_destructible_v<T>) { + if (ok()) data_.~T(); + } } void EnsureOk() const { @@ -349,7 +422,8 @@ // argument. template <typename... Arg> void MakeValue(Arg&&... arg) { - internal_statusor::PlacementNew<T>(&dummy_, std::forward<Arg>(arg)...); + internal_statusor::PlacementNew<decltype(data_)>(&dummy_, + std::forward<Arg>(arg)...); } // Construct the status (ie. status_) through placement new with the passed @@ -359,6 +433,94 @@ internal_statusor::PlacementNew<Status>(&status_, std::forward<Args>(args)...); } + + template <typename U> + T ValueOrImpl(U&& default_value) const& { + if (ok()) { + return data_; + } + return std::forward<U>(default_value); + } + + template <typename U> + T ValueOrImpl(U&& default_value) && { + if (ok()) { + return std::move(data_); + } + return std::forward<U>(default_value); + } +}; + +[[noreturn]] void ThrowBadStatusOrAccess(absl::Status status); + +template <typename T> +struct OperatorBase { + auto& self() const { return static_cast<const StatusOr<T>&>(*this); } + auto& self() { return static_cast<StatusOr<T>&>(*this); } + + const T& operator*() const& ABSL_ATTRIBUTE_LIFETIME_BOUND { + self().EnsureOk(); + return self().data_; + } + T& operator*() & ABSL_ATTRIBUTE_LIFETIME_BOUND { + self().EnsureOk(); + return self().data_; + } + const T&& operator*() const&& ABSL_ATTRIBUTE_LIFETIME_BOUND { + self().EnsureOk(); + return std::move(self().data_); + } + T&& operator*() && ABSL_ATTRIBUTE_LIFETIME_BOUND { + self().EnsureOk(); + return std::move(self().data_); + } + + const T& value() const& ABSL_ATTRIBUTE_LIFETIME_BOUND { + if (!self().ok()) internal_statusor::ThrowBadStatusOrAccess(self().status_); + return self().data_; + } + T& value() & ABSL_ATTRIBUTE_LIFETIME_BOUND { + if (!self().ok()) internal_statusor::ThrowBadStatusOrAccess(self().status_); + return self().data_; + } + const T&& value() const&& ABSL_ATTRIBUTE_LIFETIME_BOUND { + if (!self().ok()) { + internal_statusor::ThrowBadStatusOrAccess(std::move(self().status_)); + } + return std::move(self().data_); + } + T&& value() && ABSL_ATTRIBUTE_LIFETIME_BOUND { + if (!self().ok()) { + internal_statusor::ThrowBadStatusOrAccess(std::move(self().status_)); + } + return std::move(self().data_); + } + + const T* absl_nonnull operator->() const ABSL_ATTRIBUTE_LIFETIME_BOUND { + return std::addressof(**this); + } + T* absl_nonnull operator->() ABSL_ATTRIBUTE_LIFETIME_BOUND { + return std::addressof(**this); + } +}; + +template <typename T> +struct OperatorBase<T&> { + auto& self() const { return static_cast<const StatusOr<T&>&>(*this); } + + T& operator*() const { + self().EnsureOk(); + return self().data_; + } + + T& value() const { + if (!self().ok()) internal_statusor::ThrowBadStatusOrAccess(self().status_); + return self().data_; + } + + T* absl_nonnull operator->() const { + return std::addressof(**this); + } }; // Helper base classes to allow implicitly deleted constructors and assignment @@ -401,8 +563,9 @@ MoveCtorBase& operator=(MoveCtorBase&&) = default; }; -template <typename T, bool = std::is_copy_constructible<T>::value&& - std::is_copy_assignable<T>::value> +template <typename T, bool = (std::is_copy_constructible<T>::value && + std::is_copy_assignable<T>::value) || + std::is_reference_v<T>> struct CopyAssignBase { CopyAssignBase() = default; CopyAssignBase(const CopyAssignBase&) = default; @@ -420,8 +583,9 @@ CopyAssignBase& operator=(CopyAssignBase&&) = default; }; -template <typename T, bool = std::is_move_constructible<T>::value&& - std::is_move_assignable<T>::value> +template <typename T, bool = (std::is_move_constructible<T>::value && + std::is_move_assignable<T>::value) || + std::is_reference_v<T>> struct MoveAssignBase { MoveAssignBase() = default; MoveAssignBase(const MoveAssignBase&) = default; @@ -439,8 +603,6 @@ MoveAssignBase& operator=(MoveAssignBase&&) = delete; }; -[[noreturn]] void ThrowBadStatusOrAccess(absl::Status status); - // Used to introduce jitter into the output of printing functions for // `StatusOr` (i.e. `AbslStringify` and `operator<<`). class StringifyRandom {
diff --git a/absl/status/status.cc b/absl/status/status.cc index 963dab6..f219933 100644 --- a/absl/status/status.cc +++ b/absl/status/status.cc
@@ -47,6 +47,10 @@ "absl::Status assumes it can use the bottom 2 bits of a StatusRep*."); std::string StatusCodeToString(StatusCode code) { + return std::string(absl::StatusCodeToStringView(code)); +} + +absl::string_view StatusCodeToStringView(StatusCode code) { switch (code) { case StatusCode::kOk: return "OK";
diff --git a/absl/status/status.h b/absl/status/status.h index 4516822..b26d072 100644 --- a/absl/status/status.h +++ b/absl/status/status.h
@@ -284,6 +284,11 @@ // Returns the name for the status code, or "" if it is an unknown value. std::string StatusCodeToString(StatusCode code); +// StatusCodeToStringView() +// +// Same as StatusCodeToString(), but returns a string_view. +absl::string_view StatusCodeToStringView(StatusCode code); + // operator<< // // Streams StatusCodeToString(code) to `os`.
diff --git a/absl/status/status_matchers.h b/absl/status/status_matchers.h index 837660e..03e5f49 100644 --- a/absl/status/status_matchers.h +++ b/absl/status/status_matchers.h
@@ -20,6 +20,24 @@ // // Defines the following utilities: // +// ================= +// ABSL_EXPECT_OK(s) +// +// ABSL_ASSERT_OK(s) +// ================= +// Convenience macros for `EXPECT_THAT(s, IsOk())`, where `s` is either +// a `Status` or a `StatusOr<T>`. +// +// There are no EXPECT_NOT_OK/ASSERT_NOT_OK macros since they would not +// provide much value (when they fail, they would just print the OK status +// which conveys no more information than `EXPECT_FALSE(s.ok())`. You can +// of course use `EXPECT_THAT(s, Not(IsOk()))` if you prefer _THAT style. +// +// If you want to check for particular errors, better alternatives are: +// EXPECT_THAT(s, StatusIs(expected_error)); +// EXPECT_THAT(s, StatusIs(_, _, HasSubstr("expected error"))); +// +// // =============== // `IsOkAndHolds(m)` // =============== @@ -76,6 +94,13 @@ namespace absl_testing { ABSL_NAMESPACE_BEGIN +// Macros for testing the results of functions that return absl::Status or +// absl::StatusOr<T> (for any type T). +#define ABSL_EXPECT_OK(expression) \ + EXPECT_THAT(expression, ::absl_testing::IsOk()) +#define ABSL_ASSERT_OK(expression) \ + ASSERT_THAT(expression, ::absl_testing::IsOk()) + // Returns a gMock matcher that matches a StatusOr<> whose status is // OK and whose value matches the inner matcher. template <typename InnerMatcherT> @@ -112,6 +137,29 @@ return status_internal::IsOkMatcher(); } +// By defining ABSL_DEFINE_UNQUALIFIED_STATUS_TESTING_MACROS, this library also +// provides unqualified versions of macros +// +// Unqualified macro names are likely to collide with those other projects, and +// so are not recommended. Further, this is true of any transitive dependency +// of Abseil; it is impossible to be confident no downstream library will not +// also define these macros itself nor depend on a different library that also +// defines them. +// +// To enable this, define `ABSL_DEFINE_UNQUALIFIED_STATUS_TESTING_MACROS` +// preferably at the command line, e.g. +// `-DABSL_DEFINE_UNQUALIFIED_STATUS_TESTING_MACROS` or +// `local_defines = ["ABSL_DEFINE_UNQUALIFIED_STATUS_TESTING_MACROS"]` if using +// Bazel. +// +// These are turned on by default inside Google's internal codebase where their +// use is historically ubiquitous. Other OSS Google projects should use the +// qualified versions or the `EXPECT_THAT(..., IsOk())` form. +#ifdef ABSL_DEFINE_UNQUALIFIED_STATUS_TESTING_MACROS +#define EXPECT_OK(expression) ABSL_EXPECT_OK(expression) +#define ASSERT_OK(expression) ABSL_ASSERT_OK(expression) +#endif // ABSL_DEFINE_UNQUALIFIED_STATUS_TESTING_MACROS + ABSL_NAMESPACE_END } // namespace absl_testing
diff --git a/absl/status/status_matchers_test.cc b/absl/status/status_matchers_test.cc index b8ccaa4..8656b2d 100644 --- a/absl/status/status_matchers_test.cc +++ b/absl/status/status_matchers_test.cc
@@ -18,6 +18,7 @@ #include "absl/status/status_matchers.h" #include <string> +#include <vector> #include "gmock/gmock.h" #include "gtest/gtest-spi.h" @@ -31,9 +32,39 @@ using ::absl_testing::IsOk; using ::absl_testing::IsOkAndHolds; using ::absl_testing::StatusIs; +using ::testing::ElementsAre; using ::testing::Eq; using ::testing::Gt; using ::testing::MatchesRegex; +using ::testing::Not; +using ::testing::Ref; + +TEST(StatusMatcherTest, AbslExpectAssertOk) { + ABSL_EXPECT_OK(absl::OkStatus()); + ABSL_ASSERT_OK(absl::OkStatus()); + EXPECT_NONFATAL_FAILURE(ABSL_EXPECT_OK(absl::InternalError("Smigla error")), + "Smigla error"); + EXPECT_FATAL_FAILURE(ABSL_ASSERT_OK(absl::InternalError("Smigla error")), + "Smigla error"); +} + +TEST(StatusMatcherTest, ExpectAssertOk) { +#ifdef ABSL_DEFINE_UNQUALIFIED_STATUS_TESTING_MACROS + EXPECT_OK(absl::OkStatus()); + ASSERT_OK(absl::OkStatus()); + EXPECT_NONFATAL_FAILURE(EXPECT_OK(absl::InternalError("Smigla error")), + "Smigla error"); + EXPECT_FATAL_FAILURE(ASSERT_OK(absl::InternalError("Smigla error")), + "Smigla error"); +#else +#ifdef EXPECT_OK + static_assert(false, "EXPECT_OK defined despite being turned off."); +#endif // EXPECT_OK +#ifdef ASSERT_OK + static_assert(false, "ASSERT_OK defined despite being turned off."); +#endif // ASSERT_OK +#endif // ABSL_DEFINE_UNQUALIFIED_STATUS_TESTING_MACROS +} TEST(StatusMatcherTest, StatusIsOk) { EXPECT_THAT(absl::OkStatus(), IsOk()); } @@ -158,4 +189,23 @@ "ungueltig"); } +TEST(StatusMatcherTest, ReferencesWork) { + int i = 17; + int j = 19; + EXPECT_THAT(absl::StatusOr<int&>(i), IsOkAndHolds(17)); + EXPECT_THAT(absl::StatusOr<int&>(i), Not(IsOkAndHolds(19))); + EXPECT_THAT(absl::StatusOr<const int&>(i), IsOkAndHolds(17)); + + // Reference testing works as expected. + EXPECT_THAT(absl::StatusOr<int&>(i), IsOkAndHolds(Ref(i))); + EXPECT_THAT(absl::StatusOr<int&>(i), Not(IsOkAndHolds(Ref(j)))); + + // Try a more complex one. + std::vector<std::string> vec = {"A", "B", "C"}; + EXPECT_THAT(absl::StatusOr<std::vector<std::string>&>(vec), + IsOkAndHolds(ElementsAre("A", "B", "C"))); + EXPECT_THAT(absl::StatusOr<std::vector<std::string>&>(vec), + Not(IsOkAndHolds(ElementsAre("A", "X", "C")))); +} + } // namespace
diff --git a/absl/status/status_test.cc b/absl/status/status_test.cc index c3327ad..f6ac0c0 100644 --- a/absl/status/status_test.cc +++ b/absl/status/status_test.cc
@@ -39,6 +39,7 @@ std::ostringstream oss; oss << code; EXPECT_EQ(oss.str(), absl::StatusCodeToString(code)); + EXPECT_EQ(oss.str(), absl::StatusCodeToStringView(code)); } // This structure holds the details for testing a single error code,
diff --git a/absl/status/statusor.h b/absl/status/statusor.h index 6142a2f..52294ee 100644 --- a/absl/status/statusor.h +++ b/absl/status/statusor.h
@@ -45,8 +45,8 @@ #include <utility> #include "absl/base/attributes.h" -#include "absl/base/nullability.h" #include "absl/base/call_once.h" +#include "absl/base/nullability.h" #include "absl/meta/type_traits.h" #include "absl/status/internal/statusor_internal.h" #include "absl/status/status.h" @@ -189,14 +189,22 @@ // return Foo(arg); // } template <typename T> -class StatusOr : private internal_statusor::StatusOrData<T>, +class StatusOr : private internal_statusor::OperatorBase<T>, + private internal_statusor::StatusOrData<T>, private internal_statusor::CopyCtorBase<T>, private internal_statusor::MoveCtorBase<T>, private internal_statusor::CopyAssignBase<T>, private internal_statusor::MoveAssignBase<T> { +#ifndef SWIG + static_assert(!std::is_rvalue_reference_v<T>, + "rvalue references are not yet supported."); +#endif // SWIG + template <typename U> friend class StatusOr; + friend internal_statusor::OperatorBase<T>; + typedef internal_statusor::StatusOrData<T> Base; public: @@ -397,7 +405,7 @@ typename std::enable_if< internal_statusor::IsAssignmentValid<T, U, true>::value, int>::type = 0> - StatusOr& operator=(U&& v ABSL_ATTRIBUTE_LIFETIME_BOUND) { + StatusOr& operator=(U&& v ABSL_INTERNAL_ATTRIBUTE_CAPTURED_BY(this)) { this->Assign(std::forward<U>(v)); return *this; } @@ -493,10 +501,7 @@ // // The `std::move` on statusor instead of on the whole expression enables // warnings about possible uses of the statusor object after the move. - const T& value() const& ABSL_ATTRIBUTE_LIFETIME_BOUND; - T& value() & ABSL_ATTRIBUTE_LIFETIME_BOUND; - const T&& value() const&& ABSL_ATTRIBUTE_LIFETIME_BOUND; - T&& value() && ABSL_ATTRIBUTE_LIFETIME_BOUND; + using StatusOr::OperatorBase::value; // StatusOr<T>:: operator*() // @@ -508,10 +513,7 @@ // `absl::StatusOr<T>`. Alternatively, see the `value()` member function for a // similar API that guarantees crashing or throwing an exception if there is // no current value. - const T& operator*() const& ABSL_ATTRIBUTE_LIFETIME_BOUND; - T& operator*() & ABSL_ATTRIBUTE_LIFETIME_BOUND; - const T&& operator*() const&& ABSL_ATTRIBUTE_LIFETIME_BOUND; - T&& operator*() && ABSL_ATTRIBUTE_LIFETIME_BOUND; + using StatusOr::OperatorBase::operator*; // StatusOr<T>::operator->() // @@ -520,8 +522,7 @@ // REQUIRES: `this->ok() == true`, otherwise the behavior is undefined. // // Use `this->ok()` to verify that there is a current value. - const T* absl_nonnull operator->() const ABSL_ATTRIBUTE_LIFETIME_BOUND; - T* absl_nonnull operator->() ABSL_ATTRIBUTE_LIFETIME_BOUND; + using StatusOr::OperatorBase::operator->; // StatusOr<T>::value_or() // @@ -536,10 +537,34 @@ // // Unlike with `value`, calling `std::move()` on the result of `value_or` will // still trigger a copy. - template <typename U> - T value_or(U&& default_value) const&; - template <typename U> - T value_or(U&& default_value) &&; + template < + typename U, + std::enable_if_t<internal_statusor::IsValueOrValid<T, U&&, false>::value, + int> = 0> + T value_or(U&& default_value) const& { + return this->ValueOrImpl(std::forward<U>(default_value)); + } + template < + typename U, + std::enable_if_t<internal_statusor::IsValueOrValid<T, U&&, false>::value, + int> = 0> + T value_or(U&& default_value) && { + return std::move(*this).ValueOrImpl(std::forward<U>(default_value)); + } + template < + typename U, + std::enable_if_t<internal_statusor::IsValueOrValid<T, U&&, true>::value, + int> = 0> + T value_or(U&& default_value ABSL_ATTRIBUTE_LIFETIME_BOUND) const& { + return this->ValueOrImpl(std::forward<U>(default_value)); + } + template < + typename U, + std::enable_if_t<internal_statusor::IsValueOrValid<T, U&&, true>::value, + int> = 0> + T value_or(U&& default_value ABSL_ATTRIBUTE_LIFETIME_BOUND) && { + return std::move(*this).ValueOrImpl(std::forward<U>(default_value)); + } // StatusOr<T>::IgnoreError() // @@ -607,7 +632,9 @@ // operator==() // // This operator checks the equality of two `absl::StatusOr<T>` objects. -template <typename T> +template <typename T, + std::enable_if_t<internal_statusor::IsEqualityComparable<T>::value, + int> = 0> bool operator==(const StatusOr<T>& lhs, const StatusOr<T>& rhs) { if (lhs.ok() && rhs.ok()) return *lhs == *rhs; return lhs.status() == rhs.status(); @@ -616,7 +643,9 @@ // operator!=() // // This operator checks the inequality of two `absl::StatusOr<T>` objects. -template <typename T> +template <typename T, + std::enable_if_t<internal_statusor::IsEqualityComparable<T>::value, + int> = 0> bool operator!=(const StatusOr<T>& lhs, const StatusOr<T>& rhs) { return !(lhs == rhs); } @@ -704,88 +733,6 @@ } template <typename T> -const T& StatusOr<T>::value() const& { - if (!this->ok()) internal_statusor::ThrowBadStatusOrAccess(this->status_); - return this->data_; -} - -template <typename T> -T& StatusOr<T>::value() & { - if (!this->ok()) internal_statusor::ThrowBadStatusOrAccess(this->status_); - return this->data_; -} - -template <typename T> -const T&& StatusOr<T>::value() const&& { - if (!this->ok()) { - internal_statusor::ThrowBadStatusOrAccess(std::move(this->status_)); - } - return std::move(this->data_); -} - -template <typename T> -T&& StatusOr<T>::value() && { - if (!this->ok()) { - internal_statusor::ThrowBadStatusOrAccess(std::move(this->status_)); - } - return std::move(this->data_); -} - -template <typename T> -const T& StatusOr<T>::operator*() const& { - this->EnsureOk(); - return this->data_; -} - -template <typename T> -T& StatusOr<T>::operator*() & { - this->EnsureOk(); - return this->data_; -} - -template <typename T> -const T&& StatusOr<T>::operator*() const&& { - this->EnsureOk(); - return std::move(this->data_); -} - -template <typename T> -T&& StatusOr<T>::operator*() && { - this->EnsureOk(); - return std::move(this->data_); -} - -template <typename T> -const T* absl_nonnull StatusOr<T>::operator->() const { - this->EnsureOk(); - return &this->data_; -} - -template <typename T> -T* absl_nonnull StatusOr<T>::operator->() { - this->EnsureOk(); - return &this->data_; -} - -template <typename T> -template <typename U> -T StatusOr<T>::value_or(U&& default_value) const& { - if (ok()) { - return this->data_; - } - return std::forward<U>(default_value); -} - -template <typename T> -template <typename U> -T StatusOr<T>::value_or(U&& default_value) && { - if (ok()) { - return std::move(this->data_); - } - return std::forward<U>(default_value); -} - -template <typename T> void StatusOr<T>::IgnoreError() const { // no-op }
diff --git a/absl/status/statusor_test.cc b/absl/status/statusor_test.cc index 17a3384..26d4235 100644 --- a/absl/status/statusor_test.cc +++ b/absl/status/statusor_test.cc
@@ -16,6 +16,7 @@ #include <array> #include <cstddef> +#include <cstdint> #include <initializer_list> #include <map> #include <memory> @@ -1799,4 +1800,325 @@ EXPECT_THAT(absl::StrCat(print_me), error_matcher); } +TEST(StatusOr, SupportsReferenceTypes) { + int i = 1; + absl::StatusOr<int&> s = i; + EXPECT_EQ(&i, &*s); + *s = 10; + EXPECT_EQ(i, 10); +} + +TEST(StatusOr, ReferenceFromStatus) { + int i = 10; + absl::StatusOr<int&> s = i; + s = absl::InternalError("foo"); + EXPECT_EQ(s.status().message(), "foo"); + + absl::StatusOr<int&> s2 = absl::InternalError("foo2"); + EXPECT_EQ(s2.status().message(), "foo2"); +} + +TEST(StatusOr, SupportReferenceValueConstructor) { + int i = 1; + absl::StatusOr<int&> s = i; + absl::StatusOr<const int&> cs = i; + absl::StatusOr<const int&> cs2 = std::move(i); // `T&&` to `const T&` is ok. + + EXPECT_EQ(&i, &*s); + EXPECT_EQ(&i, &*cs); + + Derived d; + absl::StatusOr<const Base1&> b = d; + EXPECT_EQ(&d, &*b); + + // We disallow constructions that cause temporaries. + EXPECT_FALSE((std::is_constructible_v<absl::StatusOr<const int&>, double>)); + EXPECT_FALSE( + (std::is_constructible_v<absl::StatusOr<const int&>, const double&>)); + EXPECT_FALSE( + (std::is_constructible_v<absl::StatusOr<const absl::string_view&>, + std::string>)); + + // We disallow constructions with wrong reference. + EXPECT_FALSE((std::is_constructible_v<absl::StatusOr<int&>, int&&>)); + EXPECT_FALSE((std::is_constructible_v<absl::StatusOr<int&>, const int&>)); +} + +TEST(StatusOr, SupportReferenceConvertingConstructor) { + int i = 1; + absl::StatusOr<int&> s = i; + absl::StatusOr<const int&> cs = s; + + EXPECT_EQ(&i, &*s); + EXPECT_EQ(&i, &*cs); + + // The other direction is not allowed. + EXPECT_FALSE((std::is_constructible_v<absl::StatusOr<int&>, + absl::StatusOr<const int&>>)); + + Derived d; + absl::StatusOr<const Base1&> b = absl::StatusOr<const Derived&>(d); + EXPECT_EQ(&d, &*b); + + // The other direction is not allowed. + EXPECT_FALSE((std::is_constructible_v<absl::StatusOr<const Derived&>, + absl::StatusOr<const Base1&>>)); + + // We disallow conversions that cause temporaries. + EXPECT_FALSE((std::is_constructible_v<absl::StatusOr<const int&>, + absl::StatusOr<int>>)); + EXPECT_FALSE((std::is_constructible_v<absl::StatusOr<const int&>, + absl::StatusOr<double>>)); + EXPECT_FALSE((std::is_constructible_v<absl::StatusOr<const int&>, + absl::StatusOr<const double&>>)); + EXPECT_FALSE((std::is_constructible_v<absl::StatusOr<const double&>, + absl::StatusOr<const int&>>)); + EXPECT_FALSE( + (std::is_constructible_v<absl::StatusOr<const absl::string_view&>, + absl::StatusOr<std::string>>)); + + // We disallow constructions with wrong reference. + EXPECT_FALSE((std::is_constructible_v<absl::StatusOr<int&>, + absl::StatusOr<const int&>>)); +} + +TEST(StatusOr, SupportReferenceValueAssignment) { + int i = 1; + absl::StatusOr<int&> s = i; + absl::StatusOr<const int&> cs; + cs = i; + absl::StatusOr<const int&> cs2; + cs2 = std::move(i); // `T&&` to `const T&` is ok. + + EXPECT_EQ(&i, &*s); + EXPECT_EQ(&i, &*cs); + + Derived d; + absl::StatusOr<const Base1&> b; + b = d; + EXPECT_EQ(&d, &*b); + + // We disallow constructions that cause temporaries. + EXPECT_FALSE((std::is_assignable_v<absl::StatusOr<const int&>, double>)); + EXPECT_FALSE( + (std::is_assignable_v<absl::StatusOr<const int&>, const double&>)); + EXPECT_FALSE((std::is_assignable_v<absl::StatusOr<const absl::string_view&>, + std::string>)); + + // We disallow constructions with wrong reference. + EXPECT_FALSE((std::is_assignable_v<absl::StatusOr<int&>, int&&>)); + EXPECT_FALSE((std::is_assignable_v<absl::StatusOr<int&>, const int&>)); +} + +TEST(StatusOr, SupportReferenceConvertingAssignment) { + int i = 1; + absl::StatusOr<int&> s; + s = i; + absl::StatusOr<const int&> cs; + cs = s; + + EXPECT_EQ(&i, &*s); + EXPECT_EQ(&i, &*cs); + + // The other direction is not allowed. + EXPECT_FALSE( + (std::is_assignable_v<absl::StatusOr<int&>, absl::StatusOr<const int&>>)); + + Derived d; + absl::StatusOr<const Base1&> b; + b = absl::StatusOr<const Derived&>(d); + EXPECT_EQ(&d, &*b); + + // The other direction is not allowed. + EXPECT_FALSE((std::is_assignable_v<absl::StatusOr<const Derived&>, + absl::StatusOr<const Base1&>>)); + + // We disallow conversions that cause temporaries. + EXPECT_FALSE((std::is_assignable_v<absl::StatusOr<const int&>, + absl::StatusOr<const double&>>)); + EXPECT_FALSE((std::is_assignable_v<absl::StatusOr<const int&>, + absl::StatusOr<double>>)); + EXPECT_FALSE((std::is_assignable_v<absl::StatusOr<const absl::string_view&>, + absl::StatusOr<std::string>>)); + + // We disallow constructions with wrong reference. + EXPECT_FALSE( + (std::is_assignable_v<absl::StatusOr<int&>, absl::StatusOr<const int&>>)); +} + +TEST(StatusOr, SupportReferenceToNonReferenceConversions) { + int i = 17; + absl::StatusOr<int&> si = i; + absl::StatusOr<float> sf = si; + EXPECT_THAT(sf, IsOkAndHolds(17.)); + + i = 20; + sf = si; + EXPECT_THAT(sf, IsOkAndHolds(20.)); + + EXPECT_THAT(absl::StatusOr<int64_t>(absl::StatusOr<int&>(i)), + IsOkAndHolds(20)); + EXPECT_THAT(absl::StatusOr<int64_t>(absl::StatusOr<const int&>(i)), + IsOkAndHolds(20)); + + std::string str = "str"; + absl::StatusOr<std::string> sos = absl::StatusOr<std::string&>(str); + EXPECT_THAT(sos, IsOkAndHolds("str")); + str = "str2"; + EXPECT_THAT(sos, IsOkAndHolds("str")); + sos = absl::StatusOr<std::string&>(str); + EXPECT_THAT(sos, IsOkAndHolds("str2")); + + absl::StatusOr<absl::string_view> sosv = absl::StatusOr<std::string&>(str); + EXPECT_THAT(sosv, IsOkAndHolds("str2")); + str = "str3"; + sosv = absl::StatusOr<std::string&>(str); + EXPECT_THAT(sosv, IsOkAndHolds("str3")); + + absl::string_view view = "view"; + // This way it is constructible, but not convertible because + // string_view->string is explicit + EXPECT_THAT( + absl::StatusOr<std::string>(absl::StatusOr<absl::string_view&>(view)), + IsOkAndHolds("view")); +#if defined(ABSL_USES_STD_STRING_VIEW) + // The assignment doesn't work with normal absl::string_view because + // std::string doesn't know about it. + sos = absl::StatusOr<absl::string_view&>(view); + EXPECT_THAT(sos, IsOkAndHolds("view")); +#endif + + EXPECT_FALSE((std::is_convertible_v<absl::StatusOr<absl::string_view&>, + absl::StatusOr<std::string>>)); +} + +TEST(StatusOr, ReferenceOperatorStarAndArrow) { + std::string str = "Foo"; + absl::StatusOr<std::string&> s = str; + s->assign("Bar"); + EXPECT_EQ(str, "Bar"); + + *s = "Baz"; + EXPECT_EQ(str, "Baz"); + + const absl::StatusOr<std::string&> cs = str; + // Even if the StatusOr is const, the reference it gives is non-const so we + // can still assign. + *cs = "Finally"; + EXPECT_EQ(str, "Finally"); + + cs->clear(); + EXPECT_EQ(cs.value(), str); + EXPECT_EQ(str, ""); +} + +TEST(StatusOr, ReferenceValueOr) { + int i = 17; + absl::StatusOr<int&> si = i; + + int other = 20; + EXPECT_EQ(&i, &si.value_or(other)); + + si = absl::UnknownError(""); + EXPECT_EQ(&other, &si.value_or(other)); + + absl::StatusOr<const int&> csi = i; + EXPECT_EQ(&i, &csi.value_or(1)); + + const auto value_or_call = [](auto&& sor, auto&& v) + -> decltype(std::forward<decltype(sor)>(sor).value_or( + std::forward<decltype(v)>(v))) {}; + using Probe = decltype(value_or_call); + // Just to verify that Probe works as expected in the good cases. + EXPECT_TRUE((std::is_invocable_v<Probe, absl::StatusOr<const int&>, int&&>)); + // Causes temporary conversion. + EXPECT_FALSE( + (std::is_invocable_v<Probe, absl::StatusOr<const int&>, double&&>)); + // Const invalid. + EXPECT_FALSE((std::is_invocable_v<Probe, absl::StatusOr<int&>, const int&>)); +} + +TEST(StatusOr, ReferenceAssignmentFromStatusOr) { + std::vector<int> v = {1, 2, 3}; + absl::StatusOr<int&> si = v[0]; + absl::StatusOr<int&> si2 = v[1]; + + EXPECT_THAT(v, ElementsAre(1, 2, 3)); + EXPECT_THAT(si, IsOkAndHolds(1)); + EXPECT_THAT(si2, IsOkAndHolds(2)); + + // This rebinds the reference. + si = si2; + EXPECT_THAT(v, ElementsAre(1, 2, 3)); + EXPECT_THAT(si, IsOkAndHolds(2)); + EXPECT_THAT(si2, IsOkAndHolds(2)); + EXPECT_EQ(&*si, &*si2); +} + +TEST(StatusOr, ReferenceAssignFromReference) { + std::vector<int> v = {1, 2, 3}; + absl::StatusOr<int&> si = v[0]; + + EXPECT_THAT(v, ElementsAre(1, 2, 3)); + EXPECT_THAT(si, IsOkAndHolds(1)); + + // This rebinds the reference. + si = v[2]; + EXPECT_THAT(v, ElementsAre(1, 2, 3)); + EXPECT_THAT(si, IsOkAndHolds(3)); + EXPECT_EQ(&*si, &v[2]); +} + +TEST(StatusOr, ReferenceIsNotLifetimeBoundForStarValue) { + int i = 0; + + // op*/value should not be LIFETIME_BOUND because the ref is not limited to + // the lifetime of the StatusOr. + int& r = *absl::StatusOr<int&>(i); + EXPECT_EQ(&r, &i); + int& r2 = absl::StatusOr<int&>(i).value(); + EXPECT_EQ(&r2, &i); + + struct S { + int i; + }; + S s; + // op-> should also not be LIFETIME_BOUND for refs. + int& r3 = absl::StatusOr<S&>(s)->i; + EXPECT_EQ(&r3, &s.i); +} + +template <typename Expected, typename T> +void TestReferenceDeref() { + static_assert(std::is_same_v<Expected, decltype(*std::declval<T>())>); + static_assert(std::is_same_v<Expected, decltype(std::declval<T>().value())>); +} + +TEST(StatusOr, ReferenceTypeIsMaintainedOnDeref) { + TestReferenceDeref<int&, absl::StatusOr<int&>&>(); + TestReferenceDeref<int&, absl::StatusOr<int&>&&>(); + TestReferenceDeref<int&, const absl::StatusOr<int&>&>(); + TestReferenceDeref<int&, const absl::StatusOr<int&>&&>(); + + TestReferenceDeref<const int&, absl::StatusOr<const int&>&>(); + TestReferenceDeref<const int&, absl::StatusOr<const int&>&&>(); + TestReferenceDeref<const int&, const absl::StatusOr<const int&>&>(); + TestReferenceDeref<const int&, const absl::StatusOr<const int&>&&>(); + + struct Struct { + int value; + }; + EXPECT_TRUE( + (std::is_same_v< + int&, decltype((std::declval<absl::StatusOr<Struct&>>()->value))>)); + EXPECT_TRUE( + (std::is_same_v< + int&, + decltype((std::declval<const absl::StatusOr<Struct&>>()->value))>)); + EXPECT_TRUE( + (std::is_same_v< + const int&, + decltype((std::declval<absl::StatusOr<const Struct&>>()->value))>)); +} + } // namespace
diff --git a/absl/strings/BUILD.bazel b/absl/strings/BUILD.bazel index bb152ac..a1e5021 100644 --- a/absl/strings/BUILD.bazel +++ b/absl/strings/BUILD.bazel
@@ -13,6 +13,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load( "//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS", @@ -33,16 +36,13 @@ cc_library( name = "string_view", - srcs = ["string_view.cc"], hdrs = ["string_view.h"], copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ - "//absl/base", "//absl/base:config", "//absl/base:core_headers", "//absl/base:nullability", - "//absl/base:throw_delegate", ], ) @@ -97,8 +97,10 @@ "string_view.h", ], deps = [ + ":append_and_overwrite", ":charset", ":internal", + ":resize_and_overwrite", ":string_view", "//absl/base", "//absl/base:config", @@ -131,6 +133,7 @@ copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ + ":resize_and_overwrite", "//absl/base:config", "//absl/base:core_headers", "//absl/base:endian", @@ -139,6 +142,60 @@ ], ) +cc_library( + name = "resize_and_overwrite", + hdrs = ["resize_and_overwrite.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:dynamic_annotations", + "//absl/base:throw_delegate", + ], +) + +cc_test( + name = "resize_and_overwrite_test", + srcs = ["resize_and_overwrite_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":resize_and_overwrite", + "//absl/base:dynamic_annotations", + "//absl/log:absl_check", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + +cc_library( + name = "append_and_overwrite", + hdrs = ["internal/append_and_overwrite.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:private"], + deps = [ + ":resize_and_overwrite", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/base:throw_delegate", + ], +) + +cc_test( + name = "append_and_overwrite_test", + srcs = ["internal/append_and_overwrite_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":append_and_overwrite", + "//absl/log:absl_check", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) + cc_test( name = "match_test", size = "small", @@ -350,23 +407,6 @@ ], ) -cc_binary( - name = "string_view_benchmark", - testonly = True, - srcs = ["string_view_benchmark.cc"], - copts = ABSL_TEST_COPTS, - tags = ["benchmark"], - visibility = ["//visibility:private"], - deps = [ - ":string_view", - ":strings", - "//absl/base:core_headers", - "//absl/base:raw_logging_internal", - "//absl/random", - "@google_benchmark//:benchmark_main", - ], -) - cc_test( name = "string_view_test", size = "small", @@ -594,11 +634,13 @@ copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, deps = [ + ":append_and_overwrite", ":cord_internal", ":cordz_info", ":cordz_update_scope", ":cordz_update_tracker", ":internal", + ":resize_and_overwrite", ":strings", "//absl/base:config", "//absl/base:core_headers", @@ -651,6 +693,7 @@ "//absl/base", "//absl/base:config", "//absl/base:core_headers", + "//absl/base:no_destructor", "//absl/base:raw_logging_internal", "//absl/container:inlined_vector", "//absl/debugging:stacktrace", @@ -1092,12 +1135,12 @@ name = "resize_uninitialized_test", size = "small", srcs = [ - "internal/resize_uninitialized.h", "internal/resize_uninitialized_test.cc", ], copts = ABSL_TEST_COPTS, visibility = ["//visibility:private"], deps = [ + ":internal", "//absl/base:core_headers", "//absl/meta:type_traits", "@googletest//:gtest", @@ -1462,6 +1505,7 @@ testonly = True, srcs = ["internal/pow10_helper.cc"], hdrs = ["internal/pow10_helper.h"], + copts = ABSL_DEFAULT_COPTS, linkopts = ABSL_DEFAULT_LINKOPTS, visibility = ["//visibility:private"], deps = ["//absl/base:config"], @@ -1507,3 +1551,43 @@ "@googletest//:gtest_main", ], ) + +cc_library( + name = "generic_printer", + srcs = [ + "internal/generic_printer.cc", + "internal/generic_printer_internal.h", + ], + hdrs = ["internal/generic_printer.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = [ + "//absl:__subpackages__", + ], + deps = [ + ":str_format", + ":strings", + "//absl/base:config", + "//absl/log/internal:container", + "//absl/meta:requires", + ], +) + +cc_test( + name = "generic_printer_test", + srcs = ["internal/generic_printer_test.cc"], + copts = ABSL_TEST_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + deps = [ + ":generic_printer", + ":strings", + "//absl/base:config", + "//absl/base:core_headers", + "//absl/container:flat_hash_map", + "//absl/log", + "//absl/status", + "//absl/status:statusor", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +)
diff --git a/absl/strings/CMakeLists.txt b/absl/strings/CMakeLists.txt index 547ef26..a03943d 100644 --- a/absl/strings/CMakeLists.txt +++ b/absl/strings/CMakeLists.txt
@@ -19,16 +19,12 @@ string_view HDRS "string_view.h" - SRCS - "string_view.cc" COPTS ${ABSL_DEFAULT_COPTS} DEPS - absl::base absl::config absl::core_headers absl::nullability - absl::throw_delegate PUBLIC ) @@ -76,7 +72,9 @@ ${ABSL_DEFAULT_COPTS} DEPS absl::string_view + absl::strings_append_and_overwrite absl::strings_internal + absl::strings_resize_and_overwrite absl::base absl::bits absl::charset @@ -138,9 +136,65 @@ absl::core_headers absl::endian absl::raw_logging_internal + absl::strings_resize_and_overwrite absl::type_traits ) +absl_cc_library( + NAME + strings_resize_and_overwrite + HDRS + "resize_and_overwrite.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::config + absl::core_headers + absl::dynamic_annotations + absl::throw_delegate +) + +absl_cc_test( + NAME + strings_resize_and_overwrite_test + SRCS + "resize_and_overwrite_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::strings_resize_and_overwrite + absl::absl_check + absl::dynamic_annotations + GTest::gmock_main +) + +absl_cc_library( + NAME + strings_append_and_overwrite + HDRS + "internal/append_and_overwrite.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::config + absl::core_headers + absl::strings_resize_and_overwrite + absl::throw_delegate +) + +absl_cc_test( + NAME + strings_append_and_overwrite_test + SRCS + "internal/append_and_overwrite_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::strings_resize_and_overwrite + absl::absl_check + GTest::gmock_main +) + absl_cc_test( NAME match_test @@ -337,11 +391,11 @@ NAME resize_uninitialized_test SRCS - "internal/resize_uninitialized.h" "internal/resize_uninitialized_test.cc" COPTS ${ABSL_TEST_COPTS} DEPS + absl::strings_internal absl::base absl::core_headers absl::type_traits @@ -663,7 +717,7 @@ SRCS "internal/pow10_helper.cc" COPTS - ${ABSL_TEST_COPTS} + ${ABSL_DEFAULT_COPTS} DEPS absl::config TESTONLY @@ -845,6 +899,7 @@ absl::cordz_statistics absl::cordz_update_tracker absl::core_headers + absl::no_destructor absl::inlined_vector absl::span absl::raw_logging_internal @@ -1000,6 +1055,8 @@ absl::raw_logging_internal absl::span absl::strings + absl::strings_append_and_overwrite + absl::strings_resize_and_overwrite absl::type_traits absl::weakly_mixed_integer PUBLIC @@ -1199,3 +1256,40 @@ absl::strings GTest::gmock_main ) + +absl_cc_library( + NAME + generic_printer_internal + SRCS + "internal/generic_printer.cc" + "internal/generic_printer_internal.h" + HDRS + "internal/generic_printer.h" + COPTS + ${ABSL_DEFAULT_COPTS} + DEPS + absl::config + absl::strings + absl::str_format + absl::log_internal_container + absl::requires_internal +) + +absl_cc_test( + NAME + generic_printer_test + SRCS + "internal/generic_printer_test.cc" + COPTS + ${ABSL_TEST_COPTS} + DEPS + absl::base + absl::config + absl::flat_hash_map + absl::generic_printer_internal + absl::log + absl::status + absl::statusor + absl::strings + GTest::gmock_main +)
diff --git a/absl/strings/ascii.h b/absl/strings/ascii.h index ca0747e..6a75b1f 100644 --- a/absl/strings/ascii.h +++ b/absl/strings/ascii.h
@@ -61,6 +61,7 @@ #include "absl/base/config.h" #include "absl/base/nullability.h" #include "absl/strings/internal/resize_uninitialized.h" +#include "absl/strings/resize_and_overwrite.h" #include "absl/strings/string_view.h" namespace absl { @@ -190,8 +191,10 @@ // Creates a lowercase string from a given absl::string_view. [[nodiscard]] inline std::string AsciiStrToLower(absl::string_view s) { std::string result; - strings_internal::STLStringResizeUninitialized(&result, s.size()); - ascii_internal::AsciiStrToLower(&result[0], s.data(), s.size()); + StringResizeAndOverwrite(result, s.size(), [s](char* buf, size_t buf_size) { + ascii_internal::AsciiStrToLower(buf, s.data(), s.size()); + return buf_size; + }); return result; } @@ -219,8 +222,10 @@ // Creates an uppercase string from a given absl::string_view. [[nodiscard]] inline std::string AsciiStrToUpper(absl::string_view s) { std::string result; - strings_internal::STLStringResizeUninitialized(&result, s.size()); - ascii_internal::AsciiStrToUpper(&result[0], s.data(), s.size()); + StringResizeAndOverwrite(result, s.size(), [s](char* buf, size_t buf_size) { + ascii_internal::AsciiStrToUpper(buf, s.data(), s.size()); + return buf_size; + }); return result; }
diff --git a/absl/strings/cord.cc b/absl/strings/cord.cc index e53f914..d3014f3 100644 --- a/absl/strings/cord.cc +++ b/absl/strings/cord.cc
@@ -44,14 +44,15 @@ #include "absl/functional/function_ref.h" #include "absl/strings/cord_buffer.h" #include "absl/strings/escaping.h" +#include "absl/strings/internal/append_and_overwrite.h" #include "absl/strings/internal/cord_data_edge.h" #include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cord_rep_btree.h" #include "absl/strings/internal/cord_rep_crc.h" #include "absl/strings/internal/cord_rep_flat.h" #include "absl/strings/internal/cordz_update_tracker.h" -#include "absl/strings/internal/resize_uninitialized.h" #include "absl/strings/match.h" +#include "absl/strings/resize_and_overwrite.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" #include "absl/strings/strip.h" @@ -161,8 +162,10 @@ // -------------------------------------------------------------------- // Cord::InlineRep functions -inline void Cord::InlineRep::set_data(const char* absl_nonnull data, size_t n) { +inline void Cord::InlineRep::set_data(const char* absl_nullable data, + size_t n) { static_assert(kMaxInline == 15, "set_data is hard-coded for a length of 15"); + assert(data != nullptr || n == 0); data_.set_inline_data(data, n); } @@ -1053,18 +1056,20 @@ if (!src.contents_.is_tree()) { src.contents_.CopyTo(dst); } else { - absl::strings_internal::STLStringResizeUninitialized(dst, src.size()); - src.CopyToArraySlowPath(&(*dst)[0]); + StringResizeAndOverwrite(*dst, src.size(), + [&src](char* buf, size_t buf_size) { + src.CopyToArraySlowPath(buf); + return buf_size; + }); } } void AppendCordToString(const Cord& src, std::string* absl_nonnull dst) { - const size_t cur_dst_size = dst->size(); - const size_t new_dst_size = cur_dst_size + src.size(); - absl::strings_internal::STLStringResizeUninitializedAmortized(dst, - new_dst_size); - char* append_ptr = &(*dst)[cur_dst_size]; - src.CopyToArrayImpl(append_ptr); + strings_internal::StringAppendAndOverwrite( + *dst, src.size(), [&src](char* buf, size_t buf_size) { + src.CopyToArrayImpl(buf); + return buf_size; + }); } void Cord::CopyToArraySlowPath(char* absl_nonnull dst) const {
diff --git a/absl/strings/cord.h b/absl/strings/cord.h index 7afa419..fa6eb8a 100644 --- a/absl/strings/cord.h +++ b/absl/strings/cord.h
@@ -755,7 +755,7 @@ // NOTE: This routine is reasonably efficient. It is roughly // logarithmic based on the number of chunks that make up the cord. Still, // if you need to iterate over the contents of a cord, you should - // use a CharIterator/ChunkIterator rather than call operator[] or Get() + // use a CharIterator/ChunkIterator rather than call operator[] // repeatedly in a loop. char operator[](size_t i) const; @@ -921,7 +921,7 @@ // Returns nullptr if holding pointer const char* absl_nullable data() const; // Discards pointer, if any - void set_data(const char* absl_nonnull data, size_t n); + void set_data(const char* absl_nullable data, size_t n); char* absl_nonnull set_data(size_t n); // Write data to the result // Returns nullptr if holding bytes absl::cord_internal::CordRep* absl_nullable tree() const; @@ -1098,8 +1098,7 @@ hash_state = combiner.add_buffer(std::move(hash_state), chunk.data(), chunk.size()); }); - return H::combine(combiner.finalize(std::move(hash_state)), - hash_internal::WeaklyMixedInteger{size()}); + return combiner.finalize(std::move(hash_state)); } friend class CrcCord;
diff --git a/absl/strings/escaping.cc b/absl/strings/escaping.cc index e551c66..228e527 100644 --- a/absl/strings/escaping.cc +++ b/absl/strings/escaping.cc
@@ -28,13 +28,15 @@ #include "absl/base/internal/endian.h" #include "absl/base/internal/raw_logging.h" #include "absl/base/internal/unaligned_access.h" +#include "absl/base/macros.h" #include "absl/base/nullability.h" #include "absl/strings/ascii.h" #include "absl/strings/charset.h" +#include "absl/strings/internal/append_and_overwrite.h" #include "absl/strings/internal/escaping.h" -#include "absl/strings/internal/resize_uninitialized.h" #include "absl/strings/internal/utf8.h" #include "absl/strings/numbers.h" +#include "absl/strings/resize_and_overwrite.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" @@ -76,30 +78,31 @@ // // Unescapes C escape sequences and is the reverse of CEscape(). // -// If `src` is valid, stores the unescaped string `dst`, and returns -// true. Otherwise returns false and optionally stores the error -// description in `error`. Set `error` to nullptr to disable error -// reporting. +// If `src` is valid, stores the unescaped string in `dst` and the length of +// unescaped string in `dst_size`, and returns true. Otherwise returns false +// and optionally stores the error description in `error`. Set `error` to +// nullptr to disable error reporting. // -// `src` and `dst` may use the same underlying buffer. +// `src` and `dst` may use the same underlying buffer (but keep in mind +// that if this returns an error, it will leave both `src` and `dst` in +// an unspecified state because they are using the same underlying buffer.) +// `dst` must have at least as much space as `src`. // ---------------------------------------------------------------------- bool CUnescapeInternal(absl::string_view src, bool leave_nulls_escaped, - std::string* absl_nonnull dst, + char* absl_nonnull dst, size_t* absl_nonnull dst_size, std::string* absl_nullable error) { - strings_internal::STLStringResizeUninitialized(dst, src.size()); - absl::string_view::size_type p = 0; // Current src position. - std::string::size_type d = 0; // Current dst position. + size_t d = 0; // Current dst position. // When unescaping in-place, skip any prefix that does not have escaping. - if (src.data() == dst->data()) { + if (src.data() == dst) { while (p < src.size() && src[p] != '\\') p++, d++; } while (p < src.size()) { if (src[p] != '\\') { - (*dst)[d++] = src[p++]; + dst[d++] = src[p++]; } else { if (++p >= src.size()) { // skip past the '\\' if (error != nullptr) { @@ -108,17 +111,19 @@ return false; } switch (src[p]) { - case 'a': (*dst)[d++] = '\a'; break; - case 'b': (*dst)[d++] = '\b'; break; - case 'f': (*dst)[d++] = '\f'; break; - case 'n': (*dst)[d++] = '\n'; break; - case 'r': (*dst)[d++] = '\r'; break; - case 't': (*dst)[d++] = '\t'; break; - case 'v': (*dst)[d++] = '\v'; break; - case '\\': (*dst)[d++] = '\\'; break; - case '?': (*dst)[d++] = '\?'; break; - case '\'': (*dst)[d++] = '\''; break; - case '"': (*dst)[d++] = '\"'; break; + // clang-format off + case 'a': dst[d++] = '\a'; break; + case 'b': dst[d++] = '\b'; break; + case 'f': dst[d++] = '\f'; break; + case 'n': dst[d++] = '\n'; break; + case 'r': dst[d++] = '\r'; break; + case 't': dst[d++] = '\t'; break; + case 'v': dst[d++] = '\v'; break; + case '\\': dst[d++] = '\\'; break; + case '?': dst[d++] = '\?'; break; + case '\'': dst[d++] = '\''; break; + case '"': dst[d++] = '\"'; break; + // clang-format on case '0': case '1': case '2': @@ -145,13 +150,13 @@ } if ((ch == 0) && leave_nulls_escaped) { // Copy the escape sequence for the null character - (*dst)[d++] = '\\'; + dst[d++] = '\\'; while (octal_start <= p) { - (*dst)[d++] = src[octal_start++]; + dst[d++] = src[octal_start++]; } break; } - (*dst)[d++] = static_cast<char>(ch); + dst[d++] = static_cast<char>(ch); break; } case 'x': @@ -185,13 +190,13 @@ } if ((ch == 0) && leave_nulls_escaped) { // Copy the escape sequence for the null character - (*dst)[d++] = '\\'; + dst[d++] = '\\'; while (hex_start <= p) { - (*dst)[d++] = src[hex_start++]; + dst[d++] = src[hex_start++]; } break; } - (*dst)[d++] = static_cast<char>(ch); + dst[d++] = static_cast<char>(ch); break; } case 'u': { @@ -218,16 +223,16 @@ } if ((rune == 0) && leave_nulls_escaped) { // Copy the escape sequence for the null character - (*dst)[d++] = '\\'; + dst[d++] = '\\'; while (hex_start <= p) { - (*dst)[d++] = src[hex_start++]; + dst[d++] = src[hex_start++]; } break; } if (IsSurrogate(rune, src.substr(hex_start, 5), error)) { return false; } - d += strings_internal::EncodeUTF8Char(dst->data() + d, rune); + d += strings_internal::EncodeUTF8Char(dst + d, rune); break; } case 'U': { @@ -267,17 +272,17 @@ } if ((rune == 0) && leave_nulls_escaped) { // Copy the escape sequence for the null character - (*dst)[d++] = '\\'; + dst[d++] = '\\'; // U00000000 while (hex_start <= p) { - (*dst)[d++] = src[hex_start++]; + dst[d++] = src[hex_start++]; } break; } if (IsSurrogate(rune, src.substr(hex_start, 9), error)) { return false; } - d += strings_internal::EncodeUTF8Char(dst->data() + d, rune); + d += strings_internal::EncodeUTF8Char(dst + d, rune); break; } default: { @@ -291,7 +296,7 @@ } } - dst->erase(d); + *dst_size = d; return true; } @@ -441,22 +446,22 @@ // We keep 3 slop bytes so that we can call `little_endian::Store32` // invariably regardless of the length of the escaped character. - constexpr size_t slop_bytes = 3; + constexpr size_t kSlopBytes = 3; size_t cur_dest_len = dest->size(); - size_t new_dest_len = cur_dest_len + escaped_len + slop_bytes; - ABSL_INTERNAL_CHECK(new_dest_len > cur_dest_len, "std::string size overflow"); - strings_internal::AppendUninitializedTraits<std::string>::Append( - dest, escaped_len + slop_bytes); - char* append_ptr = &(*dest)[cur_dest_len]; - - for (char c : src) { - unsigned char uc = static_cast<unsigned char>(c); - size_t char_len = kCEscapedLen[uc]; - uint32_t little_endian_uint32 = kCEscapedLittleEndianUint32Array[uc]; - little_endian::Store32(append_ptr, little_endian_uint32); - append_ptr += char_len; - } - dest->resize(new_dest_len - slop_bytes); + size_t append_buf_len = cur_dest_len + escaped_len + kSlopBytes; + ABSL_INTERNAL_CHECK(append_buf_len > cur_dest_len, + "std::string size overflow"); + strings_internal::StringAppendAndOverwrite( + *dest, append_buf_len, [src, escaped_len](char* append_ptr, size_t) { + for (char c : src) { + unsigned char uc = static_cast<unsigned char>(c); + size_t char_len = kCEscapedLen[uc]; + uint32_t little_endian_uint32 = kCEscapedLittleEndianUint32Array[uc]; + little_endian::Store32(append_ptr, little_endian_uint32); + append_ptr += char_len; + } + return escaped_len; + }); } // Reverses the mapping in Base64EscapeInternal; see that method's @@ -807,27 +812,22 @@ // 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); - - // We are getting the destination buffer by getting the beginning of the - // string and converting it into a char *. - size_t len; - const bool ok = - Base64UnescapeInternal(src, slen, &(*dest)[0], dest_len, unbase64, &len); - if (!ok) { - dest->clear(); - return false; - } - - // could be shorter if there was padding - assert(len <= dest_len); - dest->erase(len); - - return true; + bool ok; + StringResizeAndOverwrite( + *dest, dest_len, [src, slen, unbase64, &ok](char* buf, size_t buf_size) { + size_t len; + ok = Base64UnescapeInternal(src, slen, buf, buf_size, unbase64, &len); + if (!ok) { + len = 0; + } + assert(len <= buf_size); // Could be shorter if there was padding. + return len; + }); + return ok; } /* clang-format off */ -constexpr std::array<char, 256> kHexValueLenient = { +constexpr std::array<uint8_t, 256> kHexValueLenient = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -846,7 +846,7 @@ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, }; -constexpr std::array<signed char, 256> kHexValueStrict = { +constexpr std::array<int8_t, 256> kHexValueStrict = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, @@ -874,19 +874,15 @@ 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]); + static_cast<char>(kHexValueLenient[from[i * 2 + 1] & 0xFF]); } } -// 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* absl_nullable 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) { +void BytesToHexStringInternal(const unsigned char* absl_nullable src, + char* dest, size_t num) { + for (auto src_ptr = src; src_ptr != (src + num); ++src_ptr, dest += 2) { const char* hex_p = &numbers_internal::kHexTable[*src_ptr * 2]; - std::copy(hex_p, hex_p + 2, dest_ptr); + std::copy(hex_p, hex_p + 2, dest); } } @@ -897,9 +893,35 @@ // // See CUnescapeInternal() for implementation details. // ---------------------------------------------------------------------- + bool CUnescape(absl::string_view source, std::string* absl_nonnull dest, std::string* absl_nullable error) { - return CUnescapeInternal(source, kUnescapeNulls, dest, error); + bool success; + + // `CUnescape()` allows for in-place unescaping, which means `source` may + // alias `*dest`. However, absl::StringResizeAndOverwrite() invalidates all + // iterators, pointers, and references into the string, regardless whether + // reallocation occurs. Therefore we need to avoid calling + // absl::StringResizeAndOverwrite() when `source.data() == + // dest->data()`. Comparing the sizes is sufficient to cover this case. + if (dest->size() >= source.size()) { + size_t dest_size = 0; + success = CUnescapeInternal(source, kUnescapeNulls, dest->data(), + &dest_size, error); + ABSL_ASSERT(dest_size <= dest->size()); + dest->erase(dest_size); + } else { + StringResizeAndOverwrite( + *dest, source.size(), + [source, error, &success](char* buf, size_t buf_size) { + size_t dest_size = 0; + success = + CUnescapeInternal(source, kUnescapeNulls, buf, &dest_size, error); + ABSL_ASSERT(dest_size <= buf_size); + return dest_size; + }); + } + return success; } std::string CEscape(absl::string_view src) { @@ -966,19 +988,25 @@ return false; } - absl::strings_internal::STLStringResizeUninitialized(&output, num_bytes); - auto hex_p = hex.cbegin(); - for (std::string::iterator bin_p = output.begin(); bin_p != output.end(); - ++bin_p) { - int h1 = absl::kHexValueStrict[static_cast<size_t>(*hex_p++)]; - int h2 = absl::kHexValueStrict[static_cast<size_t>(*hex_p++)]; - if (h1 == -1 || h2 == -1) { - output.resize(static_cast<size_t>(bin_p - output.begin())); - return false; - } - *bin_p = static_cast<char>((h1 << 4) + h2); - } + StringResizeAndOverwrite( + output, num_bytes, [hex](char* buf, size_t buf_size) { + auto hex_p = hex.cbegin(); + for (size_t i = 0; i < buf_size; ++i) { + int h1 = absl::kHexValueStrict[static_cast<size_t>( + static_cast<uint8_t>(*hex_p++))]; + int h2 = absl::kHexValueStrict[static_cast<size_t>( + static_cast<uint8_t>(*hex_p++))]; + if (h1 == -1 || h2 == -1) { + return size_t{0}; + } + buf[i] = static_cast<char>((h1 << 4) + h2); + } + return buf_size; + }); + if (output.size() != num_bytes) { + return false; + } *bytes = std::move(output); return true; } @@ -986,16 +1014,22 @@ std::string HexStringToBytes(absl::string_view from) { std::string result; const auto num = from.size() / 2; - strings_internal::STLStringResizeUninitialized(&result, num); - absl::HexStringToBytesInternal<std::string&>(from.data(), result, num); + StringResizeAndOverwrite(result, num, [from](char* buf, size_t buf_size) { + absl::HexStringToBytesInternal<char*>(from.data(), buf, buf_size); + return buf_size; + }); return result; } std::string BytesToHexString(absl::string_view from) { std::string result; - strings_internal::STLStringResizeUninitialized(&result, 2 * from.size()); - absl::BytesToHexStringInternal<std::string&>( - reinterpret_cast<const unsigned char*>(from.data()), result, from.size()); + StringResizeAndOverwrite( + result, 2 * from.size(), [from](char* buf, size_t buf_size) { + absl::BytesToHexStringInternal( + reinterpret_cast<const unsigned char*>(from.data()), buf, + from.size()); + return buf_size; + }); return result; }
diff --git a/absl/strings/escaping_test.cc b/absl/strings/escaping_test.cc index 4786c88..08618aa 100644 --- a/absl/strings/escaping_test.cc +++ b/absl/strings/escaping_test.cc
@@ -733,6 +733,10 @@ bytes = "abc"; EXPECT_TRUE(absl::HexStringToBytes("", &bytes)); EXPECT_EQ("", bytes); // Results in empty output. + + // Ensure there is no sign extension bug on a signed char. + hex.assign("\xC8" "b", 2); + EXPECT_FALSE(absl::HexStringToBytes(hex, &bytes)); } TEST(HexAndBack, HexStringToBytes_and_BytesToHexString) {
diff --git a/absl/strings/internal/append_and_overwrite.h b/absl/strings/internal/append_and_overwrite.h new file mode 100644 index 0000000..9dec73b --- /dev/null +++ b/absl/strings/internal/append_and_overwrite.h
@@ -0,0 +1,93 @@ +// Copyright 2025 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_APPEND_AND_OVERWRITE_H_ +#define ABSL_STRINGS_INTERNAL_APPEND_AND_OVERWRITE_H_ + +#include "absl/base/config.h" +#include "absl/base/internal/throw_delegate.h" +#include "absl/base/macros.h" +#include "absl/base/optimization.h" +#include "absl/strings/resize_and_overwrite.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace strings_internal { + +// An internal-only variant similar to `absl::StringResizeAndOverwrite()` +// optimized for repeated appends to a string that uses exponential growth so +// that the amortized complexity of increasing the string size by a small amount +// is O(1), in contrast to O(str.size()) in the case of precise growth. Use of +// this function is subtle; see https://reviews.llvm.org/D102727 to understand +// the tradeoffs. +// +// Appends at most `append_n` characters to `str`, using the user-provided +// operation `append_op` to modify the possibly indeterminate +// contents. `append_op` must return the length of the buffer appended to `str`. +// +// Invalidates all iterators, pointers, and references into `str`, regardless +// of whether reallocation occurs. +// +// `append_op(value_type* buf, size_t buf_size)` is allowed to write +// `value_type{}` to `buf[buf_size]`, which facilitiates interoperation with +// functions that write a trailing NUL. +template <typename T, typename Op> +void StringAppendAndOverwrite(T& str, typename T::size_type append_n, + Op append_op) { + if (ABSL_PREDICT_FALSE(append_n > str.max_size() - str.size())) { + absl::base_internal::ThrowStdLengthError( + "absl::strings_internal::StringAppendAndOverwrite"); + } + + auto old_size = str.size(); + auto resize = old_size + append_n; + + if (resize > str.capacity()) { + // Make sure to always grow by at least a factor of 2x. + const auto min_growth = str.capacity(); + if (ABSL_PREDICT_FALSE(str.capacity() > str.max_size() - min_growth)) { + resize = str.max_size(); + } else if (resize < str.capacity() + min_growth) { + resize = str.capacity() + min_growth; + } + } else { + resize = str.capacity(); + } + + // Avoid calling StringResizeAndOverwrite() here since it does an MSAN + // verification on the entire string. StringResizeAndOverwriteImpl() is + // StringResizeAndOverwrite() without the MSAN verification. + StringResizeAndOverwriteImpl( + str, resize, + [old_size, append_n, do_append = std::move(append_op)]( + typename T::value_type* data_ptr, typename T::size_type) mutable { + auto num_appended = + std::move(do_append)(data_ptr + old_size, append_n); + ABSL_HARDENING_ASSERT(num_appended >= 0 && num_appended <= append_n); + return old_size + num_appended; + }); + +#if defined(ABSL_HAVE_MEMORY_SANITIZER) + // Only check the region appended to. Checking the entire string would cause + // pathological quadratic verfication on repeated small appends. + __msan_check_mem_is_initialized(str.data() + old_size, str.size() - old_size); +#endif +} + +} // namespace strings_internal +ABSL_NAMESPACE_END +} // namespace absl + + +#endif // ABSL_STRINGS_INTERNAL_APPEND_AND_OVERWRITE_H_
diff --git a/absl/strings/internal/append_and_overwrite_test.cc b/absl/strings/internal/append_and_overwrite_test.cc new file mode 100644 index 0000000..aa9c7a1 --- /dev/null +++ b/absl/strings/internal/append_and_overwrite_test.cc
@@ -0,0 +1,95 @@ +// Copyright 2025 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/append_and_overwrite.h" + +#include <algorithm> +#include <cstddef> +#include <string> + +#include "gtest/gtest.h" +#include "absl/log/absl_check.h" + +namespace { + +struct AppendAndOverwriteParam { + size_t initial_size; + size_t append_capacity; + size_t append_size; +}; + +using StringAppendAndOverwriteTest = + ::testing::TestWithParam<AppendAndOverwriteParam>; + +TEST_P(StringAppendAndOverwriteTest, StringAppendAndOverwrite) { + const auto& param = GetParam(); + std::string s(param.initial_size, 'a'); + absl::strings_internal::StringAppendAndOverwrite( + s, param.append_capacity, [&](char* p, size_t n) { + ABSL_CHECK_EQ(n, param.append_capacity); + std::fill_n(p, param.append_size, 'b'); + p[param.append_size] = '\0'; + return param.append_size; + }); + + std::string expected = + std::string(param.initial_size, 'a') + + std::string(param.append_size, + 'b'); + + EXPECT_EQ(s, expected); + EXPECT_EQ(s.c_str()[s.size()], '\0'); +} + +// clang-format off +INSTANTIATE_TEST_SUITE_P(StringAppendAndOverwriteTestSuite, + StringAppendAndOverwriteTest, + ::testing::ValuesIn<AppendAndOverwriteParam>({ + {0, 10, 5}, + {10, 10, 10}, + {10, 15, 15}, + {10, 20, 15}, + {10, 40, 40}, + {10, 50, 40}, + {30, 35, 35}, + {30, 45, 35}, + {10, 30, 15}, + })); +// clang-format on + +TEST(StringAppendAndOverwrite, AmortizedComplexity) { + std::string str; + std::string expected; + size_t prev_cap = str.capacity(); + int cap_increase_count = 0; + for (int i = 0; i < 1000; ++i) { + char c = static_cast<char>('a' + (i % 26)); + absl::strings_internal::StringAppendAndOverwrite( + str, 1, [c](char* buf, size_t buf_size) { + ABSL_CHECK_EQ(buf_size, 1); + buf[0] = c; + return size_t{1}; + }); + expected.push_back(c); + EXPECT_EQ(str, expected); + size_t new_cap = str.capacity(); + if (new_cap > prev_cap) { + ++cap_increase_count; + } + prev_cap = new_cap; + } + EXPECT_LT(cap_increase_count, 50); +} + +} // namespace
diff --git a/absl/strings/internal/cord_internal.h b/absl/strings/internal/cord_internal.h index cf1f703..6637561 100644 --- a/absl/strings/internal/cord_internal.h +++ b/absl/strings/internal/cord_internal.h
@@ -381,6 +381,8 @@ this->releaser_invoker = &Release; } + const Releaser* releaser() const { return &this->template get<0>(); } + ~CordRepExternalImpl() { InvokeReleaser(Rank1{}, std::move(this->template get<0>()), absl::string_view(base, length)); @@ -915,8 +917,6 @@ inline void CordRep::Unref(CordRep* rep) { assert(rep != nullptr); - // Expect refcount to be 0. Avoiding the cost of an atomic decrement should - // typically outweigh the cost of an extra branch checking for ref == 1. if (ABSL_PREDICT_FALSE(!rep->refcount.DecrementExpectHighRefcount())) { Destroy(rep); }
diff --git a/absl/strings/internal/cord_rep_btree_test.cc b/absl/strings/internal/cord_rep_btree_test.cc index 840acf9..0950617 100644 --- a/absl/strings/internal/cord_rep_btree_test.cc +++ b/absl/strings/internal/cord_rep_btree_test.cc
@@ -1230,16 +1230,16 @@ if (api != 3) { // Does not contain contents - EXPECT_THAT(str, Not(AnyOf((HasSubstr("data = \"Hello world\""), - HasSubstr("data = \"Hello external\""), - HasSubstr("data = \"ello w\""), - HasSubstr("data = \"llo ext\""))))); + EXPECT_THAT(str, Not(AnyOf(HasSubstr("data = \"Hello world\""), + HasSubstr("data = \"Hello external\""), + HasSubstr("data = \"ello w\""), + HasSubstr("data = \"llo ext\"")))); } else { // Contains contents - EXPECT_THAT(str, AllOf((HasSubstr("data = \"Hello world\""), - HasSubstr("data = \"Hello external\""), - HasSubstr("data = \"ello w\""), - HasSubstr("data = \"llo ext\"")))); + EXPECT_THAT(str, AllOf(HasSubstr("data = \"Hello world\""), + HasSubstr("data = \"Hello external\""), + HasSubstr("data = \"ello w\""), + HasSubstr("data = \"llo ext\""))); } }
diff --git a/absl/strings/internal/cord_rep_flat.h b/absl/strings/internal/cord_rep_flat.h index 27c4b21..bf999fb 100644 --- a/absl/strings/internal/cord_rep_flat.h +++ b/absl/strings/internal/cord_rep_flat.h
@@ -20,6 +20,7 @@ #include <cstdint> #include <memory> +#include "absl/base/attributes.h" #include "absl/base/config.h" #include "absl/base/macros.h" #include "absl/strings/internal/cord_internal.h" @@ -75,6 +76,9 @@ // RoundUp logically performs `((n + m - 1) / m) * m` to round up to the nearest // multiple of `m`, optimized for the invariant that `m` is a power of 2. +// Android local modification: disable unsigned integer overflow sanitizer here +// to support enabling it in the clients. +ABSL_ATTRIBUTE_NO_SANITIZE_UNSIGNED_OVERFLOW constexpr size_t RoundUp(size_t n, size_t m) { return (n + m - 1) & (0 - m); }
diff --git a/absl/strings/internal/cordz_handle.cc b/absl/strings/internal/cordz_handle.cc index 53d5f52..a4f47f0 100644 --- a/absl/strings/internal/cordz_handle.cc +++ b/absl/strings/internal/cordz_handle.cc
@@ -54,7 +54,7 @@ CordzHandle::CordzHandle(bool is_snapshot) : is_snapshot_(is_snapshot) { Queue& global_queue = GlobalQueue(); if (is_snapshot) { - MutexLock lock(&global_queue.mutex); + MutexLock lock(global_queue.mutex); CordzHandle* dq_tail = global_queue.dq_tail.load(std::memory_order_acquire); if (dq_tail != nullptr) { dq_prev_ = dq_tail; @@ -69,7 +69,7 @@ if (is_snapshot_) { std::vector<CordzHandle*> to_delete; { - MutexLock lock(&global_queue.mutex); + MutexLock lock(global_queue.mutex); CordzHandle* next = dq_next_; if (dq_prev_ == nullptr) { // We were head of the queue, delete every CordzHandle until we reach @@ -103,7 +103,7 @@ if (handle) { Queue& queue = GlobalQueue(); if (!handle->SafeToDelete()) { - MutexLock lock(&queue.mutex); + MutexLock lock(queue.mutex); CordzHandle* dq_tail = queue.dq_tail.load(std::memory_order_acquire); if (dq_tail != nullptr) { handle->dq_prev_ = dq_tail; @@ -119,7 +119,7 @@ std::vector<const CordzHandle*> CordzHandle::DiagnosticsGetDeleteQueue() { std::vector<const CordzHandle*> handles; Queue& global_queue = GlobalQueue(); - MutexLock lock(&global_queue.mutex); + MutexLock lock(global_queue.mutex); CordzHandle* dq_tail = global_queue.dq_tail.load(std::memory_order_acquire); for (const CordzHandle* p = dq_tail; p; p = p->dq_prev_) { handles.push_back(p); @@ -134,7 +134,7 @@ if (handle->is_snapshot_) return false; bool snapshot_found = false; Queue& global_queue = GlobalQueue(); - MutexLock lock(&global_queue.mutex); + MutexLock lock(global_queue.mutex); for (const CordzHandle* p = global_queue.dq_tail; p; p = p->dq_prev_) { if (p == handle) return !snapshot_found; if (p == this) snapshot_found = true; @@ -151,7 +151,7 @@ } Queue& global_queue = GlobalQueue(); - MutexLock lock(&global_queue.mutex); + MutexLock lock(global_queue.mutex); for (const CordzHandle* p = dq_next_; p != nullptr; p = p->dq_next_) { if (!p->is_snapshot()) { handles.push_back(p);
diff --git a/absl/strings/internal/cordz_info.cc b/absl/strings/internal/cordz_info.cc index 4baaecd..38fb473 100644 --- a/absl/strings/internal/cordz_info.cc +++ b/absl/strings/internal/cordz_info.cc
@@ -17,7 +17,8 @@ #include <cstdint> #include "absl/base/config.h" -#include "absl/base/internal/spinlock.h" +#include "absl/base/const_init.h" +#include "absl/base/no_destructor.h" #include "absl/container/inlined_vector.h" #include "absl/debugging/stacktrace.h" #include "absl/strings/internal/cord_internal.h" @@ -34,8 +35,6 @@ ABSL_NAMESPACE_BEGIN namespace cord_internal { -ABSL_CONST_INIT CordzInfo::List CordzInfo::global_list_{absl::kConstInit}; - namespace { // CordRepAnalyzer performs the analysis of a cord. @@ -221,24 +220,32 @@ } // namespace +CordzInfo::List* CordzInfo::GlobalList() { + static absl::NoDestructor<CordzInfo::List> list; + return list.get(); +} + CordzInfo* CordzInfo::Head(const CordzSnapshot& snapshot) { ABSL_ASSERT(snapshot.is_snapshot()); - // We can do an 'unsafe' load of 'head', as we are guaranteed that the - // instance it points to is kept alive by the provided CordzSnapshot, so we - // can simply return the current value using an acquire load. + // We obtain the lock here as we must synchronize the first call into the list + // with any concurrent 'Untrack()` operation to avoid any read in the list to + // reorder before the observation of the thread 'untracking a cord' of the + // delete queue being empty or not. After this all next observations are safe + // as we have established all subsequent untracks will be queued for delete. // We do enforce in DEBUG builds that the 'head' value is present in the - // delete queue: ODR violations may lead to 'snapshot' and 'global_list_' + // delete queue: ODR violations may lead to 'snapshot' and 'global_list' // being in different libraries / modules. - CordzInfo* head = global_list_.head.load(std::memory_order_acquire); - ABSL_ASSERT(snapshot.DiagnosticsHandleIsSafeToInspect(head)); - return head; + auto global_list = GlobalList(); + absl::MutexLock l(global_list->mutex); + ABSL_ASSERT(snapshot.DiagnosticsHandleIsSafeToInspect(global_list->head)); + return global_list->head; } CordzInfo* CordzInfo::Next(const CordzSnapshot& snapshot) const { ABSL_ASSERT(snapshot.is_snapshot()); - // Similar to the 'Head()' function, we do not need a mutex here. + // We do not need a lock here. See also comments in Head(). CordzInfo* next = ci_next_.load(std::memory_order_acquire); ABSL_ASSERT(snapshot.DiagnosticsHandleIsSafeToInspect(this)); ABSL_ASSERT(snapshot.DiagnosticsHandleIsSafeToInspect(next)); @@ -327,22 +334,21 @@ } void CordzInfo::Track() { - SpinLockHolder l(&list_->mutex); - - CordzInfo* const head = list_->head.load(std::memory_order_acquire); + absl::MutexLock l(list_->mutex); + CordzInfo* const head = list_->head; if (head != nullptr) { head->ci_prev_.store(this, std::memory_order_release); } ci_next_.store(head, std::memory_order_release); - list_->head.store(this, std::memory_order_release); + list_->head = this; } void CordzInfo::Untrack() { ODRCheck(); { - SpinLockHolder l(&list_->mutex); + absl::MutexLock l(list_->mutex); - CordzInfo* const head = list_->head.load(std::memory_order_acquire); + CordzInfo* const head = list_->head; CordzInfo* const next = ci_next_.load(std::memory_order_acquire); CordzInfo* const prev = ci_prev_.load(std::memory_order_acquire); @@ -356,7 +362,7 @@ prev->ci_next_.store(next, std::memory_order_release); } else { ABSL_ASSERT(head == this); - list_->head.store(next, std::memory_order_release); + list_->head = next; } } @@ -370,7 +376,7 @@ // We are likely part of a snapshot, extend the life of the CordRep { - absl::MutexLock lock(&mutex_); + absl::MutexLock lock(mutex_); if (rep_) CordRep::Ref(rep_); } CordzHandle::Delete(this); @@ -378,14 +384,14 @@ void CordzInfo::Lock(MethodIdentifier method) ABSL_EXCLUSIVE_LOCK_FUNCTION(mutex_) { - mutex_.Lock(); + mutex_.lock(); update_tracker_.LossyAdd(method); assert(rep_); } void CordzInfo::Unlock() ABSL_UNLOCK_FUNCTION(mutex_) { bool tracked = rep_ != nullptr; - mutex_.Unlock(); + mutex_.unlock(); if (!tracked) { Untrack(); }
diff --git a/absl/strings/internal/cordz_info.h b/absl/strings/internal/cordz_info.h index aa92a8f..c03f2f2 100644 --- a/absl/strings/internal/cordz_info.h +++ b/absl/strings/internal/cordz_info.h
@@ -20,8 +20,8 @@ #include <functional> #include "absl/base/config.h" +#include "absl/base/const_init.h" #include "absl/base/internal/raw_logging.h" -#include "absl/base/internal/spinlock.h" #include "absl/base/thread_annotations.h" #include "absl/strings/internal/cord_internal.h" #include "absl/strings/internal/cordz_functions.h" @@ -121,12 +121,10 @@ CordzInfo& operator=(const CordzInfo&) = delete; // Retrieves the oldest existing CordzInfo. - static CordzInfo* Head(const CordzSnapshot& snapshot) - ABSL_NO_THREAD_SAFETY_ANALYSIS; + static CordzInfo* Head(const CordzSnapshot& snapshot); // Retrieves the next oldest existing CordzInfo older than 'this' instance. - CordzInfo* Next(const CordzSnapshot& snapshot) const - ABSL_NO_THREAD_SAFETY_ANALYSIS; + CordzInfo* Next(const CordzSnapshot& snapshot) const; // Locks this instance for the update identified by `method`. // Increases the count for `method` in `update_tracker`. @@ -185,20 +183,15 @@ int64_t sampling_stride() const { return sampling_stride_; } private: - using SpinLock = absl::base_internal::SpinLock; - using SpinLockHolder = ::absl::base_internal::SpinLockHolder; - // Global cordz info list. CordzInfo stores a pointer to the global list // instance to harden against ODR violations. struct List { - constexpr explicit List(absl::ConstInitType) - : mutex(absl::kConstInit, - absl::base_internal::SCHEDULE_COOPERATIVE_AND_KERNEL) {} - - SpinLock mutex; - std::atomic<CordzInfo*> head ABSL_GUARDED_BY(mutex){nullptr}; + absl::Mutex mutex; + CordzInfo* head ABSL_GUARDED_BY(mutex){nullptr}; }; + static List* GlobalList(); + static constexpr size_t kMaxStackDepth = 64; explicit CordzInfo(CordRep* rep, const CordzInfo* src, @@ -223,7 +216,7 @@ void ODRCheck() const { #ifndef NDEBUG - ABSL_RAW_CHECK(list_ == &global_list_, "ODR violation in Cord"); + ABSL_RAW_CHECK(list_ == GlobalList(), "ODR violation in Cord"); #endif } @@ -233,12 +226,11 @@ static void MaybeTrackCordImpl(InlineData& cord, const InlineData& src, MethodIdentifier method); - ABSL_CONST_INIT static List global_list_; - List* const list_ = &global_list_; + List* const list_ = GlobalList(); // ci_prev_ and ci_next_ require the global list mutex to be held. // Unfortunately we can't use thread annotations such that the thread safety - // analysis understands that list_ and global_list_ are one and the same. + // analysis understands that list_ and GlobalList() are one and the same. std::atomic<CordzInfo*> ci_prev_{nullptr}; std::atomic<CordzInfo*> ci_next_{nullptr}; @@ -296,7 +288,7 @@ #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wthread-safety-negative" inline CordRep* CordzInfo::RefCordRep() const ABSL_LOCKS_EXCLUDED(mutex_) { - MutexLock lock(&mutex_); + MutexLock lock(mutex_); return rep_ ? CordRep::Ref(rep_) : nullptr; } #pragma clang diagnostic pop
diff --git a/absl/strings/internal/escaping.h b/absl/strings/internal/escaping.h index 2186f77..b71fb7e 100644 --- a/absl/strings/internal/escaping.h +++ b/absl/strings/internal/escaping.h
@@ -17,7 +17,7 @@ #include <cassert> -#include "absl/strings/internal/resize_uninitialized.h" +#include "absl/strings/resize_and_overwrite.h" namespace absl { ABSL_NAMESPACE_BEGIN @@ -42,12 +42,14 @@ bool do_padding, const char* base64_chars) { const size_t calc_escaped_size = CalculateBase64EscapedLenInternal(szsrc, do_padding); - STLStringResizeUninitialized(dest, calc_escaped_size); - - const size_t escaped_len = Base64EscapeInternal( - src, szsrc, &(*dest)[0], dest->size(), base64_chars, do_padding); - assert(calc_escaped_size == escaped_len); - dest->erase(escaped_len); + StringResizeAndOverwrite( + *dest, calc_escaped_size, + [src, szsrc, base64_chars, do_padding](char* buf, size_t buf_size) { + const size_t escaped_len = Base64EscapeInternal( + src, szsrc, buf, buf_size, base64_chars, do_padding); + assert(escaped_len == buf_size); + return escaped_len; + }); } } // namespace strings_internal
diff --git a/absl/strings/internal/generic_printer.cc b/absl/strings/internal/generic_printer.cc new file mode 100644 index 0000000..16ca228 --- /dev/null +++ b/absl/strings/internal/generic_printer.cc
@@ -0,0 +1,107 @@ +// Copyright 2025 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/generic_printer.h" + +#include <cstddef> +#include <cstdlib> +#include <ostream> +#include <string> + +#include "absl/base/config.h" +#include "absl/strings/ascii.h" +#include "absl/strings/escaping.h" +#include "absl/strings/str_format.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace internal_generic_printer { + +// Out-of-line helper for PrintAsStringWithEscaping. +std::ostream& PrintEscapedString(std::ostream& os, absl::string_view v) { + return os << "\"" << absl::CHexEscape(v) << "\""; +} + +// Retuns a string representation of 'v', shortened if possible. +template <class T, class F> +std::string TryShorten(T v, F strtox) { + std::string printed = + absl::StrFormat("%.*g", std::numeric_limits<T>::max_digits10 / 2, v); + T parsed = strtox(printed.data()); + if (parsed != v) { + printed = + absl::StrFormat("%.*g", std::numeric_limits<T>::max_digits10 + 1, v); + } + return printed; +} + +// Out-of-line helpers for floating point values. These don't necessarily +// ensure that values are precise, but rather that they are wide enough to +// represent distinct values. go/c++17std/numeric.limits.members.html +std::ostream& PrintPreciseFP(std::ostream& os, float v) { + return os << TryShorten(v, [](const char* buf) { + char* unused; + return std::strtof(buf, &unused); + }) << "f"; +} +std::ostream& PrintPreciseFP(std::ostream& os, double v) { + return os << TryShorten(v, [](const char* buf) { + char* unused; + return std::strtod(buf, &unused); + }); +} +std::ostream& PrintPreciseFP(std::ostream& os, long double v) { + return os << TryShorten(v, [](const char* buf) { + char* unused; + return std::strtold(buf, &unused); + }) << "L"; +} + +// Prints a nibble of 'v' in hexadecimal. +inline char hexnib(int v) { + return static_cast<char>((v < 10 ? '0' : ('a' - 10)) + v); +} + +template <typename T> +static std::ostream& PrintCharImpl(std::ostream& os, T v) { + // Specialization for chars: print as 'c' if printable, otherwise + // hex-escaped. + return (absl::ascii_isprint(static_cast<unsigned char>(v)) + ? (os << (v == '\'' ? "'\\" : "'") << v) + : (os << "'\\x" << hexnib((v >> 4) & 0xf) << hexnib(v & 0xf))) + << "' (0x" << hexnib((v >> 4) & 0xf) << hexnib(v & 0xf) << " " + << static_cast<int>(v) << ")"; +} + +std::ostream& PrintChar(std::ostream& os, char c) { + return PrintCharImpl(os, c); +} + +std::ostream& PrintChar(std::ostream& os, signed char c) { + return PrintCharImpl(os, c); +} + +std::ostream& PrintChar(std::ostream& os, unsigned char c) { + return PrintCharImpl(os, c); +} + +std::ostream& PrintByte(std::ostream& os, std::byte b) { + auto v = std::to_integer<int>(b); + os << "0x" << hexnib((v >> 4) & 0xf) << hexnib(v & 0xf); + return os; +} + +} // namespace internal_generic_printer +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/strings/internal/generic_printer.h b/absl/strings/internal/generic_printer.h new file mode 100644 index 0000000..ed60155 --- /dev/null +++ b/absl/strings/internal/generic_printer.h
@@ -0,0 +1,115 @@ +// Copyright 2025 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_GENERIC_PRINTER_H_ +#define ABSL_STRINGS_INTERNAL_GENERIC_PRINTER_H_ + +#include "absl/strings/internal/generic_printer_internal.h" // IWYU pragma: export + +// Helpers for printing objects in generic code. +// +// These functions help with streaming in generic code. It may be desirable, for +// example, to log values from generic functions; however, operator<< may not be +// overloaded for all types. +// +// The helpers in this library use, in order of precedence: +// +// 1. std::string and std::string_view are quoted and escaped. (The specific +// format is not guaranteed to be stable.) +// 2. A defined AbslStringify() method. +// 3. absl::log_internal::LogContainer, if the object is a STL container. +// 4. For std::tuple, std::pair, and std::optional, recursively calls +// GenericPrint for each element. +// 5. Floating point values are printed with enough precision for round-trip. +// 6. char values are quoted, with non-printable values escaped, and the +// char's numeric value is included. +// 7. A defined operator<< overload (which should be found by ADL). +// 8. A defined DebugString() const method. +// 9. A fallback value with basic information otherwise. +// +// Note that the fallback value means that if no formatting conversion is +// defined, you will not see a compile-time error. This also means that +// GenericPrint() can safely be used in generic template contexts, and can +// format any types needed (even though the output will vary). +// +// Example usage: +// +// // All values after GenericPrint() are formatted: +// LOG(INFO) << GenericPrint() +// << int_var // <- printed normally +// << float_var // <- sufficient precision for round-trip +// << " unchanged literal text "; +// +// // Just one value is formatted: +// LOG(INFO) << GenericPrint(string("this is quoted and escaped\t\n")) +// << GenericPrint("but not this, "); +// << string("and not this."); +// +// To make a type loggable with GenericPrint, prefer defining operator<< as a +// friend function. For example: +// +// class TypeToLog { +// public: +// string ToString() const; // Many types already implement this. +// // Define out-of-line if it is complex. +// friend std::ostream& operator<<(std::ostream& os, const TypeToLog& v) { +// return os << v.ToString(); // OK to define in-line instead, if simple. +// } +// }; +// +// (Defining operator<< as an inline friend free function allows it to be found +// by Argument-Dependent Lookup, or ADL, which is the mechanism typically used +// for operator overload resolution. An inline friend function is the tightest +// scope possible for overloading the left-hand side of an operator.) + +#include <ostream> +#include <utility> + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace strings_internal { + +// Helper for logging values in generic code. +// +// Example usage: +// +// template <typename T> +// void GenericFunction() { +// T v1, v2; +// VLOG(1) << GenericPrint() << v1 << v2; // GenericPrint everything +// VLOG(1) << GenericPrint(v1) << v2; // GenericPrint just v1 +// } +// +inline constexpr internal_generic_printer::GenericPrintAdapterFactory + GenericPrint{}; + +// Generic printer type: this class can be used, for example, as a template +// argument to allow users to provide alternative printing strategies. +// +// For example, to allow callers to provide a custom strategy: +// +// template <typename T, typename PrinterT = GenericPrinter<T>> +// void GenericFunction() { +// T value; +// VLOG(1) << PrinterT{value}; +// } +// +template <typename T> +using GenericPrinter = internal_generic_printer::GenericPrinter<T>; + +} // namespace strings_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_GENERIC_PRINTER_H_
diff --git a/absl/strings/internal/generic_printer_internal.h b/absl/strings/internal/generic_printer_internal.h new file mode 100644 index 0000000..1a2d0bd --- /dev/null +++ b/absl/strings/internal/generic_printer_internal.h
@@ -0,0 +1,423 @@ +// Copyright 2025 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_GENERIC_PRINTER_INTERNAL_H_ +#define ABSL_STRINGS_INTERNAL_GENERIC_PRINTER_INTERNAL_H_ + +#include <cstddef> +#include <cstdint> +#include <memory> +#include <optional> +#include <ostream> +#include <string> +#include <tuple> +#include <type_traits> +#include <utility> +#include <variant> +#include <vector> + +#include "absl/base/config.h" +#include "absl/log/internal/container.h" +#include "absl/meta/internal/requires.h" +#include "absl/strings/has_absl_stringify.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/string_view.h" + +// NOTE: we do not want to expand the dependencies of this library. All +// compatibility detection must be done in a generic way, without having to +// include the headers of other libraries. + +// Internal implementation details: see generic_printer.h. +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace internal_generic_printer { + +template <typename T> +std::ostream& GenericPrintImpl(std::ostream& os, const T& v); + +// Scope blocker: we must always use ADL in our predicates below. +struct Anonymous; +std::ostream& operator<<(const Anonymous&, const Anonymous&) = delete; + +// Logging policy for LogContainer. Our SFINAE overload will not fail +// if the contained type cannot be printed, so make sure to circle back to +// GenericPrinter. +struct ContainerLogPolicy { + void LogOpening(std::ostream& os) const { os << "["; } + void LogClosing(std::ostream& os) const { os << "]"; } + void LogFirstSeparator(std::ostream& os) const { os << ""; } + void LogSeparator(std::ostream& os) const { os << ", "; } + int64_t MaxElements() const { return 15; } + template <typename T> + void Log(std::ostream& os, const T& element) const { + internal_generic_printer::GenericPrintImpl(os, element); + } + void LogEllipsis(std::ostream& os) const { os << "..."; } +}; + +// Out-of-line helper for PrintAsStringWithEscaping. +std::ostream& PrintEscapedString(std::ostream& os, absl::string_view v); + +// Trait to recognize a string, possibly with a custom allocator. +template <class T> +inline constexpr bool is_any_string = false; +template <class A> +inline constexpr bool + is_any_string<std::basic_string<char, std::char_traits<char>, A>> = true; + +// Trait to recognize a supported pointer. Below are documented reasons why +// raw pointers and std::shared_ptr are not currently supported. +// See also http://b/239459272#comment9 and the discussion in the comment +// threads of cl/485200996. +// +// The tl;dr: +// 1. Pointers that logically represent an object (or nullptr) are safe to print +// out. +// 2. Pointers that may represent some other concept (delineating memory bounds, +// overridden managed-memory deleters to mimic RAII, ...) may be unsafe to +// print out. +// +// raw pointers: +// - A pointer one-past the last element of an array +// - Non-null but non-dereferenceable: https://gcc.godbolt.org/z/sqsqGvKbP +// +// `std::shared_ptr`: +// - "Aliasing" / similar to raw pointers: https://gcc.godbolt.org/z/YbWPzvhae +template <class T, class = void> +inline constexpr bool is_supported_ptr = false; +// `std::unique_ptr` has the same theoretical risks as raw pointers, but those +// risks are far less likely (typically requiring a custom deleter), and the +// benefits of supporting unique_ptr outweigh the costs. +// - Note: `std::unique_ptr<T[]>` cannot safely be and is not dereferenced. +template <class A, class... Deleter> +inline constexpr bool is_supported_ptr<std::unique_ptr<A, Deleter...>> = true; +// `ArenaSafeUniquePtr` is at least as safe as `std::unique_ptr`. +template <class T> +inline constexpr bool is_supported_ptr< + T, + // Check for `ArenaSafeUniquePtr` without having to include its header here. + // This does match any type named `ArenaSafeUniquePtr`, regardless of the + // namespace it is defined in, but it's pretty plausible that any such type + // would be a fork. + decltype(T().~ArenaSafeUniquePtr())> = true; + +// Specialization for floats: print floating point types using their +// max_digits10 precision. This ensures each distinct underlying values +// can be represented uniquely, even though it's not (strictly speaking) +// the most precise representation. +std::ostream& PrintPreciseFP(std::ostream& os, float v); +std::ostream& PrintPreciseFP(std::ostream& os, double v); +std::ostream& PrintPreciseFP(std::ostream& os, long double v); + +std::ostream& PrintChar(std::ostream& os, char c); +std::ostream& PrintChar(std::ostream& os, signed char c); +std::ostream& PrintChar(std::ostream& os, unsigned char c); + +std::ostream& PrintByte(std::ostream& os, std::byte b); + +template <class... Ts> +std::ostream& PrintTuple(std::ostream& os, const std::tuple<Ts...>& tuple) { + absl::string_view sep = ""; + const auto print_one = [&](const auto& v) { + os << sep; + (GenericPrintImpl)(os, v); + sep = ", "; + }; + os << "<"; + std::apply([&](const auto&... v) { (print_one(v), ...); }, tuple); + os << ">"; + return os; +} + +template <typename T, typename U> +std::ostream& PrintPair(std::ostream& os, const std::pair<T, U>& p) { + os << "<"; + (GenericPrintImpl)(os, p.first); + os << ", "; + (GenericPrintImpl)(os, p.second); + os << ">"; + return os; +} + +template <typename T> +std::ostream& PrintOptionalLike(std::ostream& os, const T& v) { + if (v.has_value()) { + os << "<"; + (GenericPrintImpl)(os, *v); + os << ">"; + } else { + (GenericPrintImpl)(os, std::nullopt); + } + return os; +} + +template <typename... Ts> +std::ostream& PrintVariant(std::ostream& os, const std::variant<Ts...>& v) { + os << "("; + os << "'(index = " << v.index() << ")' "; + + // NOTE(derekbailey): This may throw a std::bad_variant_access if the variant + // is "valueless", which only occurs if exceptions are thrown. This is + // non-relevant when not using exceptions, but it is worth mentioning if that + // invariant is ever changed. + std::visit([&](const auto& arg) { (GenericPrintImpl)(os, arg); }, v); + os << ")"; + return os; +} + +template <typename StatusOrLike> +std::ostream& PrintStatusOrLike(std::ostream& os, const StatusOrLike& v) { + os << "<"; + if (v.ok()) { + os << "OK: "; + (GenericPrintImpl)(os, *v); + } else { + (GenericPrintImpl)(os, v.status()); + } + os << ">"; + return os; +} + +template <typename SmartPointer> +std::ostream& PrintSmartPointerContents(std::ostream& os, + const SmartPointer& v) { + os << "<"; + if (v == nullptr) { + (GenericPrintImpl)(os, nullptr); + } else { + // Cast to void* so that every type (e.g. `char*`) is printed as an address. + os << absl::implicit_cast<const void*>(v.get()) << " pointing to "; + + if constexpr (meta_internal::Requires<SmartPointer>( + [](auto&& p) -> decltype(p[0]) {})) { + // e.g. std::unique_ptr<int[]>, which only has operator[] + os << "an array"; + } else if constexpr (std::is_object_v< + typename SmartPointer::element_type>) { + (GenericPrintImpl)(os, *v); + } else { + // e.g. std::unique_ptr<void, MyCustomDeleter> + os << "a non-object type"; + } + } + os << ">"; + return os; +} + +template <typename T> +std::ostream& GenericPrintImpl(std::ostream& os, const T& v) { + if constexpr (is_any_string<T> || std::is_same_v<T, absl::string_view>) { + // Specialization for strings: prints with plausible quoting and escaping. + return PrintEscapedString(os, v); + } else if constexpr (absl::HasAbslStringify<T>::value) { + // If someone has specified `AbslStringify`, we should prefer that. + return os << absl::StrCat(v); + } else if constexpr (meta_internal::Requires<const T>( + [&](auto&& w) + -> decltype(( + PrintTuple)(std::declval<std::ostream&>(), + w)) {})) { + // For tuples, use `< elem0, ..., elemN >`. + return (PrintTuple)(os, v); + + } else if constexpr (meta_internal::Requires<const T>( + [&](auto&& w) + -> decltype(( + PrintPair)(std::declval<std::ostream&>(), + w)) {})) { + // For pairs, use `< first, second >`. + return (PrintPair)(os, v); + + } else if constexpr (meta_internal::Requires<const T>( + [&](auto&& w) + -> decltype(( + PrintVariant)(std::declval<std::ostream&>(), + w)) {})) { + // For std::variant, use `std::visit(v)` + return (PrintVariant)(os, v); + } else if constexpr (is_supported_ptr<T>) { + return (PrintSmartPointerContents)(os, v); + } else if constexpr (meta_internal::Requires<const T>( + [&](auto&& w) -> decltype(w.ok(), w.status(), *w) { + })) { + return (PrintStatusOrLike)(os, v); + } else if constexpr (meta_internal::Requires<const T>( + [&](auto&& w) -> decltype(w.has_value(), *w) {})) { + return (PrintOptionalLike)(os, v); + } else if constexpr (std::is_same_v<T, std::nullopt_t>) { + // Specialization for nullopt. + return os << "nullopt"; + + } else if constexpr (std::is_same_v<T, std::nullptr_t>) { + // Specialization for nullptr. + return os << "nullptr"; + + } else if constexpr (std::is_floating_point_v<T>) { + // For floating point print with enough precision for a roundtrip. + return PrintPreciseFP(os, v); + + } else if constexpr (std::is_same_v<T, char> || + std::is_same_v<T, signed char> || + std::is_same_v<T, unsigned char>) { + // Chars are printed as the char (if a printable char) and the integral + // representation in hex and decimal. + return PrintChar(os, v); + + } else if constexpr (std::is_same_v<T, std::byte>) { + return PrintByte(os, v); + + } else if constexpr (std::is_same_v<T, bool> || + std::is_same_v<T, + typename std::vector<bool>::reference> || + std::is_same_v< + T, typename std::vector<bool>::const_reference>) { + return os << (v ? "true" : "false"); + + } else if constexpr (meta_internal::Requires<const T>( + [&](auto&& w) + -> decltype(ProtobufInternalGetEnumDescriptor( + w)) {})) { + os << static_cast<std::underlying_type_t<T>>(v); + if (auto* desc = + ProtobufInternalGetEnumDescriptor(T{})->FindValueByNumber(v)) { + os << "(" << desc->name() << ")"; + } + return os; + } else if constexpr (!std::is_enum_v<T> && + meta_internal::Requires<const T>( + [&](auto&& w) -> decltype(absl::StrCat(w)) {})) { + return os << absl::StrCat(v); + + } else if constexpr (meta_internal::Requires<const T>( + [&](auto&& w) + -> decltype(std::declval<std::ostream&>() + << log_internal::LogContainer(w)) { + })) { + // For containers, use `[ elem0, ..., elemN ]` with a max of 15. + return os << log_internal::LogContainer(v, ContainerLogPolicy()); + + } else if constexpr (meta_internal::Requires<const T>( + [&](auto&& w) + -> decltype(std::declval<std::ostream&>() << w) { + })) { + // Streaming + return os << v; + + } else if constexpr (meta_internal::Requires<const T>( + [&](auto&& w) + -> decltype(std::declval<std::ostream&>() + << w.DebugString()) {})) { + // DebugString + return os << v.DebugString(); + + } else if constexpr (std::is_enum_v<T>) { + // In case the underlying type is some kind of char, we have to recurse. + return GenericPrintImpl(os, static_cast<std::underlying_type_t<T>>(v)); + + } else { + // Default: we don't have anything to stream the value. + return os << "[unprintable value of size " << sizeof(T) << " @" << &v + << "]"; + } +} + +// GenericPrinter always has a valid operator<<. It defers to the disjuction +// above. +template <typename T> +class GenericPrinter { + public: + explicit GenericPrinter(const T& value) : value_(value) {} + + private: + friend std::ostream& operator<<(std::ostream& os, const GenericPrinter& gp) { + return internal_generic_printer::GenericPrintImpl(os, gp.value_); + } + const T& value_; +}; + +struct GenericPrintStreamAdapter { + template <class StreamT> + struct Impl { + // Stream operator: this object will adapt to the underlying stream type, + // but only if the Impl is an rvalue. For example, this works: + // std::cout << GenericPrint() << foo; + // but not: + // auto adapter = (std::cout << GenericPrint()); + // adapter << foo; + template <typename T> + Impl&& operator<<(const T& value) && { + os << internal_generic_printer::GenericPrinter<T>(value); + return std::move(*this); + } + + // Inhibit using a stack variable for the adapter: + template <typename T> + Impl& operator<<(const T& value) & = delete; + + // Detects a Flush() method, for LogMessage compatibility. + template <typename T> + class HasFlushMethod { + private: + template <typename C> + static std::true_type Test(decltype(&C::Flush)); + template <typename C> + static std::false_type Test(...); + + public: + static constexpr bool value = decltype(Test<T>(nullptr))::value; + }; + + // LogMessage compatibility requires a Flush() method. + void Flush() { + if constexpr (HasFlushMethod<StreamT>::value) { + os.Flush(); + } + } + + StreamT& os; + }; + + // If Impl is evaluated on the RHS of an 'operator&&', and 'lhs && Impl.os' + // implicitly converts to void, then it's fine for Impl to do so, too. This + // will create precisely as many objects as 'lhs && Impl.os', so we should + // both observe any side effects, and avoid observing multiple side + // effects. (See absl::log_internal::Voidify for an example of why this might + // be useful.) + template <typename LHS, typename RHS> + friend auto operator&&(LHS&& lhs, Impl<RHS>&& rhs) + -> decltype(lhs && rhs.os) { + return lhs && rhs.os; + } + + template <class StreamT> + friend Impl<StreamT> operator<<(StreamT& os, GenericPrintStreamAdapter&&) { + return Impl<StreamT>{os}; + } +}; + +struct GenericPrintAdapterFactory { + internal_generic_printer::GenericPrintStreamAdapter operator()() const { + return internal_generic_printer::GenericPrintStreamAdapter{}; + } + template <typename T> + internal_generic_printer::GenericPrinter<T> operator()(const T& value) const { + return internal_generic_printer::GenericPrinter<T>{value}; + } +}; + +} // namespace internal_generic_printer +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_STRINGS_INTERNAL_GENERIC_PRINTER_INTERNAL_H_
diff --git a/absl/strings/internal/generic_printer_test.cc b/absl/strings/internal/generic_printer_test.cc new file mode 100644 index 0000000..4093228 --- /dev/null +++ b/absl/strings/internal/generic_printer_test.cc
@@ -0,0 +1,685 @@ +// Copyright 2025 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/generic_printer.h" + +#include <array> +#include <cstdint> +#include <limits> +#include <map> +#include <memory> +#include <optional> +#include <ostream> +#include <sstream> +#include <string> +#include <type_traits> +#include <utility> +#include <variant> +#include <vector> + +#include "gmock/gmock.h" +#include "gtest/gtest.h" +#include "absl/base/attributes.h" +#include "absl/base/config.h" +#include "absl/container/flat_hash_map.h" +#include "absl/status/status.h" +#include "absl/status/statusor.h" +#include "absl/strings/str_cat.h" +#include "absl/strings/substitute.h" + +namespace generic_logging_test { +struct NotStreamable {}; +} // namespace generic_logging_test + +static std::ostream& operator<<(std::ostream& os, + const generic_logging_test::NotStreamable&) { + return os << "This overload should NOT be found by GenericPrint."; +} + +// Types to test selection logic for streamable and non-streamable types. +namespace generic_logging_test { +struct Streamable { + int x; + friend std::ostream& operator<<(std::ostream& os, const Streamable& l) { + return os << "Streamable{" << l.x << "}"; + } +}; +} // namespace generic_logging_test + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace strings_internal { +namespace { + +using ::testing::AllOf; +using ::testing::AnyOf; +using ::testing::ContainsRegex; +using ::testing::EndsWith; +using ::testing::Eq; +using ::testing::HasSubstr; +using ::testing::MatchesRegex; + +struct AbslStringifiable { + template <typename S> + friend void AbslStringify(S& sink, const AbslStringifiable&) { + sink.Append("AbslStringifiable!"); + } +}; + +auto IsUnprintable() { +#ifdef GTEST_USES_SIMPLE_RE + return HasSubstr("unprintable value of size"); +#else + return ContainsRegex( + "\\[unprintable value of size [0-9]+ @(0x)?[0-9a-fA-F]+\\]"); +#endif +} + +auto HasExactlyNInstancesOf(int n, absl::string_view me) { +#ifdef GTEST_USES_SIMPLE_RE + (void)n; + return HasSubstr(me); +#else + absl::string_view value_m_times = "(.*$0){$1}.*"; + + return AllOf(MatchesRegex(absl::Substitute(value_m_times, me, n)), + Not(MatchesRegex(absl::Substitute(value_m_times, me, n + 1)))); +#endif +} + +template <typename T> +std::string GenericPrintToString(const T& v) { + std::stringstream ss; + ss << GenericPrint(v); + { + std::stringstream ss2; + ss2 << GenericPrint() << v; + EXPECT_EQ(ss.str(), ss2.str()); + } + return ss.str(); +} + +TEST(GenericPrinterTest, Bool) { + EXPECT_EQ("true", GenericPrintToString(true)); + EXPECT_EQ("false", GenericPrintToString(false)); +} + +TEST(GenericPrinterTest, VectorOfBool) { + std::vector<bool> v{true, false, true}; + const auto& cv = v; + EXPECT_EQ("[true, false, true]", GenericPrintToString(v)); + EXPECT_EQ("true", GenericPrintToString(v[0])); + EXPECT_EQ("true", GenericPrintToString(cv[0])); +} + +TEST(GenericPrinterTest, CharLiterals) { + EXPECT_EQ(R"(a"\b)", GenericPrintToString(R"(a"\b)")); +} + +TEST(GenericPrinterTest, Builtin) { + EXPECT_EQ("123", GenericPrintToString(123)); +} + +TEST(GenericPrinterTest, AbslStringifiable) { + EXPECT_EQ("AbslStringifiable!", GenericPrintToString(AbslStringifiable{})); +} + +TEST(GenericPrinterTest, Nullptr) { + EXPECT_EQ("nullptr", GenericPrintToString(nullptr)); +} + +TEST(GenericPrinterTest, Chars) { + EXPECT_EQ(R"('\x0a' (0x0a 10))", GenericPrintToString('\x0a')); + EXPECT_EQ(R"(' ' (0x20 32))", GenericPrintToString(' ')); + EXPECT_EQ(R"('~' (0x7e 126))", GenericPrintToString('~')); + EXPECT_EQ(R"('\'' (0x27 39))", GenericPrintToString('\'')); +} + +TEST(GenericPrinterTest, SignedChars) { + EXPECT_EQ(R"('\x0a' (0x0a 10))", + GenericPrintToString(static_cast<signed char>('\x0a'))); + EXPECT_EQ(R"(' ' (0x20 32))", + GenericPrintToString(static_cast<signed char>(' '))); + EXPECT_EQ(R"('~' (0x7e 126))", + GenericPrintToString(static_cast<signed char>('~'))); + EXPECT_EQ(R"('\'' (0x27 39))", + GenericPrintToString(static_cast<signed char>('\''))); +} + +TEST(GenericPrinterTest, UnsignedChars) { + EXPECT_EQ(R"('\x0a' (0x0a 10))", + GenericPrintToString(static_cast<unsigned char>('\x0a'))); + EXPECT_EQ(R"(' ' (0x20 32))", + GenericPrintToString(static_cast<unsigned char>(' '))); + EXPECT_EQ(R"('~' (0x7e 126))", + GenericPrintToString(static_cast<unsigned char>('~'))); + EXPECT_EQ(R"('\'' (0x27 39))", + GenericPrintToString(static_cast<unsigned char>('\''))); +} + +TEST(GenericPrinterTest, Bytes) { + EXPECT_EQ("0x00", GenericPrintToString(static_cast<std::byte>(0))); + EXPECT_EQ("0x7f", GenericPrintToString(static_cast<std::byte>(0x7F))); + EXPECT_EQ("0xff", GenericPrintToString(static_cast<std::byte>(0xFF))); +} + +TEST(GenericPrinterTest, Strings) { + const std::string expected_quotes = R"("a\"\\b")"; + EXPECT_EQ(expected_quotes, GenericPrintToString(std::string(R"(a"\b)"))); + const std::string expected_nonprintable = R"("\x00\xcd\n\xab")"; + EXPECT_EQ(expected_nonprintable, + GenericPrintToString(absl::string_view("\0\315\n\xAB", 4))); +} + +TEST(GenericPrinterTest, PreciseFloat) { + // Instead of testing exactly how the values are formatted, just check that + // they are distinct. + + // Ensure concise output for exact values: + EXPECT_EQ("1f", GenericPrintToString(1.f)); + EXPECT_EQ("1.1f", GenericPrintToString(1.1f)); + + // Plausible real-world values: + float f = 10.0000095f; + EXPECT_NE(GenericPrintToString(f), GenericPrintToString(10.0000105f)); + // Smallest increment for a real-world value: + EXPECT_NE(GenericPrintToString(f), + GenericPrintToString(std::nextafter(f, 11))); + // The two smallest (finite) values possible: + EXPECT_NE(GenericPrintToString(std::numeric_limits<float>::lowest()), + GenericPrintToString( + std::nextafter(std::numeric_limits<float>::lowest(), 1))); + // Ensure the value has the correct type suffix: + EXPECT_THAT(GenericPrintToString(0.f), EndsWith("f")); +} + +TEST(GenericPrinterTest, PreciseDouble) { + EXPECT_EQ("1", GenericPrintToString(1.)); + EXPECT_EQ("1.1", GenericPrintToString(1.1)); + double d = 10.000000000000002; + EXPECT_NE(GenericPrintToString(d), GenericPrintToString(10.000000000000004)); + EXPECT_NE(GenericPrintToString(d), + GenericPrintToString(std::nextafter(d, 11))); + EXPECT_NE(GenericPrintToString(std::numeric_limits<double>::lowest()), + GenericPrintToString( + std::nextafter(std::numeric_limits<double>::lowest(), 1))); + EXPECT_THAT(GenericPrintToString(0.), EndsWith("0")); +} + +TEST(GenericPrinterTest, PreciseLongDouble) { + EXPECT_EQ("1L", GenericPrintToString(1.L)); + EXPECT_EQ("1.1L", GenericPrintToString(1.1L)); + long double ld = 10.0000000000000000000000000000002; + EXPECT_NE(GenericPrintToString(ld), + GenericPrintToString(10.0000000000000000000000000000004)); + EXPECT_NE(GenericPrintToString(ld), + GenericPrintToString(std::nextafter(ld, 11))); + EXPECT_NE(GenericPrintToString(std::numeric_limits<long double>::lowest()), + GenericPrintToString( + std::nextafter(std::numeric_limits<long double>::lowest(), 1))); + EXPECT_THAT(GenericPrintToString(0.L), EndsWith("L")); +} + +TEST(GenericPrinterTest, StreamableLvalue) { + generic_logging_test::Streamable x{234}; + EXPECT_EQ("Streamable{234}", GenericPrintToString(x)); +} + +TEST(GenericPrinterTest, StreamableXvalue) { + EXPECT_EQ("Streamable{345}", + GenericPrintToString(generic_logging_test::Streamable{345})); +} + +TEST(GenericPrinterTest, NotStreamableWithoutGenericPrint) { + ::generic_logging_test::NotStreamable x; + std::stringstream ss; + ::operator<<(ss, x); + EXPECT_EQ(ss.str(), "This overload should NOT be found by GenericPrint."); +} + +TEST(GenericPrinterTest, NotStreamableLvalue) { + generic_logging_test::NotStreamable x; + EXPECT_THAT(GenericPrintToString(x), IsUnprintable()); +} + +TEST(GenericPrinterTest, NotStreamableXvalue) { + EXPECT_THAT(GenericPrintToString(generic_logging_test::NotStreamable{}), + IsUnprintable()); +} + +TEST(GenericPrinterTest, DebugString) { + struct WithDebugString { + std::string val; + std::string DebugString() const { + return absl::StrCat("WithDebugString{", val, "}"); + } + }; + EXPECT_EQ("WithDebugString{foo}", + GenericPrintToString(WithDebugString{"foo"})); +} + +TEST(GenericPrinterTest, Vector) { + std::vector<int> v = {4, 5, 6}; + EXPECT_THAT(GenericPrintToString(v), MatchesRegex(".*4,? 5,? 6.*")); +} + +TEST(GenericPrinterTest, StreamableVector) { + std::vector<generic_logging_test::Streamable> v = {{7}, {8}, {9}}; + EXPECT_THAT(GenericPrintToString(v), + MatchesRegex(".*Streamable.7.,? Streamable.8.,? Streamable.9.*")); +} + +TEST(GenericPrinterTest, Map) { + absl::flat_hash_map< + std::string, absl::flat_hash_map<std::string, std::pair<double, double>>> + v = {{"A", {{"B", {.5, .25}}}}}; + + EXPECT_THAT(GenericPrintToString(v), R"([<"A", [<"B", <0.5, 0.25>>]>])"); + + std::map<std::string, std::map<std::string, std::pair<double, double>>> v2 = { + {"A", {{"B", {.5, .25}}}}}; + + EXPECT_THAT(GenericPrintToString(v2), R"([<"A", [<"B", <0.5, 0.25>>]>])"); +} + +TEST(GenericPrinterTest, StreamAdapter) { + std::stringstream ss; + static_assert( + std::is_same< + typename std::remove_reference<decltype(ss << GenericPrint())>::type, + internal_generic_printer::GenericPrintStreamAdapter::Impl< + std::stringstream>>::value, + "expected ostream << GenericPrint() to yield adapter impl"); + + ss << GenericPrint() << "again, " << "back-up, " << "cue, " + << "double-u, " << "eye, " + << "four: " << generic_logging_test::NotStreamable{}; + EXPECT_THAT( + ss.str(), + MatchesRegex( + "again, back-up, cue, double-u, eye, four: .unprintable value.*")); +} + +TEST(GenericPrinterTest, NotStreamableVector) { + std::vector<generic_logging_test::NotStreamable> v = {{}, {}, {}}; +#ifdef GTEST_USES_SIMPLE_RE + EXPECT_THAT(GenericPrintToString(v), HasSubstr("unprintable")); +#else + EXPECT_THAT(GenericPrintToString(v), MatchesRegex(".*(unprintable.*){3}.*")); +#endif +} + +struct CustomContainer : public std::array<int, 4> { + template <typename Sink> + friend void AbslStringify(Sink& sink, const CustomContainer& c) { + absl::Format(&sink, "%d %d", c[0], c[1]); + } +}; + +// Checks that AbslStringify (go/totw/215) is respected for container-like +// types. +TEST(GenericPrinterTest, ContainerLikeCustomLogging) { + CustomContainer c = {1, 2, 3, 4}; + EXPECT_EQ(GenericPrintToString(c), "1 2"); +} + +// Test helper: this function demonstrates customizable printing logic: +// 'GenericPrinter<T>' can be nominated as a default template argument. +template <typename T, typename Printer = GenericPrinter<T>> +std::string SpecializablePrint(const T& v) { + std::stringstream ss; + ss << Printer{v}; + return ss.str(); +} + +TEST(GenericPrinterTest, DefaultPrinter) { + EXPECT_EQ("123", SpecializablePrint(123)); +} + +// Example of custom printing logic. This doesn't actually test anything in +// GenericPrinter, but it's a working example of customizing printing logic (as +// opposed to the comments in generic_printer.h). +struct CustomPrinter { + explicit CustomPrinter(int) {} + friend std::ostream& operator<<(std::ostream& os, CustomPrinter&&) { + return os << "custom printer"; + } +}; + +TEST(GenericPrinterTest, CustomPrinter) { + EXPECT_EQ("custom printer", (SpecializablePrint<int, CustomPrinter>(123))); +} + +TEST(GenricPrinterTest, Nullopt) { + EXPECT_EQ("nullopt", GenericPrintToString(std::nullopt)); +} + +TEST(GenericPrinterTest, Optional) { + EXPECT_EQ("nullopt", GenericPrintToString(std::optional<int>())); + EXPECT_EQ("nullopt", GenericPrintToString(std::optional<int>(std::nullopt))); + EXPECT_EQ("<3>", GenericPrintToString(std::make_optional(3))); + EXPECT_EQ("<Streamable{3}>", GenericPrintToString(std::make_optional( + generic_logging_test::Streamable{3}))); +} + +TEST(GenericPrinterTest, Tuple) { + EXPECT_EQ("<1, two, 3>", GenericPrintToString(std::make_tuple(1, "two", 3))); +} + +TEST(GenericPrinterTest, EmptyTuple) { + EXPECT_EQ("<>", GenericPrintToString(std::make_tuple())); +} + +TEST(GenericPrinterTest, TupleWithStreamableMember) { + EXPECT_EQ("<1, two, Streamable{3}>", + GenericPrintToString(std::make_tuple( + 1, "two", generic_logging_test::Streamable{3}))); +} + +TEST(GenericPrinterTest, Variant) { + EXPECT_EQ(R"(('(index = 0)' "cow"))", + GenericPrintToString(std::variant<std::string, float>("cow"))); + + EXPECT_EQ("('(index = 1)' 1.1f)", + GenericPrintToString(std::variant<std::string, float>(1.1F))); +} + +TEST(GenericPrinterTest, VariantMonostate) { + EXPECT_THAT(GenericPrintToString(std::variant<std::monostate, std::string>()), + IsUnprintable()); +} + +TEST(GenericPrinterTest, VariantNonStreamable) { + EXPECT_EQ(R"(('(index = 0)' "cow"))", + GenericPrintToString( + std::variant<std::string, generic_logging_test::NotStreamable>( + "cow"))); + + EXPECT_THAT( + GenericPrintToString( + std::variant<std::string, generic_logging_test::NotStreamable>( + generic_logging_test::NotStreamable{})), + IsUnprintable()); +} + +TEST(GenericPrinterTest, VariantNestedVariant) { + EXPECT_EQ( + "('(index = 1)' ('(index = 1)' 1.1f))", + GenericPrintToString(std::variant<std::string, std::variant<int, float>>( + std::variant<int, float>(1.1F)))); +} + +TEST(GenericPrinterTest, VariantInPlace) { + EXPECT_EQ("('(index = 0)' 17)", GenericPrintToString(std::variant<int, int>( + std::in_place_index<0>, 17))); + + EXPECT_EQ("('(index = 1)' 17)", GenericPrintToString(std::variant<int, int>( + std::in_place_index<1>, 17))); +} + +TEST(GenericPrinterTest, StatusOrLikeOkPrintsValue) { + EXPECT_EQ(R"(<OK: "cow">)", + GenericPrintToString(absl::StatusOr<std::string>("cow"))); + + EXPECT_EQ(R"(<OK: 1.1f>)", GenericPrintToString(absl::StatusOr<float>(1.1F))); +} + +TEST(GenericPrinterTest, StatusOrLikeNonOkPrintsStatus) { + EXPECT_THAT( + GenericPrintToString(absl::StatusOr<float>( + absl::InvalidArgumentError("my error message"))), + AllOf(HasSubstr("my error message"), HasSubstr("INVALID_ARGUMENT"))); + + EXPECT_THAT(GenericPrintToString( + absl::StatusOr<int>(absl::AbortedError("other message"))), + AllOf(HasSubstr("other message"), HasSubstr("ABORTED"))); +} + +TEST(GenericPrinterTest, StatusOrLikeNonStreamableValueUnprintable) { + EXPECT_THAT( + GenericPrintToString(absl::StatusOr<generic_logging_test::NotStreamable>( + generic_logging_test::NotStreamable{})), + IsUnprintable()); +} + +TEST(GenericPrinterTest, StatusOrLikeNonStreamableErrorStillPrintable) { + EXPECT_THAT( + GenericPrintToString(absl::StatusOr<generic_logging_test::NotStreamable>( + absl::AbortedError("other message"))), + AllOf(HasSubstr("other message"), HasSubstr("ABORTED"))); +} + +TEST(GenericPrinterTest, IsSupportedPointer) { + using internal_generic_printer::is_supported_ptr; + + EXPECT_TRUE(is_supported_ptr<std::unique_ptr<std::string>>); + EXPECT_TRUE(is_supported_ptr<std::unique_ptr<int[]>>); + EXPECT_TRUE((is_supported_ptr<std::unique_ptr<void, void (*)(void*)>>)); + + EXPECT_FALSE(is_supported_ptr<int*>); + EXPECT_FALSE(is_supported_ptr<std::shared_ptr<int>>); + EXPECT_FALSE(is_supported_ptr<std::weak_ptr<int>>); +} + +TEST(GenericPrinterTest, SmartPointerPrintsNullptrForAllNullptrs) { + std::unique_ptr<std::string> up; + + EXPECT_EQ("<nullptr>", GenericPrintToString(up)); +} + +TEST(GenericPrinterTest, SmartPointerPrintsValueIfNonNull) { + EXPECT_THAT(GenericPrintToString(std::make_unique<int>(5)), + HasSubstr("pointing to 5")); +} + +TEST(GenericPrinterTest, SmartPointerPrintsAddressOfPointee) { + auto i = std::make_unique<int>(5); + auto c = std::make_unique<char>('z'); + char memory[] = "abcdefg"; + auto cp = std::make_unique<char*>(memory); + + EXPECT_THAT(GenericPrintToString(i), + AnyOf(Eq(absl::StrFormat("<%016X pointing to 5>", + reinterpret_cast<intptr_t>(&*i))), + Eq(absl::StrFormat("<%#x pointing to 5>", + reinterpret_cast<intptr_t>(&*i))))); + + EXPECT_THAT( + GenericPrintToString(c), + AnyOf(HasSubstr(absl::StrFormat("<%016X pointing to 'z'", + reinterpret_cast<intptr_t>(&*c))), + HasSubstr(absl::StrFormat("<%#x pointing to 'z'", + reinterpret_cast<intptr_t>(&*c))))); + + EXPECT_THAT(GenericPrintToString(cp), + AnyOf(Eq(absl::StrFormat("<%016X pointing to abcdefg>", + reinterpret_cast<intptr_t>(&*cp))), + Eq(absl::StrFormat("<%#x pointing to abcdefg>", + reinterpret_cast<intptr_t>(&*cp))))); +} + +TEST(GenericPrinterTest, SmartPointerToArrayOnlyPrintsAddressAndHelpText) { + auto empty = std::make_unique<int[]>(0); + auto nonempty = std::make_unique<int[]>(5); + nonempty[0] = 12345; + nonempty[4] = 54321; + // NOTE: ArenaSafeUniquePtr is not meant to support array-type template + // parameters, so we skip testing that here. + // http://g/c-users/J-AEFrFHssY/UMMFzCkdBAAJ, b/265984185. + + EXPECT_THAT( + GenericPrintToString(nonempty), + AllOf(AnyOf(HasSubstr(absl::StrFormat( + "%016X", reinterpret_cast<intptr_t>(nonempty.get()))), + HasSubstr(absl::StrFormat( + "%#x", reinterpret_cast<intptr_t>(nonempty.get())))), + HasSubstr("array"), Not(HasSubstr("to 54321")), + Not(HasSubstr("to 12345")))); + + EXPECT_THAT( + GenericPrintToString(empty), + AllOf(AnyOf(HasSubstr(absl::StrFormat( + "%016X", reinterpret_cast<intptr_t>(empty.get()))), + HasSubstr(absl::StrFormat( + "%#x", reinterpret_cast<intptr_t>(empty.get())))), + HasSubstr("array"))); +} + +TEST(GenericPrinterTest, SmartPointerToNonObjectType) { + auto int_ptr_deleter = [](void* data) { + int* p = static_cast<int*>(data); + delete p; + }; + + std::unique_ptr<void, decltype(int_ptr_deleter)> void_ptr(new int(959), + int_ptr_deleter); + + EXPECT_THAT(GenericPrintToString(void_ptr), + HasSubstr("pointing to a non-object type")); +} + +TEST(GenericPrinterTest, PrintsCustomDeleterSmartPointer) { + // Delete `p` (if not nullptr) only on the 4th time the deleter is used. + auto four_deleter = [](std::string* p) { + static int counter = 0; + if (p == nullptr) return; // skip calls to moved-from destructors. + if (++counter >= 4) delete p; + }; + + // Have four `unique_ptr`s "manage" the same string-pointer, with only the + // final (4th) call to the deleter deleting the string pointer. + auto* unique_string = new std::string("unique string"); + std::vector<std::unique_ptr<std::string, decltype(four_deleter)>> test_ptrs; + for (int i = 0; i < 4; ++i) { + test_ptrs.emplace_back(unique_string, four_deleter); + } + + EXPECT_THAT(GenericPrintToString(test_ptrs), + HasExactlyNInstancesOf(4, "unique string")); +} + +// Ensure that GenericPrint is robust to recursion when a type's operator<< +// calls into GenericPrint internally. +struct CustomRecursive { + std::unique_ptr<CustomRecursive> next; + int val = 0; + + friend std::ostream& operator<<(std::ostream& os, const CustomRecursive& cr) { + return os << "custom print: next = " << GenericPrintToString(cr.next); + } +}; + +TEST(GenericPrinterTest, DISABLED_CustomPrintOverloadRecursionDetected) { + auto r1 = std::make_unique<CustomRecursive>(); + r1->val = 1; + auto& r2 = r1->next = std::make_unique<CustomRecursive>(); + r2->val = 2; + r2->next = std::move(r1); + + EXPECT_THAT(GenericPrintToString(*r2), + AllOf(HasExactlyNInstancesOf(2, "custom print"), + HasExactlyNInstancesOf(1, "<recursive>"))); + + r2->next = nullptr; // break the cycle +} +// <end DISABLED_ test section> + +enum CStyleEnum { kValue0, kValue1 }; +TEST(GenericPrinterTest, Enum) { + EXPECT_EQ("1", GenericPrintToString(kValue1)); +} + +enum class CppStyleEnum { kValue0, kValue1, kValue2 }; +TEST(GenericPrinterTest, EnumClass) { + EXPECT_EQ("2", GenericPrintToString(CppStyleEnum::kValue2)); +} + +enum class CharBasedEnum : char { kValueA = 'A', kValue1 = '\x01' }; +TEST(GenericPrinterTest, CharBasedEnum) { + EXPECT_EQ("'A' (0x41 65)", GenericPrintToString(CharBasedEnum::kValueA)); + EXPECT_EQ("'\\x01' (0x01 1)", GenericPrintToString(CharBasedEnum::kValue1)); +} + +enum class WideBasedEnum : uint64_t { + kValue = std::numeric_limits<uint64_t>::max() +}; +TEST(GenericPrinterTest, WideBasedEnum) { + EXPECT_EQ(absl::StrCat(std::numeric_limits<uint64_t>::max()), + GenericPrintToString(WideBasedEnum::kValue)); +} + +enum CStyleEnumWithStringify { kValueA = 0, kValueB = 2 }; +template <typename Sink> +void AbslStringify(Sink& sink, CStyleEnumWithStringify e) { + switch (e) { + case CStyleEnumWithStringify::kValueA: + sink.Append("A"); + return; + case CStyleEnumWithStringify::kValueB: + sink.Append("B"); + return; + } + sink.Append("??"); +} +TEST(GenericPrinterTest, CStyleEnumWithStringify) { + EXPECT_EQ("A", GenericPrintToString(CStyleEnumWithStringify::kValueA)); + EXPECT_EQ("??", + GenericPrintToString(static_cast<CStyleEnumWithStringify>(1))); +} + +enum class CppStyleEnumWithStringify { kValueA, kValueB, kValueC }; +template <typename Sink> +void AbslStringify(Sink& sink, CppStyleEnumWithStringify e) { + switch (e) { + case CppStyleEnumWithStringify::kValueA: + sink.Append("A"); + return; + case CppStyleEnumWithStringify::kValueB: + sink.Append("B"); + return; + case CppStyleEnumWithStringify::kValueC: + sink.Append("C"); + return; + } + sink.Append("??"); +} +TEST(GenericPrinterTest, CppStyleEnumWithStringify) { + EXPECT_EQ("A", GenericPrintToString(CppStyleEnumWithStringify::kValueA)); + EXPECT_EQ("??", + GenericPrintToString(static_cast<CppStyleEnumWithStringify>(17))); +} + +enum class CharBasedEnumWithStringify : char { kValueA = 'A', kValueB = 'B' }; +template <typename Sink> +void AbslStringify(Sink& sink, CharBasedEnumWithStringify e) { + switch (e) { + case CharBasedEnumWithStringify::kValueA: + sink.Append("charA"); + return; + case CharBasedEnumWithStringify::kValueB: + sink.Append("charB"); + return; + } + sink.Append("??"); +} +TEST(GenericPrinterTest, CharBasedEnumWithStringify) { + EXPECT_EQ("charA", GenericPrintToString(CharBasedEnumWithStringify::kValueA)); + EXPECT_EQ("??", + GenericPrintToString(static_cast<CharBasedEnumWithStringify>('W'))); +} + +} // namespace +} // namespace strings_internal +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/strings/internal/resize_uninitialized.h b/absl/strings/internal/resize_uninitialized.h index 49859dc..7e55e5a 100644 --- a/absl/strings/internal/resize_uninitialized.h +++ b/absl/strings/internal/resize_uninitialized.h
@@ -68,18 +68,6 @@ ResizeUninitializedTraits<string_type>::Resize(s, new_size); } -// Used to ensure exponential growth so that the amortized complexity of -// increasing the string size by a small amount is O(1), in contrast to -// O(str->size()) in the case of precise growth. -template <typename string_type> -void STLStringReserveAmortized(string_type* s, size_t new_size) { - const size_t cap = s->capacity(); - if (new_size > cap) { - // Make sure to always grow by at least a factor of 2x. - s->reserve((std::max)(new_size, 2 * cap)); - } -} - // In this type trait, we look for an __append_default_init member function, and // we use it if available, otherwise, we use append. template <typename string_type, typename = void>
diff --git a/absl/strings/internal/str_format/checker.h b/absl/strings/internal/str_format/checker.h index eab6ab9..ee3df26 100644 --- a/absl/strings/internal/str_format/checker.h +++ b/absl/strings/internal/str_format/checker.h
@@ -28,11 +28,9 @@ // We disable format checker under vscode intellisense compilation. // See https://github.com/microsoft/vscode-cpptools/issues/3683 for // more details. -#if ABSL_HAVE_ATTRIBUTE(enable_if) && !defined(__native_client__) && \ - !defined(__INTELLISENSE__) +#if ABSL_HAVE_ATTRIBUTE(enable_if) && !defined(__INTELLISENSE__) #define ABSL_INTERNAL_ENABLE_FORMAT_CHECKER 1 -#endif // ABSL_HAVE_ATTRIBUTE(enable_if) && !defined(__native_client__) && - // !defined(__INTELLISENSE__) +#endif // ABSL_HAVE_ATTRIBUTE(enable_if) && !defined(__INTELLISENSE__) #endif // ABSL_INTERNAL_ENABLE_FORMAT_CHECKER namespace absl {
diff --git a/absl/strings/internal/str_format/convert_test.cc b/absl/strings/internal/str_format/convert_test.cc index e4fa44e..f340df4 100644 --- a/absl/strings/internal/str_format/convert_test.cc +++ b/absl/strings/internal/str_format/convert_test.cc
@@ -284,6 +284,11 @@ return native_traits; } +bool IsNativeHexFloatConversion(char f) { return f == 'a' || f == 'A'; } +bool IsNativeFloatConversion(char f) { + return f == 'f' || f == 'F' || f == 'e' || f == 'E' || f == 'a' || f == 'A'; +} + class FormatConvertTest : public ::testing::Test { }; template <typename T> @@ -799,20 +804,19 @@ 'e', 'E'}) { std::string fmt_str = std::string(fmt) + f; - if (fmt == absl::string_view("%.5000") && f != 'f' && f != 'F' && - f != 'a' && f != 'A') { + if (fmt == absl::string_view("%.5000") && !IsNativeFloatConversion(f)) { // This particular test takes way too long with snprintf. // Disable for the case we are not implementing natively. continue; } - if ((f == 'a' || f == 'A') && + if (IsNativeHexFloatConversion(f) && !native_traits.hex_float_has_glibc_rounding) { continue; } if (!native_traits.hex_float_prefers_denormal_repr && - (f == 'a' || f == 'A') && + IsNativeHexFloatConversion(f) && std::fpclassify(tested_float) == FP_SUBNORMAL) { continue; } @@ -1059,6 +1063,17 @@ "0.000000000000000000000000000000000002" "25694915357879201529997415146671170141" "1837869002408041296803276054561138153076171875"); + + // Scientific exponent cases + // Round to even with all zeros after round digit + EXPECT_EQ(format("%0.13e", 1671075773261250), "1.6710757732612e+15"); + + // Rounding where precision is in first digit run + EXPECT_EQ(format("%0.1e", -1.93437090148818698e-297), "-1.9e-297"); + EXPECT_EQ(format("%0.1e", -9.92255780642280927e-298), "-9.9e-298"); + + // Rounding large negative exponent first digit + EXPECT_EQ(format("%0.1e", -8.956e-294), "-9.0e-294"); } TEST_F(FormatConvertTest, DoubleRoundA) { @@ -1317,14 +1332,13 @@ 'e', 'E'}) { std::string fmt_str = std::string(fmt) + 'L' + f; - if (fmt == absl::string_view("%.5000") && f != 'f' && f != 'F' && - f != 'a' && f != 'A') { + if (fmt == absl::string_view("%.5000") && !IsNativeFloatConversion(f)) { // This particular test takes way too long with snprintf. // Disable for the case we are not implementing natively. continue; } - if (f == 'a' || f == 'A') { + if (IsNativeHexFloatConversion(f)) { if (!native_traits.hex_float_has_glibc_rounding || !native_traits.hex_float_optimizes_leading_digit_bit_count) { continue;
diff --git a/absl/strings/internal/str_format/float_conversion.cc b/absl/strings/internal/str_format/float_conversion.cc index aa31998..bcef275 100644 --- a/absl/strings/internal/str_format/float_conversion.cc +++ b/absl/strings/internal/str_format/float_conversion.cc
@@ -20,7 +20,10 @@ #include <array> #include <cassert> #include <cmath> +#include <cstdint> +#include <cstring> #include <limits> +#include <optional> #include <string> #include "absl/base/attributes.h" @@ -31,7 +34,9 @@ #include "absl/numeric/bits.h" #include "absl/numeric/int128.h" #include "absl/numeric/internal/representation.h" +#include "absl/strings/internal/str_format/extension.h" #include "absl/strings/numbers.h" +#include "absl/strings/string_view.h" #include "absl/types/optional.h" #include "absl/types/span.h" @@ -451,6 +456,71 @@ return p; } +struct FractionalDigitPrinterResult { + char* end; + size_t skipped_zeros; + bool nonzero_remainder; +}; + +FractionalDigitPrinterResult PrintFractionalDigitsScientific( + uint64_t v, char* start, int exp, size_t precision, bool skip_zeros) { + char* p = start; + v <<= (64 - exp); + + size_t skipped_zeros = 0; + while (v != 0 && precision > 0) { + char carry = MultiplyBy10WithCarry(&v, 0); + if (skip_zeros) { + if (carry == 0) { + ++skipped_zeros; + continue; + } + skip_zeros = false; + } + *p++ = carry + '0'; + --precision; + } + return {p, skipped_zeros, v != 0}; +} + +FractionalDigitPrinterResult PrintFractionalDigitsScientific( + uint128 v, char* start, int exp, size_t precision, bool skip_zeros) { + char* p = start; + v <<= (128 - exp); + auto high = static_cast<uint64_t>(v >> 64); + auto low = static_cast<uint64_t>(v); + + size_t skipped_zeros = 0; + while (precision > 0 && low != 0) { + char carry = MultiplyBy10WithCarry(&low, 0); + carry = MultiplyBy10WithCarry(&high, carry); + if (skip_zeros) { + if (carry == 0) { + ++skipped_zeros; + continue; + } + skip_zeros = false; + } + *p++ = carry + '0'; + --precision; + } + + while (precision > 0 && high != 0) { + char carry = MultiplyBy10WithCarry(&high, 0); + if (skip_zeros) { + if (carry == 0) { + ++skipped_zeros; + continue; + } + skip_zeros = false; + } + *p++ = carry + '0'; + --precision; + } + + return {p, skipped_zeros, high != 0 || low != 0}; +} + struct FormatState { char sign_char; size_t precision; @@ -1333,6 +1403,427 @@ sink->Append(right_spaces, ' '); } +template <typename Int> +void FormatE(Int mantissa, int exp, bool uppercase, const FormatState& state) { + if (exp > 0) { + const int total_bits = + static_cast<int>(sizeof(Int) * 8) - LeadingZeros(mantissa) + exp; + if (total_bits > 128) { + FormatEPositiveExpSlow(mantissa, exp, uppercase, state); + return; + } + } else { + if (ABSL_PREDICT_FALSE(exp < -128)) { + FormatENegativeExpSlow(mantissa, exp, uppercase, state); + return; + } + } + FormatEFast(mantissa, exp, uppercase, state); +} + +// Guaranteed to fit into 128 bits at this point +template <typename Int> +void FormatEFast(Int v, int exp, bool uppercase, const FormatState& state) { + if (!v) { + absl::string_view mantissa_str = state.ShouldPrintDot() ? "0." : "0"; + FinalPrint(state, mantissa_str, 0, state.precision, + uppercase ? "E+00" : "e+00"); + return; + } + constexpr int kInputBits = sizeof(Int) * 8; + constexpr int kMaxFractionalDigits = 128; + constexpr int kBufferSize = 2 + // '.' + rounding + kMaxFixedPrecision + // Integral + kMaxFractionalDigits; // Fractional + const int total_bits = kInputBits - LeadingZeros(v) + exp; + char buffer[kBufferSize]; + char* integral_start = buffer + 2; + char* integral_end = buffer + 2 + kMaxFixedPrecision; + char* final_start; + char* final_end; + bool zero_integral = false; + int scientific_exp = 0; + size_t digits_printed = 0; + size_t trailing_zeros = 0; + bool has_more_non_zero = false; + + auto check_integral_zeros = + [](char* const begin, char* const end, + const size_t precision, size_t digits_processed) -> bool { + // When considering rounding to even, we care about the digits after the + // round digit which means the total digits to move from the start is + // precision + 2 since the first digit we print before the decimal point + // is not a part of precision. + size_t digit_upper_bound = precision + 2; + if (digits_processed > digit_upper_bound) { + return std::any_of(begin + digit_upper_bound, end, + [](char c) { return c != '0'; }); + } + return false; + }; + + if (exp >= 0) { + integral_end = total_bits <= 64 ? numbers_internal::FastIntToBuffer( + static_cast<uint64_t>(v) << exp, integral_start) + : numbers_internal::FastIntToBuffer( + static_cast<uint128>(v) << exp, integral_start); + *integral_end = '0'; + final_start = integral_start; + // Integral is guaranteed to be non-zero at this point. + scientific_exp = static_cast<int>(integral_end - integral_start) - 1; + digits_printed = static_cast<size_t>(integral_end - integral_start); + final_end = integral_end; + has_more_non_zero = check_integral_zeros(integral_start, integral_end, + state.precision, digits_printed); + } else { + exp = -exp; + if (exp < kInputBits) { + integral_end = + numbers_internal::FastIntToBuffer(v >> exp, integral_start); + } + *integral_end = '0'; + // We didn't move integral_start and it gets set to 0 in + zero_integral = exp >= kInputBits || v >> exp == 0; + if (!zero_integral) { + digits_printed = static_cast<size_t>(integral_end - integral_start); + has_more_non_zero = check_integral_zeros(integral_start, integral_end, + state.precision, digits_printed); + final_end = integral_end; + } + // Print fractional digits + char* fractional_start = integral_end; + + size_t digits_to_print = (state.precision + 1) >= digits_printed + ? state.precision + 1 - digits_printed + : 0; + bool print_extra = digits_printed <= state.precision + 1; + auto [fractional_end, skipped_zeros, has_nonzero_rem] = + exp <= 64 ? PrintFractionalDigitsScientific( + v, fractional_start, exp, digits_to_print + print_extra, + zero_integral) + : PrintFractionalDigitsScientific( + static_cast<uint128>(v), fractional_start, exp, + digits_to_print + print_extra, zero_integral); + final_end = fractional_end; + *fractional_end = '0'; + has_more_non_zero |= has_nonzero_rem; + digits_printed += static_cast<size_t>(fractional_end - fractional_start); + if (zero_integral) { + scientific_exp = -1 * static_cast<int>(skipped_zeros + 1); + } else { + scientific_exp = static_cast<int>(integral_end - integral_start) - 1; + } + // Don't do any rounding here, we will do it ourselves. + final_start = zero_integral ? fractional_start : integral_start; + } + + // For rounding + if (digits_printed >= state.precision + 1) { + final_start[-1] = '0'; + char* round_digit_ptr = final_start + 1 + state.precision; + if (*round_digit_ptr > '5') { + RoundUp(round_digit_ptr - 1); + } else if (*round_digit_ptr == '5') { + if (has_more_non_zero) { + RoundUp(round_digit_ptr - 1); + } else { + RoundToEven(round_digit_ptr - 1); + } + } + final_end = round_digit_ptr; + if (final_start[-1] == '1') { + --final_start; + ++scientific_exp; + --final_end; + } + } else { + // Need to pad with zeros. + trailing_zeros = state.precision - (digits_printed - 1); + } + + if (state.precision > 0 || state.ShouldPrintDot()) { + final_start[-1] = *final_start; + *final_start = '.'; + --final_start; + } + + // We need to add 2 to the buffer size for the +/- sign and the e + constexpr size_t kExpBufferSize = numbers_internal::kFastToBufferSize + 2; + char exp_buffer[kExpBufferSize]; + char* exp_ptr_start = exp_buffer; + char* exp_ptr = exp_ptr_start; + *exp_ptr++ = uppercase ? 'E' : 'e'; + if (scientific_exp >= 0) { + *exp_ptr++ = '+'; + } else { + *exp_ptr++ = '-'; + scientific_exp = -scientific_exp; + } + + if (scientific_exp < 10) { + *exp_ptr++ = '0'; + } + exp_ptr = numbers_internal::FastIntToBuffer(scientific_exp, exp_ptr); + FinalPrint(state, + absl::string_view(final_start, + static_cast<size_t>(final_end - final_start)), + 0, trailing_zeros, + absl::string_view(exp_ptr_start, + static_cast<size_t>(exp_ptr - exp_ptr_start))); +} + +void FormatENegativeExpSlow(uint128 mantissa, int exp, bool uppercase, + const FormatState& state) { + assert(exp < 0); + + FractionalDigitGenerator::RunConversion( + mantissa, -exp, + [&](FractionalDigitGenerator digit_gen) { + int first_digit = 0; + size_t nines = 0; + int num_leading_zeros = 0; + while (digit_gen.HasMoreDigits()) { + auto digits = digit_gen.GetDigits(); + if (digits.digit_before_nine != 0) { + first_digit = digits.digit_before_nine; + nines = digits.num_nines; + break; + } else if (digits.num_nines > 0) { + // This also means the first digit is 0 + first_digit = 9; + nines = digits.num_nines - 1; + num_leading_zeros++; + break; + } + num_leading_zeros++; + } + + bool change_to_zeros = false; + if (nines >= state.precision || state.precision == 0) { + bool round_up = false; + if (nines == state.precision) { + round_up = digit_gen.IsGreaterThanHalf(); + } else { + round_up = nines > 0 || digit_gen.IsGreaterThanHalf(); + } + if (round_up) { + first_digit = (first_digit == 9 ? 1 : first_digit + 1); + num_leading_zeros -= (first_digit == 1); + change_to_zeros = true; + } + } + int scientific_exp = -(num_leading_zeros + 1); + assert(scientific_exp < 0); + char exp_buffer[numbers_internal::kFastToBufferSize]; + char* exp_start = exp_buffer; + *exp_start++ = '-'; + if (scientific_exp > -10) { + *exp_start++ = '0'; + } + scientific_exp *= -1; + char* exp_end = + numbers_internal::FastIntToBuffer(scientific_exp, exp_start); + const size_t total_digits = + 1 // First digit + + (state.ShouldPrintDot() ? 1 : 0) // Decimal point + + state.precision // Digits after decimal + + 1 // 'e' or 'E' + + static_cast<size_t>(exp_end - exp_buffer); // Exponent digits + + 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); + } + + state.sink->Append(1, static_cast<char>(first_digit + '0')); + if (state.ShouldPrintDot()) { + state.sink->Append(1, '.'); + } + size_t digits_to_go = state.precision; + size_t nines_to_print = std::min(nines, digits_to_go); + state.sink->Append(nines_to_print, change_to_zeros ? '0' : '9'); + digits_to_go -= nines_to_print; + while (digits_to_go > 0 && digit_gen.HasMoreDigits()) { + auto digits = digit_gen.GetDigits(); + + if (digits.num_nines + 1 < digits_to_go) { + state.sink->Append(1, digits.digit_before_nine + '0'); + state.sink->Append(digits.num_nines, '9'); + digits_to_go -= digits.num_nines + 1; + } else { + bool round_up = false; + if (digits.num_nines + 1 > digits_to_go) { + round_up = true; + } else if (digit_gen.IsGreaterThanHalf()) { + round_up = true; + } else if (digit_gen.IsExactlyHalf()) { + round_up = + digits.num_nines != 0 || digits.digit_before_nine % 2 == 1; + } + if (round_up) { + state.sink->Append(1, digits.digit_before_nine + '1'); + --digits_to_go; + } else { + state.sink->Append(1, digits.digit_before_nine + '0'); + state.sink->Append(digits_to_go - 1, '9'); + digits_to_go = 0; + } + break; + } + } + state.sink->Append(digits_to_go, '0'); + state.sink->Append(1, uppercase ? 'E' : 'e'); + state.sink->Append(absl::string_view( + exp_buffer, static_cast<size_t>(exp_end - exp_buffer))); + state.sink->Append(padding.right_spaces, ' '); + }); +} + +std::optional<int> GetOneDigit(BinaryToDecimal& btd, + absl::string_view& digits_view) { + if (digits_view.empty()) { + if (!btd.AdvanceDigits()) return std::nullopt; + digits_view = btd.CurrentDigits(); + } + char d = digits_view.front(); + digits_view.remove_prefix(1); + return d - '0'; +} + +struct DigitRun { + std::optional<int> digit; + size_t nines; +}; + +DigitRun GetDigits(BinaryToDecimal& btd, absl::string_view& digits_view) { + auto peek_digit = [&]() -> std::optional<int> { + if (digits_view.empty()) { + if (!btd.AdvanceDigits()) return std::nullopt; + digits_view = btd.CurrentDigits(); + } + return digits_view.front() - '0'; + }; + + auto digit_before_nines = GetOneDigit(btd, digits_view); + if (!digit_before_nines.has_value()) return {std::nullopt, 0}; + + auto next_digit = peek_digit(); + size_t num_nines = 0; + while (next_digit == 9) { + // consume the 9 + GetOneDigit(btd, digits_view); + ++num_nines; + next_digit = peek_digit(); + } + return digit_before_nines == 9 + ? DigitRun{std::nullopt, num_nines + 1} + : DigitRun{digit_before_nines, num_nines}; +} + +void FormatEPositiveExpSlow(uint128 mantissa, int exp, bool uppercase, + const FormatState& state) { + BinaryToDecimal::RunConversion( + mantissa, exp, [&](BinaryToDecimal btd) { + int scientific_exp = static_cast<int>(btd.TotalDigits() - 1); + absl::string_view digits_view = btd.CurrentDigits(); + + size_t digits_to_go = state.precision + 1; + auto [first_digit_opt, nines] = GetDigits(btd, digits_view); + if (!first_digit_opt.has_value() && nines == 0) { + return; + } + + int first_digit = first_digit_opt.value_or(9); + if (!first_digit_opt) { + --nines; + } + + // At this point we are guaranteed to have some sort of first digit + bool change_to_zeros = false; + if (nines + 1 >= digits_to_go) { + // Everything we need to print is in the first DigitRun + auto next_digit_opt = GetDigits(btd, digits_view).digit; + if (nines == state.precision) { + change_to_zeros = next_digit_opt.value_or(0) > 4; + } else { + change_to_zeros = true; + } + if (change_to_zeros) { + if (first_digit != 9) { + first_digit = first_digit + 1; + } else { + first_digit = 1; + ++scientific_exp; + } + } + } + + char exp_buffer[numbers_internal::kFastToBufferSize]; + char* exp_buffer_end = + numbers_internal::FastIntToBuffer(scientific_exp, exp_buffer); + const size_t total_digits_out = + 1 + state.ShouldPrintDot() + state.precision + 2 + + (static_cast<size_t>(exp_buffer_end - exp_buffer)); + + const auto padding = ExtraWidthToPadding( + total_digits_out + (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); + } + state.sink->Append(1, static_cast<char>(first_digit + '0')); + --digits_to_go; + if (state.precision > 0 || state.ShouldPrintDot()) { + state.sink->Append(1, '.'); + } + state.sink->Append(std::min(digits_to_go, nines), + change_to_zeros ? '0' : '9'); + digits_to_go -= std::min(digits_to_go, nines); + while (digits_to_go > 0) { + auto [digit_opt, curr_nines] = GetDigits(btd, digits_view); + if (!digit_opt.has_value()) break; + int digit = *digit_opt; + if (curr_nines + 1 < digits_to_go) { + state.sink->Append(1, static_cast<char>(digit + '0')); + state.sink->Append(curr_nines, '9'); + digits_to_go -= curr_nines + 1; + } else { + bool need_round_up = false; + auto next_digit_opt = GetDigits(btd, digits_view).digit; + if (digits_to_go == 1) { + need_round_up = curr_nines > 0 || next_digit_opt > 4; + } else if (digits_to_go == curr_nines + 1) { + // Only round if next digit is > 4 + need_round_up = next_digit_opt.value_or(0) > 4; + } else { + // we know we need to round since nine is after precision ends + need_round_up = true; + } + state.sink->Append(1, + static_cast<char>(digit + need_round_up + '0')); + state.sink->Append(digits_to_go - 1, need_round_up ? '0' : '9'); + digits_to_go = 0; + } + } + + if (digits_to_go > 0) { + state.sink->Append(digits_to_go, '0'); + } + state.sink->Append(1, uppercase ? 'E' : 'e'); + state.sink->Append(1, scientific_exp >= 0 ? '+' : '-'); + if (scientific_exp < 10) { + state.sink->Append(1, '0'); + } + state.sink->Append(absl::string_view( + exp_buffer, static_cast<size_t>(exp_buffer_end - exp_buffer))); + state.sink->Append(padding.right_spaces, ' '); + }); +} + template <typename Float> bool FloatToSink(const Float v, const FormatConversionSpecImpl &conv, FormatSinkImpl *sink) { @@ -1371,14 +1862,10 @@ return true; } else if (c == FormatConversionCharInternal::e || c == FormatConversionCharInternal::E) { - if (!FloatToBuffer<FormatStyle::Precision>(decomposed, precision, &buffer, - &exp)) { - return FallbackToSnprintf(v, conv, sink); - } - if (!conv.has_alt_flag() && buffer.back() == '.') buffer.pop_back(); - PrintExponent( - exp, FormatConversionCharIsUpper(conv.conversion_char()) ? 'E' : 'e', - &buffer); + FormatE(decomposed.mantissa, decomposed.exponent, + FormatConversionCharIsUpper(conv.conversion_char()), + {sign_char, precision, conv, sink}); + return true; } else if (c == FormatConversionCharInternal::g || c == FormatConversionCharInternal::G) { precision = std::max(precision, size_t{1}) - 1;
diff --git a/absl/strings/internal/str_join_internal.h b/absl/strings/internal/str_join_internal.h index 31fcf6d..5f9df0e 100644 --- a/absl/strings/internal/str_join_internal.h +++ b/absl/strings/internal/str_join_internal.h
@@ -47,6 +47,7 @@ #include "absl/base/internal/raw_logging.h" #include "absl/strings/internal/ostringstream.h" #include "absl/strings/internal/resize_uninitialized.h" +#include "absl/strings/resize_and_overwrite.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" @@ -249,23 +250,25 @@ constexpr uint64_t kMaxSize = uint64_t{(std::numeric_limits<size_t>::max)()}; ABSL_INTERNAL_CHECK(result_size <= kMaxSize, "size_t overflow"); - STLStringResizeUninitialized(&result, static_cast<size_t>(result_size)); - // Joins strings - char* result_buf = &*result.begin(); - - memcpy(result_buf, start_value.data(), start_value.size()); - result_buf += start_value.size(); - for (Iterator it = start; ++it != end;) { - memcpy(result_buf, s.data(), s.size()); - result_buf += s.size(); - auto&& value = *it; - memcpy(result_buf, value.data(), value.size()); - result_buf += value.size(); - } + StringResizeAndOverwrite( + result, static_cast<size_t>(result_size), + [&start, &end, &start_value, s](char* result_buf, + size_t result_buf_size) { + // Joins strings + memcpy(result_buf, start_value.data(), start_value.size()); + result_buf += start_value.size(); + for (Iterator it = start; ++it != end;) { + memcpy(result_buf, s.data(), s.size()); + result_buf += s.size(); + auto&& value = *it; + memcpy(result_buf, value.data(), value.size()); + result_buf += value.size(); + } + return result_buf_size; + }); } } - return result; }
diff --git a/absl/strings/internal/str_split_internal.h b/absl/strings/internal/str_split_internal.h index ed1f117..31f1d65 100644 --- a/absl/strings/internal/str_split_internal.h +++ b/absl/strings/internal/str_split_internal.h
@@ -30,6 +30,7 @@ #define ABSL_STRINGS_INTERNAL_STR_SPLIT_INTERNAL_H_ #include <array> +#include <cassert> #include <cstddef> #include <initializer_list> #include <iterator> @@ -58,8 +59,12 @@ class ConvertibleToStringView { public: ConvertibleToStringView(const char* s) // NOLINT(runtime/explicit) - : value_(s) {} - ConvertibleToStringView(char* s) : value_(s) {} // NOLINT(runtime/explicit) + : value_(s) { + assert(s != nullptr); + } + ConvertibleToStringView(char* s) : value_(s) { // NOLINT(runtime/explicit) + assert(s != nullptr); + } ConvertibleToStringView(absl::string_view s) // NOLINT(runtime/explicit) : value_(s) {} ConvertibleToStringView(const std::string& s) // NOLINT(runtime/explicit)
diff --git a/absl/strings/numbers.cc b/absl/strings/numbers.cc index a83fd2c..b6a8e42 100644 --- a/absl/strings/numbers.cc +++ b/absl/strings/numbers.cc
@@ -50,11 +50,15 @@ bool SimpleAtof(absl::string_view str, float* absl_nonnull out) { *out = 0.0; str = StripAsciiWhitespace(str); + if (str.empty()) { + // absl::from_chars doesn't accept empty strings. + return false; + } // std::from_chars doesn't accept an initial +, but SimpleAtof does, so if one // is present, skip it, while avoiding accepting "+-0" as valid. - if (!str.empty() && str[0] == '+') { + if (str[0] == '+') { str.remove_prefix(1); - if (!str.empty() && str[0] == '-') { + if (str.empty() || str[0] == '-') { return false; } } @@ -81,11 +85,15 @@ bool SimpleAtod(absl::string_view str, double* absl_nonnull out) { *out = 0.0; str = StripAsciiWhitespace(str); + if (str.empty()) { + // absl::from_chars doesn't accept empty strings. + return false; + } // std::from_chars doesn't accept an initial +, but SimpleAtod does, so if one // is present, skip it, while avoiding accepting "+-0" as valid. - if (!str.empty() && str[0] == '+') { + if (str[0] == '+') { str.remove_prefix(1); - if (!str.empty() && str[0] == '-') { + if (str.empty() || str[0] == '-') { return false; } } @@ -234,6 +242,18 @@ return tens; } + +// Encodes v to buffer as 16 digits padded with leading zeros. +// Pre-condition: v must be < 10^16. +inline char* EncodePadded16(uint64_t v, char* absl_nonnull buffer) { + constexpr uint64_t k1e8 = 100000000; + uint32_t hi = static_cast<uint32_t>(v / k1e8); + uint32_t lo = static_cast<uint32_t>(v % k1e8); + little_endian::Store64(buffer, PrepareEightDigits(hi) + kEightZeroBytes); + little_endian::Store64(buffer + 8, PrepareEightDigits(lo) + kEightZeroBytes); + return buffer + 16; +} + inline ABSL_ATTRIBUTE_ALWAYS_INLINE char* absl_nonnull EncodeFullU32( uint32_t n, char* absl_nonnull out_str) { if (n < 10) { @@ -257,19 +277,19 @@ return out_str + sizeof(bottom); } -inline ABSL_ATTRIBUTE_ALWAYS_INLINE char* EncodeFullU64(uint64_t i, - char* buffer) { +inline ABSL_ATTRIBUTE_ALWAYS_INLINE char* absl_nonnull EncodeFullU64( + uint64_t i, char* absl_nonnull buffer) { if (i <= std::numeric_limits<uint32_t>::max()) { return EncodeFullU32(static_cast<uint32_t>(i), buffer); } uint32_t mod08; if (i < 1'0000'0000'0000'0000ull) { uint32_t div08 = static_cast<uint32_t>(i / 100'000'000ull); - mod08 = static_cast<uint32_t>(i % 100'000'000ull); + mod08 = static_cast<uint32_t>(i % 100'000'000ull); buffer = EncodeFullU32(div08, buffer); } else { uint64_t div08 = i / 100'000'000ull; - mod08 = static_cast<uint32_t>(i % 100'000'000ull); + mod08 = static_cast<uint32_t>(i % 100'000'000ull); uint32_t div016 = static_cast<uint32_t>(div08 / 100'000'000ull); uint32_t div08mod08 = static_cast<uint32_t>(div08 % 100'000'000ull); uint64_t mid_result = PrepareEightDigits(div08mod08) + kEightZeroBytes; @@ -282,6 +302,30 @@ return buffer + sizeof(mod_result); } +inline ABSL_ATTRIBUTE_ALWAYS_INLINE char* absl_nonnull EncodeFullU128( + uint128 i, char* absl_nonnull buffer) { + if (absl::Uint128High64(i) == 0) { + return EncodeFullU64(absl::Uint128Low64(i), buffer); + } + // We divide the number into 16-digit chunks because `EncodePadded16` is + // optimized to handle 16 digits at a time (as two 8-digit chunks). + constexpr uint64_t k1e16 = uint64_t{10'000'000'000'000'000}; + uint128 high = i / k1e16; + uint64_t low = absl::Uint128Low64(i % k1e16); + uint64_t mid = absl::Uint128Low64(high % k1e16); + high /= k1e16; + + if (high == 0) { + buffer = EncodeFullU64(mid, buffer); + buffer = EncodePadded16(low, buffer); + } else { + buffer = EncodeFullU64(absl::Uint128Low64(high), buffer); + buffer = EncodePadded16(mid, buffer); + buffer = EncodePadded16(low, buffer); + } + return buffer; +} + } // namespace void numbers_internal::PutTwoDigits(uint32_t i, char* absl_nonnull buf) { @@ -337,6 +381,25 @@ return buffer; } +char* absl_nonnull numbers_internal::FastIntToBuffer( + uint128 i, char* absl_nonnull buffer) { + buffer = EncodeFullU128(i, buffer); + *buffer = '\0'; + return buffer; +} + +char* absl_nonnull numbers_internal::FastIntToBuffer( + int128 i, char* absl_nonnull buffer) { + uint128 u = static_cast<uint128>(i); + if (i < 0) { + *buffer++ = '-'; + u = -u; + } + buffer = EncodeFullU128(u, buffer); + *buffer = '\0'; + return buffer; +} + // Given a 128-bit number expressed as a pair of uint64_t, high half first, // return that number multiplied by the given 32-bit value. If the result is // too large to fit in a 128-bit number, divide it by 2 until it fits.
diff --git a/absl/strings/numbers.h b/absl/strings/numbers.h index 9c67974..fa552af 100644 --- a/absl/strings/numbers.h +++ b/absl/strings/numbers.h
@@ -174,8 +174,9 @@ bool safe_strtou128_base(absl::string_view text, absl::uint128* absl_nonnull value, int base); -static const int kFastToBufferSize = 32; -static const int kSixDigitsToBufferSize = 16; +inline constexpr int kFastToBuffer128Size = 41; +inline constexpr int kFastToBufferSize = 32; +inline constexpr int kSixDigitsToBufferSize = 16; // Helper function for fast formatting of floating-point values. // The result is the same as printf's "%g", a.k.a. "%.6g"; that is, six @@ -188,7 +189,8 @@ // WARNING: These functions may write more characters than necessary, because // they are intended for speed. All functions take an output buffer // as an argument and return a pointer to the last byte they wrote, which is the -// terminating '\0'. At most `kFastToBufferSize` bytes are written. +// terminating '\0'. The maximum size written is `kFastToBufferSize` for 64-bit +// integers or less, and `kFastToBuffer128Size` for 128-bit integers. char* absl_nonnull FastIntToBuffer(int32_t i, char* absl_nonnull buffer) ABSL_INTERNAL_NEED_MIN_SIZE(buffer, kFastToBufferSize); char* absl_nonnull FastIntToBuffer(uint32_t n, char* absl_nonnull out_str) @@ -197,25 +199,36 @@ ABSL_INTERNAL_NEED_MIN_SIZE(buffer, kFastToBufferSize); char* absl_nonnull FastIntToBuffer(uint64_t i, char* absl_nonnull buffer) ABSL_INTERNAL_NEED_MIN_SIZE(buffer, kFastToBufferSize); +char* absl_nonnull FastIntToBuffer(int128 i, char* absl_nonnull buffer) + ABSL_INTERNAL_NEED_MIN_SIZE(buffer, kFastToBuffer128Size); +char* absl_nonnull FastIntToBuffer(uint128 i, char* absl_nonnull buffer) + ABSL_INTERNAL_NEED_MIN_SIZE(buffer, kFastToBuffer128Size); -// For enums and integer types that are not an exact match for the types above, -// use templates to call the appropriate one of the four overloads above. +// For enums and integer types that are up to 128 bits and are not an exact +// match for the types above, use templates to call the appropriate one of the +// four overloads above. template <typename int_type> -char* absl_nonnull FastIntToBuffer(int_type i, char* absl_nonnull buffer) - ABSL_INTERNAL_NEED_MIN_SIZE(buffer, kFastToBufferSize) { - static_assert(sizeof(i) <= 64 / 8, - "FastIntToBuffer works only with 64-bit-or-less integers."); +char* absl_nonnull FastIntToBuffer(int_type i, + char* absl_nonnull buffer) + ABSL_INTERNAL_NEED_MIN_SIZE( + buffer, (sizeof(int_type) > 8 ? kFastToBuffer128Size + : kFastToBufferSize)) { // These conditions are constexpr bools to suppress MSVC warning C4127. constexpr bool kIsSigned = is_signed<int_type>(); constexpr bool kUse64Bit = sizeof(i) > 32 / 8; + constexpr bool kUse128Bit = sizeof(i) > 64 / 8; if (kIsSigned) { - if (kUse64Bit) { + if constexpr (kUse128Bit) { + return FastIntToBuffer(static_cast<int128>(i), buffer); + } else if constexpr (kUse64Bit) { return FastIntToBuffer(static_cast<int64_t>(i), buffer); } else { return FastIntToBuffer(static_cast<int32_t>(i), buffer); } } else { - if (kUse64Bit) { + if constexpr (kUse128Bit) { + return FastIntToBuffer(static_cast<uint128>(i), buffer); + } else if constexpr (kUse64Bit) { return FastIntToBuffer(static_cast<uint64_t>(i), buffer); } else { return FastIntToBuffer(static_cast<uint32_t>(i), buffer);
diff --git a/absl/strings/numbers_test.cc b/absl/strings/numbers_test.cc index 1a82087..2eaa8c7 100644 --- a/absl/strings/numbers_test.cc +++ b/absl/strings/numbers_test.cc
@@ -159,6 +159,8 @@ typedef MyInteger<int64_t> MyInt64; typedef MyInteger<uint64_t> MyUInt64; +typedef MyInteger<absl::uint128> MyUInt128; +typedef MyInteger<absl::int128> MyInt128; void CheckInt32(int32_t x) { char buffer[absl::numbers_internal::kFastToBufferSize]; @@ -212,6 +214,32 @@ EXPECT_EQ(expected, std::string(&buffer[1], my_actual)) << " Input " << x; } +void CheckUInt128(absl::uint128 x) { + char buffer[absl::numbers_internal::kFastToBuffer128Size]; + char* actual = absl::numbers_internal::FastIntToBuffer(x, buffer); + std::string s; + absl::strings_internal::OStringStream strm(&s); + strm << x; + EXPECT_EQ(s, std::string(buffer, actual)) << " Input " << s; + + char* my_actual = + absl::numbers_internal::FastIntToBuffer(MyUInt128(x), buffer); + EXPECT_EQ(s, std::string(buffer, my_actual)) << " Input " << s; +} + +void CheckInt128(absl::int128 x) { + char buffer[absl::numbers_internal::kFastToBuffer128Size]; + char* actual = absl::numbers_internal::FastIntToBuffer(x, buffer); + std::string s; + absl::strings_internal::OStringStream strm(&s); + strm << x; + EXPECT_EQ(s, std::string(buffer, actual)) << " Input " << s; + + char* my_actual = + absl::numbers_internal::FastIntToBuffer(MyInt128(x), buffer); + EXPECT_EQ(s, std::string(buffer, my_actual)) << " Input " << s; +} + void CheckHex64(uint64_t v) { char expected[16 + 1]; std::string actual = absl::StrCat(absl::Hex(v, absl::kZeroPad16)); @@ -251,6 +279,34 @@ CheckUInt64(uint64_t{1000000000000000000}); CheckUInt64(uint64_t{1199999999999999999}); CheckUInt64(std::numeric_limits<uint64_t>::max()); + CheckUInt128(0); + CheckUInt128(1); + CheckUInt128(9); + CheckUInt128(10); + CheckUInt128(99); + CheckUInt128(100); + CheckUInt128(std::numeric_limits<uint64_t>::max()); + CheckUInt128(absl::uint128(std::numeric_limits<uint64_t>::max()) + 1); + CheckUInt128(absl::MakeUint128(1, 0)); + absl::uint128 k1e16 = 10000000000000000ULL; + CheckUInt128(k1e16 - 1); + CheckUInt128(k1e16); + CheckUInt128(k1e16 + 1); + CheckUInt128(k1e16 * k1e16 - 1); + CheckUInt128(k1e16 * k1e16); + CheckUInt128(k1e16 * k1e16 + 1); + CheckUInt128(absl::Uint128Max() - 1); + CheckUInt128(absl::Uint128Max()); + + CheckInt128(0); + CheckInt128(1); + CheckInt128(-1); + CheckInt128(10); + CheckInt128(-10); + CheckInt128(absl::Int128Max()); + CheckInt128(absl::Int128Min()); + CheckInt128(absl::MakeInt128(-1, 1)); + CheckInt128(absl::MakeInt128(-1, std::numeric_limits<uint64_t>::max())); for (int i = 0; i < 10000; i++) { CheckHex64(i); @@ -451,6 +507,20 @@ VerifySimpleAtoiGood<std::string::size_type>(42, 42); } +TEST(NumbersTest, AtodEmpty) { + double d; + EXPECT_FALSE(absl::SimpleAtod("", &d)); + // Empty string_view takes a different code path from "". + EXPECT_FALSE(absl::SimpleAtod({}, &d)); +} + +TEST(NumbersTest, AtofEmpty) { + float f; + EXPECT_FALSE(absl::SimpleAtof("", &f)); + // Empty string_view takes a different code path from "". + EXPECT_FALSE(absl::SimpleAtof({}, &f)); +} + TEST(NumbersTest, Atod) { // DBL_TRUE_MIN and FLT_TRUE_MIN were not mandated in <cfloat> before C++17. #if !defined(DBL_TRUE_MIN)
diff --git a/absl/strings/resize_and_overwrite.h b/absl/strings/resize_and_overwrite.h new file mode 100644 index 0000000..04c12d2 --- /dev/null +++ b/absl/strings/resize_and_overwrite.h
@@ -0,0 +1,194 @@ +// Copyright 2025 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: resize_and_overwrite.h +// ----------------------------------------------------------------------------- +// +// This file contains a polyfill for C++23's +// std::basic_string<CharT,Traits,Allocator>::resize_and_overwrite +// +// The polyfill takes the form of a free function: + +// template<typename T, typename Op> +// void StringResizeAndOverwrite(T& str, typename T::size_type count, Op op); +// +// This avoids the cost of initializing a suitably-sized std::string when it is +// intended to be used as a char array, for example, to be populated by a +// C-style API. +// +// Example usage: +// +// std::string IntToString(int n) { +// std::string result; +// constexpr size_t kMaxIntChars = 10; +// absl::StringResizeAndOverwrite( +// result, kMaxIntChars, [n](char* buffer, size_t buffer_size) { +// return snprintf(buffer, buffer_size, "%d", n); +// }); +// return result; +// } +// +// https://en.cppreference.com/w/cpp/string/basic_string/resize_and_overwrite.html +// https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1072r10.html + +#ifndef ABSL_STRINGS_RESIZE_AND_OVERWRITE_H_ +#define ABSL_STRINGS_RESIZE_AND_OVERWRITE_H_ + +#include <cstddef> +#include <string> // IWYU pragma: keep +#include <type_traits> +#include <utility> + +#include "absl/base/config.h" +#include "absl/base/dynamic_annotations.h" +#include "absl/base/internal/throw_delegate.h" +#include "absl/base/macros.h" +#include "absl/base/optimization.h" + +#if defined(__cpp_lib_string_resize_and_overwrite) && \ + __cpp_lib_string_resize_and_overwrite >= 202110L +#define ABSL_INTERNAL_HAS_RESIZE_AND_OVERWRITE 1 +#endif + +namespace absl { +ABSL_NAMESPACE_BEGIN + +namespace strings_internal { + +#ifndef ABSL_INTERNAL_HAS_RESIZE_AND_OVERWRITE + +inline size_t ProbeResizeAndOverwriteOp(char*, size_t) { return 0; } + +// Prior to C++23, Google's libc++ backports resize_and_overwrite as +// __google_nonstandard_backport_resize_and_overwrite +template <typename T, typename = void> +struct has__google_nonstandard_backport_resize_and_overwrite : std::false_type { +}; + +template <typename T> +struct has__google_nonstandard_backport_resize_and_overwrite< + T, + std::void_t< + decltype(std::declval<T&>() + .__google_nonstandard_backport_resize_and_overwrite( + std::declval<size_t>(), ProbeResizeAndOverwriteOp))>> + : std::true_type {}; + +// Prior to C++23, the version of libstdc++ that shipped with GCC >= 14 +// has __resize_and_overwrite. +template <typename T, typename = void> +struct has__resize_and_overwrite : std::false_type {}; + +template <typename T> +struct has__resize_and_overwrite< + T, std::void_t<decltype(std::declval<T&>().__resize_and_overwrite( + std::declval<size_t>(), ProbeResizeAndOverwriteOp))>> + : std::true_type {}; + +// libc++ used __resize_default_init to achieve uninitialized string resizes +// before removing it September 2025, in favor of resize_and_overwrite. +// https://github.com/llvm/llvm-project/commit/92f5d8df361bb1bb6dea88f86faeedfd295ab970 +template <typename T, typename = void> +struct has__resize_default_init : std::false_type {}; + +template <typename T> +struct has__resize_default_init< + T, std::void_t<decltype(std::declval<T&>().__resize_default_init(42))>> + : std::true_type {}; + +// Prior to C++23, some versions of MSVC have _Resize_and_overwrite. +template <typename T, typename = void> +struct has_Resize_and_overwrite : std::false_type {}; + +template <typename T> +struct has_Resize_and_overwrite< + T, std::void_t<decltype(std::declval<T&>()._Resize_and_overwrite( + std::declval<size_t>(), ProbeResizeAndOverwriteOp))>> + : std::true_type {}; + +#endif // ifndef ABSL_INTERNAL_HAS_RESIZE_AND_OVERWRITE + +// A less-efficient fallback implementation that uses resize(). +template <typename T, typename Op> +void StringResizeAndOverwriteFallback(T& str, typename T::size_type n, Op op) { + if (ABSL_PREDICT_FALSE(n > str.max_size())) { + absl::base_internal::ThrowStdLengthError("absl::StringResizeAndOverwrite"); + } +#ifdef ABSL_HAVE_MEMORY_SANITIZER + auto old_size = str.size(); +#endif + str.resize(n); +#ifdef ABSL_HAVE_MEMORY_SANITIZER + if (old_size < n) { + ABSL_ANNOTATE_MEMORY_IS_UNINITIALIZED(str.data() + old_size, n - old_size); + } +#endif + auto new_size = std::move(op)(str.data(), n); + ABSL_HARDENING_ASSERT(new_size >= 0 && new_size <= n); + ABSL_HARDENING_ASSERT(str.data()[n] == typename T::value_type{}); + str.erase(static_cast<typename T::size_type>(new_size)); +} + +template <typename T, typename Op> +void StringResizeAndOverwriteImpl(T& str, typename T::size_type n, Op op) { +#ifdef ABSL_INTERNAL_HAS_RESIZE_AND_OVERWRITE + str.resize_and_overwrite(n, std::move(op)); +#else + if constexpr (strings_internal:: + has__google_nonstandard_backport_resize_and_overwrite< + T>::value) { + str.__google_nonstandard_backport_resize_and_overwrite(n, std::move(op)); + } else if constexpr (strings_internal::has__resize_and_overwrite<T>::value) { + str.__resize_and_overwrite(n, std::move(op)); + } else if constexpr (strings_internal::has__resize_default_init<T>::value) { + str.__resize_default_init(n); + str.__resize_default_init( + static_cast<typename T::size_type>(std::move(op)(str.data(), n))); + } else if constexpr (strings_internal::has_Resize_and_overwrite<T>::value) { + str._Resize_and_overwrite(n, std::move(op)); + } else { + strings_internal::StringResizeAndOverwriteFallback(str, n, std::move(op)); + } +#endif +} + +} // namespace strings_internal + +// Resizes `str` to contain at most `n` characters, using the user-provided +// operation `op` to modify the possibly indeterminate contents. `op` must +// return the finalized length of `str`. +// +// Invalidates all iterators, pointers, and references into `str`, regardless +// of whether reallocation occurs. +// +// `op(value_type* buf, size_t buf_size)` is allowed to write `value_type{}` to +// `buf[buf_size]`, which facilitiates interoperation with functions that write +// a trailing NUL. Please note that this requirement is more strict than +// `basic_string::resize_and_overwrite()`, which allows writing an abitrary +// value to `buf[buf_size]`. +template <typename T, typename Op> +void StringResizeAndOverwrite(T& str, typename T::size_type n, Op op) { + strings_internal::StringResizeAndOverwriteImpl(str, n, std::move(op)); +#if defined(ABSL_HAVE_MEMORY_SANITIZER) + __msan_check_mem_is_initialized(str.data(), str.size()); +#endif +} + +ABSL_NAMESPACE_END +} // namespace absl + +#undef ABSL_INTERNAL_HAS_RESIZE_AND_OVERWRITE + +#endif // ABSL_STRINGS_RESIZE_AND_OVERWRITE_H_
diff --git a/absl/strings/resize_and_overwrite_test.cc b/absl/strings/resize_and_overwrite_test.cc new file mode 100644 index 0000000..1070dec --- /dev/null +++ b/absl/strings/resize_and_overwrite_test.cc
@@ -0,0 +1,154 @@ +// Copyright 2025 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/resize_and_overwrite.h" + +#include <algorithm> +#include <cstddef> +#include <string> + +#include "gtest/gtest.h" +#include "absl/base/dynamic_annotations.h" +#include "absl/log/absl_check.h" + +namespace { + +struct ResizeAndOverwriteParam { + size_t initial_size; + size_t requested_capacity; + size_t final_size; +}; + +using StringResizeAndOverwriteTest = + ::testing::TestWithParam<ResizeAndOverwriteParam>; + +TEST_P(StringResizeAndOverwriteTest, StringResizeAndOverwrite) { + const auto& param = GetParam(); + std::string s(param.initial_size, 'a'); + absl::StringResizeAndOverwrite( + s, param.requested_capacity, [&](char* p, size_t n) { + ABSL_CHECK_EQ(n, param.requested_capacity); + if (param.final_size >= param.initial_size) { + // Append case. + std::fill(p + param.initial_size, p + param.final_size, 'b'); + } else if (param.final_size > 0) { + // Truncate case. + p[param.final_size - 1] = 'b'; + } + p[param.final_size] = '\0'; + return param.final_size; + }); + + std::string expected; + if (param.final_size >= param.initial_size) { + // Append case. + expected = std::string(param.initial_size, 'a') + + std::string(param.final_size - param.initial_size, 'b'); + } else if (param.final_size > 0) { + // Truncate case. + expected = std::string(param.final_size - 1, 'a') + std::string("b"); + } + + EXPECT_EQ(s, expected); + EXPECT_EQ(s.c_str()[param.final_size], '\0'); +} + +TEST_P(StringResizeAndOverwriteTest, StringResizeAndOverwriteFallback) { + const auto& param = GetParam(); + std::string s(param.initial_size, 'a'); + absl::strings_internal::StringResizeAndOverwriteFallback( + s, param.requested_capacity, [&](char* p, size_t n) { + ABSL_CHECK_EQ(n, param.requested_capacity); + if (param.final_size >= param.initial_size) { + // Append case. + std::fill(p + param.initial_size, p + param.final_size, 'b'); + } else if (param.final_size > 0) { + // Truncate case. + p[param.final_size - 1] = 'b'; + } + p[param.final_size] = '\0'; + return param.final_size; + }); + + std::string expected; + if (param.final_size >= param.initial_size) { + // Append case. + expected = std::string(param.initial_size, 'a') + + std::string(param.final_size - param.initial_size, 'b'); + } else if (param.final_size > 0) { + // Truncate case. + expected = std::string(param.final_size - 1, 'a') + std::string("b"); + } + + EXPECT_EQ(s, expected); + EXPECT_EQ(s.c_str()[param.final_size], '\0'); +} + +#ifdef ABSL_HAVE_MEMORY_SANITIZER +constexpr bool kMSan = true; +#else +constexpr bool kMSan = false; +#endif + +TEST_P(StringResizeAndOverwriteTest, Initialized) { + if (!kMSan) { + GTEST_SKIP() << "Skipping test without MSan."; + } + + const auto& param = GetParam(); + std::string s(param.initial_size, 'a'); + + auto op = [&]() { + absl::StringResizeAndOverwrite(s, param.requested_capacity, + [&](char*, size_t) { + // Fail to initialize the buffer in full. + return param.final_size; + }); + }; + + if (param.initial_size < param.final_size) { +#ifndef NDEBUG + EXPECT_DEATH_IF_SUPPORTED(op(), + "MemorySanitizer: use-of-uninitialized-value"); +#endif + } else { + // The string is fully initialized from the initial constructor, or we skip + // the check in optimized builds. + op(); + } +} + +// clang-format off +INSTANTIATE_TEST_SUITE_P(StringResizeAndOverwriteTestSuite, + StringResizeAndOverwriteTest, + ::testing::ValuesIn<ResizeAndOverwriteParam>({ + // Append cases. + {0, 10, 5}, + {10, 10, 10}, + {10, 15, 15}, + {10, 20, 15}, + {10, 40, 40}, + {10, 50, 40}, + {30, 35, 35}, + {30, 45, 35}, + {10, 30, 15}, + // Truncate cases. + {15, 15, 10}, + {40, 40, 35}, + {40, 30, 10}, + {10, 15, 0}, + })); +// clang-format on + +} // namespace
diff --git a/absl/strings/str_cat.cc b/absl/strings/str_cat.cc index 1f3cfbf..546b8ae 100644 --- a/absl/strings/str_cat.cc +++ b/absl/strings/str_cat.cc
@@ -26,7 +26,8 @@ #include "absl/base/config.h" #include "absl/base/internal/raw_logging.h" #include "absl/base/nullability.h" -#include "absl/strings/internal/resize_uninitialized.h" +#include "absl/strings/internal/append_and_overwrite.h" +#include "absl/strings/resize_and_overwrite.h" #include "absl/strings/string_view.h" namespace absl { @@ -52,11 +53,6 @@ return after; } -inline void STLStringAppendUninitializedAmortized(std::string* dest, - size_t to_append) { - strings_internal::AppendUninitializedTraits<std::string>::Append(dest, - to_append); -} } // namespace std::string StrCat(const AlphaNum& a, const AlphaNum& b) { @@ -67,13 +63,14 @@ const uint64_t result_size = static_cast<uint64_t>(a.size()) + static_cast<uint64_t>(b.size()); ABSL_INTERNAL_CHECK(result_size <= kMaxSize, "size_t overflow"); - absl::strings_internal::STLStringResizeUninitialized( - &result, static_cast<size_t>(result_size)); - char* const begin = &result[0]; - char* out = begin; - out = Append(out, a); - out = Append(out, b); - assert(out == begin + result.size()); + absl::StringResizeAndOverwrite(result, static_cast<size_t>(result_size), + [&a, &b](char* const begin, size_t buf_size) { + char* out = begin; + out = Append(out, a); + out = Append(out, b); + assert(out == begin + buf_size); + return buf_size; + }); return result; } @@ -86,14 +83,16 @@ static_cast<uint64_t>(b.size()) + static_cast<uint64_t>(c.size()); ABSL_INTERNAL_CHECK(result_size <= kMaxSize, "size_t overflow"); - strings_internal::STLStringResizeUninitialized( - &result, static_cast<size_t>(result_size)); - char* const begin = &result[0]; - char* out = begin; - out = Append(out, a); - out = Append(out, b); - out = Append(out, c); - assert(out == begin + result.size()); + absl::StringResizeAndOverwrite( + result, static_cast<size_t>(result_size), + [&a, &b, &c](char* const begin, size_t buf_size) { + char* out = begin; + out = Append(out, a); + out = Append(out, b); + out = Append(out, c); + assert(out == begin + buf_size); + return buf_size; + }); return result; } @@ -103,20 +102,21 @@ // Use uint64_t to prevent size_t overflow. We assume it is not possible for // in memory strings to overflow a uint64_t. constexpr uint64_t kMaxSize = uint64_t{std::numeric_limits<size_t>::max()}; - const uint64_t result_size = static_cast<uint64_t>(a.size()) + - static_cast<uint64_t>(b.size()) + - static_cast<uint64_t>(c.size()) + - static_cast<uint64_t>(d.size()); + const uint64_t result_size = + static_cast<uint64_t>(a.size()) + static_cast<uint64_t>(b.size()) + + static_cast<uint64_t>(c.size()) + static_cast<uint64_t>(d.size()); ABSL_INTERNAL_CHECK(result_size <= kMaxSize, "size_t overflow"); - strings_internal::STLStringResizeUninitialized( - &result, static_cast<size_t>(result_size)); - char* const begin = &result[0]; - char* out = begin; - out = Append(out, a); - out = Append(out, b); - out = Append(out, c); - out = Append(out, d); - assert(out == begin + result.size()); + absl::StringResizeAndOverwrite( + result, static_cast<size_t>(result_size), + [&a, &b, &c, &d](char* const begin, size_t buf_size) { + char* out = begin; + out = Append(out, a); + out = Append(out, b); + out = Append(out, c); + out = Append(out, d); + assert(out == begin + buf_size); + return buf_size; + }); return result; } @@ -133,19 +133,19 @@ total_size += piece.size(); } ABSL_INTERNAL_CHECK(total_size <= kMaxSize, "size_t overflow"); - strings_internal::STLStringResizeUninitialized( - &result, static_cast<size_t>(total_size)); - - char* const begin = &result[0]; - char* out = begin; - for (absl::string_view piece : pieces) { - const size_t this_size = piece.size(); - if (this_size != 0) { - memcpy(out, piece.data(), this_size); - out += this_size; - } - } - assert(out == begin + result.size()); + absl::StringResizeAndOverwrite(result, static_cast<size_t>(total_size), + [&pieces](char* const begin, size_t buf_size) { + char* out = begin; + for (absl::string_view piece : pieces) { + const size_t this_size = piece.size(); + if (this_size != 0) { + memcpy(out, piece.data(), this_size); + out += this_size; + } + } + assert(out == begin + buf_size); + return buf_size; + }); return result; } @@ -160,49 +160,51 @@ void AppendPieces(std::string* absl_nonnull dest, std::initializer_list<absl::string_view> pieces) { - size_t old_size = dest->size(); size_t to_append = 0; for (absl::string_view piece : pieces) { ASSERT_NO_OVERLAP(*dest, piece); to_append += piece.size(); } - STLStringAppendUninitializedAmortized(dest, to_append); - - char* const begin = &(*dest)[0]; - char* out = begin + old_size; - for (absl::string_view piece : pieces) { - const size_t this_size = piece.size(); - if (this_size != 0) { - memcpy(out, piece.data(), this_size); - out += this_size; - } - } - assert(out == begin + dest->size()); + StringAppendAndOverwrite(*dest, to_append, + [&pieces](char* const buf, size_t buf_size) { + char* out = buf; + for (absl::string_view piece : pieces) { + const size_t this_size = piece.size(); + if (this_size != 0) { + memcpy(out, piece.data(), this_size); + out += this_size; + } + } + assert(out == buf + buf_size); + return buf_size; + }); } } // namespace strings_internal void StrAppend(std::string* absl_nonnull dest, const AlphaNum& a) { ASSERT_NO_OVERLAP(*dest, a); - std::string::size_type old_size = dest->size(); - STLStringAppendUninitializedAmortized(dest, a.size()); - char* const begin = &(*dest)[0]; - char* out = begin + old_size; - out = Append(out, a); - assert(out == begin + dest->size()); + strings_internal::StringAppendAndOverwrite( + *dest, a.size(), [&a](char* const buf, size_t buf_size) { + char* out = buf; + out = Append(out, a); + assert(out == buf + buf_size); + return buf_size; + }); } void StrAppend(std::string* absl_nonnull dest, const AlphaNum& a, const AlphaNum& b) { ASSERT_NO_OVERLAP(*dest, a); ASSERT_NO_OVERLAP(*dest, b); - std::string::size_type old_size = dest->size(); - STLStringAppendUninitializedAmortized(dest, a.size() + b.size()); - char* const begin = &(*dest)[0]; - char* out = begin + old_size; - out = Append(out, a); - out = Append(out, b); - assert(out == begin + dest->size()); + strings_internal::StringAppendAndOverwrite( + *dest, a.size() + b.size(), [&a, &b](char* const buf, size_t buf_size) { + char* out = buf; + out = Append(out, a); + out = Append(out, b); + assert(out == buf + buf_size); + return buf_size; + }); } void StrAppend(std::string* absl_nonnull dest, const AlphaNum& a, @@ -210,14 +212,16 @@ ASSERT_NO_OVERLAP(*dest, a); ASSERT_NO_OVERLAP(*dest, b); ASSERT_NO_OVERLAP(*dest, c); - std::string::size_type old_size = dest->size(); - STLStringAppendUninitializedAmortized(dest, a.size() + b.size() + c.size()); - char* const begin = &(*dest)[0]; - char* out = begin + old_size; - out = Append(out, a); - out = Append(out, b); - out = Append(out, c); - assert(out == begin + dest->size()); + strings_internal::StringAppendAndOverwrite( + *dest, a.size() + b.size() + c.size(), + [&a, &b, &c](char* const buf, size_t buf_size) { + char* out = buf; + out = Append(out, a); + out = Append(out, b); + out = Append(out, c); + assert(out == buf + buf_size); + return buf_size; + }); } void StrAppend(std::string* absl_nonnull dest, const AlphaNum& a, @@ -226,16 +230,17 @@ ASSERT_NO_OVERLAP(*dest, b); ASSERT_NO_OVERLAP(*dest, c); ASSERT_NO_OVERLAP(*dest, d); - std::string::size_type old_size = dest->size(); - STLStringAppendUninitializedAmortized( - dest, a.size() + b.size() + c.size() + d.size()); - char* const begin = &(*dest)[0]; - char* out = begin + old_size; - out = Append(out, a); - out = Append(out, b); - out = Append(out, c); - out = Append(out, d); - assert(out == begin + dest->size()); + strings_internal::StringAppendAndOverwrite( + *dest, a.size() + b.size() + c.size() + d.size(), + [&a, &b, &c, &d](char* const buf, size_t buf_size) { + char* out = buf; + out = Append(out, a); + out = Append(out, b); + out = Append(out, c); + out = Append(out, d); + assert(out == buf + buf_size); + return buf_size; + }); } ABSL_NAMESPACE_END
diff --git a/absl/strings/str_cat.h b/absl/strings/str_cat.h index 84db0f6..48eb6f9 100644 --- a/absl/strings/str_cat.h +++ b/absl/strings/str_cat.h
@@ -109,6 +109,7 @@ #include "absl/strings/internal/resize_uninitialized.h" #include "absl/strings/internal/stringify_sink.h" #include "absl/strings/numbers.h" +#include "absl/strings/resize_and_overwrite.h" #include "absl/strings/string_view.h" #if !defined(ABSL_USES_STD_STRING_VIEW) @@ -471,23 +472,27 @@ // with 22 bytes (including NULL at the end). constexpr size_t kMaxDigits10 = 22; std::string result; - strings_internal::STLStringResizeUninitialized(&result, kMaxDigits10); - char* start = &result[0]; - // note: this can be optimized to not write last zero. - char* end = numbers_internal::FastIntToBuffer(i, start); - auto size = static_cast<size_t>(end - start); - assert((size < result.size()) && - "StrCat(Integer) does not fit into kMaxDigits10"); - result.erase(size); + StringResizeAndOverwrite( + result, kMaxDigits10, [i](char* start, size_t buf_size) { + // Note: This can be optimized to not write last zero. + char* end = numbers_internal::FastIntToBuffer(i, start); + auto size = static_cast<size_t>(end - start); + ABSL_ASSERT(size < buf_size); + return size; + }); return result; } + template <typename Float> std::string FloatToString(Float f) { std::string result; - strings_internal::STLStringResizeUninitialized( - &result, numbers_internal::kSixDigitsToBufferSize); - char* start = &result[0]; - result.erase(numbers_internal::SixDigitsToBuffer(f, start)); + StringResizeAndOverwrite(result, numbers_internal::kSixDigitsToBufferSize, + [f](char* start, size_t buf_size) { + size_t size = + numbers_internal::SixDigitsToBuffer(f, start); + ABSL_ASSERT(size < buf_size); + return size; + }); return result; }
diff --git a/absl/strings/str_format_test.cc b/absl/strings/str_format_test.cc index 969e1f9..a4c877a 100644 --- a/absl/strings/str_format_test.cc +++ b/absl/strings/str_format_test.cc
@@ -65,8 +65,8 @@ "", "a", "%80d", -#if !defined(_MSC_VER) && !defined(__ANDROID__) && !defined(__native_client__) - // MSVC, NaCL and Android don't support positional syntax. +#if !defined(_MSC_VER) && !defined(__ANDROID__) + // MSVC and Android don't support positional syntax. "complicated multipart %% %1$d format %1$0999d", #endif // _MSC_VER }; @@ -266,8 +266,8 @@ "a", "%80d", "%d %u %c %s %f %g", -#if !defined(_MSC_VER) && !defined(__ANDROID__) && !defined(__native_client__) - // MSVC, NaCL and Android don't support positional syntax. +#if !defined(_MSC_VER) && !defined(__ANDROID__) + // MSVC and Android don't support positional syntax. "complicated multipart %% %1$d format %1$080d", #endif // _MSC_VER }; @@ -516,14 +516,11 @@ EXPECT_EQ(result, 17); EXPECT_EQ(std::string(buffer), "NUMBER: 1234567"); - // The `output` parameter is annotated nonnull, but we want to test that - // it is never written to if the size is zero. - // Use a variable instead of passing nullptr directly to avoid a `-Wnonnull` - // warning. - char* null_output = nullptr; - result = - SNPrintF(null_output, 0, "Just checking the %s of the output.", "size"); + // Test that the buffer is never written to if the size is zero. + buffer[0] = '\0'; + result = SNPrintF(buffer, 0, "Just checking the %s of the output.", "size"); EXPECT_EQ(result, 37); + EXPECT_EQ(buffer[0], '\0'); } TEST_F(FormatEntryPointTest, SNPrintFWithV) { @@ -551,14 +548,11 @@ std::string size = "size"; - // The `output` parameter is annotated nonnull, but we want to test that - // it is never written to if the size is zero. - // Use a variable instead of passing nullptr directly to avoid a `-Wnonnull` - // warning. - char* null_output = nullptr; - result = - SNPrintF(null_output, 0, "Just checking the %v of the output.", size); + // Test that the buffer is never written to if the size is zero. + buffer[0] = '\0'; + result = SNPrintF(buffer, 0, "Just checking the %v of the output.", size); EXPECT_EQ(result, 37); + EXPECT_EQ(buffer[0], '\0'); } TEST(StrFormat, BehavesAsDocumented) {
diff --git a/absl/strings/str_split.h b/absl/strings/str_split.h index 7e8e31c..29fa4f7 100644 --- a/absl/strings/str_split.h +++ b/absl/strings/str_split.h
@@ -127,7 +127,7 @@ absl::string_view Find(absl::string_view text, size_t pos) const; private: - const std::string delimiter_; + std::string delimiter_; }; // ByAsciiWhitespace @@ -277,7 +277,7 @@ class MaxSplitsImpl { public: MaxSplitsImpl(Delimiter delimiter, int limit) - : delimiter_(delimiter), limit_(limit), count_(0) {} + : delimiter_(std::move(delimiter)), limit_(limit), count_(0) {} absl::string_view Find(absl::string_view text, size_t pos) { if (count_++ == limit_) { return absl::string_view(text.data() + text.size(), @@ -382,7 +382,7 @@ // // v[0] == " a ", v[1] == " ", v[2] == "b" struct SkipWhitespace { bool operator()(absl::string_view sp) const { - sp = absl::StripAsciiWhitespace(sp); + sp = absl::StripLeadingAsciiWhitespace(sp); return !sp.empty(); } };
diff --git a/absl/strings/str_split_test.cc b/absl/strings/str_split_test.cc index b083975..c17c472 100644 --- a/absl/strings/str_split_test.cc +++ b/absl/strings/str_split_test.cc
@@ -216,7 +216,7 @@ std::multimap<std::string, std::string> m = absl::StrSplit("a,1,b,2,a,3", ','); EXPECT_EQ(3, m.size()); - auto it = m.find("a"); + auto it = m.lower_bound("a"); EXPECT_EQ("1", it->second); ++it; EXPECT_EQ("3", it->second);
diff --git a/absl/strings/string_view.cc b/absl/strings/string_view.cc deleted file mode 100644 index 33bd1bb..0000000 --- a/absl/strings/string_view.cc +++ /dev/null
@@ -1,257 +0,0 @@ -// Copyright 2017 The Abseil Authors. -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// https://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -#include "absl/strings/string_view.h" - -#ifndef ABSL_USES_STD_STRING_VIEW - -#include <algorithm> -#include <climits> -#include <cstring> -#include <ostream> - -#include "absl/base/nullability.h" - -namespace absl { -ABSL_NAMESPACE_BEGIN - -namespace { - -// This is significantly faster for case-sensitive matches with very -// few possible matches. -const char* absl_nullable memmatch(const char* absl_nullable phaystack, - size_t haylen, - const char* absl_nullable pneedle, - size_t neelen) { - if (0 == neelen) { - return phaystack; // even if haylen is 0 - } - if (haylen < neelen) return nullptr; - - const char* match; - const char* hayend = phaystack + haylen - neelen + 1; - // A static cast is used here as memchr returns a const void *, and pointer - // arithmetic is not allowed on pointers to void. - while ( - (match = static_cast<const char*>(memchr( - phaystack, pneedle[0], static_cast<size_t>(hayend - phaystack))))) { - if (memcmp(match, pneedle, neelen) == 0) - return match; - else - phaystack = match + 1; - } - return nullptr; -} - -void WritePadding(std::ostream& o, size_t pad) { - char fill_buf[32]; - memset(fill_buf, o.fill(), sizeof(fill_buf)); - while (pad) { - size_t n = std::min(pad, sizeof(fill_buf)); - o.write(fill_buf, static_cast<std::streamsize>(n)); - pad -= n; - } -} - -class LookupTable { - public: - // For each character in wanted, sets the index corresponding - // to the ASCII code of that character. This is used by - // the find_.*_of methods below to tell whether or not a character is in - // the lookup table in constant time. - explicit LookupTable(string_view wanted) { - for (char c : wanted) { - table_[Index(c)] = true; - } - } - bool operator[](char c) const { return table_[Index(c)]; } - - private: - static unsigned char Index(char c) { return static_cast<unsigned char>(c); } - bool table_[UCHAR_MAX + 1] = {}; -}; - -} // namespace - -std::ostream& operator<<(std::ostream& o, string_view piece) { - std::ostream::sentry sentry(o); - if (sentry) { - size_t lpad = 0; - size_t rpad = 0; - if (static_cast<size_t>(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 { - lpad = pad; - } - } - if (lpad) WritePadding(o, lpad); - o.write(piece.data(), static_cast<std::streamsize>(piece.size())); - if (rpad) WritePadding(o, rpad); - o.width(0); - } - return o; -} - -string_view::size_type string_view::find(string_view s, - size_type pos) const noexcept { - if (empty() || pos > length_) { - if (empty() && pos == 0 && s.empty()) return 0; - return npos; - } - const char* result = memmatch(ptr_ + pos, length_ - pos, s.ptr_, s.length_); - return result ? static_cast<size_type>(result - ptr_) : npos; -} - -string_view::size_type string_view::find(char c, size_type pos) const noexcept { - if (empty() || pos >= length_) { - return npos; - } - const char* result = - static_cast<const char*>(memchr(ptr_ + pos, c, length_ - pos)); - return result != nullptr ? static_cast<size_type>(result - ptr_) : npos; -} - -string_view::size_type string_view::rfind(string_view s, - size_type pos) const noexcept { - if (length_ < s.length_) return npos; - 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 ? static_cast<size_type>(result - ptr_) : npos; -} - -// Search range is [0..pos] inclusive. If pos == npos, search everything. -string_view::size_type string_view::rfind(char c, - size_type pos) const noexcept { - // Note: memrchr() is not available on Windows. - if (empty()) return npos; - for (size_type i = std::min(pos, length_ - 1);; --i) { - if (ptr_[i] == c) { - return i; - } - if (i == 0) break; - } - return npos; -} - -string_view::size_type string_view::find_first_of( - string_view s, size_type pos) const noexcept { - if (empty() || s.empty()) { - return npos; - } - // Avoid the cost of LookupTable() for a single-character search. - if (s.length_ == 1) return find_first_of(s.ptr_[0], pos); - LookupTable tbl(s); - for (size_type i = pos; i < length_; ++i) { - if (tbl[ptr_[i]]) { - return i; - } - } - return npos; -} - -string_view::size_type string_view::find_first_not_of( - string_view s, size_type pos) const noexcept { - if (empty()) return npos; - // Avoid the cost of LookupTable() for a single-character search. - if (s.length_ == 1) return find_first_not_of(s.ptr_[0], pos); - LookupTable tbl(s); - for (size_type i = pos; i < length_; ++i) { - if (!tbl[ptr_[i]]) { - return i; - } - } - return npos; -} - -string_view::size_type string_view::find_first_not_of( - char c, size_type pos) const noexcept { - if (empty()) return npos; - for (; pos < length_; ++pos) { - if (ptr_[pos] != c) { - return pos; - } - } - return npos; -} - -string_view::size_type string_view::find_last_of(string_view s, - size_type pos) const noexcept { - if (empty() || s.empty()) return npos; - // Avoid the cost of LookupTable() for a single-character search. - if (s.length_ == 1) return find_last_of(s.ptr_[0], pos); - LookupTable tbl(s); - for (size_type i = std::min(pos, length_ - 1);; --i) { - if (tbl[ptr_[i]]) { - return i; - } - if (i == 0) break; - } - return npos; -} - -string_view::size_type string_view::find_last_not_of( - string_view s, size_type pos) const noexcept { - if (empty()) return npos; - size_type i = std::min(pos, length_ - 1); - if (s.empty()) return i; - // Avoid the cost of LookupTable() for a single-character search. - if (s.length_ == 1) return find_last_not_of(s.ptr_[0], pos); - LookupTable tbl(s); - for (;; --i) { - if (!tbl[ptr_[i]]) { - return i; - } - if (i == 0) break; - } - return npos; -} - -string_view::size_type string_view::find_last_not_of( - char c, size_type pos) const noexcept { - if (empty()) return npos; - size_type i = std::min(pos, length_ - 1); - for (;; --i) { - if (ptr_[i] != c) { - return i; - } - if (i == 0) break; - } - return npos; -} - -ABSL_NAMESPACE_END -} // namespace absl - -#else - -// https://github.com/abseil/abseil-cpp/issues/1465 -// CMake builds on Apple platforms error when libraries are empty. -// Our CMake configuration can avoid this error on header-only libraries, -// but since this library is conditionally empty, including a single -// variable is an easy workaround. -#ifdef __APPLE__ -namespace absl { -ABSL_NAMESPACE_BEGIN -namespace strings_internal { -extern const char kAvoidEmptyStringViewLibraryWarning; -const char kAvoidEmptyStringViewLibraryWarning = 0; -} // namespace strings_internal -ABSL_NAMESPACE_END -} // namespace absl -#endif // __APPLE__ - -#endif // ABSL_USES_STD_STRING_VIEW
diff --git a/absl/strings/string_view.h b/absl/strings/string_view.h index 9a1933b..9daa149 100644 --- a/absl/strings/string_view.h +++ b/absl/strings/string_view.h
@@ -1,4 +1,3 @@ -// // Copyright 2017 The Abseil Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,736 +16,31 @@ // File: string_view.h // ----------------------------------------------------------------------------- // -// This file contains the definition of the `absl::string_view` class. A -// `string_view` points to a contiguous span of characters, often part or all of -// another `std::string`, double-quoted string literal, character array, or even -// another `string_view`. -// -// This `absl::string_view` abstraction is designed to be a drop-in -// replacement for the C++17 `std::string_view` abstraction. +// Historical note: Abseil once provided an implementation of +// `absl::string_view` as a polyfill for `std::string_view` prior to C++17. Now +// that C++17 is required, `absl::string_view` is an alias for +// `std::string_view` + #ifndef ABSL_STRINGS_STRING_VIEW_H_ #define ABSL_STRINGS_STRING_VIEW_H_ -#include <algorithm> -#include <cassert> -#include <cstddef> -#include <cstring> -#include <iosfwd> -#include <iterator> -#include <limits> -#include <string> +#include <string_view> #include "absl/base/attributes.h" -#include "absl/base/nullability.h" #include "absl/base/config.h" -#include "absl/base/internal/throw_delegate.h" -#include "absl/base/macros.h" -#include "absl/base/optimization.h" -#include "absl/base/port.h" - -#ifdef ABSL_USES_STD_STRING_VIEW - -#include <string_view> // IWYU pragma: export - -namespace absl { -ABSL_NAMESPACE_BEGIN -using string_view = std::string_view; -ABSL_NAMESPACE_END -} // namespace absl - -#else // ABSL_USES_STD_STRING_VIEW - -#if ABSL_HAVE_BUILTIN(__builtin_memcmp) || \ - (defined(__GNUC__) && !defined(__clang__)) || \ - (defined(_MSC_VER) && _MSC_VER >= 1928) -#define ABSL_INTERNAL_STRING_VIEW_MEMCMP __builtin_memcmp -#else // ABSL_HAVE_BUILTIN(__builtin_memcmp) -#define ABSL_INTERNAL_STRING_VIEW_MEMCMP memcmp -#endif // ABSL_HAVE_BUILTIN(__builtin_memcmp) +#include "absl/base/nullability.h" namespace absl { ABSL_NAMESPACE_BEGIN -// absl::string_view -// -// A `string_view` provides a lightweight view into the string data provided by -// a `std::string`, double-quoted string literal, character array, or even -// another `string_view`. A `string_view` does *not* own the string to which it -// points, and that data cannot be modified through the view. -// -// You can use `string_view` as a function or method parameter anywhere a -// parameter can receive a double-quoted string literal, `const char*`, -// `std::string`, or another `absl::string_view` argument with no need to copy -// the string data. Systematic use of `string_view` within function arguments -// reduces data copies and `strlen()` calls. -// -// Because of its small size, prefer passing `string_view` by value: -// -// void MyFunction(absl::string_view arg); -// -// If circumstances require, you may also pass one by const reference: -// -// void MyFunction(const absl::string_view& arg); // not preferred -// -// Passing by value generates slightly smaller code for many architectures. -// -// In either case, the source data of the `string_view` must outlive the -// `string_view` itself. -// -// A `string_view` is also suitable for local variables if you know that the -// lifetime of the underlying object is longer than the lifetime of your -// `string_view` variable. However, beware of binding a `string_view` to a -// temporary value: -// -// // BAD use of string_view: lifetime problem -// absl::string_view sv = obj.ReturnAString(); -// -// // GOOD use of string_view: str outlives sv -// std::string str = obj.ReturnAString(); -// absl::string_view sv = str; -// -// Due to lifetime issues, a `string_view` is sometimes a poor choice for a -// return value and usually a poor choice for a data member. If you do use a -// `string_view` this way, it is your responsibility to ensure that the object -// pointed to by the `string_view` outlives the `string_view`. -// -// A `string_view` may represent a whole string or just part of a string. For -// example, when splitting a string, `std::vector<absl::string_view>` is a -// natural data type for the output. -// -// For another example, a Cord is a non-contiguous, potentially very -// long string-like object. The Cord class has an interface that iteratively -// provides string_view objects that point to the successive pieces of a Cord -// object. -// -// When constructed from a source which is NUL-terminated, the `string_view` -// itself will not include the NUL-terminator unless a specific size (including -// the NUL) is passed to the constructor. As a result, common idioms that work -// on NUL-terminated strings do not work on `string_view` objects. If you write -// code that scans a `string_view`, you must check its length rather than test -// for nul, for example. Note, however, that nuls may still be embedded within -// a `string_view` explicitly. -// -// You may create a null `string_view` in two ways: -// -// absl::string_view sv; -// absl::string_view sv(nullptr, 0); -// -// For the above, `sv.data() == nullptr`, `sv.length() == 0`, and -// `sv.empty() == true`. Also, if you create a `string_view` with a non-null -// pointer then `sv.data() != nullptr`. Thus, you can use `string_view()` to -// signal an undefined value that is different from other `string_view` values -// in a similar fashion to how `const char* p1 = nullptr;` is different from -// `const char* p2 = "";`. However, in practice, it is not recommended to rely -// on this behavior. -// -// Be careful not to confuse a null `string_view` with an empty one. A null -// `string_view` is an empty `string_view`, but some empty `string_view`s are -// not null. Prefer checking for emptiness over checking for null. -// -// There are many ways to create an empty string_view: -// -// const char* nullcp = nullptr; -// // string_view.size() will return 0 in all cases. -// absl::string_view(); -// absl::string_view(nullcp, 0); -// absl::string_view(""); -// absl::string_view("", 0); -// absl::string_view("abcdef", 0); -// absl::string_view("abcdef" + 6, 0); -// -// All empty `string_view` objects whether null or not, are equal: -// -// absl::string_view() == absl::string_view("", 0) -// absl::string_view(nullptr, 0) == absl::string_view("abcdef"+6, 0) -class ABSL_ATTRIBUTE_VIEW string_view { - public: - using traits_type = std::char_traits<char>; - using value_type = char; - using pointer = char* absl_nullable; - using const_pointer = const char* absl_nullable; - using reference = char&; - using const_reference = const char&; - using const_iterator = const char* absl_nullable; - using iterator = const_iterator; - using const_reverse_iterator = std::reverse_iterator<const_iterator>; - using reverse_iterator = const_reverse_iterator; - using size_type = size_t; - using difference_type = std::ptrdiff_t; - using absl_internal_is_view = std::true_type; - - static constexpr size_type npos = static_cast<size_type>(-1); - - // Null `string_view` constructor - constexpr string_view() noexcept : ptr_(nullptr), length_(0) {} - - // Implicit constructors - - template <typename Allocator> - string_view( // NOLINT(runtime/explicit) - const std::basic_string<char, std::char_traits<char>, Allocator>& str - ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept - // This is implemented in terms of `string_view(p, n)` so `str.size()` - // doesn't need to be reevaluated after `ptr_` is set. - // The length check is also skipped since it is unnecessary and causes - // code bloat. - : string_view(str.data(), str.size(), SkipCheckLengthTag{}) {} - - // Implicit constructor of a `string_view` from NUL-terminated `str`. When - // accepting possibly null strings, use `absl::NullSafeStringView(str)` - // instead (see below). - // The length check is skipped since it is unnecessary and causes code bloat. - constexpr string_view( // NOLINT(runtime/explicit) - const char* absl_nonnull str) - : ptr_(str), length_(str ? StrlenInternal(str) : 0) {} - - // Constructor of a `string_view` from a `const char*` and length. - constexpr string_view(const char* absl_nullable data, size_type len) - : ptr_(data), length_(CheckLengthInternal(len)) {} - - constexpr string_view(const string_view&) noexcept = default; - string_view& operator=(const string_view&) noexcept = default; - - // Iterators - - // string_view::begin() - // - // Returns an iterator pointing to the first character at the beginning of the - // `string_view`, or `end()` if the `string_view` is empty. - constexpr const_iterator begin() const noexcept { return ptr_; } - - // string_view::end() - // - // Returns an iterator pointing just beyond the last character at the end of - // the `string_view`. This iterator acts as a placeholder; attempting to - // access it results in undefined behavior. - constexpr const_iterator end() const noexcept { return ptr_ + length_; } - - // string_view::cbegin() - // - // Returns a const iterator pointing to the first character at the beginning - // of the `string_view`, or `end()` if the `string_view` is empty. - constexpr const_iterator cbegin() const noexcept { return begin(); } - - // string_view::cend() - // - // Returns a const iterator pointing just beyond the last character at the end - // of the `string_view`. This pointer acts as a placeholder; attempting to - // access its element results in undefined behavior. - constexpr const_iterator cend() const noexcept { return end(); } - - // string_view::rbegin() - // - // Returns a reverse iterator pointing to the last character at the end of the - // `string_view`, or `rend()` if the `string_view` is empty. - const_reverse_iterator rbegin() const noexcept { - return const_reverse_iterator(end()); - } - - // string_view::rend() - // - // Returns a reverse iterator pointing just before the first character at the - // beginning of the `string_view`. This pointer acts as a placeholder; - // attempting to access its element results in undefined behavior. - const_reverse_iterator rend() const noexcept { - return const_reverse_iterator(begin()); - } - - // string_view::crbegin() - // - // Returns a const reverse iterator pointing to the last character at the end - // of the `string_view`, or `crend()` if the `string_view` is empty. - const_reverse_iterator crbegin() const noexcept { return rbegin(); } - - // string_view::crend() - // - // Returns a const reverse iterator pointing just before the first character - // at the beginning of the `string_view`. This pointer acts as a placeholder; - // attempting to access its element results in undefined behavior. - const_reverse_iterator crend() const noexcept { return rend(); } - - // Capacity Utilities - - // string_view::size() - // - // Returns the number of characters in the `string_view`. - constexpr size_type size() const noexcept { return length_; } - - // string_view::length() - // - // Returns the number of characters in the `string_view`. Alias for `size()`. - constexpr size_type length() const noexcept { return size(); } - - // string_view::max_size() - // - // Returns the maximum number of characters the `string_view` can hold. - constexpr size_type max_size() const noexcept { return kMaxSize; } - - // string_view::empty() - // - // Checks if the `string_view` is empty (refers to no characters). - constexpr bool empty() const noexcept { return length_ == 0; } - - // string_view::operator[] - // - // Returns the ith element of the `string_view` using the array operator. - // Note that this operator does not perform any bounds checking. - constexpr const_reference operator[](size_type i) const { - ABSL_HARDENING_ASSERT(i < size()); - return ptr_[i]; - } - - // string_view::at() - // - // Returns the ith element of the `string_view`. Bounds checking is performed, - // and an exception of type `std::out_of_range` will be thrown on invalid - // access. - constexpr const_reference at(size_type i) const { - if (ABSL_PREDICT_FALSE(i >= size())) { - base_internal::ThrowStdOutOfRange("absl::string_view::at"); - } - return ptr_[i]; - } - - // string_view::front() - // - // Returns the first element of a `string_view`. - constexpr const_reference front() const { - ABSL_HARDENING_ASSERT(!empty()); - return ptr_[0]; - } - - // string_view::back() - // - // Returns the last element of a `string_view`. - constexpr const_reference back() const { - ABSL_HARDENING_ASSERT(!empty()); - return ptr_[size() - 1]; - } - - // string_view::data() - // - // Returns a pointer to the underlying character array (which is of course - // stored elsewhere). Note that `string_view::data()` may contain embedded nul - // characters, but the returned buffer may or may not be NUL-terminated; - // therefore, do not pass `data()` to a routine that expects a NUL-terminated - // string. - constexpr const_pointer data() const noexcept { return ptr_; } - - // Modifiers - - // string_view::remove_prefix() - // - // Removes the first `n` characters from the `string_view`. Note that the - // underlying string is not changed, only the view. - constexpr void remove_prefix(size_type n) { - ABSL_HARDENING_ASSERT(n <= length_); - ptr_ += n; - length_ -= n; - } - - // string_view::remove_suffix() - // - // Removes the last `n` characters from the `string_view`. Note that the - // underlying string is not changed, only the view. - constexpr void remove_suffix(size_type n) { - ABSL_HARDENING_ASSERT(n <= length_); - length_ -= n; - } - - // string_view::swap() - // - // Swaps this `string_view` with another `string_view`. - constexpr void swap(string_view& s) noexcept { - auto t = *this; - *this = s; - s = t; - } - - // Explicit conversion operators - - // Converts to `std::basic_string`. - template <typename A> - explicit operator std::basic_string<char, traits_type, A>() const { - if (!data()) return {}; - return std::basic_string<char, traits_type, A>(data(), size()); - } - - // string_view::copy() - // - // Copies the contents of the `string_view` at offset `pos` and length `n` - // into `buf`. - size_type copy(char* buf, size_type n, size_type pos = 0) const { - if (ABSL_PREDICT_FALSE(pos > length_)) { - base_internal::ThrowStdOutOfRange("absl::string_view::copy"); - } - size_type rlen = (std::min)(length_ - pos, n); - if (rlen > 0) { - const char* start = ptr_ + pos; - traits_type::copy(buf, start, rlen); - } - return rlen; - } - - // string_view::substr() - // - // Returns a "substring" of the `string_view` (at offset `pos` and length - // `n`) as another string_view. This function throws `std::out_of_bounds` if - // `pos > size`. - // Use absl::ClippedSubstr if you need a truncating substr operation. - constexpr string_view substr(size_type pos = 0, size_type n = npos) const { - if (ABSL_PREDICT_FALSE(pos > length_)) { - base_internal::ThrowStdOutOfRange("absl::string_view::substr"); - } - return string_view(ptr_ + pos, (std::min)(n, length_ - pos)); - } - - // string_view::compare() - // - // Performs a lexicographical comparison between this `string_view` and - // another `string_view` `x`, returning a negative value if `*this` is less - // than `x`, 0 if `*this` is equal to `x`, and a positive value if `*this` - // is greater than `x`. - constexpr int compare(string_view x) const noexcept { - return CompareImpl(length_, x.length_, - (std::min)(length_, x.length_) == 0 - ? 0 - : ABSL_INTERNAL_STRING_VIEW_MEMCMP( - ptr_, x.ptr_, (std::min)(length_, x.length_))); - } - - // Overload of `string_view::compare()` for comparing a substring of the - // 'string_view` and another `absl::string_view`. - constexpr int compare(size_type pos1, size_type count1, string_view v) const { - return substr(pos1, count1).compare(v); - } - - // Overload of `string_view::compare()` for comparing a substring of the - // `string_view` and a substring of another `absl::string_view`. - constexpr int compare(size_type pos1, size_type count1, string_view v, - size_type pos2, size_type count2) const { - return substr(pos1, count1).compare(v.substr(pos2, count2)); - } - - // Overload of `string_view::compare()` for comparing a `string_view` and a - // a different C-style string `s`. - constexpr int compare(const char* absl_nonnull s) const { - return compare(string_view(s)); - } - - // Overload of `string_view::compare()` for comparing a substring of the - // `string_view` and a different string C-style string `s`. - constexpr int compare(size_type pos1, size_type count1, - const char* absl_nonnull s) const { - return substr(pos1, count1).compare(string_view(s)); - } - - // Overload of `string_view::compare()` for comparing a substring of the - // `string_view` and a substring of a different C-style string `s`. - constexpr int compare(size_type pos1, size_type count1, - const char* absl_nonnull s, size_type count2) const { - return substr(pos1, count1).compare(string_view(s, count2)); - } - - // Find Utilities - - // string_view::find() - // - // Finds the first occurrence of the substring `s` within the `string_view`, - // returning the position of the first character's match, or `npos` if no - // match was found. - size_type find(string_view s, size_type pos = 0) const noexcept; - - // Overload of `string_view::find()` for finding the given character `c` - // within the `string_view`. - size_type find(char c, size_type pos = 0) const noexcept; - - // Overload of `string_view::find()` for finding a substring of a different - // C-style string `s` within the `string_view`. - size_type find(const char* absl_nonnull s, size_type pos, - size_type count) const { - return find(string_view(s, count), pos); - } - - // Overload of `string_view::find()` for finding a different C-style string - // `s` within the `string_view`. - size_type find(const char* absl_nonnull s, size_type pos = 0) const { - return find(string_view(s), pos); - } - - // string_view::rfind() - // - // Finds the last occurrence of a substring `s` within the `string_view`, - // returning the position of the first character's match, or `npos` if no - // match was found. - size_type rfind(string_view s, size_type pos = npos) const noexcept; - - // Overload of `string_view::rfind()` for finding the last given character `c` - // within the `string_view`. - size_type rfind(char c, size_type pos = npos) const noexcept; - - // Overload of `string_view::rfind()` for finding a substring of a different - // C-style string `s` within the `string_view`. - size_type rfind(const char* absl_nonnull s, size_type pos, - size_type count) const { - return rfind(string_view(s, count), pos); - } - - // Overload of `string_view::rfind()` for finding a different C-style string - // `s` within the `string_view`. - size_type rfind(const char* absl_nonnull s, size_type pos = npos) const { - return rfind(string_view(s), pos); - } - - // string_view::find_first_of() - // - // Finds the first occurrence of any of the characters in `s` within the - // `string_view`, returning the start position of the match, or `npos` if no - // match was found. - size_type find_first_of(string_view s, size_type pos = 0) const noexcept; - - // Overload of `string_view::find_first_of()` for finding a character `c` - // within the `string_view`. - size_type find_first_of(char c, size_type pos = 0) const noexcept { - return find(c, pos); - } - - // Overload of `string_view::find_first_of()` for finding a substring of a - // different C-style string `s` within the `string_view`. - size_type find_first_of(const char* absl_nonnull s, size_type pos, - size_type count) const { - return find_first_of(string_view(s, count), pos); - } - - // Overload of `string_view::find_first_of()` for finding a different C-style - // string `s` within the `string_view`. - size_type find_first_of(const char* absl_nonnull s, size_type pos = 0) const { - return find_first_of(string_view(s), pos); - } - - // string_view::find_last_of() - // - // Finds the last occurrence of any of the characters in `s` within the - // `string_view`, returning the start position of the match, or `npos` if no - // match was found. - size_type find_last_of(string_view s, size_type pos = npos) const noexcept; - - // Overload of `string_view::find_last_of()` for finding a character `c` - // within the `string_view`. - size_type find_last_of(char c, size_type pos = npos) const noexcept { - return rfind(c, pos); - } - - // Overload of `string_view::find_last_of()` for finding a substring of a - // different C-style string `s` within the `string_view`. - size_type find_last_of(const char* absl_nonnull s, size_type pos, - size_type count) const { - return find_last_of(string_view(s, count), pos); - } - - // Overload of `string_view::find_last_of()` for finding a different C-style - // string `s` within the `string_view`. - size_type find_last_of(const char* absl_nonnull s, - size_type pos = npos) const { - return find_last_of(string_view(s), pos); - } - - // string_view::find_first_not_of() - // - // Finds the first occurrence of any of the characters not in `s` within the - // `string_view`, returning the start position of the first non-match, or - // `npos` if no non-match was found. - size_type find_first_not_of(string_view s, size_type pos = 0) const noexcept; - - // Overload of `string_view::find_first_not_of()` for finding a character - // that is not `c` within the `string_view`. - size_type find_first_not_of(char c, size_type pos = 0) const noexcept; - - // Overload of `string_view::find_first_not_of()` for finding a substring of a - // different C-style string `s` within the `string_view`. - size_type find_first_not_of(const char* absl_nonnull s, size_type pos, - size_type count) const { - return find_first_not_of(string_view(s, count), pos); - } - - // Overload of `string_view::find_first_not_of()` for finding a different - // C-style string `s` within the `string_view`. - size_type find_first_not_of(const char* absl_nonnull s, - size_type pos = 0) const { - return find_first_not_of(string_view(s), pos); - } - - // string_view::find_last_not_of() - // - // Finds the last occurrence of any of the characters not in `s` within the - // `string_view`, returning the start position of the last non-match, or - // `npos` if no non-match was found. - size_type find_last_not_of(string_view s, - size_type pos = npos) const noexcept; - - // Overload of `string_view::find_last_not_of()` for finding a character - // that is not `c` within the `string_view`. - size_type find_last_not_of(char c, size_type pos = npos) const noexcept; - - // Overload of `string_view::find_last_not_of()` for finding a substring of a - // different C-style string `s` within the `string_view`. - size_type find_last_not_of(const char* absl_nonnull s, size_type pos, - size_type count) const { - return find_last_not_of(string_view(s, count), pos); - } - - // Overload of `string_view::find_last_not_of()` for finding a different - // C-style string `s` within the `string_view`. - size_type find_last_not_of(const char* absl_nonnull s, - size_type pos = npos) const { - return find_last_not_of(string_view(s), pos); - } - -#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L - // string_view::starts_with() - // - // Returns true if the `string_view` starts with the prefix `s`. - // - // This method only exists when targeting at least C++20. - // If support for C++ prior to C++20 is required, use `absl::StartsWith()` - // from `//absl/strings/match.h` for compatibility. - constexpr bool starts_with(string_view s) const noexcept { - return s.empty() || - (size() >= s.size() && - ABSL_INTERNAL_STRING_VIEW_MEMCMP(data(), s.data(), s.size()) == 0); - } - - // Overload of `string_view::starts_with()` that returns true if `c` is the - // first character of the `string_view`. - constexpr bool starts_with(char c) const noexcept { - return !empty() && front() == c; - } - - // Overload of `string_view::starts_with()` that returns true if the - // `string_view` starts with the C-style prefix `s`. - constexpr bool starts_with(const char* s) const { - return starts_with(string_view(s)); - } - - // string_view::ends_with() - // - // Returns true if the `string_view` ends with the suffix `s`. - // - // This method only exists when targeting at least C++20. - // If support for C++ prior to C++20 is required, use `absl::EndsWith()` - // from `//absl/strings/match.h` for compatibility. - constexpr bool ends_with(string_view s) const noexcept { - return s.empty() || (size() >= s.size() && ABSL_INTERNAL_STRING_VIEW_MEMCMP( - data() + (size() - s.size()), - s.data(), s.size()) == 0); - } - - // Overload of `string_view::ends_with()` that returns true if `c` is the - // last character of the `string_view`. - constexpr bool ends_with(char c) const noexcept { - return !empty() && back() == c; - } - - // Overload of `string_view::ends_with()` that returns true if the - // `string_view` ends with the C-style suffix `s`. - constexpr bool ends_with(const char* s) const { - return ends_with(string_view(s)); - } -#endif // ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L - - private: - // The constructor from std::string delegates to this constructor. - // See the comment on that constructor for the rationale. - struct SkipCheckLengthTag {}; - string_view(const char* absl_nullable data, size_type len, - SkipCheckLengthTag) noexcept - : ptr_(data), length_(len) {} - - static constexpr size_type kMaxSize = - (std::numeric_limits<difference_type>::max)(); - - static constexpr size_type CheckLengthInternal(size_type len) { - ABSL_HARDENING_ASSERT(len <= kMaxSize); - return len; - } - - static constexpr size_type StrlenInternal(const char* absl_nonnull str) { -#if defined(_MSC_VER) && !defined(__clang__) - // MSVC 2017+ can evaluate this at compile-time. - const char* begin = str; - while (*str != '\0') ++str; - return str - begin; -#elif ABSL_HAVE_BUILTIN(__builtin_strlen) || \ - (defined(__GNUC__) && !defined(__clang__)) - // GCC has __builtin_strlen according to - // https://gcc.gnu.org/onlinedocs/gcc-4.7.0/gcc/Other-Builtins.html, but - // ABSL_HAVE_BUILTIN doesn't detect that, so we use the extra checks above. - // __builtin_strlen is constexpr. - return __builtin_strlen(str); -#else - return str ? strlen(str) : 0; -#endif - } - - static constexpr int CompareImpl(size_type length_a, size_type length_b, - int compare_result) { - return compare_result == 0 ? static_cast<int>(length_a > length_b) - - static_cast<int>(length_a < length_b) - : (compare_result < 0 ? -1 : 1); - } - - const char* absl_nullable ptr_; - size_type length_; -}; - -// This large function is defined inline so that in a fairly common case where -// one of the arguments is a literal, the compiler can elide a lot of the -// following comparisons. -constexpr bool operator==(string_view x, string_view y) noexcept { - return x.size() == y.size() && - (x.empty() || - ABSL_INTERNAL_STRING_VIEW_MEMCMP(x.data(), y.data(), x.size()) == 0); -} - -constexpr bool operator!=(string_view x, string_view y) noexcept { - return !(x == y); -} - -constexpr bool operator<(string_view x, string_view y) noexcept { - return x.compare(y) < 0; -} - -constexpr bool operator>(string_view x, string_view y) noexcept { - return y < x; -} - -constexpr bool operator<=(string_view x, string_view y) noexcept { - return !(y < x); -} - -constexpr bool operator>=(string_view x, string_view y) noexcept { - return !(x < y); -} - -// IO Insertion Operator -std::ostream& operator<<(std::ostream& o, string_view piece); - -ABSL_NAMESPACE_END -} // namespace absl - -#undef ABSL_INTERNAL_STRING_VIEW_MEMCMP - -#endif // ABSL_USES_STD_STRING_VIEW - -namespace absl { -ABSL_NAMESPACE_BEGIN +using std::string_view; // ClippedSubstr() // // Like `s.substr(pos, n)`, but clips `pos` to an upper bound of `s.size()`. // Provided because std::string_view::substr throws if `pos > size()` -inline string_view ClippedSubstr(string_view s, size_t pos, - size_t n = string_view::npos) { +inline string_view ClippedSubstr(string_view s ABSL_ATTRIBUTE_LIFETIME_BOUND, + size_t pos, size_t n = string_view::npos) { pos = (std::min)(pos, static_cast<size_t>(s.size())); return s.substr(pos, n); }
diff --git a/absl/strings/string_view_benchmark.cc b/absl/strings/string_view_benchmark.cc deleted file mode 100644 index 546f6ed..0000000 --- a/absl/strings/string_view_benchmark.cc +++ /dev/null
@@ -1,380 +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 <algorithm> -#include <cstddef> -#include <cstdint> -#include <map> -#include <random> -#include <string> -#include <unordered_set> -#include <vector> - -#include "absl/base/attributes.h" -#include "absl/base/internal/raw_logging.h" -#include "absl/base/macros.h" -#include "absl/random/random.h" -#include "absl/strings/str_cat.h" -#include "absl/strings/string_view.h" -#include "benchmark/benchmark.h" - -namespace { - -void BM_StringViewFromString(benchmark::State& state) { - std::string s(state.range(0), 'x'); - std::string* ps = &s; - struct SV { - SV() = default; - explicit SV(const std::string& s) : sv(s) {} - absl::string_view sv; - } sv; - SV* psv = &sv; - benchmark::DoNotOptimize(ps); - benchmark::DoNotOptimize(psv); - for (auto _ : state) { - new (psv) SV(*ps); - benchmark::DoNotOptimize(sv); - } -} -BENCHMARK(BM_StringViewFromString)->Arg(12)->Arg(128); - -// Provide a forcibly out-of-line wrapper for operator== that can be used in -// benchmarks to measure the impact of inlining. -ABSL_ATTRIBUTE_NOINLINE -bool NonInlinedEq(absl::string_view a, absl::string_view b) { return a == b; } - -// We use functions that cannot be inlined to perform the comparison loops so -// that inlining of the operator== can't optimize away *everything*. -ABSL_ATTRIBUTE_NOINLINE -void DoEqualityComparisons(benchmark::State& state, absl::string_view a, - absl::string_view b) { - for (auto _ : state) { - benchmark::DoNotOptimize(a == b); - } -} - -void BM_EqualIdentical(benchmark::State& state) { - std::string x(state.range(0), 'a'); - DoEqualityComparisons(state, x, x); -} -BENCHMARK(BM_EqualIdentical)->DenseRange(0, 3)->Range(4, 1 << 10); - -void BM_EqualSame(benchmark::State& state) { - std::string x(state.range(0), 'a'); - std::string y = x; - DoEqualityComparisons(state, x, y); -} -BENCHMARK(BM_EqualSame) - ->DenseRange(0, 10) - ->Arg(20) - ->Arg(40) - ->Arg(70) - ->Arg(110) - ->Range(160, 4096); - -void BM_EqualDifferent(benchmark::State& state) { - const int len = state.range(0); - std::string x(len, 'a'); - std::string y = x; - if (len > 0) { - y[len - 1] = 'b'; - } - DoEqualityComparisons(state, x, y); -} -BENCHMARK(BM_EqualDifferent)->DenseRange(0, 3)->Range(4, 1 << 10); - -// This benchmark is intended to check that important simplifications can be -// made with absl::string_view comparisons against constant strings. The idea is -// that if constant strings cause redundant components of the comparison, the -// compiler should detect and eliminate them. Here we use 8 different strings, -// each with the same size. Provided our comparison makes the implementation -// inline-able by the compiler, it should fold all of these away into a single -// size check once per loop iteration. -ABSL_ATTRIBUTE_NOINLINE -void DoConstantSizeInlinedEqualityComparisons(benchmark::State& state, - absl::string_view a) { - for (auto _ : state) { - benchmark::DoNotOptimize(a == "aaa"); - benchmark::DoNotOptimize(a == "bbb"); - benchmark::DoNotOptimize(a == "ccc"); - benchmark::DoNotOptimize(a == "ddd"); - benchmark::DoNotOptimize(a == "eee"); - benchmark::DoNotOptimize(a == "fff"); - benchmark::DoNotOptimize(a == "ggg"); - benchmark::DoNotOptimize(a == "hhh"); - } -} -void BM_EqualConstantSizeInlined(benchmark::State& state) { - std::string x(state.range(0), 'a'); - DoConstantSizeInlinedEqualityComparisons(state, x); -} -// We only need to check for size of 3, and <> 3 as this benchmark only has to -// do with size differences. -BENCHMARK(BM_EqualConstantSizeInlined)->DenseRange(2, 4); - -// This benchmark exists purely to give context to the above timings: this is -// what they would look like if the compiler is completely unable to simplify -// between two comparisons when they are comparing against constant strings. -ABSL_ATTRIBUTE_NOINLINE -void DoConstantSizeNonInlinedEqualityComparisons(benchmark::State& state, - absl::string_view a) { - for (auto _ : state) { - // Force these out-of-line to compare with the above function. - benchmark::DoNotOptimize(NonInlinedEq(a, "aaa")); - benchmark::DoNotOptimize(NonInlinedEq(a, "bbb")); - benchmark::DoNotOptimize(NonInlinedEq(a, "ccc")); - benchmark::DoNotOptimize(NonInlinedEq(a, "ddd")); - benchmark::DoNotOptimize(NonInlinedEq(a, "eee")); - benchmark::DoNotOptimize(NonInlinedEq(a, "fff")); - benchmark::DoNotOptimize(NonInlinedEq(a, "ggg")); - benchmark::DoNotOptimize(NonInlinedEq(a, "hhh")); - } -} - -void BM_EqualConstantSizeNonInlined(benchmark::State& state) { - std::string x(state.range(0), 'a'); - DoConstantSizeNonInlinedEqualityComparisons(state, x); -} -// We only need to check for size of 3, and <> 3 as this benchmark only has to -// do with size differences. -BENCHMARK(BM_EqualConstantSizeNonInlined)->DenseRange(2, 4); - -void BM_CompareSame(benchmark::State& state) { - const int len = state.range(0); - std::string x; - for (int i = 0; i < len; i++) { - x += 'a'; - } - std::string y = x; - absl::string_view a = x; - absl::string_view b = y; - - for (auto _ : state) { - benchmark::DoNotOptimize(a); - benchmark::DoNotOptimize(b); - benchmark::DoNotOptimize(a.compare(b)); - } -} -BENCHMARK(BM_CompareSame)->DenseRange(0, 3)->Range(4, 1 << 10); - -void BM_CompareFirstOneLess(benchmark::State& state) { - const int len = state.range(0); - std::string x(len, 'a'); - std::string y = x; - y.back() = 'b'; - absl::string_view a = x; - absl::string_view b = y; - - for (auto _ : state) { - benchmark::DoNotOptimize(a); - benchmark::DoNotOptimize(b); - benchmark::DoNotOptimize(a.compare(b)); - } -} -BENCHMARK(BM_CompareFirstOneLess)->DenseRange(1, 3)->Range(4, 1 << 10); - -void BM_CompareSecondOneLess(benchmark::State& state) { - const int len = state.range(0); - std::string x(len, 'a'); - std::string y = x; - x.back() = 'b'; - absl::string_view a = x; - absl::string_view b = y; - - for (auto _ : state) { - benchmark::DoNotOptimize(a); - benchmark::DoNotOptimize(b); - benchmark::DoNotOptimize(a.compare(b)); - } -} -BENCHMARK(BM_CompareSecondOneLess)->DenseRange(1, 3)->Range(4, 1 << 10); - -void BM_find_string_view_len_one(benchmark::State& state) { - std::string haystack(state.range(0), '0'); - absl::string_view s(haystack); - for (auto _ : state) { - benchmark::DoNotOptimize(s.find("x")); // not present; length 1 - } -} -BENCHMARK(BM_find_string_view_len_one)->Range(1, 1 << 20); - -void BM_find_string_view_len_two(benchmark::State& state) { - std::string haystack(state.range(0), '0'); - absl::string_view s(haystack); - for (auto _ : state) { - benchmark::DoNotOptimize(s.find("xx")); // not present; length 2 - } -} -BENCHMARK(BM_find_string_view_len_two)->Range(1, 1 << 20); - -void BM_find_one_char(benchmark::State& state) { - std::string haystack(state.range(0), '0'); - absl::string_view s(haystack); - for (auto _ : state) { - benchmark::DoNotOptimize(s.find('x')); // not present - } -} -BENCHMARK(BM_find_one_char)->Range(1, 1 << 20); - -void BM_rfind_one_char(benchmark::State& state) { - std::string haystack(state.range(0), '0'); - absl::string_view s(haystack); - for (auto _ : state) { - benchmark::DoNotOptimize(s.rfind('x')); // not present - } -} -BENCHMARK(BM_rfind_one_char)->Range(1, 1 << 20); - -void BM_worst_case_find_first_of(benchmark::State& state, int haystack_len) { - const int needle_len = state.range(0); - std::string needle; - for (int i = 0; i < needle_len; ++i) { - needle += 'a' + i; - } - std::string haystack(haystack_len, '0'); // 1000 zeros. - - absl::string_view s(haystack); - for (auto _ : state) { - benchmark::DoNotOptimize(s.find_first_of(needle)); - } -} - -void BM_find_first_of_short(benchmark::State& state) { - BM_worst_case_find_first_of(state, 10); -} - -void BM_find_first_of_medium(benchmark::State& state) { - BM_worst_case_find_first_of(state, 100); -} - -void BM_find_first_of_long(benchmark::State& state) { - BM_worst_case_find_first_of(state, 1000); -} - -BENCHMARK(BM_find_first_of_short)->DenseRange(0, 4)->Arg(8)->Arg(16)->Arg(32); -BENCHMARK(BM_find_first_of_medium)->DenseRange(0, 4)->Arg(8)->Arg(16)->Arg(32); -BENCHMARK(BM_find_first_of_long)->DenseRange(0, 4)->Arg(8)->Arg(16)->Arg(32); - -struct EasyMap : public std::map<absl::string_view, uint64_t> { - explicit EasyMap(size_t) {} -}; - -// This templated benchmark helper function is intended to stress operator== or -// operator< in a realistic test. It surely isn't entirely realistic, but it's -// a start. The test creates a map of type Map, a template arg, and populates -// it with table_size key/value pairs. Each key has WordsPerKey words. After -// creating the map, a number of lookups are done in random order. Some keys -// are used much more frequently than others in this phase of the test. -template <typename Map, int WordsPerKey> -void StringViewMapBenchmark(benchmark::State& state) { - const int table_size = state.range(0); - const double kFractionOfKeysThatAreHot = 0.2; - const int kNumLookupsOfHotKeys = 20; - const int kNumLookupsOfColdKeys = 1; - const char* words[] = {"the", "quick", "brown", "fox", "jumped", - "over", "the", "lazy", "dog", "and", - "found", "a", "large", "mushroom", "and", - "a", "couple", "crickets", "eating", "pie"}; - // Create some keys that consist of words in random order. - absl::InsecureBitGen rng; - std::vector<std::string> keys(table_size); - std::vector<int> all_indices; - const int kBlockSize = 1 << 12; - std::unordered_set<std::string> t(kBlockSize); - std::uniform_int_distribution<int> uniform(0, ABSL_ARRAYSIZE(words) - 1); - for (int i = 0; i < table_size; i++) { - all_indices.push_back(i); - do { - keys[i].clear(); - for (int j = 0; j < WordsPerKey; j++) { - absl::StrAppend(&keys[i], j > 0 ? " " : "", words[uniform(rng)]); - } - } while (!t.insert(keys[i]).second); - } - - // Create a list of strings to lookup: a permutation of the array of - // keys we just created, with repeats. "Hot" keys get repeated more. - std::shuffle(all_indices.begin(), all_indices.end(), rng); - const int num_hot = table_size * kFractionOfKeysThatAreHot; - const int num_cold = table_size - num_hot; - std::vector<int> hot_indices(all_indices.begin(), - all_indices.begin() + num_hot); - std::vector<int> indices; - for (int i = 0; i < kNumLookupsOfColdKeys; i++) { - indices.insert(indices.end(), all_indices.begin(), all_indices.end()); - } - for (int i = 0; i < kNumLookupsOfHotKeys - kNumLookupsOfColdKeys; i++) { - indices.insert(indices.end(), hot_indices.begin(), hot_indices.end()); - } - std::shuffle(indices.begin(), indices.end(), rng); - ABSL_RAW_CHECK( - num_cold * kNumLookupsOfColdKeys + num_hot * kNumLookupsOfHotKeys == - indices.size(), - ""); - // After constructing the array we probe it with absl::string_views built from - // test_strings. This means operator== won't see equal pointers, so - // it'll have to check for equal lengths and equal characters. - std::vector<std::string> test_strings(indices.size()); - for (int i = 0; i < indices.size(); i++) { - test_strings[i] = keys[indices[i]]; - } - - // Run the benchmark. It includes map construction but is mostly - // map lookups. - for (auto _ : state) { - Map h(table_size); - for (int i = 0; i < table_size; i++) { - h[keys[i]] = i * 2; - } - ABSL_RAW_CHECK(h.size() == table_size, ""); - uint64_t sum = 0; - for (int i = 0; i < indices.size(); i++) { - sum += h[test_strings[i]]; - } - benchmark::DoNotOptimize(sum); - } -} - -void BM_StdMap_4(benchmark::State& state) { - StringViewMapBenchmark<EasyMap, 4>(state); -} -BENCHMARK(BM_StdMap_4)->Range(1 << 10, 1 << 16); - -void BM_StdMap_8(benchmark::State& state) { - StringViewMapBenchmark<EasyMap, 8>(state); -} -BENCHMARK(BM_StdMap_8)->Range(1 << 10, 1 << 16); - -void BM_CopyToStringNative(benchmark::State& state) { - std::string src(state.range(0), 'x'); - absl::string_view sv(src); - std::string dst; - for (auto _ : state) { - dst.assign(sv.begin(), sv.end()); - } -} -BENCHMARK(BM_CopyToStringNative)->Range(1 << 3, 1 << 12); - -void BM_AppendToStringNative(benchmark::State& state) { - std::string src(state.range(0), 'x'); - absl::string_view sv(src); - std::string dst; - for (auto _ : state) { - dst.clear(); - dst.insert(dst.end(), sv.begin(), sv.end()); - } -} -BENCHMARK(BM_AppendToStringNative)->Range(1 << 3, 1 << 12); - -} // namespace
diff --git a/absl/strings/string_view_test.cc b/absl/strings/string_view_test.cc index 0a2a7a9..d6cec6f 100644 --- a/absl/strings/string_view_test.cc +++ b/absl/strings/string_view_test.cc
@@ -14,8 +14,7 @@ #include "absl/strings/string_view.h" -#include <stdlib.h> - +#include <array> #include <cstddef> #include <cstdlib> #include <cstring> @@ -34,704 +33,8 @@ #include "absl/base/config.h" #include "absl/meta/type_traits.h" -#if defined(ABSL_USES_STD_STRING_VIEW) || defined(__ANDROID__) -// We don't control the death messaging when using std::string_view. -// Android assert messages only go to system log, so death tests cannot inspect -// the message for matching. -#define ABSL_EXPECT_DEATH_IF_SUPPORTED(statement, regex) \ - EXPECT_DEATH_IF_SUPPORTED(statement, ".*") -#else -#define ABSL_EXPECT_DEATH_IF_SUPPORTED(statement, regex) \ - EXPECT_DEATH_IF_SUPPORTED(statement, regex) -#endif - namespace { -static_assert(!absl::type_traits_internal::IsOwner<absl::string_view>::value && - absl::type_traits_internal::IsView<absl::string_view>::value, - "string_view is a view, not an owner"); - -static_assert(absl::type_traits_internal::IsLifetimeBoundAssignment< - absl::string_view, std::string>::value, - "lifetimebound assignment not detected"); - -// A minimal allocator that uses malloc(). -template <typename T> -struct Mallocator { - typedef T value_type; - typedef size_t size_type; - typedef ptrdiff_t difference_type; - typedef T* pointer; - typedef const T* const_pointer; - typedef T& reference; - typedef const T& const_reference; - - size_type max_size() const { - return size_t(std::numeric_limits<size_type>::max()) / sizeof(value_type); - } - template <typename U> - struct rebind { - typedef Mallocator<U> other; - }; - Mallocator() = default; - template <class U> - Mallocator(const Mallocator<U>&) {} // NOLINT(runtime/explicit) - - T* allocate(size_t n) { return static_cast<T*>(std::malloc(n * sizeof(T))); } - void deallocate(T* p, size_t) { std::free(p); } -}; -template <typename T, typename U> -bool operator==(const Mallocator<T>&, const Mallocator<U>&) { - return true; -} -template <typename T, typename U> -bool operator!=(const Mallocator<T>&, const Mallocator<U>&) { - return false; -} - -TEST(StringViewTest, Ctor) { - { - // Null. - absl::string_view s10; - EXPECT_TRUE(s10.data() == nullptr); - EXPECT_EQ(0u, s10.length()); - } - - { - // const char* without length. - const char* hello = "hello"; - absl::string_view s20(hello); - EXPECT_TRUE(s20.data() == hello); - EXPECT_EQ(5u, s20.length()); - - // const char* with length. - absl::string_view s21(hello, 4); - EXPECT_TRUE(s21.data() == hello); - EXPECT_EQ(4u, s21.length()); - - // Not recommended, but valid C++ - absl::string_view s22(hello, 6); - EXPECT_TRUE(s22.data() == hello); - EXPECT_EQ(6u, s22.length()); - } - - { - // std::string. - std::string hola = "hola"; - absl::string_view s30(hola); - EXPECT_TRUE(s30.data() == hola.data()); - EXPECT_EQ(4u, s30.length()); - - // std::string with embedded '\0'. - hola.push_back('\0'); - hola.append("h2"); - hola.push_back('\0'); - absl::string_view s31(hola); - EXPECT_TRUE(s31.data() == hola.data()); - EXPECT_EQ(8u, s31.length()); - } - - { - using mstring = - std::basic_string<char, std::char_traits<char>, Mallocator<char>>; - mstring str1("BUNGIE-JUMPING!"); - const mstring str2("SLEEPING!"); - - absl::string_view s1(str1); - s1.remove_prefix(strlen("BUNGIE-JUM")); - - absl::string_view s2(str2); - s2.remove_prefix(strlen("SLEE")); - - EXPECT_EQ(s1, s2); - EXPECT_EQ(s1, "PING!"); - } - - // TODO(mec): absl::string_view(const absl::string_view&); -} - -TEST(StringViewTest, Swap) { - absl::string_view a("a"); - absl::string_view b("bbb"); - EXPECT_TRUE(noexcept(a.swap(b))); - a.swap(b); - EXPECT_EQ(a, "bbb"); - EXPECT_EQ(b, "a"); - a.swap(b); - EXPECT_EQ(a, "a"); - EXPECT_EQ(b, "bbb"); -} - -TEST(StringViewTest, STLComparator) { - std::string s1("foo"); - std::string s2("bar"); - std::string s3("baz"); - - absl::string_view p1(s1); - absl::string_view p2(s2); - absl::string_view p3(s3); - - typedef std::map<absl::string_view, int> TestMap; - TestMap map; - - 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(), 3u); - - TestMap::const_iterator iter = map.begin(); - EXPECT_EQ(iter->second, 1); - ++iter; - EXPECT_EQ(iter->second, 2); - ++iter; - EXPECT_EQ(iter->second, 0); - ++iter; - EXPECT_TRUE(iter == map.end()); - - TestMap::iterator new_iter = map.find("zot"); - EXPECT_TRUE(new_iter == map.end()); - - new_iter = map.find("bar"); - EXPECT_TRUE(new_iter != map.end()); - - map.erase(new_iter); - EXPECT_EQ(map.size(), 2u); - - iter = map.begin(); - EXPECT_EQ(iter->second, 2); - ++iter; - EXPECT_EQ(iter->second, 0); - ++iter; - EXPECT_TRUE(iter == map.end()); -} - -#define COMPARE(result, op, x, y) \ - EXPECT_EQ(result, absl::string_view((x)) op absl::string_view((y))); \ - EXPECT_EQ(result, absl::string_view((x)).compare(absl::string_view((y))) op 0) - -TEST(StringViewTest, ComparisonOperators) { - COMPARE(true, ==, "", ""); - COMPARE(true, ==, "", absl::string_view()); - COMPARE(true, ==, absl::string_view(), ""); - COMPARE(true, ==, "a", "a"); - COMPARE(true, ==, "aa", "aa"); - COMPARE(false, ==, "a", ""); - COMPARE(false, ==, "", "a"); - COMPARE(false, ==, "a", "b"); - COMPARE(false, ==, "a", "aa"); - COMPARE(false, ==, "aa", "a"); - - COMPARE(false, !=, "", ""); - COMPARE(false, !=, "a", "a"); - COMPARE(false, !=, "aa", "aa"); - COMPARE(true, !=, "a", ""); - COMPARE(true, !=, "", "a"); - COMPARE(true, !=, "a", "b"); - COMPARE(true, !=, "a", "aa"); - COMPARE(true, !=, "aa", "a"); - - COMPARE(true, <, "a", "b"); - COMPARE(true, <, "a", "aa"); - COMPARE(true, <, "aa", "b"); - COMPARE(true, <, "aa", "bb"); - COMPARE(false, <, "a", "a"); - COMPARE(false, <, "b", "a"); - COMPARE(false, <, "aa", "a"); - COMPARE(false, <, "b", "aa"); - COMPARE(false, <, "bb", "aa"); - - COMPARE(true, <=, "a", "a"); - COMPARE(true, <=, "a", "b"); - COMPARE(true, <=, "a", "aa"); - COMPARE(true, <=, "aa", "b"); - COMPARE(true, <=, "aa", "bb"); - COMPARE(false, <=, "b", "a"); - COMPARE(false, <=, "aa", "a"); - COMPARE(false, <=, "b", "aa"); - COMPARE(false, <=, "bb", "aa"); - - COMPARE(false, >=, "a", "b"); - COMPARE(false, >=, "a", "aa"); - COMPARE(false, >=, "aa", "b"); - COMPARE(false, >=, "aa", "bb"); - COMPARE(true, >=, "a", "a"); - COMPARE(true, >=, "b", "a"); - COMPARE(true, >=, "aa", "a"); - COMPARE(true, >=, "b", "aa"); - COMPARE(true, >=, "bb", "aa"); - - COMPARE(false, >, "a", "a"); - COMPARE(false, >, "a", "b"); - COMPARE(false, >, "a", "aa"); - COMPARE(false, >, "aa", "b"); - COMPARE(false, >, "aa", "bb"); - COMPARE(true, >, "b", "a"); - COMPARE(true, >, "aa", "a"); - COMPARE(true, >, "b", "aa"); - COMPARE(true, >, "bb", "aa"); -} - -TEST(StringViewTest, ComparisonOperatorsByCharacterPosition) { - std::string x; - for (size_t i = 0; i < 256; i++) { - x += 'a'; - std::string y = x; - COMPARE(true, ==, x, y); - for (size_t j = 0; j < i; j++) { - std::string z = x; - z[j] = 'b'; // Differs in position 'j' - COMPARE(false, ==, x, z); - COMPARE(true, <, x, z); - COMPARE(true, >, z, x); - if (j + 1 < i) { - z[j + 1] = 'A'; // Differs in position 'j+1' as well - COMPARE(false, ==, x, z); - COMPARE(true, <, x, z); - COMPARE(true, >, z, x); - z[j + 1] = 'z'; // Differs in position 'j+1' as well - COMPARE(false, ==, x, z); - COMPARE(true, <, x, z); - COMPARE(true, >, z, x); - } - } - } -} -#undef COMPARE - -// Sadly, our users often confuse std::string::npos with -// absl::string_view::npos; So much so that we test here that they are the same. -// They need to both be unsigned, and both be the maximum-valued integer of -// their type. - -template <typename T> -struct is_type { - template <typename U> - static bool same(U) { - return false; - } - static bool same(T) { return true; } -}; - -TEST(StringViewTest, NposMatchesStdStringView) { - EXPECT_EQ(absl::string_view::npos, std::string::npos); - - EXPECT_TRUE(is_type<size_t>::same(absl::string_view::npos)); - EXPECT_FALSE(is_type<size_t>::same("")); - - // Make sure absl::string_view::npos continues to be a header constant. - char test[absl::string_view::npos & 1] = {0}; - EXPECT_EQ(0, test[0]); -} - -TEST(StringViewTest, STL1) { - const absl::string_view a("abcdefghijklmnopqrstuvwxyz"); - const absl::string_view b("abc"); - const absl::string_view c("xyz"); - const absl::string_view d("foobar"); - const absl::string_view e; - std::string temp("123"); - temp += '\0'; - temp += "456"; - const absl::string_view f(temp); - - EXPECT_EQ(a[6], 'g'); - EXPECT_EQ(b[0], 'a'); - EXPECT_EQ(c[2], 'z'); - EXPECT_EQ(f[3], '\0'); - EXPECT_EQ(f[5], '5'); - - EXPECT_EQ(*d.data(), 'f'); - EXPECT_EQ(d.data()[5], 'r'); - EXPECT_TRUE(e.data() == nullptr); - - EXPECT_EQ(*a.begin(), 'a'); - EXPECT_EQ(*(b.begin() + 2), 'c'); - EXPECT_EQ(*(c.end() - 1), 'z'); - - EXPECT_EQ(*a.rbegin(), 'z'); - EXPECT_EQ(*(b.rbegin() + 2), 'a'); - EXPECT_EQ(*(c.rend() - 1), 'x'); - EXPECT_TRUE(a.rbegin() + 26 == a.rend()); - - 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()); - EXPECT_TRUE(d.begin() + 6 == d.end()); - - EXPECT_TRUE(e.empty()); - EXPECT_TRUE(e.begin() == e.end()); - - char buf[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), 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), 3u); - EXPECT_EQ(buf[0], c[0]); - EXPECT_EQ(buf[1], c[1]); - EXPECT_EQ(buf[2], c[2]); - EXPECT_EQ(buf[3], a[3]); -#ifdef ABSL_HAVE_EXCEPTIONS - EXPECT_THROW(a.copy(buf, 1, 27), std::out_of_range); -#else - ABSL_EXPECT_DEATH_IF_SUPPORTED(a.copy(buf, 1, 27), "absl::string_view::copy"); -#endif -} - -// Separated from STL1() because some compilers produce an overly -// large stack frame for the combined function. -TEST(StringViewTest, STL2) { - const absl::string_view a("abcdefghijklmnopqrstuvwxyz"); - const absl::string_view b("abc"); - const absl::string_view c("xyz"); - absl::string_view d("foobar"); - const absl::string_view e; - const absl::string_view f( - "123" - "\0" - "456", - 7); - - d = absl::string_view(); - 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), 0u); - EXPECT_EQ(a.find(b, 1), absl::string_view::npos); - 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), 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 - EXPECT_EQ(d.find(b), absl::string_view::npos); - EXPECT_EQ(e.find(b), absl::string_view::npos); - EXPECT_EQ(d.find(b, 4), absl::string_view::npos); - EXPECT_EQ(e.find(b, 7), absl::string_view::npos); - - size_t empty_search_pos = std::string().find(std::string()); - EXPECT_EQ(d.find(d), empty_search_pos); - EXPECT_EQ(d.find(e), empty_search_pos); - EXPECT_EQ(e.find(d), empty_search_pos); - EXPECT_EQ(e.find(e), empty_search_pos); - EXPECT_EQ(d.find(d, 4), std::string().find(std::string(), 4)); - EXPECT_EQ(d.find(e, 4), std::string().find(std::string(), 4)); - 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'), 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'), 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); - EXPECT_EQ(e.find('\0'), absl::string_view::npos); - EXPECT_EQ(d.find('\0', 4), absl::string_view::npos); - EXPECT_EQ(e.find('\0', 7), absl::string_view::npos); - EXPECT_EQ(d.find('x'), absl::string_view::npos); - EXPECT_EQ(e.find('x'), absl::string_view::npos); - 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), 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), - absl::string_view::npos); - // empty string nonsense - EXPECT_EQ(d.find(b.data(), 4, 0), absl::string_view::npos); - 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), 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), 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); - EXPECT_EQ(b.rfind(c), absl::string_view::npos); - 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), 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); - EXPECT_EQ(d.rfind(b, 4), absl::string_view::npos); - EXPECT_EQ(e.rfind(b, 7), absl::string_view::npos); - // empty string nonsense - EXPECT_EQ(d.rfind(d, 4), std::string().rfind(std::string())); - EXPECT_EQ(e.rfind(d, 7), std::string().rfind(std::string())); - EXPECT_EQ(d.rfind(e, 4), std::string().rfind(std::string())); - EXPECT_EQ(e.rfind(e, 7), std::string().rfind(std::string())); - EXPECT_EQ(d.rfind(d), std::string().rfind(std::string())); - EXPECT_EQ(e.rfind(d), std::string().rfind(std::string())); - 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'), 8u); - EXPECT_EQ(g.rfind('q'), absl::string_view::npos); - 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'), 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), 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 -TEST(StringViewTest, STL2FindFirst) { - const absl::string_view a("abcdefghijklmnopqrstuvwxyz"); - const absl::string_view b("abc"); - const absl::string_view c("xyz"); - absl::string_view d("foobar"); - const absl::string_view e; - const absl::string_view f( - "123" - "\0" - "456", - 7); - absl::string_view g("xx not found bb"); - - d = absl::string_view(); - 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), 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), 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 - EXPECT_EQ(a.find_first_of(d), absl::string_view::npos); - EXPECT_EQ(a.find_first_of(e), absl::string_view::npos); - EXPECT_EQ(d.find_first_of(b), absl::string_view::npos); - EXPECT_EQ(e.find_first_of(b), absl::string_view::npos); - EXPECT_EQ(d.find_first_of(d), absl::string_view::npos); - EXPECT_EQ(e.find_first_of(d), absl::string_view::npos); - 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), 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), 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), 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); - EXPECT_EQ(a.find_first_not_of(e, a.size()), absl::string_view::npos); - EXPECT_EQ(a.find_first_not_of(d, absl::string_view::npos), - absl::string_view::npos); - EXPECT_EQ(a.find_first_not_of(e, absl::string_view::npos), - absl::string_view::npos); - EXPECT_EQ(d.find_first_not_of(a), absl::string_view::npos); - EXPECT_EQ(e.find_first_not_of(a), absl::string_view::npos); - EXPECT_EQ(d.find_first_not_of(d), absl::string_view::npos); - EXPECT_EQ(e.find_first_not_of(d), absl::string_view::npos); - EXPECT_EQ(d.find_first_not_of(e), absl::string_view::npos); - EXPECT_EQ(e.find_first_not_of(e), absl::string_view::npos); - - 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'), 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); - EXPECT_EQ(d.find_first_not_of('\0'), absl::string_view::npos); - EXPECT_EQ(e.find_first_not_of('\0'), absl::string_view::npos); -} - -// Continued from STL2 -TEST(StringViewTest, STL2FindLast) { - const absl::string_view a("abcdefghijklmnopqrstuvwxyz"); - const absl::string_view b("abc"); - const absl::string_view c("xyz"); - absl::string_view d("foobar"); - const absl::string_view e; - const absl::string_view f( - "123" - "\0" - "456", - 7); - absl::string_view g("xx not found bb"); - absl::string_view h("===="); - absl::string_view i("56"); - - 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), 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), 25u); - EXPECT_EQ(a.find_last_of('z', 24), absl::string_view::npos); - 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); - EXPECT_EQ(f.find_last_of(e), absl::string_view::npos); - EXPECT_EQ(f.find_last_of(d, 4), absl::string_view::npos); - EXPECT_EQ(f.find_last_of(e, 4), absl::string_view::npos); - EXPECT_EQ(d.find_last_of(d), absl::string_view::npos); - EXPECT_EQ(d.find_last_of(e), absl::string_view::npos); - EXPECT_EQ(e.find_last_of(d), absl::string_view::npos); - EXPECT_EQ(e.find_last_of(e), absl::string_view::npos); - EXPECT_EQ(d.find_last_of(f), absl::string_view::npos); - EXPECT_EQ(e.find_last_of(f), absl::string_view::npos); - EXPECT_EQ(d.find_last_of(d, 4), absl::string_view::npos); - EXPECT_EQ(d.find_last_of(e, 4), absl::string_view::npos); - EXPECT_EQ(e.find_last_of(d, 4), absl::string_view::npos); - EXPECT_EQ(e.find_last_of(e, 4), absl::string_view::npos); - 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), 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), 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), 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); - EXPECT_EQ(e.find_last_not_of(e), absl::string_view::npos); - EXPECT_EQ(d.find_last_not_of(f), absl::string_view::npos); - EXPECT_EQ(e.find_last_not_of(f), absl::string_view::npos); - EXPECT_EQ(d.find_last_not_of(d, 4), absl::string_view::npos); - EXPECT_EQ(d.find_last_not_of(e, 4), absl::string_view::npos); - EXPECT_EQ(e.find_last_not_of(d, 4), absl::string_view::npos); - EXPECT_EQ(e.find_last_not_of(e, 4), absl::string_view::npos); - EXPECT_EQ(d.find_last_not_of(f, 4), absl::string_view::npos); - EXPECT_EQ(e.find_last_not_of(f, 4), absl::string_view::npos); - - 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'), 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), 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); - EXPECT_EQ(d.find_last_not_of('\0'), absl::string_view::npos); - EXPECT_EQ(e.find_last_not_of('\0'), absl::string_view::npos); -} - -// Continued from STL2 -TEST(StringViewTest, STL2Substr) { - const absl::string_view a("abcdefghijklmnopqrstuvwxyz"); - const absl::string_view b("abc"); - const absl::string_view c("xyz"); - absl::string_view d("foobar"); - const absl::string_view e; - - d = absl::string_view(); - EXPECT_EQ(a.substr(0, 3), b); - EXPECT_EQ(a.substr(23), c); - EXPECT_EQ(a.substr(23, 3), c); - EXPECT_EQ(a.substr(23, 99), c); - EXPECT_EQ(a.substr(0), a); - EXPECT_EQ(a.substr(), a); - EXPECT_EQ(a.substr(3, 2), "de"); - // empty string nonsense - EXPECT_EQ(d.substr(0, 99), e); - // use of npos - EXPECT_EQ(a.substr(0, absl::string_view::npos), a); - EXPECT_EQ(a.substr(23, absl::string_view::npos), c); - // throw exception -#ifdef ABSL_HAVE_EXCEPTIONS - EXPECT_THROW((void)a.substr(99, 2), std::out_of_range); -#else - ABSL_EXPECT_DEATH_IF_SUPPORTED((void)a.substr(99, 2), - "absl::string_view::substr"); -#endif -} - TEST(StringViewTest, TruncSubstr) { const absl::string_view hi("hi"); EXPECT_EQ("", absl::ClippedSubstr(hi, 0, 0)); @@ -743,304 +46,6 @@ EXPECT_EQ("", absl::ClippedSubstr(hi, 3, 2)); // truncation } -TEST(StringViewTest, UTF8) { - std::string utf8 = "\u00E1"; - std::string utf8_twice = utf8 + " " + utf8; - 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")); -} - -TEST(StringViewTest, FindConformance) { - struct { - std::string haystack; - std::string needle; - } specs[] = { - {"", ""}, - {"", "a"}, - {"a", ""}, - {"a", "a"}, - {"a", "b"}, - {"aa", ""}, - {"aa", "a"}, - {"aa", "b"}, - {"ab", "a"}, - {"ab", "b"}, - {"abcd", ""}, - {"abcd", "a"}, - {"abcd", "d"}, - {"abcd", "ab"}, - {"abcd", "bc"}, - {"abcd", "cd"}, - {"abcd", "abcd"}, - }; - for (const auto& s : specs) { - SCOPED_TRACE(s.haystack); - SCOPED_TRACE(s.needle); - std::string st = s.haystack; - absl::string_view sp = s.haystack; - for (size_t i = 0; i <= sp.size(); ++i) { - size_t pos = (i == sp.size()) ? absl::string_view::npos : i; - SCOPED_TRACE(pos); - EXPECT_EQ(sp.find(s.needle, pos), - st.find(s.needle, pos)); - EXPECT_EQ(sp.rfind(s.needle, pos), - st.rfind(s.needle, pos)); - EXPECT_EQ(sp.find_first_of(s.needle, pos), - st.find_first_of(s.needle, pos)); - EXPECT_EQ(sp.find_first_not_of(s.needle, pos), - st.find_first_not_of(s.needle, pos)); - EXPECT_EQ(sp.find_last_of(s.needle, pos), - st.find_last_of(s.needle, pos)); - EXPECT_EQ(sp.find_last_not_of(s.needle, pos), - st.find_last_not_of(s.needle, pos)); - } - } -} - -TEST(StringViewTest, Remove) { - absl::string_view a("foobar"); - std::string s1("123"); - s1 += '\0'; - s1 += "456"; - absl::string_view e; - std::string s2; - - // remove_prefix - absl::string_view c(a); - c.remove_prefix(3); - EXPECT_EQ(c, "bar"); - c = a; - c.remove_prefix(0); - EXPECT_EQ(c, a); - c.remove_prefix(c.size()); - EXPECT_EQ(c, e); - - // remove_suffix - c = a; - c.remove_suffix(3); - EXPECT_EQ(c, "foo"); - c = a; - c.remove_suffix(0); - EXPECT_EQ(c, a); - c.remove_suffix(c.size()); - EXPECT_EQ(c, e); -} - -TEST(StringViewTest, Set) { - absl::string_view a("foobar"); - absl::string_view empty; - absl::string_view b; - - // set - b = absl::string_view("foobar", 6); - EXPECT_EQ(b, a); - b = absl::string_view("foobar", 0); - EXPECT_EQ(b, empty); - b = absl::string_view("foobar", 7); - EXPECT_NE(b, a); - - b = absl::string_view("foobar"); - EXPECT_EQ(b, a); -} - -TEST(StringViewTest, FrontBack) { - static const char arr[] = "abcd"; - const absl::string_view csp(arr, 4); - EXPECT_EQ(&arr[0], &csp.front()); - EXPECT_EQ(&arr[3], &csp.back()); -} - -TEST(StringViewTest, FrontBackSingleChar) { - static const char c = 'a'; - const absl::string_view csp(&c, 1); - EXPECT_EQ(&c, &csp.front()); - EXPECT_EQ(&c, &csp.back()); -} - -TEST(StringViewTest, FrontBackEmpty) { -#ifndef ABSL_USES_STD_STRING_VIEW -#if !defined(NDEBUG) || ABSL_OPTION_HARDENED - // Abseil's string_view implementation has debug assertions that check that - // front() and back() are not called on an empty string_view. - absl::string_view sv; - ABSL_EXPECT_DEATH_IF_SUPPORTED(sv.front(), ""); - ABSL_EXPECT_DEATH_IF_SUPPORTED(sv.back(), ""); -#endif -#endif -} - -// `std::string_view::string_view(const char*)` calls -// `std::char_traits<char>::length(const char*)` to get the string length. In -// libc++, it doesn't allow `nullptr` in the constexpr context, with the error -// "read of dereferenced null pointer is not allowed in a constant expression". -// At run time, the behavior of `std::char_traits::length()` on `nullptr` is -// undefined by the standard and usually results in crash with libc++. -// GCC also started rejected this in libstdc++ starting in GCC9. -// In MSVC, creating a constexpr string_view from nullptr also triggers an -// "unevaluable pointer value" error. This compiler implementation conforms -// to the standard, but `absl::string_view` implements a different -// behavior for historical reasons. We work around tests that construct -// `string_view` from `nullptr` when using libc++. -#if !defined(ABSL_USES_STD_STRING_VIEW) || \ - (!(defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE >= 9) && \ - !defined(_LIBCPP_VERSION) && !defined(_MSC_VER)) -#define ABSL_HAVE_STRING_VIEW_FROM_NULLPTR 1 -#endif - -TEST(StringViewTest, NULLInput) { - absl::string_view s; - EXPECT_EQ(s.data(), nullptr); - EXPECT_EQ(s.size(), 0u); - -#ifdef ABSL_HAVE_STRING_VIEW_FROM_NULLPTR - // The `str` parameter is annotated nonnull, but we want to test the defensive - // null check. Use a variable instead of passing nullptr directly to avoid a - // `-Wnonnull` warning. - char* null_str = nullptr; - s = absl::string_view(null_str); - EXPECT_EQ(s.data(), nullptr); - EXPECT_EQ(s.size(), 0u); - - // .ToString() on a absl::string_view with nullptr should produce the empty - // string. - EXPECT_EQ("", std::string(s)); -#endif // ABSL_HAVE_STRING_VIEW_FROM_NULLPTR -} - -TEST(StringViewTest, Comparisons2) { - // The `compare` member has 6 overloads (v: string_view, s: const char*): - // (1) compare(v) - // (2) compare(pos1, count1, v) - // (3) compare(pos1, count1, v, pos2, count2) - // (4) compare(s) - // (5) compare(pos1, count1, s) - // (6) compare(pos1, count1, s, count2) - - absl::string_view abc("abcdefghijklmnopqrstuvwxyz"); - - // check comparison operations on strings longer than 4 bytes. - EXPECT_EQ(abc, absl::string_view("abcdefghijklmnopqrstuvwxyz")); - EXPECT_EQ(abc.compare(absl::string_view("abcdefghijklmnopqrstuvwxyz")), 0); - - EXPECT_LT(abc, absl::string_view("abcdefghijklmnopqrstuvwxzz")); - EXPECT_LT(abc.compare(absl::string_view("abcdefghijklmnopqrstuvwxzz")), 0); - - EXPECT_GT(abc, absl::string_view("abcdefghijklmnopqrstuvwxyy")); - EXPECT_GT(abc.compare(absl::string_view("abcdefghijklmnopqrstuvwxyy")), 0); - - // The "substr" variants of `compare`. - absl::string_view digits("0123456789"); - auto npos = absl::string_view::npos; - - // Taking string_view - EXPECT_EQ(digits.compare(3, npos, absl::string_view("3456789")), 0); // 2 - EXPECT_EQ(digits.compare(3, 4, absl::string_view("3456")), 0); // 2 - EXPECT_EQ(digits.compare(10, 0, absl::string_view()), 0); // 2 - EXPECT_EQ(digits.compare(3, 4, absl::string_view("0123456789"), 3, 4), - 0); // 3 - EXPECT_LT(digits.compare(3, 4, absl::string_view("0123456789"), 3, 5), - 0); // 3 - EXPECT_LT(digits.compare(0, npos, absl::string_view("0123456789"), 3, 5), - 0); // 3 - // Taking const char* - EXPECT_EQ(digits.compare(3, 4, "3456"), 0); // 5 - EXPECT_EQ(digits.compare(3, npos, "3456789"), 0); // 5 - EXPECT_EQ(digits.compare(10, 0, ""), 0); // 5 - EXPECT_EQ(digits.compare(3, 4, "0123456789", 3, 4), 0); // 6 - EXPECT_LT(digits.compare(3, 4, "0123456789", 3, 5), 0); // 6 - EXPECT_LT(digits.compare(0, npos, "0123456789", 3, 5), 0); // 6 -} - -TEST(StringViewTest, At) { - absl::string_view abc = "abc"; - EXPECT_EQ(abc.at(0), 'a'); - EXPECT_EQ(abc.at(1), 'b'); - EXPECT_EQ(abc.at(2), 'c'); -#ifdef ABSL_HAVE_EXCEPTIONS - EXPECT_THROW((void)abc.at(3), std::out_of_range); -#else - ABSL_EXPECT_DEATH_IF_SUPPORTED((void)abc.at(3), "absl::string_view::at"); -#endif -} - -#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L -TEST(StringViewTest, StartsWith) { - const absl::string_view a("foobar"); - const absl::string_view b("123\0abc", 7); - const absl::string_view e; - EXPECT_TRUE(a.starts_with(a)); - EXPECT_TRUE(a.starts_with("foo")); - EXPECT_TRUE(a.starts_with('f')); - EXPECT_TRUE(a.starts_with(e)); - EXPECT_TRUE(b.starts_with(b)); - EXPECT_TRUE(b.starts_with('1')); - EXPECT_TRUE(b.starts_with(e)); - EXPECT_TRUE(e.starts_with("")); - EXPECT_FALSE(a.starts_with(b)); - EXPECT_FALSE(b.starts_with(a)); - EXPECT_FALSE(e.starts_with(a)); - EXPECT_FALSE(a.starts_with('r')); - EXPECT_FALSE(a.starts_with('\0')); - EXPECT_FALSE(e.starts_with('r')); - EXPECT_FALSE(e.starts_with('\0')); - - // Test that constexpr compiles. - constexpr absl::string_view kFooBar("foobar"); - constexpr absl::string_view kFoo("foo"); - constexpr absl::string_view kBar("bar"); - constexpr bool k1 = kFooBar.starts_with(kFoo); - EXPECT_TRUE(k1); - constexpr bool k2 = kFooBar.starts_with(kBar); - EXPECT_FALSE(k2); - constexpr bool k3 = kFooBar.starts_with('f'); - EXPECT_TRUE(k3); - constexpr bool k4 = kFooBar.starts_with("fo"); - EXPECT_TRUE(k4); -} - -TEST(StringViewTest, EndsWith) { - const absl::string_view a("foobar"); - const absl::string_view b("123\0abc", 7); - const absl::string_view e; - EXPECT_TRUE(a.ends_with(a)); - EXPECT_TRUE(a.ends_with('r')); - EXPECT_TRUE(a.ends_with("bar")); - EXPECT_TRUE(a.ends_with(e)); - EXPECT_TRUE(b.ends_with(b)); - EXPECT_TRUE(b.ends_with('c')); - EXPECT_TRUE(b.ends_with(e)); - EXPECT_TRUE(e.ends_with("")); - EXPECT_FALSE(a.ends_with(b)); - EXPECT_FALSE(b.ends_with(a)); - EXPECT_FALSE(e.ends_with(a)); - EXPECT_FALSE(a.ends_with('f')); - EXPECT_FALSE(a.ends_with('\0')); - EXPECT_FALSE(e.ends_with('r')); - EXPECT_FALSE(e.ends_with('\0')); - - // Test that constexpr compiles. - constexpr absl::string_view kFooBar("foobar"); - constexpr absl::string_view kFoo("foo"); - constexpr absl::string_view kBar("bar"); - constexpr bool k1 = kFooBar.ends_with(kFoo); - EXPECT_FALSE(k1); - constexpr bool k2 = kFooBar.ends_with(kBar); - EXPECT_TRUE(k2); - constexpr bool k3 = kFooBar.ends_with('r'); - EXPECT_TRUE(k3); - constexpr bool k4 = kFooBar.ends_with("ar"); - EXPECT_TRUE(k4); -} -#endif // ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L - -struct MyCharAlloc : std::allocator<char> {}; - -TEST(StringViewTest, ExplicitConversionOperator) { - absl::string_view sp = "hi"; - EXPECT_EQ(sp, std::string(sp)); -} - TEST(StringViewTest, NullSafeStringView) { { absl::string_view s = absl::NullSafeStringView(nullptr); @@ -1078,321 +83,4 @@ } } -TEST(StringViewTest, ConstexprCompiles) { - constexpr absl::string_view sp; - // With `-Wnonnull` turned on, there is no way to test the defensive null - // check in the `string_view(const char*)` constructor in a constexpr context, - // as the argument needs to be constexpr. The compiler will therefore always - // know at compile time that the argument is nullptr and complain because the - // parameter is annotated nonnull. We hence turn the warning off for this - // test. -#if defined(__clang__) -#pragma clang diagnostic push -#pragma clang diagnostic ignored "-Wnonnull" -#endif -#ifdef ABSL_HAVE_STRING_VIEW_FROM_NULLPTR - constexpr absl::string_view cstr(nullptr); -#endif -#if defined(__clang__) -#pragma clang diagnostic pop -#endif - constexpr absl::string_view cstr_len("cstr", 4); - -#if defined(ABSL_USES_STD_STRING_VIEW) - // In libstdc++ (as of 7.2), `std::string_view::string_view(const char*)` - // calls `std::char_traits<char>::length(const char*)` to get the string - // length, but it is not marked constexpr yet. See GCC bug: - // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=78156 - // Also, there is a LWG issue that adds constexpr to length() which was just - // resolved 2017-06-02. See - // http://www.open-std.org/jtc1/sc22/wg21/docs/lwg-defects.html#2232 - // TODO(zhangxy): Update the condition when libstdc++ adopts the constexpr - // length(). -#if !defined(__GLIBCXX__) -#define ABSL_HAVE_CONSTEXPR_STRING_VIEW_FROM_CSTR 1 -#endif // !__GLIBCXX__ - -#else // ABSL_USES_STD_STRING_VIEW - -// This duplicates the check for __builtin_strlen in the header. -#if ABSL_HAVE_BUILTIN(__builtin_strlen) || \ - (defined(__GNUC__) && !defined(__clang__)) -#define ABSL_HAVE_CONSTEXPR_STRING_VIEW_FROM_CSTR 1 -#elif defined(__GNUC__) // GCC or clang -#error GCC/clang should have constexpr string_view. -#endif - -// MSVC 2017+ should be able to construct a constexpr string_view from a cstr. -#if defined(_MSC_VER) -#define ABSL_HAVE_CONSTEXPR_STRING_VIEW_FROM_CSTR 1 -#endif - -#endif // ABSL_USES_STD_STRING_VIEW - -#ifdef ABSL_HAVE_CONSTEXPR_STRING_VIEW_FROM_CSTR - constexpr absl::string_view cstr_strlen("foo"); - EXPECT_EQ(cstr_strlen.length(), 3u); - constexpr absl::string_view cstr_strlen2 = "bar"; - EXPECT_EQ(cstr_strlen2, "bar"); - -#if ABSL_HAVE_BUILTIN(__builtin_memcmp) || \ - (defined(__GNUC__) && !defined(__clang__)) -#define ABSL_HAVE_CONSTEXPR_STRING_VIEW_COMPARISON 1 -#endif -#ifdef ABSL_HAVE_CONSTEXPR_STRING_VIEW_COMPARISON - constexpr absl::string_view foo = "foo"; - constexpr absl::string_view bar = "bar"; - constexpr bool foo_eq_bar = foo == bar; - constexpr bool foo_ne_bar = foo != bar; - constexpr bool foo_lt_bar = foo < bar; - constexpr bool foo_le_bar = foo <= bar; - constexpr bool foo_gt_bar = foo > bar; - constexpr bool foo_ge_bar = foo >= bar; - constexpr int foo_compare_bar = foo.compare(bar); - EXPECT_FALSE(foo_eq_bar); - EXPECT_TRUE(foo_ne_bar); - EXPECT_FALSE(foo_lt_bar); - EXPECT_FALSE(foo_le_bar); - EXPECT_TRUE(foo_gt_bar); - EXPECT_TRUE(foo_ge_bar); - EXPECT_GT(foo_compare_bar, 0); -#endif -#endif - - constexpr absl::string_view::iterator const_begin_empty = sp.begin(); - constexpr absl::string_view::iterator const_end_empty = sp.end(); - EXPECT_EQ(const_begin_empty, const_end_empty); - -#ifdef ABSL_HAVE_STRING_VIEW_FROM_NULLPTR - constexpr absl::string_view::iterator const_begin_nullptr = cstr.begin(); - constexpr absl::string_view::iterator const_end_nullptr = cstr.end(); - EXPECT_EQ(const_begin_nullptr, const_end_nullptr); -#endif // ABSL_HAVE_STRING_VIEW_FROM_NULLPTR - - constexpr absl::string_view::iterator const_begin = cstr_len.begin(); - constexpr absl::string_view::iterator const_end = cstr_len.end(); - constexpr absl::string_view::size_type const_size = cstr_len.size(); - constexpr absl::string_view::size_type const_length = cstr_len.length(); - static_assert(const_begin + const_size == const_end, - "pointer arithmetic check"); - static_assert(const_begin + const_length == const_end, - "pointer arithmetic check"); -#ifndef _MSC_VER - // MSVC has bugs doing constexpr pointer arithmetic. - // https://developercommunity.visualstudio.com/content/problem/482192/bad-pointer-arithmetic-in-constepxr-2019-rc1-svc1.html - EXPECT_EQ(const_begin + const_size, const_end); - EXPECT_EQ(const_begin + const_length, const_end); -#endif - - constexpr bool isempty = sp.empty(); - EXPECT_TRUE(isempty); - - constexpr const char c = cstr_len[2]; - EXPECT_EQ(c, 't'); - - constexpr const char cfront = cstr_len.front(); - constexpr const char cback = cstr_len.back(); - EXPECT_EQ(cfront, 'c'); - EXPECT_EQ(cback, 'r'); - - constexpr const char* np = sp.data(); - constexpr const char* cstr_ptr = cstr_len.data(); - EXPECT_EQ(np, nullptr); - EXPECT_NE(cstr_ptr, nullptr); - - constexpr size_t sp_npos = sp.npos; - EXPECT_EQ(sp_npos, static_cast<size_t>(-1)); -} - -constexpr char ConstexprMethodsHelper() { -#if defined(__cplusplus) && __cplusplus >= 201402L - absl::string_view str("123", 3); - str.remove_prefix(1); - str.remove_suffix(1); - absl::string_view bar; - str.swap(bar); - return bar.front(); -#else - return '2'; -#endif -} - -TEST(StringViewTest, ConstexprMethods) { - // remove_prefix, remove_suffix, swap - static_assert(ConstexprMethodsHelper() == '2', ""); - - // substr - constexpr absl::string_view foobar("foobar", 6); - constexpr absl::string_view foo = foobar.substr(0, 3); - constexpr absl::string_view bar = foobar.substr(3); - EXPECT_EQ(foo, "foo"); - EXPECT_EQ(bar, "bar"); -} - -TEST(StringViewTest, Noexcept) { - EXPECT_TRUE((std::is_nothrow_constructible<absl::string_view, - const std::string&>::value)); - EXPECT_TRUE((std::is_nothrow_constructible<absl::string_view, - const std::string&>::value)); - EXPECT_TRUE(std::is_nothrow_constructible<absl::string_view>::value); - constexpr absl::string_view sp; - EXPECT_TRUE(noexcept(sp.begin())); - EXPECT_TRUE(noexcept(sp.end())); - EXPECT_TRUE(noexcept(sp.cbegin())); - EXPECT_TRUE(noexcept(sp.cend())); - EXPECT_TRUE(noexcept(sp.rbegin())); - EXPECT_TRUE(noexcept(sp.rend())); - EXPECT_TRUE(noexcept(sp.crbegin())); - EXPECT_TRUE(noexcept(sp.crend())); - EXPECT_TRUE(noexcept(sp.size())); - EXPECT_TRUE(noexcept(sp.length())); - EXPECT_TRUE(noexcept(sp.empty())); - EXPECT_TRUE(noexcept(sp.data())); - EXPECT_TRUE(noexcept(sp.compare(sp))); - EXPECT_TRUE(noexcept(sp.find(sp))); - EXPECT_TRUE(noexcept(sp.find('f'))); - EXPECT_TRUE(noexcept(sp.rfind(sp))); - EXPECT_TRUE(noexcept(sp.rfind('f'))); - EXPECT_TRUE(noexcept(sp.find_first_of(sp))); - EXPECT_TRUE(noexcept(sp.find_first_of('f'))); - EXPECT_TRUE(noexcept(sp.find_last_of(sp))); - EXPECT_TRUE(noexcept(sp.find_last_of('f'))); - EXPECT_TRUE(noexcept(sp.find_first_not_of(sp))); - EXPECT_TRUE(noexcept(sp.find_first_not_of('f'))); - EXPECT_TRUE(noexcept(sp.find_last_not_of(sp))); - EXPECT_TRUE(noexcept(sp.find_last_not_of('f'))); -} - -TEST(StringViewTest, BoundsCheck) { -#ifndef ABSL_USES_STD_STRING_VIEW -#if !defined(NDEBUG) || ABSL_OPTION_HARDENED - // 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[static_cast<size_t>(-1)], ""); -#endif -#endif -} - -TEST(ComparisonOpsTest, StringCompareNotAmbiguous) { - EXPECT_EQ("hello", std::string("hello")); - EXPECT_LT("hello", std::string("world")); -} - -TEST(ComparisonOpsTest, HeterogeneousStringViewEquals) { - EXPECT_EQ(absl::string_view("hello"), std::string("hello")); - EXPECT_EQ("hello", absl::string_view("hello")); -} - -TEST(FindOneCharTest, EdgeCases) { - absl::string_view a("xxyyyxx"); - - // Set a = "xyyyx". - a.remove_prefix(1); - a.remove_suffix(1); - - 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(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); - a.remove_suffix(1); - - EXPECT_EQ(absl::string_view::npos, a.find('x')); - EXPECT_EQ(absl::string_view::npos, a.rfind('x')); -} - -#ifndef ABSL_HAVE_THREAD_SANITIZER // Allocates too much memory for tsan. -TEST(HugeStringView, TwoPointTwoGB) { - if (sizeof(size_t) <= 4) - return; - // Try a huge string piece. - const size_t size = size_t{2200} * 1000 * 1000; - std::string s(size, 'a'); - absl::string_view sp(s); - EXPECT_EQ(size, sp.length()); - sp.remove_prefix(1); - EXPECT_EQ(size - 1, sp.length()); - sp.remove_suffix(2); - EXPECT_EQ(size - 1 - 2, sp.length()); -} -#endif // ABSL_HAVE_THREAD_SANITIZER - -#if !defined(NDEBUG) && !defined(ABSL_USES_STD_STRING_VIEW) -TEST(NonNegativeLenTest, NonNegativeLen) { - ABSL_EXPECT_DEATH_IF_SUPPORTED( - absl::string_view("xyz", static_cast<size_t>(-1)), "len <= kMaxSize"); -} - -TEST(LenExceedsMaxSizeTest, LenExceedsMaxSize) { - auto max_size = absl::string_view().max_size(); - - // This should construct ok (although the view itself is obviously invalid). - absl::string_view ok_view("", max_size); - - // Adding one to the max should trigger an assertion. - ABSL_EXPECT_DEATH_IF_SUPPORTED(absl::string_view("", max_size + 1), - "len <= kMaxSize"); -} -#endif // !defined(NDEBUG) && !defined(ABSL_USES_STD_STRING_VIEW) - -class StringViewStreamTest : public ::testing::Test { - public: - // Set negative 'width' for right justification. - template <typename T> - std::string Pad(const T& s, int width, char fill = 0) { - std::ostringstream oss; - if (fill != 0) { - oss << std::setfill(fill); - } - if (width < 0) { - width = -width; - oss << std::right; - } - oss << std::setw(width) << s; - return oss.str(); - } -}; - -TEST_F(StringViewStreamTest, Padding) { - std::string s("hello"); - absl::string_view sp(s); - for (int w = -64; w < 64; ++w) { - SCOPED_TRACE(w); - EXPECT_EQ(Pad(s, w), Pad(sp, w)); - } - for (int w = -64; w < 64; ++w) { - SCOPED_TRACE(w); - EXPECT_EQ(Pad(s, w, '#'), Pad(sp, w, '#')); - } -} - -TEST_F(StringViewStreamTest, ResetsWidth) { - // Width should reset after one formatted write. - // If we weren't resetting width after formatting the string_view, - // we'd have width=5 carrying over to the printing of the "]", - // creating "[###hi####]". - std::string s = "hi"; - absl::string_view sp = s; - { - std::ostringstream oss; - oss << "[" << std::setfill('#') << std::setw(5) << s << "]"; - ASSERT_EQ("[###hi]", oss.str()); - } - { - std::ostringstream oss; - oss << "[" << std::setfill('#') << std::setw(5) << sp << "]"; - EXPECT_EQ("[###hi]", oss.str()); - } -} - } // namespace
diff --git a/absl/strings/substitute.cc b/absl/strings/substitute.cc index 3c2ca5d..f5d600b 100644 --- a/absl/strings/substitute.cc +++ b/absl/strings/substitute.cc
@@ -26,7 +26,7 @@ #include "absl/base/nullability.h" #include "absl/strings/ascii.h" #include "absl/strings/escaping.h" -#include "absl/strings/internal/resize_uninitialized.h" +#include "absl/strings/internal/append_and_overwrite.h" #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" @@ -85,29 +85,29 @@ if (size == 0) return; // Build the string. - size_t original_size = output->size(); - ABSL_INTERNAL_CHECK( - size <= std::numeric_limits<size_t>::max() - original_size, - "size_t overflow"); - strings_internal::STLStringResizeUninitializedAmortized(output, - original_size + size); - char* target = &(*output)[original_size]; - for (size_t i = 0; i < format.size(); i++) { - if (format[i] == '$') { - 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. - } else if (format[i + 1] == '$') { - *target++ = '$'; - ++i; // Skip next char. - } - } else { - *target++ = format[i]; - } - } - - assert(target == output->data() + output->size()); + ABSL_INTERNAL_CHECK(size <= output->max_size() - output->size(), + "Exceeds std::string::max_size()"); + strings_internal::StringAppendAndOverwrite( + *output, size, [format, args_array](char* const buf, size_t buf_size) { + char* target = buf; + for (size_t i = 0; i < format.size(); i++) { + if (format[i] == '$') { + 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. + } else if (format[i + 1] == '$') { + *target++ = '$'; + ++i; // Skip next char. + } + } else { + *target++ = format[i]; + } + } + assert(target == buf + buf_size); + return buf_size; + }); } Arg::Arg(const void* absl_nullable value) {
diff --git a/absl/synchronization/BUILD.bazel b/absl/synchronization/BUILD.bazel index 920928e..b3b55e7 100644 --- a/absl/synchronization/BUILD.bazel +++ b/absl/synchronization/BUILD.bazel
@@ -14,6 +14,9 @@ # limitations under the License. # +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load( "//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS", @@ -69,9 +72,7 @@ "//absl/base:core_headers", "//absl/base:raw_logging_internal", "//absl/time", - ] + select({ - "//conditions:default": [], - }), + ], ) cc_test( @@ -145,6 +146,7 @@ "//absl/base:tracing_internal", "//absl/debugging:stacktrace", "//absl/debugging:symbolize", + "//absl/meta:type_traits", "//absl/time", ] + select({ "//conditions:default": [], @@ -360,6 +362,7 @@ linkopts = ABSL_DEFAULT_LINKOPTS, tags = [ "no_test_wasm", + "noubsan", # TODO(b/417700722): timeouts under UBSAN. ], deps = [ ":per_thread_sem_test_common",
diff --git a/absl/synchronization/CMakeLists.txt b/absl/synchronization/CMakeLists.txt index 9d4844d..9c4a0b1 100644 --- a/absl/synchronization/CMakeLists.txt +++ b/absl/synchronization/CMakeLists.txt
@@ -109,11 +109,11 @@ absl::core_headers absl::dynamic_annotations absl::malloc_internal + absl::meta absl::nullability absl::raw_logging_internal absl::stacktrace absl::symbolize - absl::tracing_internal absl::time absl::tracing_internal Threads::Threads
diff --git a/absl/synchronization/barrier.cc b/absl/synchronization/barrier.cc index 0dfd795..f5dad22 100644 --- a/absl/synchronization/barrier.cc +++ b/absl/synchronization/barrier.cc
@@ -26,7 +26,7 @@ } bool Barrier::Block() { - MutexLock l(&this->lock_); + MutexLock l(this->lock_); this->num_to_block_--; if (this->num_to_block_ < 0) {
diff --git a/absl/synchronization/barrier_test.cc b/absl/synchronization/barrier_test.cc index bfc6cb1..2aed272 100644 --- a/absl/synchronization/barrier_test.cc +++ b/absl/synchronization/barrier_test.cc
@@ -37,7 +37,7 @@ } // Increment the counter. - absl::MutexLock lock(&mutex); + absl::MutexLock lock(mutex); ++counter; }; @@ -57,7 +57,7 @@ // The counter should still be zero since no thread should have // been able to pass the barrier yet. { - absl::MutexLock lock(&mutex); + absl::MutexLock lock(mutex); EXPECT_EQ(counter, 0); } @@ -70,6 +70,6 @@ } // All threads should now have incremented the counter. - absl::MutexLock lock(&mutex); + absl::MutexLock lock(mutex); EXPECT_EQ(counter, kNumThreads); }
diff --git a/absl/synchronization/blocking_counter.cc b/absl/synchronization/blocking_counter.cc index a530baf..9468469 100644 --- a/absl/synchronization/blocking_counter.cc +++ b/absl/synchronization/blocking_counter.cc
@@ -42,7 +42,7 @@ "BlockingCounter::DecrementCount() called too many times"); if (count == 0) { base_internal::TraceSignal(this, TraceObjectKind()); - MutexLock l(&lock_); + MutexLock l(lock_); done_ = true; return true; } @@ -52,7 +52,7 @@ void BlockingCounter::Wait() { base_internal::TraceWait(this, TraceObjectKind()); { - MutexLock l(&this->lock_); + MutexLock l(this->lock_); // only one thread may call Wait(). To support more than one thread, // implement a counter num_to_exit, like in the Barrier class.
diff --git a/absl/synchronization/internal/create_thread_identity.cc b/absl/synchronization/internal/create_thread_identity.cc index 93cd376..0b0f920 100644 --- a/absl/synchronization/internal/create_thread_identity.cc +++ b/absl/synchronization/internal/create_thread_identity.cc
@@ -35,7 +35,7 @@ // ThreadIdentity storage is persistent, we maintain a free-list of previously // released ThreadIdentity objects. ABSL_CONST_INIT static base_internal::SpinLock freelist_lock( - absl::kConstInit, base_internal::SCHEDULE_KERNEL_ONLY); + base_internal::SCHEDULE_KERNEL_ONLY); ABSL_CONST_INIT static base_internal::ThreadIdentity* thread_identity_freelist; // A per-thread destructor for reclaiming associated ThreadIdentity objects. @@ -60,7 +60,7 @@ // association state in this case. base_internal::ClearCurrentThreadIdentity(); { - base_internal::SpinLockHolder l(&freelist_lock); + base_internal::SpinLockHolder l(freelist_lock); identity->next = thread_identity_freelist; thread_identity_freelist = identity; } @@ -108,7 +108,7 @@ { // Re-use a previously released object if possible. - base_internal::SpinLockHolder l(&freelist_lock); + base_internal::SpinLockHolder l(freelist_lock); if (thread_identity_freelist) { identity = thread_identity_freelist; // Take list-head. thread_identity_freelist = thread_identity_freelist->next;
diff --git a/absl/synchronization/internal/graphcycles.cc b/absl/synchronization/internal/graphcycles.cc index 129067c..f58fb0a 100644 --- a/absl/synchronization/internal/graphcycles.cc +++ b/absl/synchronization/internal/graphcycles.cc
@@ -33,15 +33,15 @@ #include "absl/base/internal/low_level_alloc.h" #ifndef ABSL_LOW_LEVEL_ALLOC_MISSING -#include "absl/synchronization/internal/graphcycles.h" - #include <algorithm> #include <array> #include <cinttypes> #include <limits> + #include "absl/base/internal/hide_ptr.h" #include "absl/base/internal/raw_logging.h" #include "absl/base/internal/spinlock.h" +#include "absl/synchronization/internal/graphcycles.h" // Do not use STL. This module does not use standard memory allocation. @@ -54,15 +54,14 @@ // Avoid LowLevelAlloc's default arena since it calls malloc hooks in // which people are doing things like acquiring Mutexes. ABSL_CONST_INIT static absl::base_internal::SpinLock arena_mu( - absl::kConstInit, base_internal::SCHEDULE_KERNEL_ONLY); + base_internal::SCHEDULE_KERNEL_ONLY); ABSL_CONST_INIT static base_internal::LowLevelAlloc::Arena* arena; static void InitArenaIfNecessary() { - arena_mu.Lock(); + base_internal::SpinLockHolder l(arena_mu); if (arena == nullptr) { arena = base_internal::LowLevelAlloc::NewArena(0); } - arena_mu.Unlock(); } // Number of inlined elements in Vec. Hash table implementation @@ -89,7 +88,7 @@ T* end() { return ptr_ + size_; } const T& operator[](uint32_t i) const { return ptr_[i]; } T& operator[](uint32_t i) { return ptr_[i]; } - const T& back() const { return ptr_[size_-1]; } + const T& back() const { return ptr_[size_ - 1]; } void pop_back() { size_--; } void push_back(const T& v) { @@ -178,7 +177,7 @@ } table_[i] = v; // Double when 75% full. - if (occupied_ >= table_.size() - table_.size()/4) Grow(); + if (occupied_ >= table_.size() - table_.size() / 4) Grow(); return true; } @@ -193,7 +192,7 @@ // Example: // HASH_FOR_EACH(elem, node->out) { ... } #define HASH_FOR_EACH(elem, eset) \ - for (int32_t elem, _cursor = 0; (eset).Next(&_cursor, &elem); ) + 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_[static_cast<uint32_t>(*cursor)]; @@ -209,7 +208,7 @@ private: enum : int32_t { kEmpty = -1, kDel = -2 }; Vec<int32_t> table_; - uint32_t occupied_; // Count of non-empty slots (includes deleted slots) + uint32_t occupied_; // Count of non-empty slots (includes deleted slots) static uint32_t Hash(int32_t a) { return static_cast<uint32_t>(a) * 41; } @@ -270,25 +269,23 @@ return g; } -inline int32_t NodeIndex(GraphId id) { - return static_cast<int32_t>(id.handle); -} +inline int32_t NodeIndex(GraphId id) { return static_cast<int32_t>(id.handle); } inline uint32_t NodeVersion(GraphId id) { return static_cast<uint32_t>(id.handle >> 32); } struct Node { - int32_t rank; // rank number assigned by Pearce-Kelly algorithm - uint32_t version; // Current version number - int32_t next_hash; // Next entry in hash table - bool visited; // Temporary marker used by depth-first-search - uintptr_t masked_ptr; // User-supplied pointer - NodeSet in; // List of immediate predecessor nodes in graph - NodeSet out; // List of immediate successor nodes in graph - int priority; // Priority of recorded stack trace. - int nstack; // Depth of recorded stack trace. - void* stack[40]; // stack[0,nstack-1] holds stack trace for node. + int32_t rank; // rank number assigned by Pearce-Kelly algorithm + uint32_t version; // Current version number + int32_t next_hash; // Next entry in hash table + bool visited; // Temporary marker used by depth-first-search + uintptr_t masked_ptr; // User-supplied pointer + NodeSet in; // List of immediate predecessor nodes in graph + NodeSet out; // List of immediate successor nodes in graph + int priority; // Priority of recorded stack trace. + int nstack; // Depth of recorded stack trace. + void* stack[40]; // stack[0,nstack-1] holds stack trace for node. }; // Hash table for pointer to node index lookups. @@ -318,7 +315,7 @@ // Advance through linked list while keeping track of the // predecessor slot that points to the current entry. auto masked = base_internal::HidePtr(ptr); - for (int32_t* slot = &table_[Hash(ptr)]; *slot != -1; ) { + for (int32_t* slot = &table_[Hash(ptr)]; *slot != -1;) { int32_t index = *slot; Node* n = (*nodes_)[static_cast<uint32_t>(index)]; if (n->masked_ptr == masked) { @@ -381,7 +378,9 @@ GraphCycles::~GraphCycles() { for (auto* node : rep_->nodes_) { - if (node == nullptr) { continue; } + if (node == nullptr) { + continue; + } node->Node::~Node(); base_internal::LowLevelAlloc::Free(node); } @@ -474,8 +473,7 @@ void* GraphCycles::Ptr(GraphId id) { Node* n = FindNode(rep_, id); - return n == nullptr ? nullptr - : base_internal::UnhidePtr<void>(n->masked_ptr); + return n == nullptr ? nullptr : base_internal::UnhidePtr<void>(n->masked_ptr); } bool GraphCycles::HasNode(GraphId node) { @@ -502,8 +500,8 @@ static void BackwardDFS(GraphCycles::Rep* r, int32_t n, int32_t lower_bound); static void Reorder(GraphCycles::Rep* r); static void Sort(const Vec<Node*>&, Vec<int32_t>* delta); -static void MoveToList( - GraphCycles::Rep* r, Vec<int32_t>* src, Vec<int32_t>* dst); +static void MoveToList(GraphCycles::Rep* r, Vec<int32_t>* src, + Vec<int32_t>* dst); bool GraphCycles::InsertEdge(GraphId idx, GraphId idy) { Rep* r = rep_; @@ -605,9 +603,8 @@ // Produce sorted list of all ranks that will be reassigned. r->merged_.resize(r->deltab_.size() + r->deltaf_.size()); - std::merge(r->deltab_.begin(), r->deltab_.end(), - r->deltaf_.begin(), r->deltaf_.end(), - r->merged_.begin()); + std::merge(r->deltab_.begin(), r->deltab_.end(), r->deltaf_.begin(), + r->deltaf_.end(), r->merged_.begin()); // Assign the ranks in order to the collected list. for (uint32_t i = 0; i < r->list_.size(); i++) { @@ -628,8 +625,8 @@ std::sort(delta->begin(), delta->end(), cmp); } -static void MoveToList( - GraphCycles::Rep* r, Vec<int32_t>* src, Vec<int32_t>* dst) { +static void MoveToList(GraphCycles::Rep* r, Vec<int32_t>* src, + Vec<int32_t>* dst) { for (auto& v : *src) { int32_t w = v; // Replace v entry with its rank
diff --git a/absl/synchronization/internal/kernel_timeout.h b/absl/synchronization/internal/kernel_timeout.h index 06404a7..c24fa86 100644 --- a/absl/synchronization/internal/kernel_timeout.h +++ b/absl/synchronization/internal/kernel_timeout.h
@@ -129,8 +129,8 @@ std::chrono::nanoseconds ToChronoDuration() const; // Returns true if steady (aka monotonic) clocks are supported by the system. - // This method exists because go/btm requires synchronized clocks, and - // thus requires we use the system (aka walltime) clock. + // This currently returns true on all platforms, but we have encountered + // platforms that once lacked steady clock support. static constexpr bool SupportsSteadyClock() { return true; } private:
diff --git a/absl/synchronization/internal/kernel_timeout_test.cc b/absl/synchronization/internal/kernel_timeout_test.cc index 33962f8..811246c 100644 --- a/absl/synchronization/internal/kernel_timeout_test.cc +++ b/absl/synchronization/internal/kernel_timeout_test.cc
@@ -24,12 +24,22 @@ #include "absl/time/time.h" #include "gtest/gtest.h" -// Test go/btm support by randomizing the value of clock_gettime() for -// CLOCK_MONOTONIC. This works by overriding a weak symbol in glibc. +#if 0 // All supported platforms currently have steady clocks. +#define ABSL_INTERNAL_KERNEL_TIMEOUT_SUPPORTS_STEADY_CLOCK 0 +#else +#define ABSL_INTERNAL_KERNEL_TIMEOUT_SUPPORTS_STEADY_CLOCK 1 +#endif + +static_assert( + absl::synchronization_internal::KernelTimeout::SupportsSteadyClock() == + static_cast<bool>(ABSL_INTERNAL_KERNEL_TIMEOUT_SUPPORTS_STEADY_CLOCK)); + +// Randomizing the value of clock_gettime() for CLOCK_MONOTONIC. +// This works by overriding a weak symbol in glibc. // We should be resistant to this randomization when !SupportsSteadyClock(). -#if defined(__GOOGLE_GRTE_VERSION__) && \ - !defined(ABSL_HAVE_ADDRESS_SANITIZER) && \ - !defined(ABSL_HAVE_MEMORY_SANITIZER) && \ +#if !ABSL_INTERNAL_KERNEL_TIMEOUT_SUPPORTS_STEADY_CLOCK && \ + !defined(ABSL_HAVE_ADDRESS_SANITIZER) && \ + !defined(ABSL_HAVE_MEMORY_SANITIZER) && \ !defined(ABSL_HAVE_THREAD_SANITIZER) extern "C" int __clock_gettime(clockid_t c, struct timespec* ts);
diff --git a/absl/synchronization/internal/thread_pool.h b/absl/synchronization/internal/thread_pool.h index 5eb0bb6..f87adf6 100644 --- a/absl/synchronization/internal/thread_pool.h +++ b/absl/synchronization/internal/thread_pool.h
@@ -46,7 +46,7 @@ ~ThreadPool() { { - absl::MutexLock l(&mu_); + absl::MutexLock l(mu_); for (size_t i = 0; i < threads_.size(); i++) { queue_.push(nullptr); // Shutdown signal. } @@ -59,7 +59,7 @@ // Schedule a function to be run on a ThreadPool thread immediately. void Schedule(absl::AnyInvocable<void()> func) { assert(func != nullptr); - absl::MutexLock l(&mu_); + absl::MutexLock l(mu_); queue_.push(std::move(func)); } @@ -72,7 +72,7 @@ while (true) { absl::AnyInvocable<void()> func; { - absl::MutexLock l(&mu_); + absl::MutexLock l(mu_); mu_.Await(absl::Condition(this, &ThreadPool::WorkAvailable)); func = std::move(queue_.front()); queue_.pop();
diff --git a/absl/synchronization/internal/waiter_test.cc b/absl/synchronization/internal/waiter_test.cc index 6e37415..80a6985 100644 --- a/absl/synchronization/internal/waiter_test.cc +++ b/absl/synchronization/internal/waiter_test.cc
@@ -129,7 +129,10 @@ start + absl::Seconds(10)))); absl::Duration waited = absl::Now() - start; EXPECT_GE(waited, WithTolerance(absl::Milliseconds(500))); - EXPECT_LT(waited, absl::Seconds(2)); + #ifndef _MSC_VER + // Skip on MSVC due to flakiness. + EXPECT_LT(waited, absl::Seconds(2)); + #endif } TYPED_TEST_P(WaiterTest, WaitDurationReached) { @@ -139,7 +142,10 @@ absl::synchronization_internal::KernelTimeout(absl::Milliseconds(500)))); absl::Duration waited = absl::Now() - start; EXPECT_GE(waited, WithTolerance(absl::Milliseconds(500))); - EXPECT_LT(waited, absl::Seconds(1)); + #ifndef _MSC_VER + // Skip on MSVC due to flakiness. + EXPECT_LT(waited, absl::Seconds(1)); + #endif } TYPED_TEST_P(WaiterTest, WaitTimeReached) { @@ -149,7 +155,10 @@ start + absl::Milliseconds(500)))); absl::Duration waited = absl::Now() - start; EXPECT_GE(waited, WithTolerance(absl::Milliseconds(500))); - EXPECT_LT(waited, absl::Seconds(1)); + #ifndef _MSC_VER + // Skip on MSVC due to flakiness. + EXPECT_LT(waited, absl::Seconds(1)); + #endif } REGISTER_TYPED_TEST_SUITE_P(WaiterTest,
diff --git a/absl/synchronization/lifetime_test.cc b/absl/synchronization/lifetime_test.cc index 4c4cff6..1c11431 100644 --- a/absl/synchronization/lifetime_test.cc +++ b/absl/synchronization/lifetime_test.cc
@@ -45,7 +45,7 @@ CHECK(!*state) << "*state not initialized"; { - absl::MutexLock lock(mutex); + absl::MutexLock lock(*mutex); notification->Notify(); CHECK(notification->HasBeenNotified()) << "invalid Notification"; @@ -64,7 +64,7 @@ notification->WaitForNotification(); CHECK(notification->HasBeenNotified()) << "invalid Notification"; { - absl::MutexLock lock(mutex); + absl::MutexLock lock(*mutex); *state = true; condvar->Signal(); } @@ -148,12 +148,12 @@ // before the constructors of either grab_lock or check_still_locked are run.) extern absl::Mutex const_init_sanity_mutex; OnConstruction grab_lock([]() ABSL_NO_THREAD_SAFETY_ANALYSIS { - const_init_sanity_mutex.Lock(); + const_init_sanity_mutex.lock(); }); ABSL_CONST_INIT absl::Mutex const_init_sanity_mutex(absl::kConstInit); OnConstruction check_still_locked([]() ABSL_NO_THREAD_SAFETY_ANALYSIS { const_init_sanity_mutex.AssertHeld(); - const_init_sanity_mutex.Unlock(); + const_init_sanity_mutex.unlock(); }); #endif // defined(__clang__) || !(defined(_MSC_VER) && _MSC_VER > 1900)
diff --git a/absl/synchronization/mutex.cc b/absl/synchronization/mutex.cc index 5091b8f..9b80f1f 100644 --- a/absl/synchronization/mutex.cc +++ b/absl/synchronization/mutex.cc
@@ -226,7 +226,7 @@ // Data for doing deadlock detection. ABSL_CONST_INIT static absl::base_internal::SpinLock deadlock_graph_mu( - absl::kConstInit, base_internal::SCHEDULE_KERNEL_ONLY); + base_internal::SCHEDULE_KERNEL_ONLY); // Graph used to detect deadlocks. ABSL_CONST_INIT static GraphCycles* deadlock_graph @@ -292,7 +292,7 @@ }; ABSL_CONST_INIT static absl::base_internal::SpinLock synch_event_mu( - absl::kConstInit, base_internal::SCHEDULE_KERNEL_ONLY); + base_internal::SCHEDULE_KERNEL_ONLY); // Hash table size; should be prime > 2. // Can't be too small, as it's used for deadlock detection information. @@ -330,7 +330,7 @@ const char* name, intptr_t bits, intptr_t lockbit) { uint32_t h = reinterpret_cast<uintptr_t>(addr) % kNSynchEvent; - synch_event_mu.Lock(); + synch_event_mu.lock(); // When a Mutex/CondVar is destroyed, we don't remove the associated // SynchEvent to keep destructors empty in release builds for performance // reasons. If the current call is the first to set bits (kMuEvent/kCVEvent), @@ -392,16 +392,16 @@ } else { e->refcount++; // for return value } - synch_event_mu.Unlock(); + synch_event_mu.unlock(); return e; } // Decrement the reference count of *e, or do nothing if e==null. static void UnrefSynchEvent(SynchEvent* e) { if (e != nullptr) { - synch_event_mu.Lock(); + synch_event_mu.lock(); bool del = (--(e->refcount) == 0); - synch_event_mu.Unlock(); + synch_event_mu.unlock(); if (del) { base_internal::LowLevelAlloc::Free(e); } @@ -414,7 +414,7 @@ static SynchEvent* GetSynchEvent(const void* addr) { uint32_t h = reinterpret_cast<uintptr_t>(addr) % kNSynchEvent; SynchEvent* e; - synch_event_mu.Lock(); + synch_event_mu.lock(); for (e = synch_event[h]; e != nullptr && e->masked_addr != base_internal::HidePtr(addr); e = e->next) { @@ -422,7 +422,7 @@ if (e != nullptr) { e->refcount++; } - synch_event_mu.Unlock(); + synch_event_mu.unlock(); return e; } @@ -509,10 +509,10 @@ const Condition* cond; // The condition that this thread is waiting for. // In Mutex, this field is set to zero if a timeout // expires. - KernelTimeout timeout; // timeout expiry---absolute time - // In Mutex, this field is set to zero if a timeout - // expires. - Mutex* const cvmu; // used for transfer from cond var to mutex + KernelTimeout timeout; // timeout expiry---absolute time + // In Mutex, this field is set to zero if a timeout + // expires. + Mutex* const cvmu; // used for transfer from cond var to mutex PerThreadSynch* const thread; // thread that is waiting // If not null, thread should be enqueued on the CondVar whose state @@ -745,7 +745,8 @@ Mutex::~Mutex() { Dtor(); } #endif -#if !defined(NDEBUG) || defined(ABSL_HAVE_THREAD_SANITIZER) +#if !defined(NDEBUG) || defined(ABSL_HAVE_THREAD_SANITIZER) || \ + defined(ABSL_BUILD_DLL) void Mutex::Dtor() { if (kDebugMode) { this->ForgetDeadlockInfo(); @@ -1223,9 +1224,8 @@ } static GraphId GetGraphId(Mutex* mu) ABSL_LOCKS_EXCLUDED(deadlock_graph_mu) { - deadlock_graph_mu.Lock(); + base_internal::SpinLockHolder l(deadlock_graph_mu); GraphId id = GetGraphIdLocked(mu); - deadlock_graph_mu.Unlock(); return id; } @@ -1327,8 +1327,7 @@ char sym[kSymLen]; int len = 0; for (int i = 0; i != n; i++) { - if (len >= maxlen) - return buf; + if (len >= maxlen) return buf; size_t count = static_cast<size_t>(maxlen - len); if (symbolize) { if (!absl::Symbolize(pcs[i], sym, kSymLen)) { @@ -1387,7 +1386,7 @@ SynchLocksHeld* all_locks = Synch_GetAllLocks(); - absl::base_internal::SpinLockHolder lock(&deadlock_graph_mu); + absl::base_internal::SpinLockHolder lock(deadlock_graph_mu); const GraphId mu_id = GetGraphIdLocked(mu); if (all_locks->n == 0) { @@ -1457,7 +1456,7 @@ } if (synch_deadlock_detection.load(std::memory_order_acquire) == OnDeadlockCycle::kAbort) { - deadlock_graph_mu.Unlock(); // avoid deadlock in fatal sighandler + deadlock_graph_mu.unlock(); // avoid deadlock in fatal sighandler ABSL_RAW_LOG(FATAL, "dying due to potential deadlock"); return mu_id; } @@ -1482,11 +1481,11 @@ void Mutex::ForgetDeadlockInfo() { if (kDebugMode && synch_deadlock_detection.load(std::memory_order_acquire) != OnDeadlockCycle::kIgnore) { - deadlock_graph_mu.Lock(); + deadlock_graph_mu.lock(); if (deadlock_graph != nullptr) { deadlock_graph->RemoveNode(this); } - deadlock_graph_mu.Unlock(); + deadlock_graph_mu.unlock(); } } @@ -1528,7 +1527,7 @@ return false; } -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); @@ -1546,7 +1545,7 @@ ABSL_TSAN_MUTEX_POST_LOCK(this, 0, 0); } -void Mutex::ReaderLock() { +void Mutex::lock_shared() { ABSL_TSAN_MUTEX_PRE_LOCK(this, __tsan_mutex_read_lock); GraphId id = DebugOnlyDeadlockCheck(this); intptr_t v = mu_.load(std::memory_order_relaxed); @@ -1606,7 +1605,7 @@ return res; } -bool Mutex::TryLock() { +bool Mutex::try_lock() { ABSL_TSAN_MUTEX_PRE_LOCK(this, __tsan_mutex_try_lock); intptr_t v = mu_.load(std::memory_order_relaxed); // Try fast acquire. @@ -1644,7 +1643,7 @@ return false; } -bool Mutex::ReaderTryLock() { +bool Mutex::try_lock_shared() { ABSL_TSAN_MUTEX_PRE_LOCK(this, __tsan_mutex_read_lock | __tsan_mutex_try_lock); intptr_t v = mu_.load(std::memory_order_relaxed); @@ -1706,7 +1705,7 @@ return false; } -void Mutex::Unlock() { +void Mutex::unlock() { ABSL_TSAN_MUTEX_PRE_UNLOCK(this, 0); DebugOnlyLockLeave(this); intptr_t v = mu_.load(std::memory_order_relaxed); @@ -1776,7 +1775,7 @@ return (v & kMuMultipleWaitersMask) == 0; } -void Mutex::ReaderUnlock() { +void Mutex::unlock_shared() { ABSL_TSAN_MUTEX_PRE_UNLOCK(this, __tsan_mutex_read_lock); DebugOnlyLockLeave(this); intptr_t v = mu_.load(std::memory_order_relaxed); @@ -2286,7 +2285,7 @@ // set up to walk the list PerThreadSynch* w_walk; // current waiter during list walk PerThreadSynch* pw_walk; // previous waiter during list walk - if (old_h != nullptr) { // we've searched up to old_h before + if (old_h != nullptr) { // we've searched up to old_h before pw_walk = old_h; w_walk = old_h->next; } else { // no prior search, start at beginning @@ -2762,7 +2761,7 @@ void ReleasableMutexLock::Release() { ABSL_RAW_CHECK(this->mu_ != nullptr, "ReleasableMutexLock::Release may only be called once"); - this->mu_->Unlock(); + this->mu_->unlock(); this->mu_ = nullptr; }
diff --git a/absl/synchronization/mutex.h b/absl/synchronization/mutex.h index 78b1c7a..39ea0d0 100644 --- a/absl/synchronization/mutex.h +++ b/absl/synchronization/mutex.h
@@ -61,18 +61,16 @@ #include <atomic> #include <cstdint> #include <cstring> -#include <iterator> -#include <string> #include "absl/base/attributes.h" +#include "absl/base/config.h" #include "absl/base/const_init.h" -#include "absl/base/internal/identity.h" -#include "absl/base/internal/low_level_alloc.h" #include "absl/base/internal/thread_identity.h" #include "absl/base/internal/tsan_mutex_interface.h" +#include "absl/base/macros.h" #include "absl/base/nullability.h" -#include "absl/base/port.h" #include "absl/base/thread_annotations.h" +#include "absl/meta/type_traits.h" #include "absl/synchronization/internal/kernel_timeout.h" #include "absl/synchronization/internal/per_thread_sem.h" #include "absl/time/time.h" @@ -92,10 +90,10 @@ // invariants. Proper usage of mutexes prevents concurrent access by different // threads to the same resource. // -// A `Mutex` has two basic operations: `Mutex::Lock()` and `Mutex::Unlock()`. -// The `Lock()` operation *acquires* a `Mutex` (in a state known as an -// *exclusive* -- or *write* -- lock), and the `Unlock()` operation *releases* a -// Mutex. During the span of time between the Lock() and Unlock() operations, +// A `Mutex` has two basic operations: `Mutex::lock()` and `Mutex::unlock()`. +// The `lock()` operation *acquires* a `Mutex` (in a state known as an +// *exclusive* -- or *write* -- lock), and the `unlock()` operation *releases* a +// Mutex. During the span of time between the lock() and unlock() operations, // a mutex is said to be *held*. By design, all mutexes support exclusive/write // locks, as this is the most common way to use a mutex. // @@ -106,23 +104,23 @@ // // The `Mutex` state machine for basic lock/unlock operations is quite simple: // -// | | Lock() | Unlock() | +// | | lock() | unlock() | // |----------------+------------------------+----------| // | Free | Exclusive | invalid | // | Exclusive | blocks, then exclusive | Free | // // The full conditions are as follows. // -// * Calls to `Unlock()` require that the mutex be held, and must be made in the -// same thread that performed the corresponding `Lock()` operation which +// * Calls to `unlock()` require that the mutex be held, and must be made in the +// same thread that performed the corresponding `lock()` operation which // acquired the mutex; otherwise the call is invalid. // // * The mutex being non-reentrant (or non-recursive) means that a call to -// `Lock()` or `TryLock()` must not be made in a thread that already holds the -// mutex; such a call is invalid. +// `lock()` or `try_lock()` must not be made in a thread that already holds +// the mutex; such a call is invalid. // // * In other words, the state of being "held" has both a temporal component -// (from `Lock()` until `Unlock()`) as well as a thread identity component: +// (from `lock()` until `unlock()`) as well as a thread identity component: // the mutex is held *by a particular thread*. // // An "invalid" operation has undefined behavior. The `Mutex` implementation @@ -174,24 +172,35 @@ ~Mutex(); - // Mutex::Lock() + // Mutex::lock() // // Blocks the calling thread, if necessary, until this `Mutex` is free, and // then acquires it exclusively. (This lock is also known as a "write lock.") - void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION(); + void lock() ABSL_EXCLUSIVE_LOCK_FUNCTION(); - // Mutex::Unlock() + ABSL_DEPRECATE_AND_INLINE() + inline void Lock() ABSL_EXCLUSIVE_LOCK_FUNCTION() { lock(); } + + // Mutex::unlock() // // Releases this `Mutex` and returns it from the exclusive/write state to the // free state. Calling thread must hold the `Mutex` exclusively. - void Unlock() ABSL_UNLOCK_FUNCTION(); + void unlock() ABSL_UNLOCK_FUNCTION(); - // Mutex::TryLock() + ABSL_DEPRECATE_AND_INLINE() + inline void Unlock() ABSL_UNLOCK_FUNCTION() { unlock(); } + + // Mutex::try_lock() // // If the mutex can be acquired without blocking, does so exclusively and // returns `true`. Otherwise, returns `false`. Returns `true` with high // probability if the `Mutex` was free. - [[nodiscard]] bool TryLock() ABSL_EXCLUSIVE_TRYLOCK_FUNCTION(true); + [[nodiscard]] bool try_lock() ABSL_EXCLUSIVE_TRYLOCK_FUNCTION(true); + + ABSL_DEPRECATE_AND_INLINE() + [[nodiscard]] bool TryLock() ABSL_EXCLUSIVE_TRYLOCK_FUNCTION(true) { + return try_lock(); + } // Mutex::AssertHeld() // @@ -211,19 +220,19 @@ // Neither read-locks nor write-locks are reentrant/recursive to avoid // potential client programming errors. // - // The Mutex API provides `Writer*()` aliases for the existing `Lock()`, - // `Unlock()` and `TryLock()` methods for use within applications mixing - // reader/writer locks. Using `Reader*()` and `Writer*()` operations in this + // The Mutex API provides `Writer*()` aliases for the existing `lock()`, + // `unlock()` and `try_lock()` methods for use within applications mixing + // reader/writer locks. Using `*_shared()` and `Writer*()` operations in this // manner can make locking behavior clearer when mixing read and write modes. // // Introducing reader locks necessarily complicates the `Mutex` state // machine somewhat. The table below illustrates the allowed state transitions - // of a mutex in such cases. Note that ReaderLock() may block even if the lock - // is held in shared mode; this occurs when another thread is blocked on a - // call to WriterLock(). + // of a mutex in such cases. Note that lock_shared() may block even if the + // lock is held in shared mode; this occurs when another thread is blocked on + // a call to lock(). // // --------------------------------------------------------------------------- - // Operation: WriterLock() Unlock() ReaderLock() ReaderUnlock() + // Operation: lock() unlock() lock_shared() unlock_shared() // --------------------------------------------------------------------------- // State // --------------------------------------------------------------------------- @@ -235,28 +244,38 @@ // // In comments below, "shared" refers to a state of Shared(n) for any n > 0. - // Mutex::ReaderLock() + // Mutex::lock_shared() // // Blocks the calling thread, if necessary, until this `Mutex` is either free, // or in shared mode, and then acquires a share of it. Note that - // `ReaderLock()` will block if some other thread has an exclusive/writer lock - // on the mutex. + // `lock_shared()` will block if some other thread has an exclusive/writer + // lock on the mutex. + void lock_shared() ABSL_SHARED_LOCK_FUNCTION(); - void ReaderLock() ABSL_SHARED_LOCK_FUNCTION(); + ABSL_DEPRECATE_AND_INLINE() + void ReaderLock() ABSL_SHARED_LOCK_FUNCTION() { lock_shared(); } - // Mutex::ReaderUnlock() + // Mutex::unlock_shared() // - // Releases a read share of this `Mutex`. `ReaderUnlock` may return a mutex to - // the free state if this thread holds the last reader lock on the mutex. Note - // that you cannot call `ReaderUnlock()` on a mutex held in write mode. - void ReaderUnlock() ABSL_UNLOCK_FUNCTION(); + // Releases a read share of this `Mutex`. `unlock_shared` may return a mutex + // to the free state if this thread holds the last reader lock on the mutex. + // Note that you cannot call `unlock_shared()` on a mutex held in write mode. + void unlock_shared() ABSL_UNLOCK_FUNCTION(); - // Mutex::ReaderTryLock() + ABSL_DEPRECATE_AND_INLINE() + void ReaderUnlock() ABSL_UNLOCK_FUNCTION() { unlock_shared(); } + + // Mutex::try_lock_shared() // // If the mutex can be acquired without blocking, acquires this mutex for // shared access and returns `true`. Otherwise, returns `false`. Returns // `true` with high probability if the `Mutex` was free or shared. - [[nodiscard]] bool ReaderTryLock() ABSL_SHARED_TRYLOCK_FUNCTION(true); + [[nodiscard]] bool try_lock_shared() ABSL_SHARED_TRYLOCK_FUNCTION(true); + + ABSL_DEPRECATE_AND_INLINE() + [[nodiscard]] bool ReaderTryLock() ABSL_SHARED_TRYLOCK_FUNCTION(true) { + return try_lock_shared(); + } // Mutex::AssertReaderHeld() // @@ -278,12 +297,15 @@ // These methods may be used (along with the complementary `Reader*()` // methods) to distinguish simple exclusive `Mutex` usage (`Lock()`, // etc.) from reader/writer lock usage. - void WriterLock() ABSL_EXCLUSIVE_LOCK_FUNCTION() { this->Lock(); } + ABSL_DEPRECATE_AND_INLINE() + void WriterLock() ABSL_EXCLUSIVE_LOCK_FUNCTION() { lock(); } - void WriterUnlock() ABSL_UNLOCK_FUNCTION() { this->Unlock(); } + ABSL_DEPRECATE_AND_INLINE() + void WriterUnlock() ABSL_UNLOCK_FUNCTION() { unlock(); } + ABSL_DEPRECATE_AND_INLINE() [[nodiscard]] bool WriterTryLock() ABSL_EXCLUSIVE_TRYLOCK_FUNCTION(true) { - return this->TryLock(); + return try_lock(); } // --------------------------------------------------------------------------- @@ -546,10 +568,10 @@ base_internal::PerThreadSynch* absl_nonnull w); void Dtor(); - friend class CondVar; // for access to Trans()/Fer(). + friend class CondVar; // for access to Trans()/Fer(). void Trans(MuHow absl_nonnull how); // used for CondVar->Mutex transfer void Fer(base_internal::PerThreadSynch* absl_nonnull - w); // used for CondVar->Mutex transfer + w); // used for CondVar->Mutex transfer // Catch the error of writing Mutex when intending MutexLock. explicit Mutex(const volatile Mutex* absl_nullable /*ignored*/) {} @@ -572,7 +594,7 @@ // Class Foo { // public: // Foo::Bar* Baz() { -// MutexLock lock(&mu_); +// MutexLock lock(mu_); // ... // return bar; // } @@ -584,32 +606,46 @@ public: // Constructors - // Calls `mu->Lock()` and returns when that call returns. That is, `*mu` is - // guaranteed to be locked when this object is constructed. Requires that - // `mu` be dereferenceable. - explicit MutexLock(Mutex* absl_nonnull mu) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) - : mu_(mu) { - this->mu_->Lock(); - } - - // Like above, but calls `mu->LockWhen(cond)` instead. That is, in addition to - // the above, the condition given by `cond` is also guaranteed to hold when - // this object is constructed. - explicit MutexLock(Mutex* absl_nonnull mu, const Condition& cond) + // Calls `mu.lock()` and returns when that call returns. That is, `mu` is + // guaranteed to be locked when this object is constructed. + explicit MutexLock(Mutex& mu ABSL_INTERNAL_ATTRIBUTE_CAPTURED_BY(this)) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) : mu_(mu) { - this->mu_->LockWhen(cond); + this->mu_.lock(); } + // Calls `mu->lock()` and returns when that call returns. That is, `*mu` is + // guaranteed to be locked when this object is constructed. Requires that + // `mu` be dereferenceable. + [[deprecated("Use the constructor that takes a reference instead")]] + ABSL_REFACTOR_INLINE + explicit MutexLock(Mutex* absl_nonnull mu) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) + : MutexLock(*mu) {} + + // Like above, but calls `mu.LockWhen(cond)` instead. That is, in addition to + // the above, the condition given by `cond` is also guaranteed to hold when + // this object is constructed. + explicit MutexLock(Mutex& mu ABSL_INTERNAL_ATTRIBUTE_CAPTURED_BY(this), + const Condition& cond) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) + : mu_(mu) { + this->mu_.LockWhen(cond); + } + + [[deprecated("Use the constructor that takes a reference instead")]] + ABSL_REFACTOR_INLINE + explicit MutexLock(Mutex* absl_nonnull mu, const Condition& cond) + ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) + : MutexLock(*mu, cond) {} + MutexLock(const MutexLock&) = delete; // NOLINT(runtime/mutex) MutexLock(MutexLock&&) = delete; // NOLINT(runtime/mutex) MutexLock& operator=(const MutexLock&) = delete; MutexLock& operator=(MutexLock&&) = delete; - ~MutexLock() ABSL_UNLOCK_FUNCTION() { this->mu_->Unlock(); } + ~MutexLock() ABSL_UNLOCK_FUNCTION() { this->mu_.unlock(); } private: - Mutex* absl_nonnull const mu_; + Mutex& mu_; }; // ReaderMutexLock @@ -618,26 +654,38 @@ // releases a shared lock on a `Mutex` via RAII. class ABSL_SCOPED_LOCKABLE ReaderMutexLock { public: - explicit ReaderMutexLock(Mutex* absl_nonnull mu) ABSL_SHARED_LOCK_FUNCTION(mu) - : mu_(mu) { - mu->ReaderLock(); - } - - explicit ReaderMutexLock(Mutex* absl_nonnull mu, const Condition& cond) + explicit ReaderMutexLock(Mutex& mu ABSL_INTERNAL_ATTRIBUTE_CAPTURED_BY(this)) ABSL_SHARED_LOCK_FUNCTION(mu) : mu_(mu) { - mu->ReaderLockWhen(cond); + mu.lock_shared(); } + [[deprecated("Use the constructor that takes a reference instead")]] + ABSL_REFACTOR_INLINE + explicit ReaderMutexLock(Mutex* absl_nonnull mu) ABSL_SHARED_LOCK_FUNCTION(mu) + : ReaderMutexLock(*mu) {} + + explicit ReaderMutexLock(Mutex& mu ABSL_INTERNAL_ATTRIBUTE_CAPTURED_BY(this), + const Condition& cond) ABSL_SHARED_LOCK_FUNCTION(mu) + : mu_(mu) { + mu.ReaderLockWhen(cond); + } + + [[deprecated("Use the constructor that takes a reference instead")]] + ABSL_REFACTOR_INLINE + explicit ReaderMutexLock(Mutex* absl_nonnull mu, const Condition& cond) + ABSL_SHARED_LOCK_FUNCTION(mu) + : ReaderMutexLock(*mu, cond) {} + ReaderMutexLock(const ReaderMutexLock&) = delete; ReaderMutexLock(ReaderMutexLock&&) = delete; ReaderMutexLock& operator=(const ReaderMutexLock&) = delete; ReaderMutexLock& operator=(ReaderMutexLock&&) = delete; - ~ReaderMutexLock() ABSL_UNLOCK_FUNCTION() { this->mu_->ReaderUnlock(); } + ~ReaderMutexLock() ABSL_UNLOCK_FUNCTION() { this->mu_.unlock_shared(); } private: - Mutex* absl_nonnull const mu_; + Mutex& mu_; }; // WriterMutexLock @@ -646,27 +694,40 @@ // releases a write (exclusive) lock on a `Mutex` via RAII. class ABSL_SCOPED_LOCKABLE WriterMutexLock { public: - explicit WriterMutexLock(Mutex* absl_nonnull mu) + explicit WriterMutexLock(Mutex& mu ABSL_INTERNAL_ATTRIBUTE_CAPTURED_BY(this)) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) : mu_(mu) { - mu->WriterLock(); + mu.lock(); } - explicit WriterMutexLock(Mutex* absl_nonnull mu, const Condition& cond) + [[deprecated("Use the constructor that takes a reference instead")]] + ABSL_REFACTOR_INLINE + explicit WriterMutexLock(Mutex* absl_nonnull mu) + ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) + : WriterMutexLock(*mu) {} + + explicit WriterMutexLock(Mutex& mu ABSL_INTERNAL_ATTRIBUTE_CAPTURED_BY(this), + const Condition& cond) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) : mu_(mu) { - mu->WriterLockWhen(cond); + mu.WriterLockWhen(cond); } + [[deprecated("Use the constructor that takes a reference instead")]] + ABSL_REFACTOR_INLINE + explicit WriterMutexLock(Mutex* absl_nonnull mu, const Condition& cond) + ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) + : WriterMutexLock(*mu, cond) {} + WriterMutexLock(const WriterMutexLock&) = delete; WriterMutexLock(WriterMutexLock&&) = delete; WriterMutexLock& operator=(const WriterMutexLock&) = delete; WriterMutexLock& operator=(WriterMutexLock&&) = delete; - ~WriterMutexLock() ABSL_UNLOCK_FUNCTION() { this->mu_->WriterUnlock(); } + ~WriterMutexLock() ABSL_UNLOCK_FUNCTION() { this->mu_.unlock(); } private: - Mutex* absl_nonnull const mu_; + Mutex& mu_; }; // ----------------------------------------------------------------------------- @@ -713,7 +774,7 @@ // Example using a scope guard: // // { -// MutexLock lock(&mu_, count_is_zero); +// MutexLock lock(mu_, count_is_zero); // // ... // } // @@ -754,27 +815,27 @@ template <typename T, typename = void> Condition( bool (*absl_nonnull func)(T* absl_nullability_unknown), - typename absl::internal::type_identity<T>::type* absl_nullability_unknown - arg); + typename absl::type_identity<T>::type* absl_nullability_unknown + arg); // Templated version for invoking a method that returns a `bool`. // // `Condition(object, &Class::Method)` constructs a `Condition` that evaluates // `object->Method()`. // - // Implementation Note: `absl::internal::type_identity` is used to allow + // Implementation Note: `absl::type_identity` is used to allow // methods to come from base classes. A simpler signature like // `Condition(T*, bool (T::*)())` does not suffice. template <typename T> Condition( T* absl_nonnull object, - bool (absl::internal::type_identity<T>::type::* absl_nonnull method)()); + bool (absl::type_identity<T>::type::* absl_nonnull method)()); // Same as above, for const members template <typename T> Condition( const T* absl_nonnull object, - bool (absl::internal::type_identity<T>::type::* absl_nonnull method)() + bool (absl::type_identity<T>::type::* absl_nonnull method)() const); // A Condition that returns the value of `*cond` @@ -1019,7 +1080,7 @@ ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) : mu_(mu) { if (this->mu_ != nullptr) { - this->mu_->Lock(); + this->mu_->lock(); } } @@ -1033,7 +1094,7 @@ ~MutexLockMaybe() ABSL_UNLOCK_FUNCTION() { if (this->mu_ != nullptr) { - this->mu_->Unlock(); + this->mu_->unlock(); } } @@ -1051,28 +1112,41 @@ // mutex before destruction. `Release()` may be called at most once. class ABSL_SCOPED_LOCKABLE ReleasableMutexLock { public: - explicit ReleasableMutexLock(Mutex* absl_nonnull mu) - ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) - : mu_(mu) { - this->mu_->Lock(); + explicit ReleasableMutexLock(Mutex& mu ABSL_INTERNAL_ATTRIBUTE_CAPTURED_BY( + this)) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) + : mu_(&mu) { + this->mu_->lock(); } - explicit ReleasableMutexLock(Mutex* absl_nonnull mu, const Condition& cond) + [[deprecated("Use the constructor that takes a reference instead")]] + ABSL_REFACTOR_INLINE + explicit ReleasableMutexLock(Mutex* absl_nonnull mu) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) - : mu_(mu) { + : ReleasableMutexLock(*mu) {} + + explicit ReleasableMutexLock( + Mutex& mu ABSL_INTERNAL_ATTRIBUTE_CAPTURED_BY(this), + const Condition& cond) ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) + : mu_(&mu) { this->mu_->LockWhen(cond); } + [[deprecated("Use the constructor that takes a reference instead")]] + ABSL_REFACTOR_INLINE + explicit ReleasableMutexLock(Mutex* absl_nonnull mu, const Condition& cond) + ABSL_EXCLUSIVE_LOCK_FUNCTION(mu) + : ReleasableMutexLock(*mu, cond) {} + ~ReleasableMutexLock() ABSL_UNLOCK_FUNCTION() { if (this->mu_ != nullptr) { - this->mu_->Unlock(); + this->mu_->unlock(); } } void Release() ABSL_UNLOCK_FUNCTION(); private: - Mutex* absl_nonnull mu_; + Mutex* absl_nullable mu_; ReleasableMutexLock(const ReleasableMutexLock&) = delete; ReleasableMutexLock(ReleasableMutexLock&&) = delete; ReleasableMutexLock& operator=(const ReleasableMutexLock&) = delete; @@ -1090,12 +1164,13 @@ inline Mutex::~Mutex() { Dtor(); } #endif -#if defined(NDEBUG) && !defined(ABSL_HAVE_THREAD_SANITIZER) -// Use default (empty) destructor in release build for performance reasons. -// We need to mark both Dtor and ~Mutex as always inline for inconsistent -// builds that use both NDEBUG and !NDEBUG with dynamic libraries. In these -// cases we want the empty functions to dissolve entirely rather than being -// exported from dynamic libraries and potentially override the non-empty ones. +#if defined(NDEBUG) && !defined(ABSL_HAVE_THREAD_SANITIZER) && \ + !defined(ABSL_BUILD_DLL) +// Under NDEBUG and without TSAN, Dtor is normally fully inlined for +// performance. However, when building Abseil as a shared library +// (ABSL_BUILD_DLL), we must provide an out-of-line definition. This ensures the +// Mutex::Dtor symbol is exported from the DLL, maintaining ABI compatibility +// with clients that might be built in debug mode and thus expect the symbol. ABSL_ATTRIBUTE_ALWAYS_INLINE inline void Mutex::Dtor() {} #endif @@ -1134,15 +1209,15 @@ template <typename T, typename> inline Condition::Condition( bool (*absl_nonnull func)(T* absl_nullability_unknown), - typename absl::internal::type_identity<T>::type* absl_nullability_unknown - arg) + typename absl::type_identity<T>::type* absl_nullability_unknown + arg) // Just delegate to the overload above. : Condition(func, arg) {} template <typename T> inline Condition::Condition( T* absl_nonnull object, - bool (absl::internal::type_identity<T>::type::* absl_nonnull method)()) + bool (absl::type_identity<T>::type::* absl_nonnull method)()) : eval_(&CastAndCallMethod<T, decltype(method)>), arg_(object) { static_assert(sizeof(&method) <= sizeof(callback_), "An overlarge method pointer was passed to Condition."); @@ -1152,7 +1227,7 @@ template <typename T> inline Condition::Condition( const T* absl_nonnull object, - bool (absl::internal::type_identity<T>::type::* absl_nonnull method)() + bool (absl::type_identity<T>::type::* absl_nonnull method)() const) : eval_(&CastAndCallMethod<const T, decltype(method)>), arg_(reinterpret_cast<void*>(const_cast<T*>(object))) {
diff --git a/absl/synchronization/mutex_benchmark.cc b/absl/synchronization/mutex_benchmark.cc index 06888df..d2c6495 100644 --- a/absl/synchronization/mutex_benchmark.cc +++ b/absl/synchronization/mutex_benchmark.cc
@@ -30,7 +30,7 @@ void BM_Mutex(benchmark::State& state) { static absl::NoDestructor<absl::Mutex> mu; for (auto _ : state) { - absl::MutexLock lock(mu.get()); + absl::MutexLock lock(*mu.get()); } } BENCHMARK(BM_Mutex)->UseRealTime()->Threads(1)->ThreadPerCpu(); @@ -38,7 +38,7 @@ void BM_ReaderLock(benchmark::State& state) { static absl::NoDestructor<absl::Mutex> mu; for (auto _ : state) { - absl::ReaderMutexLock lock(mu.get()); + absl::ReaderMutexLock lock(*mu.get()); } } BENCHMARK(BM_ReaderLock)->UseRealTime()->Threads(1)->ThreadPerCpu(); @@ -46,8 +46,8 @@ void BM_TryLock(benchmark::State& state) { absl::Mutex mu; for (auto _ : state) { - if (mu.TryLock()) { - mu.Unlock(); + if (mu.try_lock()) { + mu.unlock(); } } } @@ -56,8 +56,8 @@ void BM_ReaderTryLock(benchmark::State& state) { static absl::NoDestructor<absl::Mutex> mu; for (auto _ : state) { - if (mu->ReaderTryLock()) { - mu->ReaderUnlock(); + if (mu->try_lock_shared()) { + mu->unlock_shared(); } } } @@ -72,24 +72,6 @@ } } -template <typename MutexType> -class RaiiLocker { - public: - explicit RaiiLocker(MutexType* mu) : mu_(mu) { mu_->Lock(); } - ~RaiiLocker() { mu_->Unlock(); } - private: - MutexType* mu_; -}; - -template <> -class RaiiLocker<std::mutex> { - public: - explicit RaiiLocker(std::mutex* mu) : mu_(mu) { mu_->lock(); } - ~RaiiLocker() { mu_->unlock(); } - private: - std::mutex* mu_; -}; - // RAII object to change the Mutex priority of the running thread. class ScopedThreadMutexPriority { public: @@ -163,7 +145,7 @@ shared->looping_threads.fetch_add(1); for (int i = 0; i < kBatchSize; i++) { { - absl::MutexLock l(&shared->mu); + absl::MutexLock l(shared->mu); shared->thread_has_mutex.store(true, std::memory_order_relaxed); // Spin until all other threads are either out of the benchmark loop // or blocked on the mutex. This ensures that the mutex queue is kept @@ -226,7 +208,7 @@ // to keep ratio between local work and critical section approximately // equal regardless of number of threads. DelayNs(100 * state.threads(), &local); - RaiiLocker<MutexType> locker(&shared->mu); + std::scoped_lock locker(shared->mu); DelayNs(state.range(0), &shared->data); } } @@ -291,7 +273,7 @@ init->DecrementCount(); m->LockWhen(absl::Condition( static_cast<bool (*)(int*)>([](int* v) { return *v == 0; }), p)); - m->Unlock(); + m->unlock(); } }; @@ -317,15 +299,15 @@ init.Wait(); for (auto _ : state) { - mu.Lock(); - mu.Unlock(); // Each unlock requires Condition evaluation for our waiters. + mu.lock(); + mu.unlock(); // Each unlock requires Condition evaluation for our waiters. } - mu.Lock(); + mu.lock(); for (int i = 0; i < num_classes; i++) { equivalence_classes[i] = 0; } - mu.Unlock(); + mu.unlock(); } // Some configurations have higher thread limits than others.
diff --git a/absl/synchronization/mutex_test.cc b/absl/synchronization/mutex_test.cc index a3eb3db..793acf8 100644 --- a/absl/synchronization/mutex_test.cc +++ b/absl/synchronization/mutex_test.cc
@@ -23,7 +23,9 @@ #include <cstdlib> #include <functional> #include <memory> +#include <mutex> // NOLINT(build/c++11) #include <random> +#include <shared_mutex> // NOLINT(build/c++14) #include <string> #include <thread> // NOLINT(build/c++11) #include <type_traits> @@ -106,7 +108,7 @@ static void TestMu(TestContext *cxt, int c) { for (int i = 0; i != cxt->iterations; i++) { - absl::MutexLock l(&cxt->mu); + absl::MutexLock l(cxt->mu); int a = cxt->g0 + 1; cxt->g0 = a; cxt->g1--; @@ -117,17 +119,17 @@ for (int i = 0; i != cxt->iterations; i++) { do { std::this_thread::yield(); - } while (!cxt->mu.TryLock()); + } while (!cxt->mu.try_lock()); int a = cxt->g0 + 1; cxt->g0 = a; cxt->g1--; - cxt->mu.Unlock(); + cxt->mu.unlock(); } } static void TestR20ms(TestContext *cxt, int c) { for (int i = 0; i != cxt->iterations; i++) { - absl::ReaderMutexLock l(&cxt->mu); + absl::ReaderMutexLock l(cxt->mu); absl::SleepFor(absl::Milliseconds(20)); cxt->mu.AssertReaderHeld(); } @@ -136,7 +138,7 @@ static void TestRW(TestContext *cxt, int c) { if ((c & 1) == 0) { for (int i = 0; i != cxt->iterations; i++) { - absl::WriterMutexLock l(&cxt->mu); + absl::WriterMutexLock l(cxt->mu); cxt->g0++; cxt->g1--; cxt->mu.AssertHeld(); @@ -144,7 +146,7 @@ } } else { for (int i = 0; i != cxt->iterations; i++) { - absl::ReaderMutexLock l(&cxt->mu); + absl::ReaderMutexLock l(cxt->mu); CHECK_EQ(cxt->g0, -cxt->g1) << "Error in TestRW"; cxt->mu.AssertReaderHeld(); } @@ -166,7 +168,7 @@ MyContext mc; mc.target = c; mc.cxt = cxt; - absl::MutexLock l(&cxt->mu); + absl::MutexLock l(cxt->mu); cxt->mu.AssertHeld(); while (cxt->g0 < cxt->iterations) { cxt->mu.Await(absl::Condition(&mc, &MyContext::MyTurn)); @@ -182,7 +184,7 @@ static void TestSignalAll(TestContext *cxt, int c) { int target = c; - absl::MutexLock l(&cxt->mu); + absl::MutexLock l(cxt->mu); cxt->mu.AssertHeld(); while (cxt->g0 < cxt->iterations) { while (cxt->g0 != target && cxt->g0 != cxt->iterations) { @@ -200,7 +202,7 @@ static void TestSignal(TestContext *cxt, int c) { CHECK_EQ(cxt->threads, 2) << "TestSignal should use 2 threads"; int target = c; - absl::MutexLock l(&cxt->mu); + absl::MutexLock l(cxt->mu); cxt->mu.AssertHeld(); while (cxt->g0 < cxt->iterations) { while (cxt->g0 != target && cxt->g0 != cxt->iterations) { @@ -217,7 +219,7 @@ static void TestCVTimeout(TestContext *cxt, int c) { int target = c; - absl::MutexLock l(&cxt->mu); + absl::MutexLock l(cxt->mu); cxt->mu.AssertHeld(); while (cxt->g0 < cxt->iterations) { while (cxt->g0 != target && cxt->g0 != cxt->iterations) { @@ -241,7 +243,7 @@ absl::Condition false_cond(&kFalse); absl::Condition g0ge2(G0GE2, cxt); if (c == 0) { - absl::MutexLock l(&cxt->mu); + absl::MutexLock l(cxt->mu); absl::Time start = absl::Now(); if (use_cv) { @@ -309,7 +311,7 @@ CHECK_EQ(cxt->g0, cxt->threads) << "TestTime failed"; } else if (c == 1) { - absl::MutexLock l(&cxt->mu); + absl::MutexLock l(cxt->mu); const absl::Time start = absl::Now(); if (use_cv) { cxt->cv.WaitWithTimeout(&cxt->mu, absl::Milliseconds(500)); @@ -322,7 +324,7 @@ << "TestTime failed"; cxt->g0++; } else if (c == 2) { - absl::MutexLock l(&cxt->mu); + absl::MutexLock l(cxt->mu); if (use_cv) { while (cxt->g0 < 2) { cxt->cv.WaitWithTimeout(&cxt->mu, absl::Seconds(100)); @@ -333,7 +335,7 @@ } cxt->g0++; } else { - absl::MutexLock l(&cxt->mu); + absl::MutexLock l(cxt->mu); if (use_cv) { while (cxt->g0 < 2) { cxt->cv.Wait(&cxt->mu); @@ -351,11 +353,11 @@ static void EndTest(int *c0, int *c1, absl::Mutex *mu, absl::CondVar *cv, const std::function<void(int)> &cb) { - mu->Lock(); + mu->lock(); int c = (*c0)++; - mu->Unlock(); + mu->unlock(); cb(c); - absl::MutexLock l(mu); + absl::MutexLock l(*mu); (*c1)++; cv->Signal(); } @@ -377,11 +379,11 @@ &EndTest, &c0, &c1, &mu2, &cv2, std::function<void(int)>(std::bind(test, cxt, std::placeholders::_1)))); } - mu2.Lock(); + mu2.lock(); while (c1 != threads) { cv2.Wait(&mu2); } - mu2.Unlock(); + mu2.unlock(); return cxt->g0; } @@ -422,7 +424,7 @@ static void WaitForA(TimeoutBugStruct *x) { x->mu.LockWhen(absl::Condition(&x->a)); x->a_waiter_count--; - x->mu.Unlock(); + x->mu.unlock(); } static bool NoAWaiters(TimeoutBugStruct *x) { return x->a_waiter_count == 0; } @@ -445,27 +447,27 @@ // Thread A. Sets barrier, waits for release using Mutex::Await, then // signals released_cv. pool->Schedule([&state] { - state.release_mu.Lock(); + state.release_mu.lock(); - state.barrier_mu.Lock(); + state.barrier_mu.lock(); state.barrier = true; - state.barrier_mu.Unlock(); + state.barrier_mu.unlock(); state.release_mu.Await(absl::Condition(&state.release)); state.released_cv.Signal(); - state.release_mu.Unlock(); + state.release_mu.unlock(); }); state.barrier_mu.LockWhen(absl::Condition(&state.barrier)); - state.barrier_mu.Unlock(); - state.release_mu.Lock(); + state.barrier_mu.unlock(); + state.release_mu.lock(); // Thread A is now blocked on release by way of Mutex::Await(). // Set release. Calling released_cv.Wait() should un-block thread A, // which will signal released_cv. If not, the test will hang. state.release = true; state.released_cv.Wait(&state.release_mu); - state.release_mu.Unlock(); + state.release_mu.unlock(); } // Test that a CondVar.WaitWithTimeout(&mutex) can un-block a call to @@ -486,20 +488,20 @@ // Thread A. Sets barrier, waits for release using Mutex::Await, then // signals released_cv. pool->Schedule([&state] { - state.release_mu.Lock(); + state.release_mu.lock(); - state.barrier_mu.Lock(); + state.barrier_mu.lock(); state.barrier = true; - state.barrier_mu.Unlock(); + state.barrier_mu.unlock(); state.release_mu.Await(absl::Condition(&state.release)); state.released_cv.Signal(); - state.release_mu.Unlock(); + state.release_mu.unlock(); }); state.barrier_mu.LockWhen(absl::Condition(&state.barrier)); - state.barrier_mu.Unlock(); - state.release_mu.Lock(); + state.barrier_mu.unlock(); + state.release_mu.lock(); // Thread A is now blocked on release by way of Mutex::Await(). // Set release. Calling released_cv.Wait() should un-block thread A, @@ -510,7 +512,7 @@ << "; Unrecoverable test failure: CondVar::WaitWithTimeout did not " "unblock the absl::Mutex::Await call in another thread."; - state.release_mu.Unlock(); + state.release_mu.unlock(); } // Test for regression of a bug in loop of TryRemove() @@ -536,7 +538,7 @@ x.a = true; // wakeup the two waiters on A x.mu.Await(absl::Condition(&NoAWaiters, &x)); // wait for them to exit - x.mu.Unlock(); + x.mu.unlock(); } struct CondVarWaitDeadlock : testing::TestWithParam<int> { @@ -556,27 +558,27 @@ void Waiter1() { if (read_lock1) { - mu.ReaderLock(); + mu.lock_shared(); while (!cond1) { cv.Wait(&mu); } - mu.ReaderUnlock(); + mu.unlock_shared(); } else { - mu.Lock(); + mu.lock(); while (!cond1) { cv.Wait(&mu); } - mu.Unlock(); + mu.unlock(); } } void Waiter2() { if (read_lock2) { mu.ReaderLockWhen(absl::Condition(&cond2)); - mu.ReaderUnlock(); + mu.unlock_shared(); } else { mu.LockWhen(absl::Condition(&cond2)); - mu.Unlock(); + mu.unlock(); } } }; @@ -600,21 +602,21 @@ absl::SleepFor(absl::Milliseconds(100)); // Wake condwaiter. - mu.Lock(); + mu.lock(); cond1 = true; if (signal_unlocked) { - mu.Unlock(); + mu.unlock(); cv.Signal(); } else { cv.Signal(); - mu.Unlock(); + mu.unlock(); } waiter1.reset(); // "join" waiter1 // Wake waiter. - mu.Lock(); + mu.lock(); cond2 = true; - mu.Unlock(); + mu.unlock(); waiter2.reset(); // "join" waiter2 } @@ -639,19 +641,19 @@ // Test for regression of a bug in loop of DequeueAllWakeable() static void AcquireAsReader(DequeueAllWakeableBugStruct *x) { - x->mu.ReaderLock(); - x->mu2.Lock(); + x->mu.lock_shared(); + x->mu2.lock(); x->unfinished_count--; x->done1 = (x->unfinished_count == 0); - x->mu2.Unlock(); + x->mu2.unlock(); // make sure that both readers acquired mu before we release it. absl::SleepFor(absl::Seconds(2)); - x->mu.ReaderUnlock(); + x->mu.unlock_shared(); - x->mu2.Lock(); + x->mu2.lock(); x->finished_count--; x->done2 = (x->finished_count == 0); - x->mu2.Unlock(); + x->mu2.unlock(); } // Test for regression of a bug in loop of DequeueAllWakeable() @@ -663,21 +665,21 @@ x.done1 = false; x.finished_count = 2; x.done2 = false; - x.mu.Lock(); // acquire mu exclusively + x.mu.lock(); // acquire mu exclusively // queue two thread that will block on reader locks on x.mu tp->Schedule(std::bind(&AcquireAsReader, &x)); tp->Schedule(std::bind(&AcquireAsReader, &x)); absl::SleepFor(absl::Seconds(1)); // give time for reader threads to block - x.mu.Unlock(); // wake them up + x.mu.unlock(); // wake them up // both readers should finish promptly EXPECT_TRUE( x.mu2.LockWhenWithTimeout(absl::Condition(&x.done1), absl::Seconds(10))); - x.mu2.Unlock(); + x.mu2.unlock(); EXPECT_TRUE( x.mu2.LockWhenWithTimeout(absl::Condition(&x.done2), absl::Seconds(10))); - x.mu2.Unlock(); + x.mu2.unlock(); } struct LockWhenTestStruct { @@ -689,15 +691,15 @@ }; static bool LockWhenTestIsCond(LockWhenTestStruct *s) { - s->mu2.Lock(); + s->mu2.lock(); s->waiting = true; - s->mu2.Unlock(); + s->mu2.unlock(); return s->cond; } static void LockWhenTestWaitForIsCond(LockWhenTestStruct *s) { s->mu1.LockWhen(absl::Condition(&LockWhenTestIsCond, s)); - s->mu1.Unlock(); + s->mu1.unlock(); } TEST(Mutex, LockWhen) { @@ -705,11 +707,11 @@ std::thread t(LockWhenTestWaitForIsCond, &s); s.mu2.LockWhen(absl::Condition(&s.waiting)); - s.mu2.Unlock(); + s.mu2.unlock(); - s.mu1.Lock(); + s.mu1.lock(); s.cond = true; - s.mu1.Unlock(); + s.mu1.unlock(); t.join(); } @@ -724,20 +726,20 @@ bool (*cond_lt_10)(int *) = [](int *p) { return *p < 10; }; std::thread t1([&mu, &n, &done, cond_eq_10]() { - absl::ReaderMutexLock lock(&mu, absl::Condition(cond_eq_10, &n)); + absl::ReaderMutexLock lock(mu, absl::Condition(cond_eq_10, &n)); done = true; }); std::thread t2[10]; for (std::thread &t : t2) { t = std::thread([&mu, &n, cond_lt_10]() { - absl::WriterMutexLock lock(&mu, absl::Condition(cond_lt_10, &n)); + absl::WriterMutexLock lock(mu, absl::Condition(cond_lt_10, &n)); ++n; }); } { - absl::MutexLock lock(&mu); + absl::MutexLock lock(mu); n = 0; } @@ -749,7 +751,7 @@ } // -------------------------------------------------------- -// The following test requires Mutex::ReaderLock to be a real shared +// The following test requires Mutex::lock_shared to be a real shared // lock, which is not the case in all builds. #if !defined(ABSL_MUTEX_READER_LOCK_IS_EXCLUSIVE) @@ -776,9 +778,9 @@ // L >= mu, L < mu_waiting_on_cond static bool IsCond(void *v) { ReaderDecrementBugStruct *x = reinterpret_cast<ReaderDecrementBugStruct *>(v); - x->mu2.Lock(); + x->mu2.lock(); x->waiting_on_cond = true; - x->mu2.Unlock(); + x->mu2.unlock(); return x->cond; } @@ -791,23 +793,23 @@ // L={} static void WaitForCond(ReaderDecrementBugStruct *x) { absl::Mutex dummy; - absl::MutexLock l(&dummy); + absl::MutexLock l(dummy); x->mu.LockWhen(absl::Condition(&IsCond, x)); x->done--; - x->mu.Unlock(); + x->mu.unlock(); } // L={} static void GetReadLock(ReaderDecrementBugStruct *x) { - x->mu.ReaderLock(); - x->mu2.Lock(); + x->mu.lock_shared(); + x->mu2.lock(); x->have_reader_lock = true; x->mu2.Await(absl::Condition(&x->complete)); - x->mu2.Unlock(); - x->mu.ReaderUnlock(); - x->mu.Lock(); + x->mu2.unlock(); + x->mu.unlock_shared(); + x->mu.lock(); x->done--; - x->mu.Unlock(); + x->mu.unlock(); } // Test for reader counter being decremented incorrectly by waiter @@ -823,32 +825,32 @@ // Run WaitForCond() and wait for it to sleep std::thread thread1(WaitForCond, &x); x.mu2.LockWhen(absl::Condition(&x.waiting_on_cond)); - x.mu2.Unlock(); + x.mu2.unlock(); // Run GetReadLock(), and wait for it to get the read lock std::thread thread2(GetReadLock, &x); x.mu2.LockWhen(absl::Condition(&x.have_reader_lock)); - x.mu2.Unlock(); + x.mu2.unlock(); // Get the reader lock ourselves, and release it. - x.mu.ReaderLock(); - x.mu.ReaderUnlock(); + x.mu.lock_shared(); + x.mu.unlock_shared(); // The lock should be held in read mode by GetReadLock(). // If we have the bug, the lock will be free. x.mu.AssertReaderHeld(); // Wake up all the threads. - x.mu2.Lock(); + x.mu2.lock(); x.complete = true; - x.mu2.Unlock(); + x.mu2.unlock(); // TODO(delesley): turn on analysis once lock upgrading is supported. // (This call upgrades the lock from shared to exclusive.) - x.mu.Lock(); + x.mu.lock(); x.cond = true; x.mu.Await(absl::Condition(&AllDone, &x)); - x.mu.Unlock(); + x.mu.unlock(); thread1.join(); thread2.join(); @@ -869,9 +871,9 @@ auto mu = absl::make_unique<absl::Mutex[]>(kNumLocks); for (int j = 0; j != kNumLocks; j++) { if ((j % 2) == 0) { - mu[j].WriterLock(); + mu[j].lock(); } else { - mu[j].ReaderLock(); + mu[j].lock_shared(); } } } @@ -1067,15 +1069,15 @@ int *running) { absl::InsecureBitGen gen; std::uniform_int_distribution<int> random_millis(0, 15); - mu->ReaderLock(); + mu->lock_shared(); while (*running == 3) { absl::SleepFor(absl::Milliseconds(random_millis(gen))); cv->WaitWithTimeout(mu, absl::Milliseconds(random_millis(gen))); } - mu->ReaderUnlock(); - mu->Lock(); + mu->unlock_shared(); + mu->lock(); (*running)--; - mu->Unlock(); + mu->unlock(); } static bool IntIsZero(int *x) { return *x == 0; } @@ -1090,10 +1092,10 @@ tp->Schedule(std::bind(&ReaderForReaderOnCondVar, &mu, &cv, &running)); tp->Schedule(std::bind(&ReaderForReaderOnCondVar, &mu, &cv, &running)); absl::SleepFor(absl::Seconds(2)); - mu.Lock(); + mu.lock(); running--; mu.Await(absl::Condition(&IntIsZero, &running)); - mu.Unlock(); + mu.unlock(); } // -------------------------------------------------------- @@ -1117,7 +1119,7 @@ bool always_false = false; x->mu1.LockWhenWithTimeout(absl::Condition(&always_false), absl::Milliseconds(100)); - x->mu1.Unlock(); + x->mu1.unlock(); } CHECK_LT(x->value, 4) << "should not be invoked a fourth time"; @@ -1129,7 +1131,7 @@ // wait for cond0 to become true x->mu0.LockWhen(absl::Condition(&ConditionWithAcquire, x)); x->done = true; - x->mu0.Unlock(); + x->mu0.unlock(); } // Test for Condition whose function acquires other Mutexes @@ -1145,12 +1147,12 @@ // return false. absl::SleepFor(absl::Milliseconds(500)); // allow T time to hang - x.mu0.Lock(); + x.mu0.lock(); x.cv.WaitWithTimeout(&x.mu0, absl::Milliseconds(500)); // wake T // T will be woken because the Wait() will call ConditionWithAcquire() // for the second time, and it will return true. - x.mu0.Unlock(); + x.mu0.unlock(); // T will then acquire the lock and recheck its own condition. // It will find the condition true, as this is the third invocation, @@ -1166,7 +1168,7 @@ // is conceptually waiting both on the condition variable, and on mu2. x.mu0.LockWhen(absl::Condition(&x.done)); - x.mu0.Unlock(); + x.mu0.unlock(); } TEST(Mutex, DeadlockDetector) { @@ -1178,20 +1180,20 @@ absl::Mutex m3; absl::Mutex m4; - m1.Lock(); // m1 gets ID1 - m2.Lock(); // m2 gets ID2 - m3.Lock(); // m3 gets ID3 - m3.Unlock(); - m2.Unlock(); + m1.lock(); // m1 gets ID1 + m2.lock(); // m2 gets ID2 + m3.lock(); // m3 gets ID3 + m3.unlock(); + m2.unlock(); // m1 still held m1.ForgetDeadlockInfo(); // m1 loses ID - m2.Lock(); // m2 gets ID2 - m3.Lock(); // m3 gets ID3 - m4.Lock(); // m4 gets ID4 - m3.Unlock(); - m2.Unlock(); - m4.Unlock(); - m1.Unlock(); + m2.lock(); // m2 gets ID2 + m3.lock(); // m3 gets ID3 + m4.lock(); // m4 gets ID4 + m3.unlock(); + m2.unlock(); + m4.unlock(); + m1.unlock(); } // Bazel has a test "warning" file that programs can write to if the @@ -1246,18 +1248,18 @@ absl::Mutex mu0; absl::Mutex mu1; - bool got_mu0 = mu0.TryLock(); - mu1.Lock(); // acquire mu1 while holding mu0 + bool got_mu0 = mu0.try_lock(); + mu1.lock(); // acquire mu1 while holding mu0 if (got_mu0) { - mu0.Unlock(); + mu0.unlock(); } - if (mu0.TryLock()) { // try lock shouldn't cause deadlock detector to fire - mu0.Unlock(); + if (mu0.try_lock()) { // try lock shouldn't cause deadlock detector to fire + mu0.unlock(); } - mu0.Lock(); // acquire mu0 while holding mu1; should get one deadlock + mu0.lock(); // acquire mu0 while holding mu1; should get one deadlock // report here - mu0.Unlock(); - mu1.Unlock(); + mu0.unlock(); + mu1.unlock(); absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kAbort); } @@ -1272,10 +1274,10 @@ // Check that we survive a deadlock with a lock cycle. std::vector<absl::Mutex> mutex(100); for (size_t i = 0; i != mutex.size(); i++) { - mutex[i].Lock(); - mutex[(i + 1) % mutex.size()].Lock(); - mutex[i].Unlock(); - mutex[(i + 1) % mutex.size()].Unlock(); + mutex[i].lock(); + mutex[(i + 1) % mutex.size()].lock(); + mutex[i].unlock(); + mutex[(i + 1) % mutex.size()].unlock(); } absl::SetMutexDeadlockDetectionMode(absl::OnDeadlockCycle::kAbort); @@ -1295,10 +1297,10 @@ int end = std::min(n_locks, i + 5); // acquire and then release locks i, i+1, ..., i+4 for (int j = i; j < end; j++) { - array_of_locks[j].Lock(); + array_of_locks[j].lock(); } for (int j = i; j < end; j++) { - array_of_locks[j].Unlock(); + array_of_locks[j].unlock(); } } } @@ -1319,11 +1321,11 @@ absl::Mutex b, c; // Hold mutex. - a->Lock(); + a->lock(); // Force deadlock id assignment by acquiring another lock. - b.Lock(); - b.Unlock(); + b.lock(); + b.unlock(); // Delete the mutex. The Mutex destructor tries to remove held locks, // but the attempt isn't foolproof. It can fail if: @@ -1338,8 +1340,8 @@ // We should end up getting assigned the same deadlock id that was // freed up when "a" was deleted, which will cause a spurious deadlock // report if the held lock entry for "a" was not invalidated. - c.Lock(); - c.Unlock(); + c.lock(); + c.unlock(); } // -------------------------------------------------------- @@ -1574,11 +1576,11 @@ std::unique_ptr<absl::synchronization_internal::ThreadPool> pool = CreateDefaultPool(); RunAfterDelay(params.satisfy_condition_delay, pool.get(), [&] { - absl::MutexLock l(&mu); + absl::MutexLock l(mu); value = true; }); - absl::MutexLock lock(&mu); + absl::MutexLock lock(mu); absl::Time start_time = absl::Now(); absl::Condition cond(&value); bool result = @@ -1608,7 +1610,7 @@ std::unique_ptr<absl::synchronization_internal::ThreadPool> pool = CreateDefaultPool(); RunAfterDelay(params.satisfy_condition_delay, pool.get(), [&] { - absl::MutexLock l(&mu); + absl::MutexLock l(mu); value = true; }); @@ -1618,7 +1620,7 @@ params.use_absolute_deadline ? mu.LockWhenWithDeadline(cond, start_time + params.wait_timeout) : mu.LockWhenWithTimeout(cond, params.wait_timeout); - mu.Unlock(); + mu.unlock(); if (DelayIsWithinBounds(params.expected_delay, absl::Now() - start_time)) { EXPECT_EQ(params.expected_result, result); @@ -1643,7 +1645,7 @@ std::unique_ptr<absl::synchronization_internal::ThreadPool> pool = CreateDefaultPool(); RunAfterDelay(params.satisfy_condition_delay, pool.get(), [&] { - absl::MutexLock l(&mu); + absl::MutexLock l(mu); value = true; }); @@ -1654,7 +1656,7 @@ start_time + params.wait_timeout) : mu.ReaderLockWhenWithTimeout(absl::Condition(&value), params.wait_timeout); - mu.ReaderUnlock(); + mu.unlock_shared(); if (DelayIsWithinBounds(params.expected_delay, absl::Now() - start_time)) { EXPECT_EQ(params.expected_result, result); @@ -1680,12 +1682,12 @@ std::unique_ptr<absl::synchronization_internal::ThreadPool> pool = CreateDefaultPool(); RunAfterDelay(params.satisfy_condition_delay, pool.get(), [&] { - absl::MutexLock l(&mu); + absl::MutexLock l(mu); value = true; cv.Signal(); }); - absl::MutexLock lock(&mu); + absl::MutexLock lock(mu); absl::Time start_time = absl::Now(); absl::Duration timeout = params.wait_timeout; absl::Time deadline = start_time + timeout; @@ -1711,13 +1713,13 @@ logged_mutex.EnableDebugLog("fido_mutex"); absl::CondVar logged_cv; logged_cv.EnableDebugLog("rover_cv"); - logged_mutex.Lock(); + logged_mutex.lock(); logged_cv.WaitWithTimeout(&logged_mutex, absl::Milliseconds(20)); - logged_mutex.Unlock(); - logged_mutex.ReaderLock(); - logged_mutex.ReaderUnlock(); - logged_mutex.Lock(); - logged_mutex.Unlock(); + logged_mutex.unlock(); + logged_mutex.lock_shared(); + logged_mutex.unlock_shared(); + logged_mutex.lock(); + logged_mutex.unlock(); logged_cv.Signal(); logged_cv.SignalAll(); } @@ -1735,8 +1737,8 @@ alive[i] = true; mu->EnableDebugLog("Mutex"); mu->EnableInvariantDebugging(invariant, &alive[i]); - mu->Lock(); - mu->Unlock(); + mu->lock(); + mu->unlock(); mu->~Mutex(); alive[i] = false; } @@ -1762,8 +1764,8 @@ { absl::Mutex mu; mu.EnableInvariantDebugging([](void *) {}, nullptr); - mu.Lock(); - mu.Unlock(); + mu.lock(); + mu.unlock(); } { absl::Mutex mu; @@ -1900,7 +1902,7 @@ } TEST(Mutex, SignalExitedThread) { - // The test may expose a race when Mutex::Unlock signals a thread + // The test may expose a race when Mutex::unlock signals a thread // that has already exited. #if defined(__wasm__) || defined(__asmjs__) constexpr int kThreads = 1; // OOMs under WASM @@ -1913,11 +1915,11 @@ for (int i = 0; i < kThreads; i++) { absl::Mutex mu; std::thread t([&]() { - mu.Lock(); - mu.Unlock(); + mu.lock(); + mu.unlock(); }); - mu.Lock(); - mu.Unlock(); + mu.lock(); + mu.unlock(); t.join(); } }); @@ -1931,7 +1933,7 @@ std::atomic<bool> saw_wrote{false}; auto readfunc = [&]() { for (size_t i = 0; i < 10; ++i) { - absl::ReaderMutexLock lock(&mu); + absl::ReaderMutexLock lock(mu); if (wrote) { saw_wrote = true; break; @@ -1946,7 +1948,7 @@ // PerThreadSynch::priority, so the writer intentionally runs on a new thread. std::thread t3([&]() { // The writer should be able squeeze between the two alternating readers. - absl::MutexLock lock(&mu); + absl::MutexLock lock(mu); wrote = true; }); t1.join(); @@ -1978,30 +1980,30 @@ bool morph = false; std::thread th([&]() { EXPECT_EQ(0, pthread_setschedparam(pthread_self(), SCHED_FIFO, ¶m)); - mu.Lock(); + mu.lock(); locked = true; mu.Await(absl::Condition(¬ified)); - mu.Unlock(); + mu.unlock(); EXPECT_EQ(absl::synchronization_internal::GetOrCreateCurrentThreadIdentity() ->per_thread_synch.priority, param.sched_priority); - mu.Lock(); + mu.lock(); mu.Await(absl::Condition(&waiting)); morph = true; absl::SleepFor(absl::Seconds(1)); cv.Signal(); - mu.Unlock(); + mu.unlock(); }); - mu.Lock(); + mu.lock(); mu.Await(absl::Condition(&locked)); notified = true; - mu.Unlock(); - mu.Lock(); + mu.unlock(); + mu.lock(); waiting = true; while (!morph) { cv.Wait(&mu); } - mu.Unlock(); + mu.unlock(); th.join(); EXPECT_NE(absl::synchronization_internal::GetOrCreateCurrentThreadIdentity() ->per_thread_synch.priority, @@ -2016,22 +2018,34 @@ const bool kAlwaysTrue = true, kAlwaysFalse = false; const absl::Condition kTrueCond(&kAlwaysTrue), kFalseCond(&kAlwaysFalse); EXPECT_TRUE(mu.LockWhenWithTimeout(kTrueCond, absl::Milliseconds(1))); - mu.Unlock(); + mu.unlock(); EXPECT_FALSE(mu.LockWhenWithTimeout(kFalseCond, absl::Milliseconds(1))); EXPECT_TRUE(mu.AwaitWithTimeout(kTrueCond, absl::Milliseconds(1))); EXPECT_FALSE(mu.AwaitWithTimeout(kFalseCond, absl::Milliseconds(1))); std::thread th1([&]() { EXPECT_TRUE(mu.LockWhenWithTimeout(kTrueCond, absl::Milliseconds(1))); - mu.Unlock(); + mu.unlock(); }); std::thread th2([&]() { EXPECT_FALSE(mu.LockWhenWithTimeout(kFalseCond, absl::Milliseconds(1))); - mu.Unlock(); + mu.unlock(); }); absl::SleepFor(absl::Milliseconds(100)); - mu.Unlock(); + mu.unlock(); th1.join(); th2.join(); } +TEST(Mutex, ScopedLock) { + absl::Mutex mu; + { + std::scoped_lock l(mu); + } + + { + std::shared_lock l(mu); + EXPECT_TRUE(l.owns_lock()); + } +} + } // namespace
diff --git a/absl/synchronization/notification.cc b/absl/synchronization/notification.cc index a5853ab..a890c1b 100644 --- a/absl/synchronization/notification.cc +++ b/absl/synchronization/notification.cc
@@ -26,7 +26,7 @@ void Notification::Notify() { base_internal::TraceSignal(this, TraceObjectKind()); - MutexLock l(&this->mutex_); + MutexLock l(this->mutex_); #ifndef NDEBUG if (ABSL_PREDICT_FALSE(notified_yet_.load(std::memory_order_relaxed))) { @@ -43,7 +43,7 @@ Notification::~Notification() { // Make sure that the thread running Notify() exits before the object is // destructed. - MutexLock l(&this->mutex_); + MutexLock l(this->mutex_); } void Notification::WaitForNotification() const { @@ -51,7 +51,7 @@ if (!HasBeenNotifiedInternal(&this->notified_yet_)) { this->mutex_.LockWhen( Condition(&HasBeenNotifiedInternal, &this->notified_yet_)); - this->mutex_.Unlock(); + this->mutex_.unlock(); } base_internal::TraceContinue(this, TraceObjectKind()); } @@ -63,7 +63,7 @@ if (!notified) { notified = this->mutex_.LockWhenWithTimeout( Condition(&HasBeenNotifiedInternal, &this->notified_yet_), timeout); - this->mutex_.Unlock(); + this->mutex_.unlock(); } base_internal::TraceContinue(notified ? this : nullptr, TraceObjectKind()); return notified; @@ -75,7 +75,7 @@ if (!notified) { notified = this->mutex_.LockWhenWithDeadline( Condition(&HasBeenNotifiedInternal, &this->notified_yet_), deadline); - this->mutex_.Unlock(); + this->mutex_.unlock(); } base_internal::TraceContinue(notified ? this : nullptr, TraceObjectKind()); return notified;
diff --git a/absl/synchronization/notification.h b/absl/synchronization/notification.h index 1ceffdb..12df31b 100644 --- a/absl/synchronization/notification.h +++ b/absl/synchronization/notification.h
@@ -52,7 +52,7 @@ #include <atomic> -#include "absl/base/attributes.h" +#include "absl/base/config.h" #include "absl/base/internal/tracing.h" #include "absl/synchronization/mutex.h" #include "absl/time/time.h"
diff --git a/absl/synchronization/notification_test.cc b/absl/synchronization/notification_test.cc index eedad17..ac5dccd 100644 --- a/absl/synchronization/notification_test.cc +++ b/absl/synchronization/notification_test.cc
@@ -34,17 +34,17 @@ ThreadSafeCounter() : count_(0) {} void Increment() { - MutexLock lock(&mutex_); + MutexLock lock(mutex_); ++count_; } int Get() const { - MutexLock lock(&mutex_); + MutexLock lock(mutex_); return count_; } void WaitUntilGreaterOrEqual(int n) { - MutexLock lock(&mutex_); + MutexLock lock(mutex_); auto cond = [this, n]() { return count_ >= n; }; mutex_.Await(Condition(&cond)); }
diff --git a/absl/time/BUILD.bazel b/absl/time/BUILD.bazel index ad0313c..b68dd85 100644 --- a/absl/time/BUILD.bazel +++ b/absl/time/BUILD.bazel
@@ -14,6 +14,9 @@ # limitations under the License. # +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load( "//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS", @@ -118,6 +121,7 @@ "no_test_android_arm", "no_test_android_arm64", "no_test_android_x86", + "no_test_ios_sim_arm64", "no_test_ios_x86_64", "no_test_lexan", "no_test_loonix",
diff --git a/absl/time/CMakeLists.txt b/absl/time/CMakeLists.txt index ea91ba3..34a5ad4 100644 --- a/absl/time/CMakeLists.txt +++ b/absl/time/CMakeLists.txt
@@ -77,6 +77,8 @@ "internal/cctz/src/time_zone_posix.h" "internal/cctz/src/tzfile.h" "internal/cctz/src/zone_info_source.cc" + $<$<PLATFORM_ID:Windows>:internal/cctz/src/time_zone_name_win.cc> + $<$<PLATFORM_ID:Windows>:internal/cctz/src/time_zone_name_win.h> COPTS ${ABSL_DEFAULT_COPTS} DEPS
diff --git a/absl/time/civil_time_benchmark.cc b/absl/time/civil_time_benchmark.cc index 2de0233..2818a90 100644 --- a/absl/time/civil_time_benchmark.cc +++ b/absl/time/civil_time_benchmark.cc
@@ -12,14 +12,13 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "absl/time/civil_time.h" - #include <cstddef> #include <numeric> #include <string> #include <vector> #include "absl/hash/hash.h" +#include "absl/time/civil_time.h" #include "benchmark/benchmark.h" namespace {
diff --git a/absl/time/civil_time_test.cc b/absl/time/civil_time_test.cc index 6e5c35d..a0cb29c 100644 --- a/absl/time/civil_time_test.cc +++ b/absl/time/civil_time_test.cc
@@ -78,8 +78,7 @@ absl::FormatCivilTime(absl::CivilMinute(2015, 1, 2))); EXPECT_EQ("2015-01-01T00:00", absl::FormatCivilTime(absl::CivilMinute(2015, 1))); - EXPECT_EQ("2015-01-01T00:00", - absl::FormatCivilTime(absl::CivilMinute(2015))); + EXPECT_EQ("2015-01-01T00:00", absl::FormatCivilTime(absl::CivilMinute(2015))); EXPECT_EQ("2015-01-02T03", absl::FormatCivilTime(absl::CivilHour(2015, 1, 2, 3, 4, 5))); @@ -89,82 +88,63 @@ absl::FormatCivilTime(absl::CivilHour(2015, 1, 2, 3))); EXPECT_EQ("2015-01-02T00", absl::FormatCivilTime(absl::CivilHour(2015, 1, 2))); - EXPECT_EQ("2015-01-01T00", - absl::FormatCivilTime(absl::CivilHour(2015, 1))); - EXPECT_EQ("2015-01-01T00", - absl::FormatCivilTime(absl::CivilHour(2015))); + EXPECT_EQ("2015-01-01T00", absl::FormatCivilTime(absl::CivilHour(2015, 1))); + EXPECT_EQ("2015-01-01T00", absl::FormatCivilTime(absl::CivilHour(2015))); EXPECT_EQ("2015-01-02", absl::FormatCivilTime(absl::CivilDay(2015, 1, 2, 3, 4, 5))); EXPECT_EQ("2015-01-02", absl::FormatCivilTime(absl::CivilDay(2015, 1, 2, 3, 4))); - EXPECT_EQ("2015-01-02", - absl::FormatCivilTime(absl::CivilDay(2015, 1, 2, 3))); - EXPECT_EQ("2015-01-02", - absl::FormatCivilTime(absl::CivilDay(2015, 1, 2))); - EXPECT_EQ("2015-01-01", - absl::FormatCivilTime(absl::CivilDay(2015, 1))); - EXPECT_EQ("2015-01-01", - absl::FormatCivilTime(absl::CivilDay(2015))); + EXPECT_EQ("2015-01-02", absl::FormatCivilTime(absl::CivilDay(2015, 1, 2, 3))); + EXPECT_EQ("2015-01-02", absl::FormatCivilTime(absl::CivilDay(2015, 1, 2))); + EXPECT_EQ("2015-01-01", absl::FormatCivilTime(absl::CivilDay(2015, 1))); + EXPECT_EQ("2015-01-01", absl::FormatCivilTime(absl::CivilDay(2015))); EXPECT_EQ("2015-01", absl::FormatCivilTime(absl::CivilMonth(2015, 1, 2, 3, 4, 5))); EXPECT_EQ("2015-01", absl::FormatCivilTime(absl::CivilMonth(2015, 1, 2, 3, 4))); - EXPECT_EQ("2015-01", - absl::FormatCivilTime(absl::CivilMonth(2015, 1, 2, 3))); - EXPECT_EQ("2015-01", - absl::FormatCivilTime(absl::CivilMonth(2015, 1, 2))); - EXPECT_EQ("2015-01", - absl::FormatCivilTime(absl::CivilMonth(2015, 1))); - EXPECT_EQ("2015-01", - absl::FormatCivilTime(absl::CivilMonth(2015))); + EXPECT_EQ("2015-01", absl::FormatCivilTime(absl::CivilMonth(2015, 1, 2, 3))); + EXPECT_EQ("2015-01", absl::FormatCivilTime(absl::CivilMonth(2015, 1, 2))); + EXPECT_EQ("2015-01", absl::FormatCivilTime(absl::CivilMonth(2015, 1))); + EXPECT_EQ("2015-01", absl::FormatCivilTime(absl::CivilMonth(2015))); EXPECT_EQ("2015", absl::FormatCivilTime(absl::CivilYear(2015, 1, 2, 3, 4, 5))); - EXPECT_EQ("2015", - absl::FormatCivilTime(absl::CivilYear(2015, 1, 2, 3, 4))); - EXPECT_EQ("2015", - absl::FormatCivilTime(absl::CivilYear(2015, 1, 2, 3))); - EXPECT_EQ("2015", - absl::FormatCivilTime(absl::CivilYear(2015, 1, 2))); - EXPECT_EQ("2015", - absl::FormatCivilTime(absl::CivilYear(2015, 1))); - EXPECT_EQ("2015", - absl::FormatCivilTime(absl::CivilYear(2015))); + EXPECT_EQ("2015", absl::FormatCivilTime(absl::CivilYear(2015, 1, 2, 3, 4))); + EXPECT_EQ("2015", absl::FormatCivilTime(absl::CivilYear(2015, 1, 2, 3))); + EXPECT_EQ("2015", absl::FormatCivilTime(absl::CivilYear(2015, 1, 2))); + EXPECT_EQ("2015", absl::FormatCivilTime(absl::CivilYear(2015, 1))); + EXPECT_EQ("2015", absl::FormatCivilTime(absl::CivilYear(2015))); } TEST(CivilTime, FieldsConstructionLimits) { const int kIntMax = std::numeric_limits<int>::max(); - EXPECT_EQ("2038-01-19T03:14:07", - absl::FormatCivilTime(absl::CivilSecond( - 1970, 1, 1, 0, 0, kIntMax))); - EXPECT_EQ("6121-02-11T05:21:07", - absl::FormatCivilTime(absl::CivilSecond( - 1970, 1, 1, 0, kIntMax, kIntMax))); + EXPECT_EQ("2038-01-19T03:14:07", absl::FormatCivilTime(absl::CivilSecond( + 1970, 1, 1, 0, 0, kIntMax))); + EXPECT_EQ("6121-02-11T05:21:07", absl::FormatCivilTime(absl::CivilSecond( + 1970, 1, 1, 0, kIntMax, kIntMax))); EXPECT_EQ("251104-11-20T12:21:07", - absl::FormatCivilTime(absl::CivilSecond( - 1970, 1, 1, kIntMax, kIntMax, kIntMax))); + absl::FormatCivilTime( + absl::CivilSecond(1970, 1, 1, kIntMax, kIntMax, kIntMax))); EXPECT_EQ("6130715-05-30T12:21:07", - absl::FormatCivilTime(absl::CivilSecond( - 1970, 1, kIntMax, kIntMax, kIntMax, kIntMax))); + absl::FormatCivilTime(absl::CivilSecond(1970, 1, kIntMax, kIntMax, + kIntMax, kIntMax))); EXPECT_EQ("185087685-11-26T12:21:07", absl::FormatCivilTime(absl::CivilSecond( 1970, kIntMax, kIntMax, kIntMax, kIntMax, kIntMax))); const int kIntMin = std::numeric_limits<int>::min(); - EXPECT_EQ("1901-12-13T20:45:52", - absl::FormatCivilTime(absl::CivilSecond( - 1970, 1, 1, 0, 0, kIntMin))); - EXPECT_EQ("-2182-11-20T18:37:52", - absl::FormatCivilTime(absl::CivilSecond( - 1970, 1, 1, 0, kIntMin, kIntMin))); + EXPECT_EQ("1901-12-13T20:45:52", absl::FormatCivilTime(absl::CivilSecond( + 1970, 1, 1, 0, 0, kIntMin))); + EXPECT_EQ("-2182-11-20T18:37:52", absl::FormatCivilTime(absl::CivilSecond( + 1970, 1, 1, 0, kIntMin, kIntMin))); EXPECT_EQ("-247165-02-11T10:37:52", - absl::FormatCivilTime(absl::CivilSecond( - 1970, 1, 1, kIntMin, kIntMin, kIntMin))); + absl::FormatCivilTime( + absl::CivilSecond(1970, 1, 1, kIntMin, kIntMin, kIntMin))); EXPECT_EQ("-6126776-08-01T10:37:52", - absl::FormatCivilTime(absl::CivilSecond( - 1970, 1, kIntMin, kIntMin, kIntMin, kIntMin))); + absl::FormatCivilTime(absl::CivilSecond(1970, 1, kIntMin, kIntMin, + kIntMin, kIntMin))); EXPECT_EQ("-185083747-10-31T10:37:52", absl::FormatCivilTime(absl::CivilSecond( 1970, kIntMin, kIntMin, kIntMin, kIntMin, kIntMin))); @@ -173,14 +153,10 @@ TEST(CivilTime, RangeLimits) { const absl::civil_year_t kYearMax = std::numeric_limits<absl::civil_year_t>::max(); - EXPECT_EQ(absl::CivilYear(kYearMax), - absl::CivilYear::max()); - EXPECT_EQ(absl::CivilMonth(kYearMax, 12), - absl::CivilMonth::max()); - EXPECT_EQ(absl::CivilDay(kYearMax, 12, 31), - absl::CivilDay::max()); - EXPECT_EQ(absl::CivilHour(kYearMax, 12, 31, 23), - absl::CivilHour::max()); + EXPECT_EQ(absl::CivilYear(kYearMax), absl::CivilYear::max()); + EXPECT_EQ(absl::CivilMonth(kYearMax, 12), absl::CivilMonth::max()); + EXPECT_EQ(absl::CivilDay(kYearMax, 12, 31), absl::CivilDay::max()); + EXPECT_EQ(absl::CivilHour(kYearMax, 12, 31, 23), absl::CivilHour::max()); EXPECT_EQ(absl::CivilMinute(kYearMax, 12, 31, 23, 59), absl::CivilMinute::max()); EXPECT_EQ(absl::CivilSecond(kYearMax, 12, 31, 23, 59, 59), @@ -188,16 +164,11 @@ const absl::civil_year_t kYearMin = std::numeric_limits<absl::civil_year_t>::min(); - EXPECT_EQ(absl::CivilYear(kYearMin), - absl::CivilYear::min()); - EXPECT_EQ(absl::CivilMonth(kYearMin, 1), - absl::CivilMonth::min()); - EXPECT_EQ(absl::CivilDay(kYearMin, 1, 1), - absl::CivilDay::min()); - EXPECT_EQ(absl::CivilHour(kYearMin, 1, 1, 0), - absl::CivilHour::min()); - EXPECT_EQ(absl::CivilMinute(kYearMin, 1, 1, 0, 0), - absl::CivilMinute::min()); + EXPECT_EQ(absl::CivilYear(kYearMin), absl::CivilYear::min()); + EXPECT_EQ(absl::CivilMonth(kYearMin, 1), absl::CivilMonth::min()); + EXPECT_EQ(absl::CivilDay(kYearMin, 1, 1), absl::CivilDay::min()); + EXPECT_EQ(absl::CivilHour(kYearMin, 1, 1, 0), absl::CivilHour::min()); + EXPECT_EQ(absl::CivilMinute(kYearMin, 1, 1, 0, 0), absl::CivilMinute::min()); EXPECT_EQ(absl::CivilSecond(kYearMin, 1, 1, 0, 0, 0), absl::CivilSecond::min()); } @@ -250,8 +221,7 @@ (std::is_convertible<absl::CivilSecond, absl::CivilMinute>::value)); EXPECT_FALSE( (std::is_convertible<absl::CivilSecond, absl::CivilHour>::value)); - EXPECT_FALSE( - (std::is_convertible<absl::CivilSecond, absl::CivilDay>::value)); + EXPECT_FALSE((std::is_convertible<absl::CivilSecond, absl::CivilDay>::value)); EXPECT_FALSE( (std::is_convertible<absl::CivilSecond, absl::CivilMonth>::value)); EXPECT_FALSE( @@ -259,27 +229,20 @@ EXPECT_FALSE( (std::is_convertible<absl::CivilMinute, absl::CivilHour>::value)); - EXPECT_FALSE( - (std::is_convertible<absl::CivilMinute, absl::CivilDay>::value)); + EXPECT_FALSE((std::is_convertible<absl::CivilMinute, absl::CivilDay>::value)); EXPECT_FALSE( (std::is_convertible<absl::CivilMinute, absl::CivilMonth>::value)); EXPECT_FALSE( (std::is_convertible<absl::CivilMinute, absl::CivilYear>::value)); - EXPECT_FALSE( - (std::is_convertible<absl::CivilHour, absl::CivilDay>::value)); - EXPECT_FALSE( - (std::is_convertible<absl::CivilHour, absl::CivilMonth>::value)); - EXPECT_FALSE( - (std::is_convertible<absl::CivilHour, absl::CivilYear>::value)); + EXPECT_FALSE((std::is_convertible<absl::CivilHour, absl::CivilDay>::value)); + EXPECT_FALSE((std::is_convertible<absl::CivilHour, absl::CivilMonth>::value)); + EXPECT_FALSE((std::is_convertible<absl::CivilHour, absl::CivilYear>::value)); - EXPECT_FALSE( - (std::is_convertible<absl::CivilDay, absl::CivilMonth>::value)); - EXPECT_FALSE( - (std::is_convertible<absl::CivilDay, absl::CivilYear>::value)); + EXPECT_FALSE((std::is_convertible<absl::CivilDay, absl::CivilMonth>::value)); + EXPECT_FALSE((std::is_convertible<absl::CivilDay, absl::CivilYear>::value)); - EXPECT_FALSE( - (std::is_convertible<absl::CivilMonth, absl::CivilYear>::value)); + EXPECT_FALSE((std::is_convertible<absl::CivilMonth, absl::CivilYear>::value)); } TEST(CivilTime, ExplicitCrossAlignment) { @@ -417,8 +380,7 @@ // Tests the relational operators of two different civil-time types. TEST_RELATIONAL(absl::CivilDay(2014, 1, 1), absl::CivilMinute(2014, 1, 1, 1, 1)); - TEST_RELATIONAL(absl::CivilDay(2014, 1, 1), - absl::CivilMonth(2014, 2)); + TEST_RELATIONAL(absl::CivilDay(2014, 1, 1), absl::CivilMonth(2014, 2)); #undef TEST_RELATIONAL } @@ -812,8 +774,7 @@ EXPECT_EQ("-9223372036854775808-01-01T00:00", absl::FormatCivilTime(mm)); absl::CivilHour hh; - EXPECT_TRUE( - absl::ParseLenientCivilTime("9223372036854775807-12-31T23", &hh)); + EXPECT_TRUE(absl::ParseLenientCivilTime("9223372036854775807-12-31T23", &hh)); EXPECT_EQ("9223372036854775807-12-31T23", absl::FormatCivilTime(hh)); EXPECT_TRUE( absl::ParseLenientCivilTime("-9223372036854775808-01-01T00", &hh)); @@ -1181,19 +1142,13 @@ int day; } leap_day; // The date of the day after Feb 28. } kLeapYearTable[]{ - {1900, 365, {3, 1}}, - {1999, 365, {3, 1}}, + {1900, 365, {3, 1}}, {1999, 365, {3, 1}}, {2000, 366, {2, 29}}, // leap year - {2001, 365, {3, 1}}, - {2002, 365, {3, 1}}, - {2003, 365, {3, 1}}, - {2004, 366, {2, 29}}, // leap year - {2005, 365, {3, 1}}, - {2006, 365, {3, 1}}, - {2007, 365, {3, 1}}, - {2008, 366, {2, 29}}, // leap year - {2009, 365, {3, 1}}, - {2100, 365, {3, 1}}, + {2001, 365, {3, 1}}, {2002, 365, {3, 1}}, + {2003, 365, {3, 1}}, {2004, 366, {2, 29}}, // leap year + {2005, 365, {3, 1}}, {2006, 365, {3, 1}}, + {2007, 365, {3, 1}}, {2008, 366, {2, 29}}, // leap year + {2009, 365, {3, 1}}, {2100, 365, {3, 1}}, }; for (int i = 0; i < ABSL_ARRAYSIZE(kLeapYearTable); ++i) { @@ -1223,7 +1178,7 @@ // Bonus: Date of Thanksgiving in the United States // Rule: Fourth Thursday of November - const absl::CivilDay thanksgiving = thursday + 7 * 3; + const absl::CivilDay thanksgiving = thursday + 7 * 3; EXPECT_EQ("2014-11-27", absl::FormatCivilTime(thanksgiving)); }
diff --git a/absl/time/clock.cc b/absl/time/clock.cc index ecd539e..2a5f41b 100644 --- a/absl/time/clock.cc +++ b/absl/time/clock.cc
@@ -125,7 +125,7 @@ // spin-delay tuning. // Acquire seqlock (*seq) and return the value to be written to unlock. -static inline uint64_t SeqAcquire(std::atomic<uint64_t> *seq) { +static inline uint64_t SeqAcquire(std::atomic<uint64_t>* seq) { uint64_t x = seq->fetch_add(1, std::memory_order_relaxed); // We put a release fence between update to *seq and writes to shared data. @@ -135,12 +135,12 @@ // fetch_add would be before it, not after. std::atomic_thread_fence(std::memory_order_release); - return x + 2; // original word plus 2 + return x + 2; // original word plus 2 } // Release seqlock (*seq) by writing x to it---a value previously returned by // SeqAcquire. -static inline void SeqRelease(std::atomic<uint64_t> *seq, uint64_t x) { +static inline void SeqRelease(std::atomic<uint64_t>* seq, uint64_t x) { // The unlock store to *seq must have release ordering so that all // updates to shared data must finish before this store. seq->store(x, std::memory_order_release); // release lock for readers @@ -160,8 +160,8 @@ // We require that kMinNSBetweenSamples shifted by kScale // have at least a bit left over for 64-bit calculations. static_assert(((kMinNSBetweenSamples << (kScale + 1)) >> (kScale + 1)) == - kMinNSBetweenSamples, - "cannot represent kMaxBetweenSamplesNSScaled"); + kMinNSBetweenSamples, + "cannot represent kMaxBetweenSamplesNSScaled"); // data from a sample of the kernel's time value struct TimeSampleAtomic { @@ -206,8 +206,7 @@ // A reader-writer lock protecting the static locations below. // See SeqAcquire() and SeqRelease() above. - absl::base_internal::SpinLock lock{absl::kConstInit, - base_internal::SCHEDULE_KERNEL_ONLY}; + absl::base_internal::SpinLock lock{base_internal::SCHEDULE_KERNEL_ONLY}; }; ABSL_CONST_INIT static TimeState time_state; @@ -219,7 +218,7 @@ // assumed to be complete resyncs, which shouldn't happen. If they do, a full // reinitialization of the outer algorithm should occur.) static int64_t GetCurrentTimeNanosFromKernel(uint64_t last_cycleclock, - uint64_t *cycleclock) + uint64_t* cycleclock) ABSL_EXCLUSIVE_LOCKS_REQUIRED(time_state.lock) { uint64_t local_approx_syscall_time_in_cycles = // local copy time_state.approx_syscall_time_in_cycles.load(std::memory_order_relaxed); @@ -275,8 +274,8 @@ // Read the contents of *atomic into *sample. // Each field is read atomically, but to maintain atomicity between fields, // the access must be done under a lock. -static void ReadTimeSampleAtomic(const struct TimeSampleAtomic *atomic, - struct TimeSample *sample) { +static void ReadTimeSampleAtomic(const struct TimeSampleAtomic* atomic, + struct TimeSample* sample) { sample->base_ns = atomic->base_ns.load(std::memory_order_relaxed); sample->base_cycles = atomic->base_cycles.load(std::memory_order_relaxed); sample->nsscaled_per_cycle = @@ -339,18 +338,20 @@ // to the same shared data. seq_read0 = time_state.seq.load(std::memory_order_acquire); - base_ns = time_state.last_sample.base_ns.load(std::memory_order_relaxed); + // The algorithm does not require that the following four loads be ordered + // with respect to one another; it requires only that they precede the load of + // time_state.seq below them. Nevertheless, we mark each of them as an + // acquire-load, rather than using a barrier immediately before the + // time_state.seq load, because the former is likely faster on most CPUs of + // interest. Architectures that may see a regression because of this approach + // include PowerPC and MIPS. + base_ns = time_state.last_sample.base_ns.load(std::memory_order_acquire); base_cycles = - time_state.last_sample.base_cycles.load(std::memory_order_relaxed); + time_state.last_sample.base_cycles.load(std::memory_order_acquire); nsscaled_per_cycle = - time_state.last_sample.nsscaled_per_cycle.load(std::memory_order_relaxed); + time_state.last_sample.nsscaled_per_cycle.load(std::memory_order_acquire); min_cycles_per_sample = time_state.last_sample.min_cycles_per_sample.load( - std::memory_order_relaxed); - - // This acquire fence pairs with the release fence in SeqAcquire. Since it - // is sequenced between reads of shared data and seq_read1, the reads of - // shared data are effectively acquiring. - std::atomic_thread_fence(std::memory_order_acquire); + std::memory_order_acquire); // The shared-data reads are effectively acquire ordered, and the // shared-data writes are effectively release ordered. Therefore if our @@ -398,7 +399,7 @@ static uint64_t UpdateLastSample( uint64_t now_cycles, uint64_t now_ns, uint64_t delta_cycles, - const struct TimeSample *sample) ABSL_ATTRIBUTE_COLD; + const struct TimeSample* sample) ABSL_ATTRIBUTE_COLD; // The slow path of GetCurrentTimeNanos(). This is taken while gathering // initial samples, when enough time has elapsed since the last sample, and if @@ -416,7 +417,7 @@ ABSL_LOCKS_EXCLUDED(time_state.lock) { // Serialize access to slow-path. Fast-path readers are not blocked yet, and // code below must not modify last_sample until the seqlock is acquired. - time_state.lock.Lock(); + base_internal::SpinLockHolder l(time_state.lock); // Sample the kernel time base. This is the definition of // "now" if we take the slow path. @@ -439,16 +440,14 @@ if (delta_cycles < sample.min_cycles_per_sample) { // Another thread updated the sample. This path does not take the seqlock // so that blocked readers can make progress without blocking new readers. - estimated_base_ns = sample.base_ns + - ((delta_cycles * sample.nsscaled_per_cycle) >> kScale); + estimated_base_ns = + sample.base_ns + ((delta_cycles * sample.nsscaled_per_cycle) >> kScale); time_state.stats_fast_slow_paths++; } else { estimated_base_ns = UpdateLastSample(now_cycles, now_ns, delta_cycles, &sample); } - time_state.lock.Unlock(); - return static_cast<int64_t>(estimated_base_ns); } @@ -457,7 +456,7 @@ // for readers. Returns the new estimated time. static uint64_t UpdateLastSample(uint64_t now_cycles, uint64_t now_ns, uint64_t delta_cycles, - const struct TimeSample *sample) + const struct TimeSample* sample) ABSL_EXCLUSIVE_LOCKS_REQUIRED(time_state.lock) { uint64_t estimated_base_ns = now_ns; uint64_t lock_value = @@ -494,8 +493,8 @@ estimated_scaled_ns = (delta_cycles >> s) * sample->nsscaled_per_cycle; } while (estimated_scaled_ns / sample->nsscaled_per_cycle != (delta_cycles >> s)); - estimated_base_ns = sample->base_ns + - (estimated_scaled_ns >> (kScale - s)); + estimated_base_ns = + sample->base_ns + (estimated_scaled_ns >> (kScale - s)); } // Compute the assumed cycle time kMinNSBetweenSamples ns into the future @@ -522,8 +521,8 @@ diff_ns - (diff_ns / 16)); uint64_t new_nsscaled_per_cycle = SafeDivideAndScale(ns, assumed_next_sample_delta_cycles); - if (new_nsscaled_per_cycle != 0 && - diff_ns < 100 * 1000 * 1000 && -diff_ns < 100 * 1000 * 1000) { + if (new_nsscaled_per_cycle != 0 && diff_ns < 100 * 1000 * 1000 && + -diff_ns < 100 * 1000 * 1000) { // record the cycle time measurement time_state.last_sample.nsscaled_per_cycle.store( new_nsscaled_per_cycle, std::memory_order_relaxed);
diff --git a/absl/time/clock_test.cc b/absl/time/clock_test.cc index bc77dbc..fb1452a 100644 --- a/absl/time/clock_test.cc +++ b/absl/time/clock_test.cc
@@ -114,8 +114,8 @@ EXPECT_TRUE(AssertSleepForBounded(d, early, late, timeout, AlarmPolicy::kWithoutAlarm)); #if defined(ABSL_HAVE_ALARM) - EXPECT_TRUE(AssertSleepForBounded(d, early, late, timeout, - AlarmPolicy::kWithAlarm)); + EXPECT_TRUE( + AssertSleepForBounded(d, early, late, timeout, AlarmPolicy::kWithAlarm)); #endif }
diff --git a/absl/time/duration.cc b/absl/time/duration.cc index 38c4b63..fb7c90a 100644 --- a/absl/time/duration.cc +++ b/absl/time/duration.cc
@@ -469,7 +469,7 @@ Duration& Duration::operator*=(double r) { if (time_internal::IsInfiniteDuration(*this) || !IsFinite(r)) { - const bool is_neg = std::signbit(r) != (rep_hi_.Get() < 0); + const bool is_neg = std::isnan(r) || std::signbit(r) != (rep_hi_.Get() < 0); return *this = is_neg ? -InfiniteDuration() : InfiniteDuration(); } return *this = ScaleDouble<std::multiplies>(*this, r); @@ -485,7 +485,7 @@ Duration& Duration::operator/=(double r) { if (time_internal::IsInfiniteDuration(*this) || !IsValidDivisor(r)) { - const bool is_neg = std::signbit(r) != (rep_hi_.Get() < 0); + const bool is_neg = std::isnan(r) || std::signbit(r) != (rep_hi_.Get() < 0); return *this = is_neg ? -InfiniteDuration() : InfiniteDuration(); } return *this = ScaleDouble<std::divides>(*this, r);
diff --git a/absl/time/duration_benchmark.cc b/absl/time/duration_benchmark.cc index fdb26bb..e2dd4d2 100644 --- a/absl/time/duration_benchmark.cc +++ b/absl/time/duration_benchmark.cc
@@ -359,6 +359,150 @@ BENCHMARK(BM_Duration_ToInt64Hours); // +// ToDoubleXYZ +// +void BM_Duration_ToDoubleNanoseconds(benchmark::State& state) { + absl::Duration d = absl::Seconds(100000); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(d); + double result = absl::ToDoubleNanoseconds(d); + benchmark::DoNotOptimize(result); + } +} +BENCHMARK(BM_Duration_ToDoubleNanoseconds); + +void BM_Duration_ToDoubleMicroseconds(benchmark::State& state) { + absl::Duration d = absl::Seconds(100000); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(d); + double result = absl::ToDoubleMicroseconds(d); + benchmark::DoNotOptimize(result); + } +} +BENCHMARK(BM_Duration_ToDoubleMicroseconds); + +void BM_Duration_ToDoubleMilliseconds(benchmark::State& state) { + absl::Duration d = absl::Seconds(100000); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(d); + double result = absl::ToDoubleMilliseconds(d); + benchmark::DoNotOptimize(result); + } +} +BENCHMARK(BM_Duration_ToDoubleMilliseconds); + +void BM_Duration_ToDoubleSeconds(benchmark::State& state) { + absl::Duration d = absl::Seconds(100000); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(d); + double result = absl::ToDoubleSeconds(d); + benchmark::DoNotOptimize(result); + } +} +BENCHMARK(BM_Duration_ToDoubleSeconds); + +void BM_Duration_ToDoubleMinutes(benchmark::State& state) { + absl::Duration d = absl::Seconds(100000); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(d); + double result = absl::ToDoubleMinutes(d); + benchmark::DoNotOptimize(result); + } +} +BENCHMARK(BM_Duration_ToDoubleMinutes); + +void BM_Duration_ToDoubleHours(benchmark::State& state) { + absl::Duration d = absl::Seconds(100000); + while (state.KeepRunning()) { + benchmark::DoNotOptimize(d); + double result = absl::ToDoubleHours(d); + benchmark::DoNotOptimize(result); + } +} +BENCHMARK(BM_Duration_ToDoubleHours); + +// +// ToDoubleXYZ Latency +// +void BM_Duration_ToDoubleNanoseconds_Latency(benchmark::State& state) { + absl::Duration d1 = absl::Seconds(100000); + absl::Duration d2 = absl::Seconds(100000); + double result = 1; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(d1); + benchmark::DoNotOptimize(d2); + benchmark::DoNotOptimize(result); + result = absl::ToDoubleNanoseconds(result < 0 ? d1 : d2); + } +} +BENCHMARK(BM_Duration_ToDoubleNanoseconds_Latency); + +void BM_Duration_ToDoubleMicroseconds_Latency(benchmark::State& state) { + absl::Duration d1 = absl::Seconds(100000); + absl::Duration d2 = absl::Seconds(100000); + double result = 1; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(d1); + benchmark::DoNotOptimize(d2); + benchmark::DoNotOptimize(result); + result = absl::ToDoubleMicroseconds(result < 0 ? d1 : d2); + } +} +BENCHMARK(BM_Duration_ToDoubleMicroseconds_Latency); + +void BM_Duration_ToDoubleMilliseconds_Latency(benchmark::State& state) { + absl::Duration d1 = absl::Seconds(100000); + absl::Duration d2 = absl::Seconds(100000); + double result = 1; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(d1); + benchmark::DoNotOptimize(d2); + benchmark::DoNotOptimize(result); + result = absl::ToDoubleMilliseconds(result < 0 ? d1 : d2); + } +} +BENCHMARK(BM_Duration_ToDoubleMilliseconds_Latency); + +void BM_Duration_ToDoubleSeconds_Latency(benchmark::State& state) { + absl::Duration d1 = absl::Seconds(100000); + absl::Duration d2 = absl::Seconds(100000); + double result = 1; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(d1); + benchmark::DoNotOptimize(d2); + benchmark::DoNotOptimize(result); + result = absl::ToDoubleSeconds(result < 0 ? d1 : d2); + } +} +BENCHMARK(BM_Duration_ToDoubleSeconds_Latency); + +void BM_Duration_ToDoubleMinutes_Latency(benchmark::State& state) { + absl::Duration d1 = absl::Seconds(100000); + absl::Duration d2 = absl::Seconds(100000); + double result = 1; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(d1); + benchmark::DoNotOptimize(d2); + benchmark::DoNotOptimize(result); + result = absl::ToDoubleMinutes(result < 0 ? d1 : d2); + } +} +BENCHMARK(BM_Duration_ToDoubleMinutes_Latency); + +void BM_Duration_ToDoubleHours_Latency(benchmark::State& state) { + absl::Duration d1 = absl::Seconds(100000); + absl::Duration d2 = absl::Seconds(100000); + double result = 1; + while (state.KeepRunning()) { + benchmark::DoNotOptimize(d1); + benchmark::DoNotOptimize(d2); + benchmark::DoNotOptimize(result); + result = absl::ToDoubleHours(result < 0 ? d1 : d2); + } +} +BENCHMARK(BM_Duration_ToDoubleHours_Latency); + +// // To/FromTimespec //
diff --git a/absl/time/duration_test.cc b/absl/time/duration_test.cc index 1e3fe67..dc14665 100644 --- a/absl/time/duration_test.cc +++ b/absl/time/duration_test.cc
@@ -57,8 +57,7 @@ // timespec ts1, ts2; // EXPECT_THAT(ts1, TimespecMatcher(ts2)); MATCHER_P(TimespecMatcher, ts, "") { - if (ts.tv_sec == arg.tv_sec && ts.tv_nsec == arg.tv_nsec) - return true; + if (ts.tv_sec == arg.tv_sec && ts.tv_nsec == arg.tv_nsec) return true; *result_listener << "expected: {" << ts.tv_sec << ", " << ts.tv_nsec << "} "; *result_listener << "actual: {" << arg.tv_sec << ", " << arg.tv_nsec << "}"; return false; @@ -68,8 +67,7 @@ // timeval tv1, tv2; // EXPECT_THAT(tv1, TimevalMatcher(tv2)); MATCHER_P(TimevalMatcher, tv, "") { - if (tv.tv_sec == arg.tv_sec && tv.tv_usec == arg.tv_usec) - return true; + if (tv.tv_sec == arg.tv_sec && tv.tv_usec == arg.tv_usec) return true; *result_listener << "expected: {" << tv.tv_sec << ", " << tv.tv_usec << "} "; *result_listener << "actual: {" << arg.tv_sec << ", " << arg.tv_usec << "}"; return false; @@ -223,12 +221,12 @@ template <int64_t N> void TestFromChronoBasicEquality() { - using std::chrono::nanoseconds; + using std::chrono::hours; using std::chrono::microseconds; using std::chrono::milliseconds; - using std::chrono::seconds; using std::chrono::minutes; - using std::chrono::hours; + using std::chrono::nanoseconds; + using std::chrono::seconds; static_assert(absl::Nanoseconds(N) == absl::FromChrono(nanoseconds(N)), ""); static_assert(absl::Microseconds(N) == absl::FromChrono(microseconds(N)), ""); @@ -288,12 +286,12 @@ template <int64_t N> void TestToChrono() { - using std::chrono::nanoseconds; + using std::chrono::hours; using std::chrono::microseconds; using std::chrono::milliseconds; - using std::chrono::seconds; using std::chrono::minutes; - using std::chrono::hours; + using std::chrono::nanoseconds; + using std::chrono::seconds; EXPECT_EQ(nanoseconds(N), absl::ToChronoNanoseconds(absl::Nanoseconds(N))); EXPECT_EQ(microseconds(N), absl::ToChronoMicroseconds(absl::Microseconds(N))); @@ -320,12 +318,12 @@ } TEST(Duration, ToChrono) { - using std::chrono::nanoseconds; + using std::chrono::hours; using std::chrono::microseconds; using std::chrono::milliseconds; - using std::chrono::seconds; using std::chrono::minutes; - using std::chrono::hours; + using std::chrono::nanoseconds; + using std::chrono::seconds; TestToChrono<kint64min>(); TestToChrono<-1>(); @@ -546,18 +544,18 @@ const absl::Duration sec_min = absl::Seconds(kint64min); const absl::Duration inf = absl::InfiniteDuration(); -#define TEST_INF_MUL_WITH_TYPE(T) \ - EXPECT_EQ(inf, inf * static_cast<T>(2)); \ - EXPECT_EQ(-inf, inf * static_cast<T>(-2)); \ - EXPECT_EQ(-inf, -inf * static_cast<T>(2)); \ - EXPECT_EQ(inf, -inf * static_cast<T>(-2)); \ - EXPECT_EQ(inf, inf * static_cast<T>(0)); \ - EXPECT_EQ(-inf, -inf * static_cast<T>(0)); \ - EXPECT_EQ(inf, sec_max * static_cast<T>(2)); \ - EXPECT_EQ(inf, sec_min * static_cast<T>(-2)); \ - EXPECT_EQ(inf, (sec_max / static_cast<T>(2)) * static_cast<T>(3)); \ - EXPECT_EQ(-inf, sec_max * static_cast<T>(-2)); \ - EXPECT_EQ(-inf, sec_min * static_cast<T>(2)); \ +#define TEST_INF_MUL_WITH_TYPE(T) \ + EXPECT_EQ(inf, inf* static_cast<T>(2)); \ + EXPECT_EQ(-inf, inf* static_cast<T>(-2)); \ + EXPECT_EQ(-inf, -inf* static_cast<T>(2)); \ + EXPECT_EQ(inf, -inf* static_cast<T>(-2)); \ + EXPECT_EQ(inf, inf* static_cast<T>(0)); \ + EXPECT_EQ(-inf, -inf* static_cast<T>(0)); \ + EXPECT_EQ(inf, sec_max* static_cast<T>(2)); \ + EXPECT_EQ(inf, sec_min* static_cast<T>(-2)); \ + EXPECT_EQ(inf, (sec_max / static_cast<T>(2)) * static_cast<T>(3)); \ + EXPECT_EQ(-inf, sec_max* static_cast<T>(-2)); \ + EXPECT_EQ(-inf, sec_min* static_cast<T>(2)); \ EXPECT_EQ(-inf, (sec_min / static_cast<T>(2)) * static_cast<T>(3)); TEST_INF_MUL_WITH_TYPE(int64_t); // NOLINT(readability/function) @@ -841,18 +839,18 @@ TEST(Duration, NaN) { // Note that IEEE 754 does not define the behavior of a nan's sign when it is - // copied, so the code below allows for either + or - InfiniteDuration. + // copied. We return -InfiniteDuration in either case. #define TEST_NAN_HANDLING(NAME, NAN) \ do { \ const auto inf = absl::InfiniteDuration(); \ auto x = NAME(NAN); \ - EXPECT_TRUE(x == inf || x == -inf); \ + EXPECT_TRUE(x == -inf); \ auto y = NAME(42); \ y *= NAN; \ - EXPECT_TRUE(y == inf || y == -inf); \ + EXPECT_TRUE(y == -inf); \ auto z = NAME(42); \ z /= NAN; \ - EXPECT_TRUE(z == inf || z == -inf); \ + EXPECT_TRUE(z == -inf); \ } while (0) const double nan = std::numeric_limits<double>::quiet_NaN(); @@ -932,9 +930,9 @@ #ifdef ABSL_INTERNAL_TIME_HAS_THREE_WAY_COMPARISON TEST(Duration, SpaceshipOperators) { -#define TEST_REL_OPS(UNIT) \ +#define TEST_REL_OPS(UNIT) \ static_assert(UNIT(2) <=> UNIT(2) == std::strong_ordering::equal, ""); \ - static_assert(UNIT(1) <=> UNIT(2) == std::strong_ordering::less, ""); \ + static_assert(UNIT(1) <=> UNIT(2) == std::strong_ordering::less, ""); \ static_assert(UNIT(3) <=> UNIT(2) == std::strong_ordering::greater, ""); TEST_REL_OPS(absl::Nanoseconds); @@ -1159,8 +1157,7 @@ EXPECT_EQ(0, absl::Nanoseconds(-1) / absl::Seconds(1)); // Actual -1e-9 // Tests identity a = (a/b)*b + a%b -#define TEST_MOD_IDENTITY(a, b) \ - EXPECT_EQ((a), ((a) / (b))*(b) + ((a)%(b))) +#define TEST_MOD_IDENTITY(a, b) EXPECT_EQ((a), ((a) / (b)) * (b) + ((a) % (b))) TEST_MOD_IDENTITY(absl::Seconds(0), absl::Seconds(2)); TEST_MOD_IDENTITY(absl::Seconds(1), absl::Seconds(1));
diff --git a/absl/time/internal/cctz/BUILD.bazel b/absl/time/internal/cctz/BUILD.bazel index da30a0f..e7e2ee0 100644 --- a/absl/time/internal/cctz/BUILD.bazel +++ b/absl/time/internal/cctz/BUILD.bazel
@@ -12,6 +12,9 @@ # See the License for the specific language governing permissions and # limitations under the License. +load("@rules_cc//cc:cc_binary.bzl", "cc_binary") +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load("//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS", "ABSL_DEFAULT_LINKOPTS", "ABSL_TEST_COPTS") package(features = [ @@ -56,7 +59,13 @@ "src/time_zone_posix.h", "src/tzfile.h", "src/zone_info_source.cc", - ], + ] + select({ + "@platforms//os:windows": [ + "src/time_zone_name_win.cc", + "src/time_zone_name_win.h", + ], + "//conditions:default": [], + }), hdrs = [ "include/cctz/time_zone.h", "include/cctz/zone_info_source.h", @@ -80,9 +89,15 @@ ### tests -test_suite( - name = "all_tests", - visibility = ["//visibility:public"], +cc_library( + name = "test_time_zone_names", + testonly = True, + srcs = ["src/test_time_zone_names.cc"], + hdrs = ["src/test_time_zone_names.h"], + copts = ABSL_DEFAULT_COPTS, + linkopts = ABSL_DEFAULT_LINKOPTS, + visibility = ["//visibility:private"], + deps = ["//absl/base:config"], ) cc_test( @@ -137,6 +152,7 @@ ], deps = [ ":civil_time", + ":test_time_zone_names", ":time_zone", "//absl/base:config", "@googletest//:gtest", @@ -161,6 +177,7 @@ tags = ["benchmark"], deps = [ ":civil_time", + ":test_time_zone_names", ":time_zone", "//absl/base:config", "@google_benchmark//:benchmark_main",
diff --git a/absl/time/internal/cctz/include/cctz/civil_time_detail.h b/absl/time/internal/cctz/include/cctz/civil_time_detail.h index 2b0aed5..fe3b8bd 100644 --- a/absl/time/internal/cctz/include/cctz/civil_time_detail.h +++ b/absl/time/internal/cctz/include/cctz/civil_time_detail.h
@@ -96,6 +96,18 @@ CONSTEXPR_F int days_per_year(year_t y, month_t m) noexcept { return is_leap_year(y + (m > 2)) ? 366 : 365; } +// The compiler cannot optimize away the check if we use +// -fsanitize=array-bounds. +// m is guaranteed to be in [1:12] in the caller, but the compiler cannot +// optimize away the check even when this function is inlined into BreakTime. +// To reduce the overhead, we use no_sanitize to skip the unnecessary +// -fsanitize=array-bounds check. Remove no_sanitize once the missed +// optimization is fixed. +#if defined(__clang__) && defined(__has_cpp_attribute) +#if __has_cpp_attribute(clang::no_sanitize) +[[clang::no_sanitize("array-bounds")]] +#endif +#endif CONSTEXPR_F int days_per_month(year_t y, month_t m) noexcept { CONSTEXPR_D int k_days_per_month[1 + 12] = { -1, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 // non leap year
diff --git a/absl/time/internal/cctz/src/cctz_benchmark.cc b/absl/time/internal/cctz/src/cctz_benchmark.cc index ba7e149..ce27818 100644 --- a/absl/time/internal/cctz/src/cctz_benchmark.cc +++ b/absl/time/internal/cctz/src/cctz_benchmark.cc
@@ -23,6 +23,7 @@ #include "benchmark/benchmark.h" #include "absl/time/internal/cctz/include/cctz/civil_time.h" #include "absl/time/internal/cctz/include/cctz/time_zone.h" +#include "absl/time/internal/cctz/src/test_time_zone_names.h" #include "absl/time/internal/cctz/src/time_zone_impl.h" namespace { @@ -103,498 +104,10 @@ const char RFC1123_full[] = "%a, %d %b %Y %H:%M:%S %z"; const char RFC1123_no_wday[] = "%d %b %Y %H:%M:%S %z"; -// A list of known time-zone names. -// TODO: Refactor with src/time_zone_lookup_test.cc. -const char* const kTimeZoneNames[] = {"Africa/Abidjan", - "Africa/Accra", - "Africa/Addis_Ababa", - "Africa/Algiers", - "Africa/Asmara", - "Africa/Bamako", - "Africa/Bangui", - "Africa/Banjul", - "Africa/Bissau", - "Africa/Blantyre", - "Africa/Brazzaville", - "Africa/Bujumbura", - "Africa/Cairo", - "Africa/Casablanca", - "Africa/Ceuta", - "Africa/Conakry", - "Africa/Dakar", - "Africa/Dar_es_Salaam", - "Africa/Djibouti", - "Africa/Douala", - "Africa/El_Aaiun", - "Africa/Freetown", - "Africa/Gaborone", - "Africa/Harare", - "Africa/Johannesburg", - "Africa/Juba", - "Africa/Kampala", - "Africa/Khartoum", - "Africa/Kigali", - "Africa/Kinshasa", - "Africa/Lagos", - "Africa/Libreville", - "Africa/Lome", - "Africa/Luanda", - "Africa/Lubumbashi", - "Africa/Lusaka", - "Africa/Malabo", - "Africa/Maputo", - "Africa/Maseru", - "Africa/Mbabane", - "Africa/Mogadishu", - "Africa/Monrovia", - "Africa/Nairobi", - "Africa/Ndjamena", - "Africa/Niamey", - "Africa/Nouakchott", - "Africa/Ouagadougou", - "Africa/Porto-Novo", - "Africa/Sao_Tome", - "Africa/Timbuktu", - "Africa/Tripoli", - "Africa/Tunis", - "Africa/Windhoek", - "America/Adak", - "America/Anchorage", - "America/Anguilla", - "America/Antigua", - "America/Araguaina", - "America/Argentina/Buenos_Aires", - "America/Argentina/Catamarca", - "America/Argentina/Cordoba", - "America/Argentina/Jujuy", - "America/Argentina/La_Rioja", - "America/Argentina/Mendoza", - "America/Argentina/Rio_Gallegos", - "America/Argentina/Salta", - "America/Argentina/San_Juan", - "America/Argentina/San_Luis", - "America/Argentina/Tucuman", - "America/Argentina/Ushuaia", - "America/Aruba", - "America/Asuncion", - "America/Atikokan", - "America/Atka", - "America/Bahia", - "America/Bahia_Banderas", - "America/Barbados", - "America/Belem", - "America/Belize", - "America/Blanc-Sablon", - "America/Boa_Vista", - "America/Bogota", - "America/Boise", - "America/Cambridge_Bay", - "America/Campo_Grande", - "America/Cancun", - "America/Caracas", - "America/Cayenne", - "America/Cayman", - "America/Chicago", - "America/Chihuahua", - "America/Ciudad_Juarez", - "America/Coral_Harbour", - "America/Costa_Rica", - "America/Coyhaique", - "America/Creston", - "America/Cuiaba", - "America/Curacao", - "America/Danmarkshavn", - "America/Dawson", - "America/Dawson_Creek", - "America/Denver", - "America/Detroit", - "America/Dominica", - "America/Edmonton", - "America/Eirunepe", - "America/El_Salvador", - "America/Ensenada", - "America/Fort_Nelson", - "America/Fortaleza", - "America/Glace_Bay", - "America/Goose_Bay", - "America/Grand_Turk", - "America/Grenada", - "America/Guadeloupe", - "America/Guatemala", - "America/Guayaquil", - "America/Guyana", - "America/Halifax", - "America/Havana", - "America/Hermosillo", - "America/Indiana/Indianapolis", - "America/Indiana/Knox", - "America/Indiana/Marengo", - "America/Indiana/Petersburg", - "America/Indiana/Tell_City", - "America/Indiana/Vevay", - "America/Indiana/Vincennes", - "America/Indiana/Winamac", - "America/Inuvik", - "America/Iqaluit", - "America/Jamaica", - "America/Juneau", - "America/Kentucky/Louisville", - "America/Kentucky/Monticello", - "America/Kralendijk", - "America/La_Paz", - "America/Lima", - "America/Los_Angeles", - "America/Lower_Princes", - "America/Maceio", - "America/Managua", - "America/Manaus", - "America/Marigot", - "America/Martinique", - "America/Matamoros", - "America/Mazatlan", - "America/Menominee", - "America/Merida", - "America/Metlakatla", - "America/Mexico_City", - "America/Miquelon", - "America/Moncton", - "America/Monterrey", - "America/Montevideo", - "America/Montreal", - "America/Montserrat", - "America/Nassau", - "America/New_York", - "America/Nipigon", - "America/Nome", - "America/Noronha", - "America/North_Dakota/Beulah", - "America/North_Dakota/Center", - "America/North_Dakota/New_Salem", - "America/Nuuk", - "America/Ojinaga", - "America/Panama", - "America/Pangnirtung", - "America/Paramaribo", - "America/Phoenix", - "America/Port-au-Prince", - "America/Port_of_Spain", - "America/Porto_Acre", - "America/Porto_Velho", - "America/Puerto_Rico", - "America/Punta_Arenas", - "America/Rainy_River", - "America/Rankin_Inlet", - "America/Recife", - "America/Regina", - "America/Resolute", - "America/Rio_Branco", - "America/Santa_Isabel", - "America/Santarem", - "America/Santiago", - "America/Santo_Domingo", - "America/Sao_Paulo", - "America/Scoresbysund", - "America/Shiprock", - "America/Sitka", - "America/St_Barthelemy", - "America/St_Johns", - "America/St_Kitts", - "America/St_Lucia", - "America/St_Thomas", - "America/St_Vincent", - "America/Swift_Current", - "America/Tegucigalpa", - "America/Thule", - "America/Thunder_Bay", - "America/Tijuana", - "America/Toronto", - "America/Tortola", - "America/Vancouver", - "America/Virgin", - "America/Whitehorse", - "America/Winnipeg", - "America/Yakutat", - "America/Yellowknife", - "Antarctica/Casey", - "Antarctica/Davis", - "Antarctica/DumontDUrville", - "Antarctica/Macquarie", - "Antarctica/Mawson", - "Antarctica/McMurdo", - "Antarctica/Palmer", - "Antarctica/Rothera", - "Antarctica/Syowa", - "Antarctica/Troll", - "Antarctica/Vostok", - "Arctic/Longyearbyen", - "Asia/Aden", - "Asia/Almaty", - "Asia/Amman", - "Asia/Anadyr", - "Asia/Aqtau", - "Asia/Aqtobe", - "Asia/Ashgabat", - "Asia/Atyrau", - "Asia/Baghdad", - "Asia/Bahrain", - "Asia/Baku", - "Asia/Bangkok", - "Asia/Barnaul", - "Asia/Beirut", - "Asia/Bishkek", - "Asia/Brunei", - "Asia/Chita", - "Asia/Choibalsan", - "Asia/Chongqing", - "Asia/Colombo", - "Asia/Damascus", - "Asia/Dhaka", - "Asia/Dili", - "Asia/Dubai", - "Asia/Dushanbe", - "Asia/Famagusta", - "Asia/Gaza", - "Asia/Harbin", - "Asia/Hebron", - "Asia/Ho_Chi_Minh", - "Asia/Hong_Kong", - "Asia/Hovd", - "Asia/Irkutsk", - "Asia/Istanbul", - "Asia/Jakarta", - "Asia/Jayapura", - "Asia/Jerusalem", - "Asia/Kabul", - "Asia/Kamchatka", - "Asia/Karachi", - "Asia/Kashgar", - "Asia/Kathmandu", - "Asia/Khandyga", - "Asia/Kolkata", - "Asia/Krasnoyarsk", - "Asia/Kuala_Lumpur", - "Asia/Kuching", - "Asia/Kuwait", - "Asia/Macau", - "Asia/Magadan", - "Asia/Makassar", - "Asia/Manila", - "Asia/Muscat", - "Asia/Nicosia", - "Asia/Novokuznetsk", - "Asia/Novosibirsk", - "Asia/Omsk", - "Asia/Oral", - "Asia/Phnom_Penh", - "Asia/Pontianak", - "Asia/Pyongyang", - "Asia/Qatar", - "Asia/Qostanay", - "Asia/Qyzylorda", - "Asia/Riyadh", - "Asia/Sakhalin", - "Asia/Samarkand", - "Asia/Seoul", - "Asia/Shanghai", - "Asia/Singapore", - "Asia/Srednekolymsk", - "Asia/Taipei", - "Asia/Tashkent", - "Asia/Tbilisi", - "Asia/Tehran", - "Asia/Tel_Aviv", - "Asia/Thimphu", - "Asia/Tokyo", - "Asia/Tomsk", - "Asia/Ulaanbaatar", - "Asia/Urumqi", - "Asia/Ust-Nera", - "Asia/Vientiane", - "Asia/Vladivostok", - "Asia/Yakutsk", - "Asia/Yangon", - "Asia/Yekaterinburg", - "Asia/Yerevan", - "Atlantic/Azores", - "Atlantic/Bermuda", - "Atlantic/Canary", - "Atlantic/Cape_Verde", - "Atlantic/Faroe", - "Atlantic/Jan_Mayen", - "Atlantic/Madeira", - "Atlantic/Reykjavik", - "Atlantic/South_Georgia", - "Atlantic/St_Helena", - "Atlantic/Stanley", - "Australia/Adelaide", - "Australia/Brisbane", - "Australia/Broken_Hill", - "Australia/Canberra", - "Australia/Currie", - "Australia/Darwin", - "Australia/Eucla", - "Australia/Hobart", - "Australia/Lindeman", - "Australia/Lord_Howe", - "Australia/Melbourne", - "Australia/Perth", - "Australia/Sydney", - "Australia/Yancowinna", - "Etc/GMT", - "Etc/GMT+0", - "Etc/GMT+1", - "Etc/GMT+10", - "Etc/GMT+11", - "Etc/GMT+12", - "Etc/GMT+2", - "Etc/GMT+3", - "Etc/GMT+4", - "Etc/GMT+5", - "Etc/GMT+6", - "Etc/GMT+7", - "Etc/GMT+8", - "Etc/GMT+9", - "Etc/GMT-0", - "Etc/GMT-1", - "Etc/GMT-10", - "Etc/GMT-11", - "Etc/GMT-12", - "Etc/GMT-13", - "Etc/GMT-14", - "Etc/GMT-2", - "Etc/GMT-3", - "Etc/GMT-4", - "Etc/GMT-5", - "Etc/GMT-6", - "Etc/GMT-7", - "Etc/GMT-8", - "Etc/GMT-9", - "Etc/GMT0", - "Etc/Greenwich", - "Etc/UCT", - "Etc/UTC", - "Etc/Universal", - "Etc/Zulu", - "Europe/Amsterdam", - "Europe/Andorra", - "Europe/Astrakhan", - "Europe/Athens", - "Europe/Belfast", - "Europe/Belgrade", - "Europe/Berlin", - "Europe/Bratislava", - "Europe/Brussels", - "Europe/Bucharest", - "Europe/Budapest", - "Europe/Busingen", - "Europe/Chisinau", - "Europe/Copenhagen", - "Europe/Dublin", - "Europe/Gibraltar", - "Europe/Guernsey", - "Europe/Helsinki", - "Europe/Isle_of_Man", - "Europe/Istanbul", - "Europe/Jersey", - "Europe/Kaliningrad", - "Europe/Kirov", - "Europe/Kyiv", - "Europe/Lisbon", - "Europe/Ljubljana", - "Europe/London", - "Europe/Luxembourg", - "Europe/Madrid", - "Europe/Malta", - "Europe/Mariehamn", - "Europe/Minsk", - "Europe/Monaco", - "Europe/Moscow", - "Europe/Nicosia", - "Europe/Oslo", - "Europe/Paris", - "Europe/Podgorica", - "Europe/Prague", - "Europe/Riga", - "Europe/Rome", - "Europe/Samara", - "Europe/San_Marino", - "Europe/Sarajevo", - "Europe/Saratov", - "Europe/Simferopol", - "Europe/Skopje", - "Europe/Sofia", - "Europe/Stockholm", - "Europe/Tallinn", - "Europe/Tirane", - "Europe/Tiraspol", - "Europe/Ulyanovsk", - "Europe/Vaduz", - "Europe/Vatican", - "Europe/Vienna", - "Europe/Vilnius", - "Europe/Volgograd", - "Europe/Warsaw", - "Europe/Zagreb", - "Europe/Zurich", - "Factory", - "Indian/Antananarivo", - "Indian/Chagos", - "Indian/Christmas", - "Indian/Cocos", - "Indian/Comoro", - "Indian/Kerguelen", - "Indian/Mahe", - "Indian/Maldives", - "Indian/Mauritius", - "Indian/Mayotte", - "Indian/Reunion", - "Pacific/Apia", - "Pacific/Auckland", - "Pacific/Bougainville", - "Pacific/Chatham", - "Pacific/Chuuk", - "Pacific/Easter", - "Pacific/Efate", - "Pacific/Fakaofo", - "Pacific/Fiji", - "Pacific/Funafuti", - "Pacific/Galapagos", - "Pacific/Gambier", - "Pacific/Guadalcanal", - "Pacific/Guam", - "Pacific/Honolulu", - "Pacific/Johnston", - "Pacific/Kanton", - "Pacific/Kiritimati", - "Pacific/Kosrae", - "Pacific/Kwajalein", - "Pacific/Majuro", - "Pacific/Marquesas", - "Pacific/Midway", - "Pacific/Nauru", - "Pacific/Niue", - "Pacific/Norfolk", - "Pacific/Noumea", - "Pacific/Pago_Pago", - "Pacific/Palau", - "Pacific/Pitcairn", - "Pacific/Pohnpei", - "Pacific/Port_Moresby", - "Pacific/Rarotonga", - "Pacific/Saipan", - "Pacific/Samoa", - "Pacific/Tahiti", - "Pacific/Tarawa", - "Pacific/Tongatapu", - "Pacific/Wake", - "Pacific/Wallis", - "Pacific/Yap", - "UTC", - nullptr}; - std::vector<std::string> AllTimeZoneNames() { std::vector<std::string> names; - for (const char* const* namep = kTimeZoneNames; *namep != nullptr; ++namep) { + for (const char* const* namep = cctz::kTimeZoneNames; *namep != nullptr; + ++namep) { names.push_back(std::string("file:") + *namep); } assert(!names.empty()); @@ -889,6 +402,7 @@ RFC3339_sec, // 3 "%Y-%m-%d%ET%H:%M:%S", // 4 "%Y-%m-%d", // 5 + "%F%ET%T", // 6 }; const int kNumFormats = sizeof(kFormats) / sizeof(kFormats[0]);
diff --git a/absl/time/internal/cctz/src/test_time_zone_names.cc b/absl/time/internal/cctz/src/test_time_zone_names.cc new file mode 100644 index 0000000..ab54c9a --- /dev/null +++ b/absl/time/internal/cctz/src/test_time_zone_names.cc
@@ -0,0 +1,515 @@ +// Copyright 2025 Google Inc. All Rights Reserved. +// +// 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/time/internal/cctz/src/test_time_zone_names.h" + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace time_internal { +namespace cctz { + +// A list of known time-zone names. +const char* const kTimeZoneNames[] = {"Africa/Abidjan", + "Africa/Accra", + "Africa/Addis_Ababa", + "Africa/Algiers", + "Africa/Asmara", + "Africa/Bamako", + "Africa/Bangui", + "Africa/Banjul", + "Africa/Bissau", + "Africa/Blantyre", + "Africa/Brazzaville", + "Africa/Bujumbura", + "Africa/Cairo", + "Africa/Casablanca", + "Africa/Ceuta", + "Africa/Conakry", + "Africa/Dakar", + "Africa/Dar_es_Salaam", + "Africa/Djibouti", + "Africa/Douala", + "Africa/El_Aaiun", + "Africa/Freetown", + "Africa/Gaborone", + "Africa/Harare", + "Africa/Johannesburg", + "Africa/Juba", + "Africa/Kampala", + "Africa/Khartoum", + "Africa/Kigali", + "Africa/Kinshasa", + "Africa/Lagos", + "Africa/Libreville", + "Africa/Lome", + "Africa/Luanda", + "Africa/Lubumbashi", + "Africa/Lusaka", + "Africa/Malabo", + "Africa/Maputo", + "Africa/Maseru", + "Africa/Mbabane", + "Africa/Mogadishu", + "Africa/Monrovia", + "Africa/Nairobi", + "Africa/Ndjamena", + "Africa/Niamey", + "Africa/Nouakchott", + "Africa/Ouagadougou", + "Africa/Porto-Novo", + "Africa/Sao_Tome", + "Africa/Timbuktu", + "Africa/Tripoli", + "Africa/Tunis", + "Africa/Windhoek", + "America/Adak", + "America/Anchorage", + "America/Anguilla", + "America/Antigua", + "America/Araguaina", + "America/Argentina/Buenos_Aires", + "America/Argentina/Catamarca", + "America/Argentina/Cordoba", + "America/Argentina/Jujuy", + "America/Argentina/La_Rioja", + "America/Argentina/Mendoza", + "America/Argentina/Rio_Gallegos", + "America/Argentina/Salta", + "America/Argentina/San_Juan", + "America/Argentina/San_Luis", + "America/Argentina/Tucuman", + "America/Argentina/Ushuaia", + "America/Aruba", + "America/Asuncion", + "America/Atikokan", + "America/Atka", + "America/Bahia", + "America/Bahia_Banderas", + "America/Barbados", + "America/Belem", + "America/Belize", + "America/Blanc-Sablon", + "America/Boa_Vista", + "America/Bogota", + "America/Boise", + "America/Cambridge_Bay", + "America/Campo_Grande", + "America/Cancun", + "America/Caracas", + "America/Cayenne", + "America/Cayman", + "America/Chicago", + "America/Chihuahua", + "America/Ciudad_Juarez", + "America/Coral_Harbour", + "America/Costa_Rica", + "America/Coyhaique", + "America/Creston", + "America/Cuiaba", + "America/Curacao", + "America/Danmarkshavn", + "America/Dawson", + "America/Dawson_Creek", + "America/Denver", + "America/Detroit", + "America/Dominica", + "America/Edmonton", + "America/Eirunepe", + "America/El_Salvador", + "America/Ensenada", + "America/Fort_Nelson", + "America/Fortaleza", + "America/Glace_Bay", + "America/Goose_Bay", + "America/Grand_Turk", + "America/Grenada", + "America/Guadeloupe", + "America/Guatemala", + "America/Guayaquil", + "America/Guyana", + "America/Halifax", + "America/Havana", + "America/Hermosillo", + "America/Indiana/Indianapolis", + "America/Indiana/Knox", + "America/Indiana/Marengo", + "America/Indiana/Petersburg", + "America/Indiana/Tell_City", + "America/Indiana/Vevay", + "America/Indiana/Vincennes", + "America/Indiana/Winamac", + "America/Inuvik", + "America/Iqaluit", + "America/Jamaica", + "America/Juneau", + "America/Kentucky/Louisville", + "America/Kentucky/Monticello", + "America/Kralendijk", + "America/La_Paz", + "America/Lima", + "America/Los_Angeles", + "America/Lower_Princes", + "America/Maceio", + "America/Managua", + "America/Manaus", + "America/Marigot", + "America/Martinique", + "America/Matamoros", + "America/Mazatlan", + "America/Menominee", + "America/Merida", + "America/Metlakatla", + "America/Mexico_City", + "America/Miquelon", + "America/Moncton", + "America/Monterrey", + "America/Montevideo", + "America/Montreal", + "America/Montserrat", + "America/Nassau", + "America/New_York", + "America/Nipigon", + "America/Nome", + "America/Noronha", + "America/North_Dakota/Beulah", + "America/North_Dakota/Center", + "America/North_Dakota/New_Salem", + "America/Nuuk", + "America/Ojinaga", + "America/Panama", + "America/Pangnirtung", + "America/Paramaribo", + "America/Phoenix", + "America/Port-au-Prince", + "America/Port_of_Spain", + "America/Porto_Acre", + "America/Porto_Velho", + "America/Puerto_Rico", + "America/Punta_Arenas", + "America/Rainy_River", + "America/Rankin_Inlet", + "America/Recife", + "America/Regina", + "America/Resolute", + "America/Rio_Branco", + "America/Santa_Isabel", + "America/Santarem", + "America/Santiago", + "America/Santo_Domingo", + "America/Sao_Paulo", + "America/Scoresbysund", + "America/Shiprock", + "America/Sitka", + "America/St_Barthelemy", + "America/St_Johns", + "America/St_Kitts", + "America/St_Lucia", + "America/St_Thomas", + "America/St_Vincent", + "America/Swift_Current", + "America/Tegucigalpa", + "America/Thule", + "America/Thunder_Bay", + "America/Tijuana", + "America/Toronto", + "America/Tortola", + "America/Vancouver", + "America/Virgin", + "America/Whitehorse", + "America/Winnipeg", + "America/Yakutat", + "America/Yellowknife", + "Antarctica/Casey", + "Antarctica/Davis", + "Antarctica/DumontDUrville", + "Antarctica/Macquarie", + "Antarctica/Mawson", + "Antarctica/McMurdo", + "Antarctica/Palmer", + "Antarctica/Rothera", + "Antarctica/Syowa", + "Antarctica/Troll", + "Antarctica/Vostok", + "Arctic/Longyearbyen", + "Asia/Aden", + "Asia/Almaty", + "Asia/Amman", + "Asia/Anadyr", + "Asia/Aqtau", + "Asia/Aqtobe", + "Asia/Ashgabat", + "Asia/Atyrau", + "Asia/Baghdad", + "Asia/Bahrain", + "Asia/Baku", + "Asia/Bangkok", + "Asia/Barnaul", + "Asia/Beirut", + "Asia/Bishkek", + "Asia/Brunei", + "Asia/Chita", + "Asia/Choibalsan", + "Asia/Chongqing", + "Asia/Colombo", + "Asia/Damascus", + "Asia/Dhaka", + "Asia/Dili", + "Asia/Dubai", + "Asia/Dushanbe", + "Asia/Famagusta", + "Asia/Gaza", + "Asia/Harbin", + "Asia/Hebron", + "Asia/Ho_Chi_Minh", + "Asia/Hong_Kong", + "Asia/Hovd", + "Asia/Irkutsk", + "Asia/Istanbul", + "Asia/Jakarta", + "Asia/Jayapura", + "Asia/Jerusalem", + "Asia/Kabul", + "Asia/Kamchatka", + "Asia/Karachi", + "Asia/Kashgar", + "Asia/Kathmandu", + "Asia/Khandyga", + "Asia/Kolkata", + "Asia/Krasnoyarsk", + "Asia/Kuala_Lumpur", + "Asia/Kuching", + "Asia/Kuwait", + "Asia/Macau", + "Asia/Magadan", + "Asia/Makassar", + "Asia/Manila", + "Asia/Muscat", + "Asia/Nicosia", + "Asia/Novokuznetsk", + "Asia/Novosibirsk", + "Asia/Omsk", + "Asia/Oral", + "Asia/Phnom_Penh", + "Asia/Pontianak", + "Asia/Pyongyang", + "Asia/Qatar", + "Asia/Qostanay", + "Asia/Qyzylorda", + "Asia/Riyadh", + "Asia/Sakhalin", + "Asia/Samarkand", + "Asia/Seoul", + "Asia/Shanghai", + "Asia/Singapore", + "Asia/Srednekolymsk", + "Asia/Taipei", + "Asia/Tashkent", + "Asia/Tbilisi", + "Asia/Tehran", + "Asia/Tel_Aviv", + "Asia/Thimphu", + "Asia/Tokyo", + "Asia/Tomsk", + "Asia/Ulaanbaatar", + "Asia/Urumqi", + "Asia/Ust-Nera", + "Asia/Vientiane", + "Asia/Vladivostok", + "Asia/Yakutsk", + "Asia/Yangon", + "Asia/Yekaterinburg", + "Asia/Yerevan", + "Atlantic/Azores", + "Atlantic/Bermuda", + "Atlantic/Canary", + "Atlantic/Cape_Verde", + "Atlantic/Faroe", + "Atlantic/Jan_Mayen", + "Atlantic/Madeira", + "Atlantic/Reykjavik", + "Atlantic/South_Georgia", + "Atlantic/St_Helena", + "Atlantic/Stanley", + "Australia/Adelaide", + "Australia/Brisbane", + "Australia/Broken_Hill", + "Australia/Canberra", + "Australia/Currie", + "Australia/Darwin", + "Australia/Eucla", + "Australia/Hobart", + "Australia/Lindeman", + "Australia/Lord_Howe", + "Australia/Melbourne", + "Australia/Perth", + "Australia/Sydney", + "Australia/Yancowinna", + "Etc/GMT", + "Etc/GMT+0", + "Etc/GMT+1", + "Etc/GMT+10", + "Etc/GMT+11", + "Etc/GMT+12", + "Etc/GMT+2", + "Etc/GMT+3", + "Etc/GMT+4", + "Etc/GMT+5", + "Etc/GMT+6", + "Etc/GMT+7", + "Etc/GMT+8", + "Etc/GMT+9", + "Etc/GMT-0", + "Etc/GMT-1", + "Etc/GMT-10", + "Etc/GMT-11", + "Etc/GMT-12", + "Etc/GMT-13", + "Etc/GMT-14", + "Etc/GMT-2", + "Etc/GMT-3", + "Etc/GMT-4", + "Etc/GMT-5", + "Etc/GMT-6", + "Etc/GMT-7", + "Etc/GMT-8", + "Etc/GMT-9", + "Etc/GMT0", + "Etc/Greenwich", + "Etc/UCT", + "Etc/UTC", + "Etc/Universal", + "Etc/Zulu", + "Europe/Amsterdam", + "Europe/Andorra", + "Europe/Astrakhan", + "Europe/Athens", + "Europe/Belfast", + "Europe/Belgrade", + "Europe/Berlin", + "Europe/Bratislava", + "Europe/Brussels", + "Europe/Bucharest", + "Europe/Budapest", + "Europe/Busingen", + "Europe/Chisinau", + "Europe/Copenhagen", + "Europe/Dublin", + "Europe/Gibraltar", + "Europe/Guernsey", + "Europe/Helsinki", + "Europe/Isle_of_Man", + "Europe/Istanbul", + "Europe/Jersey", + "Europe/Kaliningrad", + "Europe/Kirov", + "Europe/Kyiv", + "Europe/Lisbon", + "Europe/Ljubljana", + "Europe/London", + "Europe/Luxembourg", + "Europe/Madrid", + "Europe/Malta", + "Europe/Mariehamn", + "Europe/Minsk", + "Europe/Monaco", + "Europe/Moscow", + "Europe/Nicosia", + "Europe/Oslo", + "Europe/Paris", + "Europe/Podgorica", + "Europe/Prague", + "Europe/Riga", + "Europe/Rome", + "Europe/Samara", + "Europe/San_Marino", + "Europe/Sarajevo", + "Europe/Saratov", + "Europe/Simferopol", + "Europe/Skopje", + "Europe/Sofia", + "Europe/Stockholm", + "Europe/Tallinn", + "Europe/Tirane", + "Europe/Tiraspol", + "Europe/Ulyanovsk", + "Europe/Vaduz", + "Europe/Vatican", + "Europe/Vienna", + "Europe/Vilnius", + "Europe/Volgograd", + "Europe/Warsaw", + "Europe/Zagreb", + "Europe/Zurich", + "Factory", + "Indian/Antananarivo", + "Indian/Chagos", + "Indian/Christmas", + "Indian/Cocos", + "Indian/Comoro", + "Indian/Kerguelen", + "Indian/Mahe", + "Indian/Maldives", + "Indian/Mauritius", + "Indian/Mayotte", + "Indian/Reunion", + "Pacific/Apia", + "Pacific/Auckland", + "Pacific/Bougainville", + "Pacific/Chatham", + "Pacific/Chuuk", + "Pacific/Easter", + "Pacific/Efate", + "Pacific/Fakaofo", + "Pacific/Fiji", + "Pacific/Funafuti", + "Pacific/Galapagos", + "Pacific/Gambier", + "Pacific/Guadalcanal", + "Pacific/Guam", + "Pacific/Honolulu", + "Pacific/Johnston", + "Pacific/Kanton", + "Pacific/Kiritimati", + "Pacific/Kosrae", + "Pacific/Kwajalein", + "Pacific/Majuro", + "Pacific/Marquesas", + "Pacific/Midway", + "Pacific/Nauru", + "Pacific/Niue", + "Pacific/Norfolk", + "Pacific/Noumea", + "Pacific/Pago_Pago", + "Pacific/Palau", + "Pacific/Pitcairn", + "Pacific/Pohnpei", + "Pacific/Port_Moresby", + "Pacific/Rarotonga", + "Pacific/Saipan", + "Pacific/Samoa", + "Pacific/Tahiti", + "Pacific/Tarawa", + "Pacific/Tongatapu", + "Pacific/Wake", + "Pacific/Wallis", + "Pacific/Yap", + "UTC", + nullptr}; + +} // namespace cctz +} // namespace time_internal +ABSL_NAMESPACE_END +} // namespace absl
diff --git a/absl/time/internal/cctz/src/test_time_zone_names.h b/absl/time/internal/cctz/src/test_time_zone_names.h new file mode 100644 index 0000000..1993994 --- /dev/null +++ b/absl/time/internal/cctz/src/test_time_zone_names.h
@@ -0,0 +1,33 @@ +// Copyright 2025 Google Inc. All Rights Reserved. +// +// 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_TIME_INTERNAL_CCTZ_TEST_TIME_ZONE_NAMES_H_ +#define ABSL_TIME_INTERNAL_CCTZ_TEST_TIME_ZONE_NAMES_H_ + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace time_internal { +namespace cctz { + +// A list of known time-zone names. +extern const char* const kTimeZoneNames[]; + +} // namespace cctz +} // namespace time_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_TIME_INTERNAL_CCTZ_TEST_TIME_ZONE_NAMES_H_
diff --git a/absl/time/internal/cctz/src/time_zone_format.cc b/absl/time/internal/cctz/src/time_zone_format.cc index 0e5f32f..5b80c80 100644 --- a/absl/time/internal/cctz/src/time_zone_format.cc +++ b/absl/time/internal/cctz/src/time_zone_format.cc
@@ -13,12 +13,14 @@ // limitations under the License. #if !defined(HAS_STRPTIME) -#if !defined(_MSC_VER) && !defined(__MINGW32__) && !defined(__VXWORKS__) +#if defined(_MSC_VER) || defined(__MINGW32__) || defined(__VXWORKS__) +#define HAS_STRPTIME 0 +#else #define HAS_STRPTIME 1 // Assume everyone else has strptime(). #endif #endif -#if defined(HAS_STRPTIME) && HAS_STRPTIME +#if HAS_STRPTIME #if !defined(_XOPEN_SOURCE) && !defined(__FreeBSD__) && !defined(__OpenBSD__) #define _XOPEN_SOURCE 500 // Exposes definitions for SUSv2 (UNIX 98). #endif @@ -117,7 +119,7 @@ tm.tm_mday = al.cs.day(); tm.tm_mon = al.cs.month() - 1; - // Saturate tm.tm_year is cases of over/underflow. + // Saturate tm.tm_year in cases of over/underflow. if (al.cs.year() < std::numeric_limits<int>::min() + 1900) { tm.tm_year = std::numeric_limits<int>::min(); } else if (al.cs.year() - 1900 > std::numeric_limits<int>::max()) { @@ -338,7 +340,7 @@ const std::tm tm = ToTM(al); // Scratch buffer for internal conversions. - char buf[3 + kDigits10_64]; // enough for longest conversion + char buf[6 + (kDigits10_64 + 2)]; // enough for longest conversion %F char* const ep = buf + sizeof(buf); char* bp; // works back from ep @@ -382,7 +384,7 @@ if (cur == end || (cur - percent) % 2 == 0) continue; // Simple specifiers that we handle ourselves. - if (strchr("YmdeUuWwHMSzZs%", *cur)) { + if (strchr("YmdeFUuWwHMSTzZs%", *cur)) { if (cur - 1 != pending) { FormatTM(&result, std::string(pending, cur - 1), tm); } @@ -403,6 +405,14 @@ if (*cur == 'e' && *bp == '0') *bp = ' '; // for Windows result.append(bp, static_cast<std::size_t>(ep - bp)); break; + case 'F': + bp = Format02d(ep, al.cs.day()); + *--bp = '-'; + bp = Format02d(bp, al.cs.month()); + *--bp = '-'; + bp = Format64(bp, 0, al.cs.year()); + result.append(bp, static_cast<std::size_t>(ep - bp)); + break; case 'U': bp = Format02d(ep, ToWeek(civil_day(al.cs), weekday::sunday)); result.append(bp, static_cast<std::size_t>(ep - bp)); @@ -431,6 +441,14 @@ bp = Format02d(ep, al.cs.second()); result.append(bp, static_cast<std::size_t>(ep - bp)); break; + case 'T': + bp = Format02d(ep, al.cs.second()); + *--bp = ':'; + bp = Format02d(bp, al.cs.minute()); + *--bp = ':'; + bp = Format02d(bp, al.cs.hour()); + result.append(bp, static_cast<std::size_t>(ep - bp)); + break; case 'z': bp = FormatOffset(ep, al.offset, ""); result.append(bp, static_cast<std::size_t>(ep - bp)); @@ -769,6 +787,20 @@ data = ParseInt(data, 2, 1, 31, &tm.tm_mday); week_num = -1; continue; + case 'F': + data = ParseInt(data, 0, kyearmin, kyearmax, &year); + if (data != nullptr) { + saw_year = true; + data = (*data == '-' ? data + 1 : nullptr); + } + data = ParseInt(data, 2, 1, 12, &tm.tm_mon); + if (data != nullptr) { + tm.tm_mon -= 1; + data = (*data == '-' ? data + 1 : nullptr); + } + data = ParseInt(data, 2, 1, 31, &tm.tm_mday); + week_num = -1; + continue; case 'U': data = ParseInt(data, 0, 0, 53, &week_num); week_start = weekday::sunday; @@ -794,13 +826,20 @@ case 'S': data = ParseInt(data, 2, 0, 60, &tm.tm_sec); continue; + case 'T': + data = ParseInt(data, 2, 0, 23, &tm.tm_hour); + twelve_hour = false; + data = (data != nullptr && *data == ':' ? data + 1 : nullptr); + data = ParseInt(data, 2, 0, 59, &tm.tm_min); + data = (data != nullptr && *data == ':' ? data + 1 : nullptr); + data = ParseInt(data, 2, 0, 60, &tm.tm_sec); + continue; case 'I': case 'l': case 'r': // probably uses %I twelve_hour = true; break; case 'R': // uses %H - case 'T': // uses %H case 'c': // probably uses %H case 'X': // probably uses %H twelve_hour = false;
diff --git a/absl/time/internal/cctz/src/time_zone_format_test.cc b/absl/time/internal/cctz/src/time_zone_format_test.cc index 4a6c71f..a270f4d 100644 --- a/absl/time/internal/cctz/src/time_zone_format_test.cc +++ b/absl/time/internal/cctz/src/time_zone_format_test.cc
@@ -169,23 +169,22 @@ TEST(Format, PosixConversions) { const time_zone tz = utc_time_zone(); - auto tp = chrono::system_clock::from_time_t(0); + auto tp = + chrono::system_clock::from_time_t(308189482); // 1979-10-08T00:11:22Z - TestFormatSpecifier(tp, tz, "%d", "01"); - TestFormatSpecifier(tp, tz, "%e", " 1"); // extension but internal support + TestFormatSpecifier(tp, tz, "%d", "08"); + TestFormatSpecifier(tp, tz, "%e", " 8"); // extension but internal support TestFormatSpecifier(tp, tz, "%H", "00"); TestFormatSpecifier(tp, tz, "%I", "12"); - TestFormatSpecifier(tp, tz, "%j", "001"); - TestFormatSpecifier(tp, tz, "%m", "01"); - TestFormatSpecifier(tp, tz, "%M", "00"); - TestFormatSpecifier(tp, tz, "%S", "00"); - TestFormatSpecifier(tp, tz, "%U", "00"); -#if !defined(__EMSCRIPTEN__) - TestFormatSpecifier(tp, tz, "%w", "4"); // 4=Thursday -#endif - TestFormatSpecifier(tp, tz, "%W", "00"); - TestFormatSpecifier(tp, tz, "%y", "70"); - TestFormatSpecifier(tp, tz, "%Y", "1970"); + TestFormatSpecifier(tp, tz, "%j", "281"); + TestFormatSpecifier(tp, tz, "%m", "10"); + TestFormatSpecifier(tp, tz, "%M", "11"); + TestFormatSpecifier(tp, tz, "%S", "22"); + TestFormatSpecifier(tp, tz, "%U", "40"); + TestFormatSpecifier(tp, tz, "%w", "1"); // 1=Monday + TestFormatSpecifier(tp, tz, "%W", "41"); + TestFormatSpecifier(tp, tz, "%y", "79"); + TestFormatSpecifier(tp, tz, "%Y", "1979"); TestFormatSpecifier(tp, tz, "%z", "+0000"); TestFormatSpecifier(tp, tz, "%Z", "UTC"); TestFormatSpecifier(tp, tz, "%%", "%"); @@ -193,21 +192,21 @@ #if defined(__linux__) // SU/C99/TZ extensions TestFormatSpecifier(tp, tz, "%C", "19"); - TestFormatSpecifier(tp, tz, "%D", "01/01/70"); - TestFormatSpecifier(tp, tz, "%F", "1970-01-01"); - TestFormatSpecifier(tp, tz, "%g", "70"); - TestFormatSpecifier(tp, tz, "%G", "1970"); + TestFormatSpecifier(tp, tz, "%D", "10/08/79"); + TestFormatSpecifier(tp, tz, "%F", "1979-10-08"); + TestFormatSpecifier(tp, tz, "%g", "79"); + TestFormatSpecifier(tp, tz, "%G", "1979"); #if defined(__GLIBC__) TestFormatSpecifier(tp, tz, "%k", " 0"); TestFormatSpecifier(tp, tz, "%l", "12"); #endif TestFormatSpecifier(tp, tz, "%n", "\n"); - TestFormatSpecifier(tp, tz, "%R", "00:00"); + TestFormatSpecifier(tp, tz, "%R", "00:11"); TestFormatSpecifier(tp, tz, "%t", "\t"); - TestFormatSpecifier(tp, tz, "%T", "00:00:00"); - TestFormatSpecifier(tp, tz, "%u", "4"); // 4=Thursday - TestFormatSpecifier(tp, tz, "%V", "01"); - TestFormatSpecifier(tp, tz, "%s", "0"); + TestFormatSpecifier(tp, tz, "%T", "00:11:22"); + TestFormatSpecifier(tp, tz, "%u", "1"); // 1=Monday + TestFormatSpecifier(tp, tz, "%V", "41"); + TestFormatSpecifier(tp, tz, "%s", "308189482"); #endif }
diff --git a/absl/time/internal/cctz/src/time_zone_lookup.cc b/absl/time/internal/cctz/src/time_zone_lookup.cc index 90b2972..d1078de 100644 --- a/absl/time/internal/cctz/src/time_zone_lookup.cc +++ b/absl/time/internal/cctz/src/time_zone_lookup.cc
@@ -32,31 +32,6 @@ #include <zircon/types.h> #endif -#if defined(_WIN32) -// Include only when <icu.h> is available. -// https://learn.microsoft.com/en-us/windows/win32/intl/international-components-for-unicode--icu- -// https://devblogs.microsoft.com/oldnewthing/20210527-00/?p=105255 -#if defined(__has_include) -#if __has_include(<icu.h>) -#define USE_WIN32_LOCAL_TIME_ZONE -#include <windows.h> -#pragma push_macro("_WIN32_WINNT") -#pragma push_macro("NTDDI_VERSION") -// Minimum _WIN32_WINNT and NTDDI_VERSION to use ucal_getTimeZoneIDForWindowsID -#undef _WIN32_WINNT -#define _WIN32_WINNT 0x0A00 // == _WIN32_WINNT_WIN10 -#undef NTDDI_VERSION -#define NTDDI_VERSION 0x0A000004 // == NTDDI_WIN10_RS3 -#include <icu.h> -#pragma pop_macro("NTDDI_VERSION") -#pragma pop_macro("_WIN32_WINNT") -#include <timezoneapi.h> - -#include <atomic> -#endif // __has_include(<icu.h>) -#endif // __has_include -#endif // _WIN32 - #include <array> #include <cstdint> #include <cstdlib> @@ -66,87 +41,15 @@ #include "absl/time/internal/cctz/src/time_zone_fixed.h" #include "absl/time/internal/cctz/src/time_zone_impl.h" +#if defined(_WIN32) +#include "absl/time/internal/cctz/src/time_zone_name_win.h" +#endif // _WIN32 + namespace absl { ABSL_NAMESPACE_BEGIN namespace time_internal { namespace cctz { -namespace { -#if defined(USE_WIN32_LOCAL_TIME_ZONE) -// True if we have already failed to load the API. -static std::atomic_bool g_ucal_getTimeZoneIDForWindowsIDUnavailable; -static std::atomic<decltype(ucal_getTimeZoneIDForWindowsID)*> - g_ucal_getTimeZoneIDForWindowsIDRef; - -std::string win32_local_time_zone() { - // If we have already failed to load the API, then just give up. - if (g_ucal_getTimeZoneIDForWindowsIDUnavailable.load()) { - return ""; - } - - auto ucal_getTimeZoneIDForWindowsIDFunc = - g_ucal_getTimeZoneIDForWindowsIDRef.load(); - if (ucal_getTimeZoneIDForWindowsIDFunc == nullptr) { - // If we have already failed to load the API, then just give up. - if (g_ucal_getTimeZoneIDForWindowsIDUnavailable.load()) { - return ""; - } - - const HMODULE icudll = - ::LoadLibraryExW(L"icu.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32); - - if (icudll == nullptr) { - g_ucal_getTimeZoneIDForWindowsIDUnavailable.store(true); - return ""; - } - - ucal_getTimeZoneIDForWindowsIDFunc = - reinterpret_cast<decltype(ucal_getTimeZoneIDForWindowsID)*>( - ::GetProcAddress(icudll, "ucal_getTimeZoneIDForWindowsID")); - - if (ucal_getTimeZoneIDForWindowsIDFunc == nullptr) { - g_ucal_getTimeZoneIDForWindowsIDUnavailable.store(true); - return ""; - } - // store-race is not a problem here, because ::GetProcAddress() returns the - // same address for the same function in the same DLL. - g_ucal_getTimeZoneIDForWindowsIDRef.store( - ucal_getTimeZoneIDForWindowsIDFunc); - - // We intentionally do not call ::FreeLibrary() here to avoid frequent DLL - // loadings and unloading. As "icu.dll" is a system library, keeping it on - // memory is supposed to have no major drawback. - } - - DYNAMIC_TIME_ZONE_INFORMATION info = {}; - if (::GetDynamicTimeZoneInformation(&info) == TIME_ZONE_ID_INVALID) { - return ""; - } - - std::array<UChar, 128> buffer; - UErrorCode status = U_ZERO_ERROR; - const auto num_chars_in_buffer = ucal_getTimeZoneIDForWindowsIDFunc( - reinterpret_cast<const UChar*>(info.TimeZoneKeyName), -1, nullptr, - buffer.data(), static_cast<int32_t>(buffer.size()), &status); - if (status != U_ZERO_ERROR || num_chars_in_buffer <= 0 || - num_chars_in_buffer > static_cast<int32_t>(buffer.size())) { - return ""; - } - - const int num_bytes_in_utf8 = ::WideCharToMultiByte( - CP_UTF8, 0, reinterpret_cast<const wchar_t*>(buffer.data()), - static_cast<int>(num_chars_in_buffer), nullptr, 0, nullptr, nullptr); - std::string local_time_str; - local_time_str.resize(static_cast<size_t>(num_bytes_in_utf8)); - ::WideCharToMultiByte( - CP_UTF8, 0, reinterpret_cast<const wchar_t*>(buffer.data()), - static_cast<int>(num_chars_in_buffer), &local_time_str[0], - num_bytes_in_utf8, nullptr, nullptr); - return local_time_str; -} -#endif // USE_WIN32_LOCAL_TIME_ZONE -} // namespace - std::string time_zone::name() const { return effective_impl().Name(); } time_zone::absolute_lookup time_zone::lookup( @@ -261,8 +164,8 @@ zone = primary_tz.c_str(); } #endif -#if defined(USE_WIN32_LOCAL_TIME_ZONE) - std::string win32_tz = win32_local_time_zone(); +#if defined(_WIN32) + std::string win32_tz = GetWindowsLocalTimeZone(); if (!win32_tz.empty()) { zone = win32_tz.c_str(); }
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 e1bea28..cd08a35 100644 --- a/absl/time/internal/cctz/src/time_zone_lookup_test.cc +++ b/absl/time/internal/cctz/src/time_zone_lookup_test.cc
@@ -29,6 +29,7 @@ #include "gtest/gtest.h" #include "absl/time/internal/cctz/include/cctz/civil_time.h" +#include "absl/time/internal/cctz/src/test_time_zone_names.h" namespace chrono = std::chrono; @@ -39,494 +40,6 @@ namespace { -// A list of known time-zone names. -const char* const kTimeZoneNames[] = {"Africa/Abidjan", - "Africa/Accra", - "Africa/Addis_Ababa", - "Africa/Algiers", - "Africa/Asmara", - "Africa/Bamako", - "Africa/Bangui", - "Africa/Banjul", - "Africa/Bissau", - "Africa/Blantyre", - "Africa/Brazzaville", - "Africa/Bujumbura", - "Africa/Cairo", - "Africa/Casablanca", - "Africa/Ceuta", - "Africa/Conakry", - "Africa/Dakar", - "Africa/Dar_es_Salaam", - "Africa/Djibouti", - "Africa/Douala", - "Africa/El_Aaiun", - "Africa/Freetown", - "Africa/Gaborone", - "Africa/Harare", - "Africa/Johannesburg", - "Africa/Juba", - "Africa/Kampala", - "Africa/Khartoum", - "Africa/Kigali", - "Africa/Kinshasa", - "Africa/Lagos", - "Africa/Libreville", - "Africa/Lome", - "Africa/Luanda", - "Africa/Lubumbashi", - "Africa/Lusaka", - "Africa/Malabo", - "Africa/Maputo", - "Africa/Maseru", - "Africa/Mbabane", - "Africa/Mogadishu", - "Africa/Monrovia", - "Africa/Nairobi", - "Africa/Ndjamena", - "Africa/Niamey", - "Africa/Nouakchott", - "Africa/Ouagadougou", - "Africa/Porto-Novo", - "Africa/Sao_Tome", - "Africa/Timbuktu", - "Africa/Tripoli", - "Africa/Tunis", - "Africa/Windhoek", - "America/Adak", - "America/Anchorage", - "America/Anguilla", - "America/Antigua", - "America/Araguaina", - "America/Argentina/Buenos_Aires", - "America/Argentina/Catamarca", - "America/Argentina/Cordoba", - "America/Argentina/Jujuy", - "America/Argentina/La_Rioja", - "America/Argentina/Mendoza", - "America/Argentina/Rio_Gallegos", - "America/Argentina/Salta", - "America/Argentina/San_Juan", - "America/Argentina/San_Luis", - "America/Argentina/Tucuman", - "America/Argentina/Ushuaia", - "America/Aruba", - "America/Asuncion", - "America/Atikokan", - "America/Atka", - "America/Bahia", - "America/Bahia_Banderas", - "America/Barbados", - "America/Belem", - "America/Belize", - "America/Blanc-Sablon", - "America/Boa_Vista", - "America/Bogota", - "America/Boise", - "America/Cambridge_Bay", - "America/Campo_Grande", - "America/Cancun", - "America/Caracas", - "America/Cayenne", - "America/Cayman", - "America/Chicago", - "America/Chihuahua", - "America/Ciudad_Juarez", - "America/Coral_Harbour", - "America/Costa_Rica", - "America/Coyhaique", - "America/Creston", - "America/Cuiaba", - "America/Curacao", - "America/Danmarkshavn", - "America/Dawson", - "America/Dawson_Creek", - "America/Denver", - "America/Detroit", - "America/Dominica", - "America/Edmonton", - "America/Eirunepe", - "America/El_Salvador", - "America/Ensenada", - "America/Fort_Nelson", - "America/Fortaleza", - "America/Glace_Bay", - "America/Goose_Bay", - "America/Grand_Turk", - "America/Grenada", - "America/Guadeloupe", - "America/Guatemala", - "America/Guayaquil", - "America/Guyana", - "America/Halifax", - "America/Havana", - "America/Hermosillo", - "America/Indiana/Indianapolis", - "America/Indiana/Knox", - "America/Indiana/Marengo", - "America/Indiana/Petersburg", - "America/Indiana/Tell_City", - "America/Indiana/Vevay", - "America/Indiana/Vincennes", - "America/Indiana/Winamac", - "America/Inuvik", - "America/Iqaluit", - "America/Jamaica", - "America/Juneau", - "America/Kentucky/Louisville", - "America/Kentucky/Monticello", - "America/Kralendijk", - "America/La_Paz", - "America/Lima", - "America/Los_Angeles", - "America/Lower_Princes", - "America/Maceio", - "America/Managua", - "America/Manaus", - "America/Marigot", - "America/Martinique", - "America/Matamoros", - "America/Mazatlan", - "America/Menominee", - "America/Merida", - "America/Metlakatla", - "America/Mexico_City", - "America/Miquelon", - "America/Moncton", - "America/Monterrey", - "America/Montevideo", - "America/Montreal", - "America/Montserrat", - "America/Nassau", - "America/New_York", - "America/Nipigon", - "America/Nome", - "America/Noronha", - "America/North_Dakota/Beulah", - "America/North_Dakota/Center", - "America/North_Dakota/New_Salem", - "America/Nuuk", - "America/Ojinaga", - "America/Panama", - "America/Pangnirtung", - "America/Paramaribo", - "America/Phoenix", - "America/Port-au-Prince", - "America/Port_of_Spain", - "America/Porto_Acre", - "America/Porto_Velho", - "America/Puerto_Rico", - "America/Punta_Arenas", - "America/Rainy_River", - "America/Rankin_Inlet", - "America/Recife", - "America/Regina", - "America/Resolute", - "America/Rio_Branco", - "America/Santa_Isabel", - "America/Santarem", - "America/Santiago", - "America/Santo_Domingo", - "America/Sao_Paulo", - "America/Scoresbysund", - "America/Shiprock", - "America/Sitka", - "America/St_Barthelemy", - "America/St_Johns", - "America/St_Kitts", - "America/St_Lucia", - "America/St_Thomas", - "America/St_Vincent", - "America/Swift_Current", - "America/Tegucigalpa", - "America/Thule", - "America/Thunder_Bay", - "America/Tijuana", - "America/Toronto", - "America/Tortola", - "America/Vancouver", - "America/Virgin", - "America/Whitehorse", - "America/Winnipeg", - "America/Yakutat", - "America/Yellowknife", - "Antarctica/Casey", - "Antarctica/Davis", - "Antarctica/DumontDUrville", - "Antarctica/Macquarie", - "Antarctica/Mawson", - "Antarctica/McMurdo", - "Antarctica/Palmer", - "Antarctica/Rothera", - "Antarctica/Syowa", - "Antarctica/Troll", - "Antarctica/Vostok", - "Arctic/Longyearbyen", - "Asia/Aden", - "Asia/Almaty", - "Asia/Amman", - "Asia/Anadyr", - "Asia/Aqtau", - "Asia/Aqtobe", - "Asia/Ashgabat", - "Asia/Atyrau", - "Asia/Baghdad", - "Asia/Bahrain", - "Asia/Baku", - "Asia/Bangkok", - "Asia/Barnaul", - "Asia/Beirut", - "Asia/Bishkek", - "Asia/Brunei", - "Asia/Chita", - "Asia/Choibalsan", - "Asia/Chongqing", - "Asia/Colombo", - "Asia/Damascus", - "Asia/Dhaka", - "Asia/Dili", - "Asia/Dubai", - "Asia/Dushanbe", - "Asia/Famagusta", - "Asia/Gaza", - "Asia/Harbin", - "Asia/Hebron", - "Asia/Ho_Chi_Minh", - "Asia/Hong_Kong", - "Asia/Hovd", - "Asia/Irkutsk", - "Asia/Istanbul", - "Asia/Jakarta", - "Asia/Jayapura", - "Asia/Jerusalem", - "Asia/Kabul", - "Asia/Kamchatka", - "Asia/Karachi", - "Asia/Kashgar", - "Asia/Kathmandu", - "Asia/Khandyga", - "Asia/Kolkata", - "Asia/Krasnoyarsk", - "Asia/Kuala_Lumpur", - "Asia/Kuching", - "Asia/Kuwait", - "Asia/Macau", - "Asia/Magadan", - "Asia/Makassar", - "Asia/Manila", - "Asia/Muscat", - "Asia/Nicosia", - "Asia/Novokuznetsk", - "Asia/Novosibirsk", - "Asia/Omsk", - "Asia/Oral", - "Asia/Phnom_Penh", - "Asia/Pontianak", - "Asia/Pyongyang", - "Asia/Qatar", - "Asia/Qostanay", - "Asia/Qyzylorda", - "Asia/Riyadh", - "Asia/Sakhalin", - "Asia/Samarkand", - "Asia/Seoul", - "Asia/Shanghai", - "Asia/Singapore", - "Asia/Srednekolymsk", - "Asia/Taipei", - "Asia/Tashkent", - "Asia/Tbilisi", - "Asia/Tehran", - "Asia/Tel_Aviv", - "Asia/Thimphu", - "Asia/Tokyo", - "Asia/Tomsk", - "Asia/Ulaanbaatar", - "Asia/Urumqi", - "Asia/Ust-Nera", - "Asia/Vientiane", - "Asia/Vladivostok", - "Asia/Yakutsk", - "Asia/Yangon", - "Asia/Yekaterinburg", - "Asia/Yerevan", - "Atlantic/Azores", - "Atlantic/Bermuda", - "Atlantic/Canary", - "Atlantic/Cape_Verde", - "Atlantic/Faroe", - "Atlantic/Jan_Mayen", - "Atlantic/Madeira", - "Atlantic/Reykjavik", - "Atlantic/South_Georgia", - "Atlantic/St_Helena", - "Atlantic/Stanley", - "Australia/Adelaide", - "Australia/Brisbane", - "Australia/Broken_Hill", - "Australia/Canberra", - "Australia/Currie", - "Australia/Darwin", - "Australia/Eucla", - "Australia/Hobart", - "Australia/Lindeman", - "Australia/Lord_Howe", - "Australia/Melbourne", - "Australia/Perth", - "Australia/Sydney", - "Australia/Yancowinna", - "Etc/GMT", - "Etc/GMT+0", - "Etc/GMT+1", - "Etc/GMT+10", - "Etc/GMT+11", - "Etc/GMT+12", - "Etc/GMT+2", - "Etc/GMT+3", - "Etc/GMT+4", - "Etc/GMT+5", - "Etc/GMT+6", - "Etc/GMT+7", - "Etc/GMT+8", - "Etc/GMT+9", - "Etc/GMT-0", - "Etc/GMT-1", - "Etc/GMT-10", - "Etc/GMT-11", - "Etc/GMT-12", - "Etc/GMT-13", - "Etc/GMT-14", - "Etc/GMT-2", - "Etc/GMT-3", - "Etc/GMT-4", - "Etc/GMT-5", - "Etc/GMT-6", - "Etc/GMT-7", - "Etc/GMT-8", - "Etc/GMT-9", - "Etc/GMT0", - "Etc/Greenwich", - "Etc/UCT", - "Etc/UTC", - "Etc/Universal", - "Etc/Zulu", - "Europe/Amsterdam", - "Europe/Andorra", - "Europe/Astrakhan", - "Europe/Athens", - "Europe/Belfast", - "Europe/Belgrade", - "Europe/Berlin", - "Europe/Bratislava", - "Europe/Brussels", - "Europe/Bucharest", - "Europe/Budapest", - "Europe/Busingen", - "Europe/Chisinau", - "Europe/Copenhagen", - "Europe/Dublin", - "Europe/Gibraltar", - "Europe/Guernsey", - "Europe/Helsinki", - "Europe/Isle_of_Man", - "Europe/Istanbul", - "Europe/Jersey", - "Europe/Kaliningrad", - "Europe/Kirov", - "Europe/Kyiv", - "Europe/Lisbon", - "Europe/Ljubljana", - "Europe/London", - "Europe/Luxembourg", - "Europe/Madrid", - "Europe/Malta", - "Europe/Mariehamn", - "Europe/Minsk", - "Europe/Monaco", - "Europe/Moscow", - "Europe/Nicosia", - "Europe/Oslo", - "Europe/Paris", - "Europe/Podgorica", - "Europe/Prague", - "Europe/Riga", - "Europe/Rome", - "Europe/Samara", - "Europe/San_Marino", - "Europe/Sarajevo", - "Europe/Saratov", - "Europe/Simferopol", - "Europe/Skopje", - "Europe/Sofia", - "Europe/Stockholm", - "Europe/Tallinn", - "Europe/Tirane", - "Europe/Tiraspol", - "Europe/Ulyanovsk", - "Europe/Vaduz", - "Europe/Vatican", - "Europe/Vienna", - "Europe/Vilnius", - "Europe/Volgograd", - "Europe/Warsaw", - "Europe/Zagreb", - "Europe/Zurich", - "Factory", - "Indian/Antananarivo", - "Indian/Chagos", - "Indian/Christmas", - "Indian/Cocos", - "Indian/Comoro", - "Indian/Kerguelen", - "Indian/Mahe", - "Indian/Maldives", - "Indian/Mauritius", - "Indian/Mayotte", - "Indian/Reunion", - "Pacific/Apia", - "Pacific/Auckland", - "Pacific/Bougainville", - "Pacific/Chatham", - "Pacific/Chuuk", - "Pacific/Easter", - "Pacific/Efate", - "Pacific/Fakaofo", - "Pacific/Fiji", - "Pacific/Funafuti", - "Pacific/Galapagos", - "Pacific/Gambier", - "Pacific/Guadalcanal", - "Pacific/Guam", - "Pacific/Honolulu", - "Pacific/Johnston", - "Pacific/Kanton", - "Pacific/Kiritimati", - "Pacific/Kosrae", - "Pacific/Kwajalein", - "Pacific/Majuro", - "Pacific/Marquesas", - "Pacific/Midway", - "Pacific/Nauru", - "Pacific/Niue", - "Pacific/Norfolk", - "Pacific/Noumea", - "Pacific/Pago_Pago", - "Pacific/Palau", - "Pacific/Pitcairn", - "Pacific/Pohnpei", - "Pacific/Port_Moresby", - "Pacific/Rarotonga", - "Pacific/Saipan", - "Pacific/Samoa", - "Pacific/Tahiti", - "Pacific/Tarawa", - "Pacific/Tongatapu", - "Pacific/Wake", - "Pacific/Wallis", - "Pacific/Yap", - "UTC", - nullptr}; - // Helper to return a loaded time zone by value (UTC on error). time_zone LoadZone(const std::string& name) { time_zone tz;
diff --git a/absl/time/internal/cctz/src/time_zone_name_win.cc b/absl/time/internal/cctz/src/time_zone_name_win.cc new file mode 100644 index 0000000..db5cd28 --- /dev/null +++ b/absl/time/internal/cctz/src/time_zone_name_win.cc
@@ -0,0 +1,192 @@ +// Copyright 2025 Google Inc. All Rights Reserved. +// +// 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. + +// Android local modification: ifdef out entire file to avoid complicating +// the build system. +#if defined(_WIN32) + +#include "absl/time/internal/cctz/src/time_zone_name_win.h" + +#include "absl/base/config.h" + +#if !defined(NOMINMAX) +#define NOMINMAX +#endif // !defined(NOMINMAX) +#include <windows.h> + +#include <algorithm> +#include <atomic> +#include <cstddef> +#include <cstdint> +#include <limits> +#include <string> +#include <type_traits> +#include <utility> + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace time_internal { +namespace cctz { +namespace { + +// Define UChar as wchar_t here because Win32 APIs receive UTF-16 strings as +// wchar_t* instead of char16_t*. Using char16_t would require additional casts. +using UChar = wchar_t; + +enum UErrorCode : std::int32_t { + U_ZERO_ERROR = 0, + U_BUFFER_OVERFLOW_ERROR = 15, +}; + +bool U_SUCCESS(UErrorCode error) { return error <= U_ZERO_ERROR; } + +using ucal_getTimeZoneIDForWindowsID_func = std::int32_t(__cdecl*)( + const UChar* winid, std::int32_t len, const char* region, UChar* id, + std::int32_t id_capacity, UErrorCode* status); + +std::atomic<bool> g_unavailable; +std::atomic<ucal_getTimeZoneIDForWindowsID_func> + g_ucal_getTimeZoneIDForWindowsID; + +template <typename T> +static T AsProcAddress(HMODULE module, const char* name) { + static_assert( + std::is_pointer<T>::value && + std::is_function<typename std::remove_pointer<T>::type>::value, + "T must be a function pointer type"); + const auto proc_address = ::GetProcAddress(module, name); + return reinterpret_cast<T>(reinterpret_cast<void*>(proc_address)); +} + +std::wstring GetSystem32Dir() { + std::wstring result; + std::uint32_t len = std::max<std::uint32_t>( + static_cast<std::uint32_t>(std::min<size_t>( + result.capacity(), std::numeric_limits<std::uint32_t>::max())), + 1); + do { + result.resize(len); + len = ::GetSystemDirectoryW(&result[0], len); + } while (len > result.size()); + result.resize(len); + return result; +} + +ucal_getTimeZoneIDForWindowsID_func LoadIcuGetTimeZoneIDForWindowsID() { + // This function is intended to be lock free to avoid potential deadlocks + // with loader-lock taken inside LoadLibraryW. As LoadLibraryW and + // GetProcAddress are idempotent unless the DLL is unloaded, we just need to + // make sure global variables are read/written atomically, where + // memory_order_relaxed is also acceptable. + + if (g_unavailable.load(std::memory_order_relaxed)) { + return nullptr; + } + + { + const auto ucal_getTimeZoneIDForWindowsIDRef = + g_ucal_getTimeZoneIDForWindowsID.load(std::memory_order_relaxed); + if (ucal_getTimeZoneIDForWindowsIDRef != nullptr) { + return ucal_getTimeZoneIDForWindowsIDRef; + } + } + + const std::wstring system32_dir = GetSystem32Dir(); + if (system32_dir.empty()) { + g_unavailable.store(true, std::memory_order_relaxed); + return nullptr; + } + + // Here LoadLibraryExW(L"icu.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32) does + // not work if "icu.dll" is already loaded from somewhere other than the + // system32 directory. Specifying the full path with LoadLibraryW is more + // reliable. + const std::wstring icu_dll_path = system32_dir + L"\\icu.dll"; + const HMODULE icu_dll = ::LoadLibraryW(icu_dll_path.c_str()); + if (icu_dll == nullptr) { + g_unavailable.store(true, std::memory_order_relaxed); + return nullptr; + } + + const auto ucal_getTimeZoneIDForWindowsIDRef = + AsProcAddress<ucal_getTimeZoneIDForWindowsID_func>( + icu_dll, "ucal_getTimeZoneIDForWindowsID"); + if (ucal_getTimeZoneIDForWindowsIDRef != nullptr) { + g_unavailable.store(true, std::memory_order_relaxed); + return nullptr; + } + + g_ucal_getTimeZoneIDForWindowsID.store(ucal_getTimeZoneIDForWindowsIDRef, + std::memory_order_relaxed); + + return ucal_getTimeZoneIDForWindowsIDRef; +} + +// Convert wchar_t array (UTF-16) to UTF-8 string +std::string Utf16ToUtf8(const wchar_t* ptr, size_t size) { + if (size > std::numeric_limits<int>::max()) { + return std::string(); + } + const int chars_len = static_cast<int>(size); + std::string result; + std::size_t len = std::max<std::size_t>( + std::min<size_t>(result.capacity(), std::numeric_limits<int>::max()), 1); + do { + result.resize(len); + // TODO: Switch to std::string::data() when we require C++17 or higher. + len = static_cast<std::size_t>(::WideCharToMultiByte( + CP_UTF8, WC_ERR_INVALID_CHARS, ptr, chars_len, &result[0], + static_cast<int>(len), nullptr, nullptr)); + } while (len > result.size()); + result.resize(len); + return result; +} + +} // namespace + +std::string GetWindowsLocalTimeZone() { + const auto getTimeZoneIDForWindowsID = LoadIcuGetTimeZoneIDForWindowsID(); + if (getTimeZoneIDForWindowsID == nullptr) { + return std::string(); + } + + DYNAMIC_TIME_ZONE_INFORMATION info = {}; + if (::GetDynamicTimeZoneInformation(&info) == TIME_ZONE_ID_INVALID) { + return std::string(); + } + + std::wstring result; + std::size_t len = std::max<std::size_t>( + std::min<size_t>(result.capacity(), std::numeric_limits<int>::max()), 1); + for (;;) { + UErrorCode status = U_ZERO_ERROR; + result.resize(len); + len = static_cast<std::size_t>( + getTimeZoneIDForWindowsID(info.TimeZoneKeyName, -1, nullptr, &result[0], + static_cast<int>(len), &status)); + if (U_SUCCESS(status)) { + return Utf16ToUtf8(result.data(), len); + } + if (status != U_BUFFER_OVERFLOW_ERROR) { + return std::string(); + } + } +} + +} // namespace cctz +} // namespace time_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // _WIN32
diff --git a/absl/time/internal/cctz/src/time_zone_name_win.h b/absl/time/internal/cctz/src/time_zone_name_win.h new file mode 100644 index 0000000..f53b5a9 --- /dev/null +++ b/absl/time/internal/cctz/src/time_zone_name_win.h
@@ -0,0 +1,37 @@ +// Copyright 2025 Google Inc. All Rights Reserved. +// +// 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_TIME_INTERNAL_CCTZ_TIME_ZONE_NAME_WIN_H_ +#define ABSL_TIME_INTERNAL_CCTZ_TIME_ZONE_NAME_WIN_H_ + +#include <string> + +#include "absl/base/config.h" + +namespace absl { +ABSL_NAMESPACE_BEGIN +namespace time_internal { +namespace cctz { + +// Returns the local time zone ID in IANA format (e.g. "America/Los_Angeles"), +// or the empty string on failure. Not supported on Windows 10 1809 and earlier, +// where "icu.dll" is not available in the System32 directory. +std::string GetWindowsLocalTimeZone(); + +} // namespace cctz +} // namespace time_internal +ABSL_NAMESPACE_END +} // namespace absl + +#endif // ABSL_TIME_INTERNAL_CCTZ_TIME_ZONE_NAME_WIN_H_
diff --git a/absl/time/internal/cctz/testdata/version b/absl/time/internal/cctz/testdata/version index ef468ad..cb3be9a 100644 --- a/absl/time/internal/cctz/testdata/version +++ b/absl/time/internal/cctz/testdata/version
@@ -1 +1 @@ -2025b +2025c
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Ensenada b/absl/time/internal/cctz/testdata/zoneinfo/America/Ensenada index 18d0d14..0d8c993 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Ensenada +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Ensenada Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Santa_Isabel b/absl/time/internal/cctz/testdata/zoneinfo/America/Santa_Isabel index 18d0d14..0d8c993 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Santa_Isabel +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Santa_Isabel Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/America/Tijuana b/absl/time/internal/cctz/testdata/zoneinfo/America/Tijuana index 18d0d14..0d8c993 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/America/Tijuana +++ b/absl/time/internal/cctz/testdata/zoneinfo/America/Tijuana Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaNorte b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaNorte index 18d0d14..0d8c993 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaNorte +++ b/absl/time/internal/cctz/testdata/zoneinfo/Mexico/BajaNorte Binary files differ
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab b/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab index 402c015..4ae3523 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab +++ b/absl/time/internal/cctz/testdata/zoneinfo/iso3166.tab
@@ -3,22 +3,22 @@ # This file is in the public domain, so clarified as of # 2009-05-17 by Arthur David Olson. # -# From Paul Eggert (2023-09-06): +# From Paul Eggert (2025-07-01): # This file contains a table of two-letter country codes. Columns are -# separated by a single tab. Lines beginning with '#' are comments. +# 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/TC 46 N1108 (2023-04-05). See: ISO/TC 46 Documents +# ISO/TC 46 N1127 (2024-02-29). See: ISO/TC 46 Documents # https://www.iso.org/committee/48750.html?view=documents # 2. The usual English name for the coded region. This sometimes # departs from ISO-listed names, sometimes so that sorted subsets -# of names are useful (e.g., "Samoa (American)" and "Samoa -# (western)" rather than "American Samoa" and "Samoa"), +# of names are useful (e.g., “Samoa (American)” and “Samoa +# (western)” rather than “American Samoa” and “Samoa”), # sometimes to avoid confusion among non-experts (e.g., -# "Czech Republic" and "Turkey" rather than "Czechia" and "Türkiye"), -# and sometimes to omit needless detail or churn (e.g., "Netherlands" -# rather than "Netherlands (the)" or "Netherlands (Kingdom of the)"). +# “Czech Republic” and “Turkey” rather than “Czechia” and “Türkiye”), +# and sometimes to omit needless detail or churn (e.g., “Netherlands” +# rather than “Netherlands (the)” or “Netherlands (Kingdom of the)”). # # The table is sorted by country code. # @@ -71,7 +71,7 @@ CF Central African Rep. CG Congo (Rep.) CH Switzerland -CI Côte d'Ivoire +CI Côte d’Ivoire CK Cook Islands CL Chile CM Cameroon
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab b/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab index 36535bd..cd43e3d 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab +++ b/absl/time/internal/cctz/testdata/zoneinfo/zone1970.tab
@@ -2,15 +2,15 @@ # # This file is in the public domain. # -# From Paul Eggert (2018-06-27): +# From Paul Eggert (2025-05-15): # This file contains a table where each row stands for a timezone where # civil timestamps have agreed since 1970. Columns are separated by -# a single tab. Lines beginning with '#' are comments. All text uses +# a single tab. Lines beginning with ‘#’ are comments. All text uses # UTF-8 encoding. The columns of the table are as follows: # # 1. The countries that overlap the timezone, as a comma-separated list -# of ISO 3166 2-character country codes. See the file 'iso3166.tab'. -# 2. Latitude and longitude of the timezone's principal location +# of ISO 3166 2-character country codes. +# 2. Latitude and longitude of the timezone’s principal location # in ISO 6709 sign-degrees-minutes-seconds format, # either ±DDMM±DDDMM or ±DDMMSS±DDDMMSS, # first latitude (+ is north), then longitude (+ is east). @@ -197,7 +197,7 @@ KZ +5312+06337 Asia/Qostanay Qostanay/Kostanay/Kustanay KZ +5017+05710 Asia/Aqtobe Aqtöbe/Aktobe KZ +4431+05016 Asia/Aqtau MangghystaÅ«/Mankistau -KZ +4707+05156 Asia/Atyrau AtyraÅ«/Atirau/Gur'yev +KZ +4707+05156 Asia/Atyrau AtyraÅ«/Atirau/Gur’yev KZ +5113+05121 Asia/Oral West Kazakhstan LB +3353+03530 Asia/Beirut LK +0656+07951 Asia/Colombo @@ -245,7 +245,7 @@ PF -1732-14934 Pacific/Tahiti Society Islands PF -0900-13930 Pacific/Marquesas Marquesas Islands PF -2308-13457 Pacific/Gambier Gambier Islands -PG,AQ,FM -0930+14710 Pacific/Port_Moresby Papua New Guinea (most areas), Chuuk, Yap, 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 +143512+1205804 Asia/Manila PK +2452+06703 Asia/Karachi @@ -265,7 +265,7 @@ RS,BA,HR,ME,MK,SI +4450+02030 Europe/Belgrade RU +5443+02030 Europe/Kaliningrad MSK-01 - Kaliningrad RU +554521+0373704 Europe/Moscow MSK+00 - Moscow area -# Mention RU and UA alphabetically. See "territorial claims" above. +# Mention RU and UA alphabetically. See “territorial claims” above. RU,UA +4457+03406 Europe/Simferopol Crimea RU +5836+04939 Europe/Kirov MSK+00 - Kirov RU +4844+04425 Europe/Volgograd MSK+00 - Volgograd @@ -353,20 +353,20 @@ # 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 +# 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. +# 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 +# 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/". +# 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/". +# corresponds to Zone names starting with both ‘Europe/’ and ‘Asia/’. # #@AQ Antarctica/ #@IS,SH Atlantic/
diff --git a/absl/time/internal/cctz/testdata/zoneinfo/zonenow.tab b/absl/time/internal/cctz/testdata/zoneinfo/zonenow.tab index 093f0a0..1d64b39 100644 --- a/absl/time/internal/cctz/testdata/zoneinfo/zonenow.tab +++ b/absl/time/internal/cctz/testdata/zoneinfo/zonenow.tab
@@ -5,12 +5,12 @@ # From Paul Eggert (2023-12-18): # This file contains a table where each row stands for a timezone # where civil timestamps are predicted to agree from now on. -# This file is like zone1970.tab (see zone1970.tab's comments), +# This file is like zone1970.tab (see zone1970.tab’s comments), # but with the following changes: # # 1. Each timezone corresponds to a set of clocks that are planned # to agree from now on. This is a larger set of clocks than in -# zone1970.tab, where each timezone's clocks must agree from 1970 on. +# zone1970.tab, where each timezone’s clocks must agree from 1970 on. # 2. The first column is irrelevant and ignored. # 3. The table is sorted in a different way: # first by standard time UTC offset; @@ -29,19 +29,19 @@ #XX coordinates TZ comments # # -11 - SST -XX -1416-17042 Pacific/Pago_Pago Midway; Samoa ("SST") +XX -1416-17042 Pacific/Pago_Pago Midway; Samoa (SST) # # -11 XX -1901-16955 Pacific/Niue Niue # # -10 - HST -XX +211825-1575130 Pacific/Honolulu Hawaii ("HST") +XX +211825-1575130 Pacific/Honolulu Hawaii (HST) # # -10 XX -1732-14934 Pacific/Tahiti Tahiti; Cook Islands # # -10/-09 - HST / HDT (North America DST) -XX +515248-1763929 America/Adak western Aleutians in Alaska ("HST/HDT") +XX +515248-1763929 America/Adak western Aleutians in Alaska (HST/HDT) # # -09:30 XX -0900-13930 Pacific/Marquesas Marquesas @@ -50,58 +50,58 @@ XX -2308-13457 Pacific/Gambier Gambier # # -09/-08 - AKST/AKDT (North America DST) -XX +611305-1495401 America/Anchorage most of Alaska ("AKST/AKDT") +XX +611305-1495401 America/Anchorage most of Alaska (AKST/AKDT) # # -08 XX -2504-13005 Pacific/Pitcairn Pitcairn # # -08/-07 - PST/PDT (North America DST) -XX +340308-1181434 America/Los_Angeles Pacific ("PST/PDT") - US & Canada; Mexico near US border +XX +340308-1181434 America/Los_Angeles Pacific (PST/PDT) - US & Canada; Mexico near US border # # -07 - MST -XX +332654-1120424 America/Phoenix Mountain Standard ("MST") - Arizona; western Mexico; Yukon +XX +332654-1120424 America/Phoenix Mountain Standard (MST) - Arizona; western Mexico; Yukon # # -07/-06 - MST/MDT (North America DST) -XX +394421-1045903 America/Denver Mountain ("MST/MDT") - US & Canada; Mexico near US border +XX +394421-1045903 America/Denver Mountain (MST/MDT) - US & Canada; Mexico near US border # # -06 XX -0054-08936 Pacific/Galapagos Galápagos # # -06 - CST -XX +1924-09909 America/Mexico_City Central Standard ("CST") - Saskatchewan; central Mexico; Central America +XX +1924-09909 America/Mexico_City Central Standard (CST) - Saskatchewan; central Mexico; Central America # # -06/-05 (Chile DST) XX -2709-10926 Pacific/Easter Easter Island # # -06/-05 - CST/CDT (North America DST) -XX +415100-0873900 America/Chicago Central ("CST/CDT") - US & Canada; Mexico near US border +XX +415100-0873900 America/Chicago Central (CST/CDT) - US & Canada; Mexico near US border # # -05 XX -1203-07703 America/Lima eastern South America # # -05 - EST -XX +175805-0764736 America/Jamaica Eastern Standard ("EST") - Caymans; Jamaica; eastern Mexico; Panama +XX +175805-0764736 America/Jamaica Eastern Standard (EST) - Caymans; Jamaica; eastern Mexico; Panama # # -05/-04 - CST/CDT (Cuba DST) XX +2308-08222 America/Havana Cuba # # -05/-04 - EST/EDT (North America DST) -XX +404251-0740023 America/New_York Eastern ("EST/EDT") - US & Canada +XX +404251-0740023 America/New_York Eastern (EST/EDT) - US & Canada # # -04 XX +1030-06656 America/Caracas western South America # # -04 - AST -XX +1828-06954 America/Santo_Domingo Atlantic Standard ("AST") - eastern Caribbean +XX +1828-06954 America/Santo_Domingo Atlantic Standard (AST) - eastern Caribbean # # -04/-03 (Chile DST) XX -3327-07040 America/Santiago most of Chile # # -04/-03 - AST/ADT (North America DST) -XX +4439-06336 America/Halifax Atlantic ("AST/ADT") - Canada; Bermuda +XX +4439-06336 America/Halifax Atlantic (AST/ADT) - Canada; Bermuda # # -03:30/-02:30 - NST/NDT (North America DST) -XX +4734-05243 America/St_Johns Newfoundland ("NST/NDT") +XX +4734-05243 America/St_Johns Newfoundland (NST/NDT) # # -03 XX -2332-04637 America/Sao_Paulo eastern and southern South America @@ -122,43 +122,43 @@ XX +3744-02540 Atlantic/Azores Azores # # +00 - GMT -XX +0519-00402 Africa/Abidjan far western Africa; Iceland ("GMT") +XX +0519-00402 Africa/Abidjan far western Africa; Iceland (GMT) # # +00/+01 - GMT/BST (EU DST) -XX +513030-0000731 Europe/London United Kingdom ("GMT/BST") +XX +513030-0000731 Europe/London United Kingdom (GMT/BST) # # +00/+01 - WET/WEST (EU DST) -XX +3843-00908 Europe/Lisbon western Europe ("WET/WEST") +XX +3843-00908 Europe/Lisbon western Europe (WET/WEST) # # +00/+02 - Troll DST XX -720041+0023206 Antarctica/Troll Troll Station in Antarctica # # +01 - CET -XX +3647+00303 Africa/Algiers Algeria, Tunisia ("CET") +XX +3647+00303 Africa/Algiers Algeria, Tunisia (CET) # # +01 - WAT -XX +0627+00324 Africa/Lagos western Africa ("WAT") +XX +0627+00324 Africa/Lagos western Africa (WAT) # # +01/+00 - IST/GMT (EU DST in reverse) -XX +5320-00615 Europe/Dublin Ireland ("IST/GMT") +XX +5320-00615 Europe/Dublin Ireland (IST/GMT) # # +01/+00 - (Morocco DST) XX +3339-00735 Africa/Casablanca Morocco # # +01/+02 - CET/CEST (EU DST) -XX +4852+00220 Europe/Paris central Europe ("CET/CEST") +XX +4852+00220 Europe/Paris central Europe (CET/CEST) # # +02 - CAT -XX -2558+03235 Africa/Maputo central Africa ("CAT") +XX -2558+03235 Africa/Maputo central Africa (CAT) # # +02 - EET -XX +3254+01311 Africa/Tripoli Libya; Kaliningrad ("EET") +XX +3254+01311 Africa/Tripoli Libya; Kaliningrad (EET) # # +02 - SAST -XX -2615+02800 Africa/Johannesburg southern Africa ("SAST") +XX -2615+02800 Africa/Johannesburg southern Africa (SAST) # # +02/+03 - EET/EEST (EU DST) -XX +3758+02343 Europe/Athens eastern Europe ("EET/EEST") +XX +3758+02343 Europe/Athens eastern Europe (EET/EEST) # # +02/+03 - EET/EEST (Egypt DST) XX +3003+03115 Africa/Cairo Egypt @@ -179,10 +179,10 @@ XX +4101+02858 Europe/Istanbul Near East; Belarus # # +03 - EAT -XX -0117+03649 Africa/Nairobi eastern Africa ("EAT") +XX -0117+03649 Africa/Nairobi eastern Africa (EAT) # # +03 - MSK -XX +554521+0373704 Europe/Moscow Moscow ("MSK") +XX +554521+0373704 Europe/Moscow Moscow (MSK) # # +03:30 XX +3540+05126 Asia/Tehran Iran @@ -197,13 +197,13 @@ XX +4120+06918 Asia/Tashkent Russia; Kazakhstan; Tajikistan; Turkmenistan; Uzbekistan; Maldives # # +05 - PKT -XX +2452+06703 Asia/Karachi Pakistan ("PKT") +XX +2452+06703 Asia/Karachi Pakistan (PKT) # # +05:30 XX +0656+07951 Asia/Colombo Sri Lanka # # +05:30 - IST -XX +2232+08822 Asia/Kolkata India ("IST") +XX +2232+08822 Asia/Kolkata India (IST) # # +05:45 XX +2743+08519 Asia/Kathmandu Nepal @@ -218,25 +218,25 @@ XX +1345+10031 Asia/Bangkok Russia; Indochina; Christmas Island # # +07 - WIB -XX -0610+10648 Asia/Jakarta Indonesia ("WIB") +XX -0610+10648 Asia/Jakarta Indonesia (WIB) # # +08 XX +0117+10351 Asia/Singapore Russia; Brunei; Malaysia; Singapore; Concordia # # +08 - AWST -XX -3157+11551 Australia/Perth Western Australia ("AWST") +XX -3157+11551 Australia/Perth Western Australia (AWST) # # +08 - CST -XX +3114+12128 Asia/Shanghai China ("CST") +XX +3114+12128 Asia/Shanghai China (CST) # # +08 - HKT -XX +2217+11409 Asia/Hong_Kong Hong Kong ("HKT") +XX +2217+11409 Asia/Hong_Kong Hong Kong (HKT) # # +08 - PHT -XX +143512+1205804 Asia/Manila Philippines ("PHT") +XX +143512+1205804 Asia/Manila Philippines (PHT) # # +08 - WITA -XX -0507+11924 Asia/Makassar Indonesia ("WITA") +XX -0507+11924 Asia/Makassar Indonesia (WITA) # # +08:45 XX -3143+12852 Australia/Eucla Eucla @@ -245,31 +245,31 @@ XX +5203+11328 Asia/Chita Russia; Palau; East Timor # # +09 - JST -XX +353916+1394441 Asia/Tokyo Japan ("JST"); Eyre Bird Observatory +XX +353916+1394441 Asia/Tokyo Japan (JST); Eyre Bird Observatory # # +09 - KST -XX +3733+12658 Asia/Seoul Korea ("KST") +XX +3733+12658 Asia/Seoul Korea (KST) # # +09 - WIT -XX -0232+14042 Asia/Jayapura Indonesia ("WIT") +XX -0232+14042 Asia/Jayapura Indonesia (WIT) # # +09:30 - ACST -XX -1228+13050 Australia/Darwin Northern Territory ("ACST") +XX -1228+13050 Australia/Darwin Northern Territory (ACST) # # +09:30/+10:30 - ACST/ACDT (Australia DST) -XX -3455+13835 Australia/Adelaide South Australia ("ACST/ACDT") +XX -3455+13835 Australia/Adelaide South Australia (ACST/ACDT) # # +10 -XX +4310+13156 Asia/Vladivostok Russia; Yap; Chuuk; Papua New Guinea; Dumont d'Urville +XX +4310+13156 Asia/Vladivostok Russia; Yap; Chuuk; Papua New Guinea; Dumont d’Urville # # +10 - AEST -XX -2728+15302 Australia/Brisbane Queensland ("AEST") +XX -2728+15302 Australia/Brisbane Queensland (AEST) # # +10 - ChST -XX +1328+14445 Pacific/Guam Mariana Islands ("ChST") +XX +1328+14445 Pacific/Guam Mariana Islands (ChST) # # +10/+11 - AEST/AEDT (Australia DST) -XX -3352+15113 Australia/Sydney southeast Australia ("AEST/AEDT") +XX -3352+15113 Australia/Sydney southeast Australia (AEST/AEDT) # # +10:30/+11 XX -3133+15905 Australia/Lord_Howe Lord Howe Island @@ -284,7 +284,7 @@ XX +5301+15839 Asia/Kamchatka Russia; Tuvalu; Fiji; etc. # # +12/+13 (New Zealand DST) -XX -3652+17446 Pacific/Auckland New Zealand ("NZST/NZDT") +XX -3652+17446 Pacific/Auckland New Zealand (NZST/NZDT) # # +12:45/+13:45 (Chatham DST) XX -4357-17633 Pacific/Chatham Chatham Islands
diff --git a/absl/time/time.cc b/absl/time/time.cc index d983c12..faef864 100644 --- a/absl/time/time.cc +++ b/absl/time/time.cc
@@ -249,8 +249,8 @@ int64_t ToUnixNanos(Time t) { if (time_internal::GetRepHi(time_internal::ToUnixDuration(t)) >= 0 && time_internal::GetRepHi(time_internal::ToUnixDuration(t)) >> 33 == 0) { - return (time_internal::GetRepHi(time_internal::ToUnixDuration(t)) * - 1000 * 1000 * 1000) + + return (time_internal::GetRepHi(time_internal::ToUnixDuration(t)) * 1000 * + 1000 * 1000) + (time_internal::GetRepLo(time_internal::ToUnixDuration(t)) / 4); } return FloorToUnit(time_internal::ToUnixDuration(t), absl::Nanoseconds(1)); @@ -259,8 +259,8 @@ int64_t ToUnixMicros(Time t) { if (time_internal::GetRepHi(time_internal::ToUnixDuration(t)) >= 0 && time_internal::GetRepHi(time_internal::ToUnixDuration(t)) >> 43 == 0) { - return (time_internal::GetRepHi(time_internal::ToUnixDuration(t)) * - 1000 * 1000) + + return (time_internal::GetRepHi(time_internal::ToUnixDuration(t)) * 1000 * + 1000) + (time_internal::GetRepLo(time_internal::ToUnixDuration(t)) / 4000); } return FloorToUnit(time_internal::ToUnixDuration(t), absl::Microseconds(1));
diff --git a/absl/time/time.h b/absl/time/time.h index 53bca90..8e8df2e 100644 --- a/absl/time/time.h +++ b/absl/time/time.h
@@ -589,9 +589,10 @@ } return time_internal::MakePosDoubleDuration(n); } else { - if (std::isnan(n)) - return std::signbit(n) ? -InfiniteDuration() : InfiniteDuration(); - if (n <= (std::numeric_limits<int64_t>::min)()) return -InfiniteDuration(); + if (std::isnan(n)) return -InfiniteDuration(); + if (n <= static_cast<T>((std::numeric_limits<int64_t>::min)())) { + return -InfiniteDuration(); + } return -time_internal::MakePosDoubleDuration(-n); } } @@ -731,7 +732,6 @@ // `absl::ParseDuration()`. bool AbslParseFlag(absl::string_view text, Duration* dst, std::string* error); - // AbslUnparseFlag() // // Unparses a Duration value into a command-line string representation using @@ -1680,14 +1680,16 @@ return (v <= (std::numeric_limits<int64_t>::max)() / 60 && v >= (std::numeric_limits<int64_t>::min)() / 60) ? MakeDuration(v * 60) - : v > 0 ? InfiniteDuration() : -InfiniteDuration(); + : v > 0 ? InfiniteDuration() + : -InfiniteDuration(); } 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) - : v > 0 ? InfiniteDuration() : -InfiniteDuration(); + : v > 0 ? InfiniteDuration() + : -InfiniteDuration(); } // IsValidRep64<T>(0) is true if the expression `int64_t{std::declval<T>()}` is @@ -1805,13 +1807,12 @@ (std::numeric_limits<int64_t>::min)() ? InfiniteDuration() : time_internal::MakeDuration(-time_internal::GetRepHi(d)) - : time_internal::IsInfiniteDuration(d) - ? time_internal::OppositeInfinity(d) - : time_internal::MakeDuration( - time_internal::NegateAndSubtractOne( - time_internal::GetRepHi(d)), - time_internal::kTicksPerSecond - - time_internal::GetRepLo(d)); + : time_internal::IsInfiniteDuration(d) + ? time_internal::OppositeInfinity(d) + : time_internal::MakeDuration( + time_internal::NegateAndSubtractOne( + time_internal::GetRepHi(d)), + time_internal::kTicksPerSecond - time_internal::GetRepLo(d)); } ABSL_ATTRIBUTE_CONST_FUNCTION constexpr Duration InfiniteDuration() {
diff --git a/absl/time/time_test.cc b/absl/time/time_test.cc index 7033326..6714a27 100644 --- a/absl/time/time_test.cc +++ b/absl/time/time_test.cc
@@ -13,6 +13,7 @@ // limitations under the License. #include "absl/time/time.h" + #include "absl/time/civil_time.h" #if defined(_MSC_VER)
diff --git a/absl/types/BUILD.bazel b/absl/types/BUILD.bazel index 0668a2e..cac5e4b 100644 --- a/absl/types/BUILD.bazel +++ b/absl/types/BUILD.bazel
@@ -13,6 +13,8 @@ # See the License for the specific language governing permissions and # limitations under the License. +load("@rules_cc//cc:cc_library.bzl", "cc_library") +load("@rules_cc//cc:cc_test.bzl", "cc_test") load( "//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS",
diff --git a/absl/types/compare.h b/absl/types/compare.h index 3cf4a91..edf1039 100644 --- a/absl/types/compare.h +++ b/absl/types/compare.h
@@ -120,8 +120,7 @@ // A no-op expansion that can be followed by a semicolon at class level. #define ABSL_COMPARE_INLINE_BASECLASS_DECL(name) static_assert(true, "") -#define ABSL_COMPARE_INLINE_SUBCLASS_DECL(type, name) \ - static const type name +#define ABSL_COMPARE_INLINE_SUBCLASS_DECL(type, name) static const type name #define ABSL_COMPARE_INLINE_INIT(type, name, init) \ inline constexpr type type::name(init) @@ -190,28 +189,28 @@ ABSL_COMPARE_INLINE_SUBCLASS_DECL(partial_ordering, unordered); // Comparisons - friend constexpr bool operator==( - partial_ordering v, compare_internal::OnlyLiteralZero) noexcept { + friend constexpr bool operator==(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 { + friend constexpr bool operator!=(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 { + friend constexpr bool operator<(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 { + friend constexpr bool operator<=(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 { + friend constexpr bool operator>(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 { + friend constexpr bool operator>=(partial_ordering v, + compare_internal::OnlyLiteralZero) noexcept { return v.is_ordered() && v.value_ >= 0; } friend constexpr bool operator==(compare_internal::OnlyLiteralZero, @@ -278,28 +277,28 @@ : partial_ordering::greater); } // Comparisons - friend constexpr bool operator==( - weak_ordering v, compare_internal::OnlyLiteralZero) noexcept { + friend constexpr bool operator==(weak_ordering v, + compare_internal::OnlyLiteralZero) noexcept { return v.value_ == 0; } - friend constexpr bool operator!=( - weak_ordering v, compare_internal::OnlyLiteralZero) noexcept { + friend constexpr bool operator!=(weak_ordering v, + compare_internal::OnlyLiteralZero) noexcept { return v.value_ != 0; } - friend constexpr bool operator<( - weak_ordering v, compare_internal::OnlyLiteralZero) noexcept { + friend constexpr bool operator<(weak_ordering v, + compare_internal::OnlyLiteralZero) noexcept { return v.value_ < 0; } - friend constexpr bool operator<=( - weak_ordering v, compare_internal::OnlyLiteralZero) noexcept { + friend constexpr bool operator<=(weak_ordering v, + compare_internal::OnlyLiteralZero) noexcept { return v.value_ <= 0; } - friend constexpr bool operator>( - weak_ordering v, compare_internal::OnlyLiteralZero) noexcept { + friend constexpr bool operator>(weak_ordering v, + compare_internal::OnlyLiteralZero) noexcept { return v.value_ > 0; } - friend constexpr bool operator>=( - weak_ordering v, compare_internal::OnlyLiteralZero) noexcept { + friend constexpr bool operator>=(weak_ordering v, + compare_internal::OnlyLiteralZero) noexcept { return v.value_ >= 0; } friend constexpr bool operator==(compare_internal::OnlyLiteralZero, @@ -370,28 +369,28 @@ : (value_ < 0 ? weak_ordering::less : weak_ordering::greater); } // Comparisons - friend constexpr bool operator==( - strong_ordering v, compare_internal::OnlyLiteralZero) noexcept { + friend constexpr bool operator==(strong_ordering v, + compare_internal::OnlyLiteralZero) noexcept { return v.value_ == 0; } - friend constexpr bool operator!=( - strong_ordering v, compare_internal::OnlyLiteralZero) noexcept { + friend constexpr bool operator!=(strong_ordering v, + compare_internal::OnlyLiteralZero) noexcept { return v.value_ != 0; } - friend constexpr bool operator<( - strong_ordering v, compare_internal::OnlyLiteralZero) noexcept { + friend constexpr bool operator<(strong_ordering v, + compare_internal::OnlyLiteralZero) noexcept { return v.value_ < 0; } - friend constexpr bool operator<=( - strong_ordering v, compare_internal::OnlyLiteralZero) noexcept { + friend constexpr bool operator<=(strong_ordering v, + compare_internal::OnlyLiteralZero) noexcept { return v.value_ <= 0; } - friend constexpr bool operator>( - strong_ordering v, compare_internal::OnlyLiteralZero) noexcept { + friend constexpr bool operator>(strong_ordering v, + compare_internal::OnlyLiteralZero) noexcept { return v.value_ > 0; } - friend constexpr bool operator>=( - strong_ordering v, compare_internal::OnlyLiteralZero) noexcept { + friend constexpr bool operator>=(strong_ordering v, + compare_internal::OnlyLiteralZero) noexcept { return v.value_ >= 0; } friend constexpr bool operator==(compare_internal::OnlyLiteralZero, @@ -451,14 +450,16 @@ // SFINAE prevents implicit conversions to bool (such as from int). 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 BoolT r) { + return r; +} constexpr bool compare_result_as_less_than(const absl::weak_ordering r) { return r < 0; } template <typename Compare, typename K, typename LK> -constexpr bool do_less_than_comparison(const Compare &compare, const K &x, - const LK &y) { +constexpr bool do_less_than_comparison(const Compare& compare, const K& x, + const LK& y) { return compare_result_as_less_than(compare(x, y)); } @@ -468,34 +469,34 @@ template <typename Int, absl::enable_if_t<std::is_same<int, Int>::value, int> = 0> constexpr absl::weak_ordering compare_result_as_ordering(const Int c) { - return c < 0 ? absl::weak_ordering::less - : c == 0 ? absl::weak_ordering::equivalent - : absl::weak_ordering::greater; + return c < 0 ? absl::weak_ordering::less + : c == 0 ? absl::weak_ordering::equivalent + : absl::weak_ordering::greater; } constexpr absl::weak_ordering compare_result_as_ordering( const absl::weak_ordering c) { return c; } -template < - typename Compare, typename K, typename LK, - absl::enable_if_t<!std::is_same<bool, absl::result_of_t<Compare( - const K &, const LK &)>>::value, - int> = 0> -constexpr absl::weak_ordering do_three_way_comparison(const Compare &compare, - const K &x, const LK &y) { +template <typename Compare, typename K, typename LK, + absl::enable_if_t< + !std::is_same< + bool, absl::result_of_t<Compare(const K&, const LK&)>>::value, + int> = 0> +constexpr absl::weak_ordering do_three_way_comparison(const Compare& compare, + const K& x, const LK& y) { return compare_result_as_ordering(compare(x, y)); } -template < - typename Compare, typename K, typename LK, - absl::enable_if_t<std::is_same<bool, absl::result_of_t<Compare( - const K &, const LK &)>>::value, - int> = 0> -constexpr absl::weak_ordering do_three_way_comparison(const Compare &compare, - const K &x, const LK &y) { - return compare(x, y) ? absl::weak_ordering::less - : compare(y, x) ? absl::weak_ordering::greater - : absl::weak_ordering::equivalent; +template <typename Compare, typename K, typename LK, + absl::enable_if_t< + std::is_same< + bool, absl::result_of_t<Compare(const K&, const LK&)>>::value, + int> = 0> +constexpr absl::weak_ordering do_three_way_comparison(const Compare& compare, + const K& x, const LK& y) { + return compare(x, y) ? absl::weak_ordering::less + : compare(y, x) ? absl::weak_ordering::greater + : absl::weak_ordering::equivalent; } } // namespace compare_internal
diff --git a/absl/types/compare_test.cc b/absl/types/compare_test.cc index 455cdbb..352c9f1 100644 --- a/absl/types/compare_test.cc +++ b/absl/types/compare_test.cc
@@ -160,9 +160,9 @@ struct WeakOrderingLess { template <typename T> absl::weak_ordering operator()(const T& a, const T& b) const { - return a < b ? absl::weak_ordering::less - : a == b ? absl::weak_ordering::equivalent - : absl::weak_ordering::greater; + return a < b ? absl::weak_ordering::less + : a == b ? absl::weak_ordering::equivalent + : absl::weak_ordering::greater; } };
diff --git a/absl/types/internal/span.h b/absl/types/internal/span.h index 1039f61..cdf3941 100644 --- a/absl/types/internal/span.h +++ b/absl/types/internal/span.h
@@ -121,6 +121,7 @@ using ConstData = decltype(span_internal::GetData(std::declval<const Container&>())); using MutData = decltype(span_internal::GetData(std::declval<Container&>())); + public: static constexpr bool value = std::is_same<ConstData, MutData>::value; };
diff --git a/absl/types/optional.h b/absl/types/optional.h index 65bba64..5b68a5b 100644 --- a/absl/types/optional.h +++ b/absl/types/optional.h
@@ -31,10 +31,10 @@ namespace absl { ABSL_NAMESPACE_BEGIN using std::bad_optional_access; -using std::optional; using std::make_optional; -using std::nullopt_t; using std::nullopt; +using std::nullopt_t; +using std::optional; ABSL_NAMESPACE_END } // namespace absl
diff --git a/absl/types/span.h b/absl/types/span.h index 0e46ee6..03daac2 100644 --- a/absl/types/span.h +++ b/absl/types/span.h
@@ -47,6 +47,9 @@ // * `absl::Span` has no static extent template parameter, nor constructors // which exist only because of the static extent parameter. // * `absl::Span` has an explicit mutable-reference constructor +// * `absl::Span::subspan(pos, len)` always truncates `len` to +// `size() - pos`, whereas `std::span::subspan()` only truncates when the +// `len` parameter is defaulted. // // For more information, see the class comments below. #ifndef ABSL_TYPES_SPAN_H_ @@ -96,10 +99,10 @@ #if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 201911L #include <ranges> // NOLINT(build/c++20) template <typename T> - // NOLINTNEXTLINE(build/c++20) +// NOLINTNEXTLINE(build/c++20) inline constexpr bool std::ranges::enable_view<absl::Span<T>> = true; template <typename T> - // NOLINTNEXTLINE(build/c++20) +// NOLINTNEXTLINE(build/c++20) inline constexpr bool std::ranges::enable_borrowed_range<absl::Span<T>> = true; #endif @@ -193,8 +196,9 @@ // type C. template <typename C> using EnableIfConvertibleFrom = - typename std::enable_if<span_internal::HasData<T, C>::value && - span_internal::HasSize<C>::value>::type; + std::enable_if_t<!std::is_same_v<Span, std::remove_reference_t<C>> && + span_internal::HasData<T, C>::value && + span_internal::HasSize<C>::value>; // Used to SFINAE-enable a function when the slice elements are const. template <typename U> @@ -455,6 +459,11 @@ // will be trimmed to at most size() - `pos`. A default `len` value of `npos` // ensures the returned subspan continues until the end of the span. // + // Note that trimming behavior differs from `std::span::subspan()`. + // `std::span::subspan()` requires `len == npos || pos + len <= size()`. + // In other words, `std::span::subspan()` only trims `len` when its value is + // defaulted. + // // Examples: // // std::vector<int> vec = {10, 11, 12, 13}; @@ -506,8 +515,7 @@ // Support for absl::Hash. template <typename H> friend H AbslHashValue(H h, Span v) { - return H::combine(H::combine_contiguous(std::move(h), v.data(), v.size()), - hash_internal::WeaklyMixedInteger{v.size()}); + return H::combine_contiguous(std::move(h), v.data(), v.size()); } private: @@ -733,24 +741,38 @@ // } // template <int&... ExplicitArgumentBarrier, typename T> -constexpr Span<T> MakeSpan(T* absl_nullable ptr, size_t size) noexcept { +constexpr Span<T> MakeSpan(T* absl_nullable ptr ABSL_ATTRIBUTE_LIFETIME_BOUND, + size_t size) noexcept { return Span<T>(ptr, size); } template <int&... ExplicitArgumentBarrier, typename T> -Span<T> MakeSpan(T* absl_nullable begin, T* absl_nullable end) noexcept { +Span<T> MakeSpan(T* absl_nullable begin ABSL_ATTRIBUTE_LIFETIME_BOUND, + T* absl_nullable end) noexcept { ABSL_HARDENING_ASSERT(begin <= end); return Span<T>(begin, static_cast<size_t>(end - begin)); } template <int&... ExplicitArgumentBarrier, typename C> constexpr auto MakeSpan(C& c) noexcept // NOLINT(runtime/references) - -> decltype(absl::MakeSpan(span_internal::GetData(c), c.size())) { + -> std::enable_if_t<span_internal::IsView<C>::value, + decltype(absl::MakeSpan(span_internal::GetData(c), + c.size()))> { + return MakeSpan(span_internal::GetData(c), c.size()); +} + +template <int&... ExplicitArgumentBarrier, typename C> +constexpr auto MakeSpan( + C& c ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept // NOLINT(runtime/references) + -> std::enable_if_t<!span_internal::IsView<C>::value, + decltype(absl::MakeSpan(span_internal::GetData(c), + c.size()))> { return MakeSpan(span_internal::GetData(c), c.size()); } template <int&... ExplicitArgumentBarrier, typename T, size_t N> -constexpr Span<T> MakeSpan(T (&array)[N]) noexcept { +constexpr Span<T> MakeSpan( + T (&array ABSL_ATTRIBUTE_LIFETIME_BOUND)[N]) noexcept { return Span<T>(array, N); } @@ -779,25 +801,36 @@ // ProcessInts(absl::MakeConstSpan(std::vector<int>{ 0, 0, 0 })); // template <int&... ExplicitArgumentBarrier, typename T> -constexpr Span<const T> MakeConstSpan(T* absl_nullable ptr, - size_t size) noexcept { +constexpr Span<const T> MakeConstSpan( + T* absl_nullable ptr ABSL_ATTRIBUTE_LIFETIME_BOUND, size_t size) noexcept { return Span<const T>(ptr, size); } template <int&... ExplicitArgumentBarrier, typename T> -Span<const T> MakeConstSpan(T* absl_nullable begin, +Span<const T> MakeConstSpan(T* absl_nullable begin + ABSL_ATTRIBUTE_LIFETIME_BOUND, T* absl_nullable end) noexcept { ABSL_HARDENING_ASSERT(begin <= end); return Span<const T>(begin, end - begin); } template <int&... ExplicitArgumentBarrier, typename C> -constexpr auto MakeConstSpan(const C& c) noexcept -> decltype(MakeSpan(c)) { +constexpr auto MakeConstSpan(const C& c) noexcept + -> std::enable_if_t<span_internal::IsView<C>::value, + decltype(MakeSpan(c))> { + return MakeSpan(c); +} + +template <int&... ExplicitArgumentBarrier, typename C> +constexpr auto MakeConstSpan(const C& c ABSL_ATTRIBUTE_LIFETIME_BOUND) noexcept + -> std::enable_if_t<!span_internal::IsView<C>::value, + decltype(MakeSpan(c))> { return MakeSpan(c); } template <int&... ExplicitArgumentBarrier, typename T, size_t N> -constexpr Span<const T> MakeConstSpan(const T (&array)[N]) noexcept { +constexpr Span<const T> MakeConstSpan( + const T (&array ABSL_ATTRIBUTE_LIFETIME_BOUND)[N]) noexcept { return Span<const T>(array, N); } ABSL_NAMESPACE_END
diff --git a/absl/types/span_test.cc b/absl/types/span_test.cc index 6700b81..21b49ed 100644 --- a/absl/types/span_test.cc +++ b/absl/types/span_test.cc
@@ -41,6 +41,20 @@ absl::type_traits_internal::IsView<absl::Span<int>>::value, "Span is a view, not an owner"); +using S = absl::Span<int>; + +static_assert( + std::is_trivially_destructible_v<S> && std::is_trivially_copyable_v<S> && + std::is_trivially_assignable_v<S, S&> && + std::is_trivially_copy_assignable_v<S> && + std::is_trivially_move_assignable_v<S> && + std::is_trivially_assignable_v<S, const S&&> && + std::is_trivially_constructible_v<S, S&> && + std::is_trivially_copy_constructible_v<S> && + std::is_trivially_move_constructible_v<S> && + std::is_trivially_constructible_v<S, const S&&>, + "Span should be trivial in everything except default-constructibility"); + MATCHER_P(DataIs, data, absl::StrCat("data() ", negation ? "isn't " : "is ", testing::PrintToString(data))) {
diff --git a/absl/utility/BUILD.bazel b/absl/utility/BUILD.bazel index 773f949..a714b02 100644 --- a/absl/utility/BUILD.bazel +++ b/absl/utility/BUILD.bazel
@@ -14,6 +14,7 @@ # limitations under the License. # +load("@rules_cc//cc:cc_library.bzl", "cc_library") load( "//absl:copts/configure_copts.bzl", "ABSL_DEFAULT_COPTS",
diff --git a/absl/utility/utility.h b/absl/utility/utility.h index 4637b03..4d72c31 100644 --- a/absl/utility/utility.h +++ b/absl/utility/utility.h
@@ -49,6 +49,19 @@ using std::make_integer_sequence; using std::move; +#if ABSL_INTERNAL_CPLUSPLUS_LANG >= 202002L +// Backfill for std::nontype_t. An instance of this class can be provided as a +// disambiguation tag to `absl::function_ref` to pass the address of a known +// callable at compile time. +// Requires C++20 due to `auto` template parameter. +template <auto> +struct nontype_t { + explicit nontype_t() = default; +}; +template <auto V> +constexpr nontype_t<V> nontype{}; +#endif + ABSL_NAMESPACE_END } // namespace absl
diff --git a/ci/absl_alternate_options.h b/ci/absl_alternate_options.h index a563859..d5567f3 100644 --- a/ci/absl_alternate_options.h +++ b/ci/absl_alternate_options.h
@@ -15,13 +15,11 @@ // Alternate options.h file, used in continuous integration testing to exercise // option settings not used by default. +// SKIP_ABSL_INLINE_NAMESPACE_CHECK + #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 -#define ABSL_OPTION_USE_STD_STRING_VIEW 0 -#define ABSL_OPTION_USE_STD_VARIANT 0 #define ABSL_OPTION_USE_STD_ORDERING 0 #define ABSL_OPTION_USE_INLINE_NAMESPACE 1 #define ABSL_OPTION_INLINE_NAMESPACE_NAME ns
diff --git a/ci/cmake_common.sh b/ci/cmake_common.sh index 484230c..53d3a37 100644 --- a/ci/cmake_common.sh +++ b/ci/cmake_common.sh
@@ -16,4 +16,4 @@ # Keep this in sync with the commit in the MODULE.bazel file. readonly ABSL_GOOGLETEST_VERSION="1.17.0" -readonly ABSL_GOOGLETEST_DOWNLOAD_URL="https://github.com/google/googletest/releases/download/v${ABSL_GOOGLETEST_VERSION}/googletest-${ABSL_GOOGLETEST_VERSION}.tar.gz" +ABSL_GOOGLETEST_DOWNLOAD_URL="https://github.com/google/googletest/releases/download/v${ABSL_GOOGLETEST_VERSION}/googletest-${ABSL_GOOGLETEST_VERSION}.tar.gz"
diff --git a/ci/cmake_install_test.sh b/ci/cmake_install_test.sh index 871490f..5e3e07b 100755 --- a/ci/cmake_install_test.sh +++ b/ci/cmake_install_test.sh
@@ -26,6 +26,12 @@ source "${ABSEIL_ROOT}/ci/cmake_common.sh" +# Avoid depending on GitHub by looking for a cached copy of GoogleTest. +if [[ -r "${KOKORO_GFILE_DIR:-}/distdir/googletest-${ABSL_GOOGLETEST_VERSION}.tar.gz" ]]; then + ABSL_GOOGLETEST_DOWNLOAD_URL="file:///distdir/googletest-${ABSL_GOOGLETEST_VERSION}.tar.gz" + DOCKER_EXTRA_ARGS="--mount type=bind,source=${KOKORO_GFILE_DIR}/distdir,target=/distdir,readonly ${DOCKER_EXTRA_ARGS:-}" +fi + source "${ABSEIL_ROOT}/ci/linux_docker_containers.sh" readonly DOCKER_CONTAINER=${LINUX_GCC_LATEST_CONTAINER}
diff --git a/ci/linux_arm_clang-latest_libcxx_bazel.sh b/ci/linux_arm_clang-latest_libcxx_bazel.sh index 631a8bd..5ad9fcc 100755 --- a/ci/linux_arm_clang-latest_libcxx_bazel.sh +++ b/ci/linux_arm_clang-latest_libcxx_bazel.sh
@@ -88,6 +88,7 @@ --features=external_include_paths \ --keep_going \ --linkopt=-stdlib=libc++ \ + --per_file_copt=external/.*@-w \ --show_timestamps \ --test_env=\"GTEST_INSTALL_FAILURE_SIGNAL_HANDLER=1\" \ --test_env=\"TZDIR=/abseil-cpp/absl/time/internal/cctz/testdata/zoneinfo\" \
diff --git a/ci/linux_clang-latest_libcxx_asan_bazel.sh b/ci/linux_clang-latest_libcxx_asan_bazel.sh index cfc5510..1200ef0 100755 --- a/ci/linux_clang-latest_libcxx_asan_bazel.sh +++ b/ci/linux_clang-latest_libcxx_asan_bazel.sh
@@ -81,6 +81,7 @@ --action_env=\"CPLUS_INCLUDE_PATH=/opt/llvm/libcxx/include/c++/v1\" \ --compilation_mode=\"${compilation_mode}\" \ --copt=\"${exceptions_mode}\" \ + --copt=\"-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_DEBUG\" \ --copt=\"-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1\" \ --copt=\"-fsanitize=address\" \ --copt=\"-fsanitize=${UBSAN_CHECKS}\" \ @@ -92,6 +93,7 @@ --keep_going \ --linkopt=\"-fsanitize=address\" \ --linkopt=\"-fsanitize-link-c++-runtime\" \ + --per_file_copt=external/.*@-w \ --show_timestamps \ --test_env=\"ASAN_SYMBOLIZER_PATH=/opt/llvm/clang/bin/llvm-symbolizer\" \ --test_env=\"TZDIR=/abseil-cpp/absl/time/internal/cctz/testdata/zoneinfo\" \
diff --git a/ci/linux_clang-latest_libcxx_bazel.sh b/ci/linux_clang-latest_libcxx_bazel.sh index 5c51d15..74af10c 100755 --- a/ci/linux_clang-latest_libcxx_bazel.sh +++ b/ci/linux_clang-latest_libcxx_bazel.sh
@@ -89,6 +89,7 @@ --enable_bzlmod=true \ --features=external_include_paths \ --keep_going \ + --per_file_copt=external/.*@-w \ --show_timestamps \ --test_env=\"GTEST_INSTALL_FAILURE_SIGNAL_HANDLER=1\" \ --test_env=\"TZDIR=/abseil-cpp/absl/time/internal/cctz/testdata/zoneinfo\" \
diff --git a/ci/linux_clang-latest_libcxx_tsan_bazel.sh b/ci/linux_clang-latest_libcxx_tsan_bazel.sh index c9ea22d..8634e8d 100755 --- a/ci/linux_clang-latest_libcxx_tsan_bazel.sh +++ b/ci/linux_clang-latest_libcxx_tsan_bazel.sh
@@ -87,6 +87,7 @@ --features=external_include_paths \ --keep_going \ --linkopt=\"-fsanitize=thread\" \ + --per_file_copt=external/.*@-w \ --show_timestamps \ --test_env=\"TSAN_SYMBOLIZER_PATH=/opt/llvm/clang/bin/llvm-symbolizer\" \ --test_env=\"TZDIR=/abseil-cpp/absl/time/internal/cctz/testdata/zoneinfo\" \
diff --git a/ci/linux_clang-latest_libstdcxx_bazel.sh b/ci/linux_clang-latest_libstdcxx_bazel.sh index a1620e0..3175e41 100755 --- a/ci/linux_clang-latest_libstdcxx_bazel.sh +++ b/ci/linux_clang-latest_libstdcxx_bazel.sh
@@ -85,6 +85,7 @@ --features=external_include_paths \ --keep_going \ --linkopt=\"--gcc-toolchain=/usr/local\" \ + --per_file_copt=external/.*@-w \ --show_timestamps \ --test_env=\"GTEST_INSTALL_FAILURE_SIGNAL_HANDLER=1\" \ --test_env=\"TZDIR=/abseil-cpp/absl/time/internal/cctz/testdata/zoneinfo\" \
diff --git a/ci/linux_docker_containers.sh b/ci/linux_docker_containers.sh index 0f45471..cb0904c 100644 --- a/ci/linux_docker_containers.sh +++ b/ci/linux_docker_containers.sh
@@ -16,7 +16,7 @@ # Test scripts should source this file to get the identifiers. readonly LINUX_ALPINE_CONTAINER="gcr.io/google.com/absl-177019/alpine:20230612" -readonly LINUX_CLANG_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20250430" +readonly LINUX_CLANG_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20250527" readonly LINUX_ARM_CLANG_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_arm_hybrid-latest:20250430" -readonly LINUX_GCC_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20250430" +readonly LINUX_GCC_LATEST_CONTAINER="gcr.io/google.com/absl-177019/linux_hybrid-latest:20250527" readonly LINUX_GCC_FLOOR_CONTAINER="gcr.io/google.com/absl-177019/linux_gcc-floor:20250430"
diff --git a/ci/linux_gcc-floor_libstdcxx_bazel.sh b/ci/linux_gcc-floor_libstdcxx_bazel.sh index b683b60..0ee412d 100755 --- a/ci/linux_gcc-floor_libstdcxx_bazel.sh +++ b/ci/linux_gcc-floor_libstdcxx_bazel.sh
@@ -81,6 +81,7 @@ --define=\"absl=1\" \ --features=external_include_paths \ --keep_going \ + --per_file_copt=external/.*@-w \ --show_timestamps \ --test_env=\"GTEST_INSTALL_FAILURE_SIGNAL_HANDLER=1\" \ --test_env=\"TZDIR=/abseil-cpp/absl/time/internal/cctz/testdata/zoneinfo\" \
diff --git a/ci/linux_gcc-latest_libstdcxx_bazel.sh b/ci/linux_gcc-latest_libstdcxx_bazel.sh index b092c1d..6e9b5ec 100755 --- a/ci/linux_gcc-latest_libstdcxx_bazel.sh +++ b/ci/linux_gcc-latest_libstdcxx_bazel.sh
@@ -87,6 +87,7 @@ --enable_bzlmod=true \ --features=external_include_paths \ --keep_going \ + --per_file_copt=external/.*@-w \ --show_timestamps \ --test_env=\"GTEST_INSTALL_FAILURE_SIGNAL_HANDLER=1\" \ --test_env=\"TZDIR=/abseil-cpp/absl/time/internal/cctz/testdata/zoneinfo\" \
diff --git a/ci/linux_gcc-latest_libstdcxx_cmake.sh b/ci/linux_gcc-latest_libstdcxx_cmake.sh index d75209b..2f69bba 100755 --- a/ci/linux_gcc-latest_libstdcxx_cmake.sh +++ b/ci/linux_gcc-latest_libstdcxx_cmake.sh
@@ -22,6 +22,12 @@ source "${ABSEIL_ROOT}/ci/cmake_common.sh" +# Avoid depending on GitHub by looking for a cached copy of GoogleTest. +if [[ -r "${KOKORO_GFILE_DIR:-}/distdir/googletest-${ABSL_GOOGLETEST_VERSION}.tar.gz" ]]; then + ABSL_GOOGLETEST_DOWNLOAD_URL="file:///distdir/googletest-${ABSL_GOOGLETEST_VERSION}.tar.gz" + DOCKER_EXTRA_ARGS="--mount type=bind,source=${KOKORO_GFILE_DIR}/distdir,target=/distdir,readonly ${DOCKER_EXTRA_ARGS:-}" +fi + if [[ -z ${ABSL_CMAKE_CXX_STANDARDS:-} ]]; then ABSL_CMAKE_CXX_STANDARDS="17 20" fi
diff --git a/ci/linux_gcc_alpine_cmake.sh b/ci/linux_gcc_alpine_cmake.sh index 7cf25f7..1d64a95 100755 --- a/ci/linux_gcc_alpine_cmake.sh +++ b/ci/linux_gcc_alpine_cmake.sh
@@ -22,6 +22,12 @@ source "${ABSEIL_ROOT}/ci/cmake_common.sh" +# Avoid depending on GitHub by looking for a cached copy of GoogleTest. +if [[ -r "${KOKORO_GFILE_DIR:-}/distdir/googletest-${ABSL_GOOGLETEST_VERSION}.tar.gz" ]]; then + ABSL_GOOGLETEST_DOWNLOAD_URL="file:///distdir/googletest-${ABSL_GOOGLETEST_VERSION}.tar.gz" + DOCKER_EXTRA_ARGS="--mount type=bind,source=${KOKORO_GFILE_DIR}/distdir,target=/distdir,readonly ${DOCKER_EXTRA_ARGS:-}" +fi + if [[ -z ${ABSL_CMAKE_CXX_STANDARDS:-} ]]; then ABSL_CMAKE_CXX_STANDARDS="17" fi
diff --git a/ci/macos_xcode_bazel.sh b/ci/macos_xcode_bazel.sh index b05cfac..a09b405 100755 --- a/ci/macos_xcode_bazel.sh +++ b/ci/macos_xcode_bazel.sh
@@ -19,8 +19,8 @@ set -euox pipefail -# Use Xcode 16.0 -sudo xcode-select -s /Applications/Xcode_16.0.app/Contents/Developer +# Use Xcode 16.3 +sudo xcode-select -s /Applications/Xcode_16.3.app/Contents/Developer if [[ -z ${ABSEIL_ROOT:-} ]]; then ABSEIL_ROOT="$(realpath $(dirname ${0})/..)" @@ -37,8 +37,8 @@ # Use Bazel Vendor mode to reduce reliance on external dependencies. if [[ ${KOKORO_GFILE_DIR:-} ]] && [[ -f "${KOKORO_GFILE_DIR}/distdir/abseil-cpp_vendor.tar.gz" ]]; then - tar -xf "${KOKORO_GFILE_DIR}/distdir/abseil-cpp_vendor.tar.gz" -C "${TMP}/" - BAZEL_EXTRA_ARGS="--vendor_dir=\"${TMP}/abseil-cpp_vendor\" ${BAZEL_EXTRA_ARGS:-}" + tar -xf "${KOKORO_GFILE_DIR}/distdir/abseil-cpp_vendor.tar.gz" -C "${HOME}/" + BAZEL_EXTRA_ARGS="--vendor_dir=${HOME}/abseil-cpp_vendor ${BAZEL_EXTRA_ARGS:-}" fi # Print the compiler and Bazel versions. @@ -54,9 +54,6 @@ cp ${ALTERNATE_OPTIONS:-} absl/base/options.h || exit 1 fi -# Avoid using the system version of google-benchmark. -brew uninstall google-benchmark - ${BAZEL_BIN} test ... \ --copt="-DGTEST_REMOVE_LEGACY_TEST_CASEAPI_=1" \ --copt="-Werror" \ @@ -64,6 +61,7 @@ --enable_bzlmod=true \ --features=external_include_paths \ --keep_going \ + --per_file_copt=external/.*@-w \ --show_timestamps \ --test_env="TZDIR=${ABSEIL_ROOT}/absl/time/internal/cctz/testdata/zoneinfo" \ --test_output=errors \
diff --git a/ci/macos_xcode_cmake.sh b/ci/macos_xcode_cmake.sh index 6811b87..37be939 100755 --- a/ci/macos_xcode_cmake.sh +++ b/ci/macos_xcode_cmake.sh
@@ -16,8 +16,10 @@ set -euox pipefail -# Use Xcode 16.0 -sudo xcode-select -s /Applications/Xcode_16.0.app/Contents/Developer +# Use Xcode 16.3 +sudo xcode-select -s /Applications/Xcode_16.3.app/Contents/Developer + +brew install cmake export CMAKE_BUILD_PARALLEL_LEVEL=$(sysctl -n hw.ncpu) export CTEST_PARALLEL_LEVEL=$(sysctl -n hw.ncpu) @@ -29,6 +31,11 @@ source "${ABSEIL_ROOT}/ci/cmake_common.sh" +# Avoid depending on GitHub by looking for a cached copy of GoogleTest. +if [[ -r "${KOKORO_GFILE_DIR:-}/distdir/googletest-${ABSL_GOOGLETEST_VERSION}.tar.gz" ]]; then + ABSL_GOOGLETEST_DOWNLOAD_URL="${KOKORO_GFILE_DIR}/distdir/googletest-${ABSL_GOOGLETEST_VERSION}.tar.gz" +fi + if [[ -z ${ABSL_CMAKE_BUILD_TYPES:-} ]]; then ABSL_CMAKE_BUILD_TYPES="Debug" fi
diff --git a/ci/windows_clangcl_bazel.bat b/ci/windows_clangcl_bazel.bat index 26fd5af..94d27aa 100755 --- a/ci/windows_clangcl_bazel.bat +++ b/ci/windows_clangcl_bazel.bat
@@ -59,6 +59,7 @@ --extra_execution_platforms=//:x64_windows-clang-cl ^ --extra_toolchains=@local_config_cc//:cc-toolchain-x64_windows-clang-cl ^ --keep_going ^ + --per_file_copt=external/.*@/w ^ --test_env="GTEST_INSTALL_FAILURE_SIGNAL_HANDLER=1" ^ --test_env=TZDIR="%CD%\absl\time\internal\cctz\testdata\zoneinfo" ^ --test_output=errors ^
diff --git a/ci/windows_msvc_bazel.bat b/ci/windows_msvc_bazel.bat index bbb57b4..6318ff1 100755 --- a/ci/windows_msvc_bazel.bat +++ b/ci/windows_msvc_bazel.bat
@@ -50,6 +50,7 @@ --define=absl=1 ^ --enable_bzlmod=true ^ --keep_going ^ + --per_file_copt=external/.*@/w ^ --test_env="GTEST_INSTALL_FAILURE_SIGNAL_HANDLER=1" ^ --test_env=TZDIR="%CD%\absl\time\internal\cctz\testdata\zoneinfo" ^ --test_output=errors ^
diff --git a/ci/windows_msvc_cmake.bat b/ci/windows_msvc_cmake.bat index 62cdb70..c0083a1 100755 --- a/ci/windows_msvc_cmake.bat +++ b/ci/windows_msvc_cmake.bat
@@ -17,7 +17,12 @@ :: The version of GoogleTest to be used in the CMake tests in this directory. :: Keep this in sync with the version in the WORKSPACE file. SET ABSL_GOOGLETEST_VERSION=1.17.0 -SET ABSL_GOOGLETEST_DOWNLOAD_URL=https://github.com/google/googletest/releases/download/v%ABSL_GOOGLETEST_VERSION%/googletest-%ABSL_GOOGLETEST_VERSION%.tar.gz + +IF EXIST %KOKORO_GFILE_DIR%\distdir\googletest-%ABSL_GOOGLETEST_VERSION%.tar.gz ( + SET ABSL_GOOGLETEST_DOWNLOAD_URL=file://%KOKORO_GFILE_DIR%\distdir\googletest-%ABSL_GOOGLETEST_VERSION%.tar.gz +) ELSE ( + SET ABSL_GOOGLETEST_DOWNLOAD_URL=https://github.com/google/googletest/releases/download/v%ABSL_GOOGLETEST_VERSION%/googletest-%ABSL_GOOGLETEST_VERSION%.tar.gz +) :: Replace '\' with '/' in Windows paths for CMake. :: Note that this cannot go inside the IF block above, because BAT files are weird.
diff --git a/generate_bp.py b/generate_bp.py index 6797ba4..a34eb25 100755 --- a/generate_bp.py +++ b/generate_bp.py
@@ -17,16 +17,19 @@ '//absl/flags:flag', '//absl/flags:parse', '//absl/functional:bind_front', + '//absl/hash:hash_testing', '//absl/log:absl_check', '//absl/log:absl_log', '//absl/log:check', '//absl/log:die_if_null', '//absl/log:initialize', '//absl/log:log', + '//absl/log:log_streamer', '//absl/log:scoped_mock_log', '//absl/random:bit_gen_ref', '//absl/random:random', '//absl/status:statusor', + '//absl/status:status_matchers', '//absl/strings:strings', '//absl/synchronization:synchronization', ] @@ -112,13 +115,6 @@ vendor_available: true, stl: "libc++", } - - cc_defaults { - name: "absl_notls_test_defaults", - host_supported: true, - stl: "libc++", - cflags: ["-DANDROID_DISABLE_TLS_FOR_LINKER=1"], - } ''' libs_graph = {} @@ -230,7 +226,6 @@ if testonly: module_type = 'cc_test_library' defaults_module = 'absl_test_defaults' - notls_defaults_module = 'absl_notls_test_defaults' extra_attributes = ''' static_libs: ["libgmock", "libgtest"], shared: { @@ -276,28 +271,29 @@ }} ''' - # We need to generate separate versions of the library with TLS disabled - # for use in the dynamic linker and its dependencies, which does not - # support ELF TLS segments when loading itself. - bp_notls_deps_for_bp = ['"' + d + '_notls"' for d in bp_deps] - bp += f''' - {module_type} {{ - name: "{bp_mod_name}_notls", - defaults: ["{notls_defaults_module}"], - {visibility_prop_notls} - srcs: [ - {',\n'.join(src_files_for_bp)} - ], - {generated_hdrs_attr} - whole_static_libs: [ - {',\n'.join(bp_notls_deps_for_bp)} - ], - export_static_lib_headers: [ - {',\n'.join(bp_notls_deps_for_bp)} - ], - {extra_attributes} - }} - ''' + if not testonly: + # We need to generate separate versions of the library with TLS disabled + # for use in the dynamic linker and its dependencies, which does not + # support ELF TLS segments when loading itself. + bp_notls_deps_for_bp = ['"' + d + '_notls"' for d in bp_deps] + bp += f''' + {module_type} {{ + name: "{bp_mod_name}_notls", + defaults: ["{notls_defaults_module}"], + {visibility_prop_notls} + srcs: [ + {',\n'.join(src_files_for_bp)} + ], + {generated_hdrs_attr} + whole_static_libs: [ + {',\n'.join(bp_notls_deps_for_bp)} + ], + export_static_lib_headers: [ + {',\n'.join(bp_notls_deps_for_bp)} + ], + {extra_attributes} + }} + ''' queue.extend(deps)